// 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 "content/browser/renderer_host/media/media_stream_manager.h" #include #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/threading/thread.h" #include "content/browser/renderer_host/media/audio_input_device_manager.h" #include "content/browser/renderer_host/media/device_request_message_filter.h" #include "content/browser/renderer_host/media/media_stream_requester.h" #include "content/browser/renderer_host/media/media_stream_ui_proxy.h" #include "content/browser/renderer_host/media/video_capture_manager.h" #include "content/browser/renderer_host/media/web_contents_capture_util.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/media_observer.h" #include "content/public/browser/media_request_state.h" #include "content/public/common/content_switches.h" #include "content/public/common/media_stream_request.h" #include "media/audio/audio_manager_base.h" #include "media/audio/audio_parameters.h" #include "media/base/channel_layout.h" #include "url/gurl.h" #if defined(OS_WIN) #include "base/win/scoped_com_initializer.h" #endif namespace content { // Creates a random label used to identify requests. static std::string RandomLabel() { // An earlier PeerConnection spec, // http://dev.w3.org/2011/webrtc/editor/webrtc.html, specified the // MediaStream::label alphabet as containing 36 characters from // range: U+0021, U+0023 to U+0027, U+002A to U+002B, U+002D to U+002E, // U+0030 to U+0039, U+0041 to U+005A, U+005E to U+007E. // Here we use a safe subset. static const char kAlphabet[] = "0123456789" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::string label(36, ' '); for (size_t i = 0; i < label.size(); ++i) { int random_char = base::RandGenerator(sizeof(kAlphabet) - 1); label[i] = kAlphabet[random_char]; } return label; } // Helper to verify if a media stream type is part of options or not. static bool Requested(const MediaStreamRequest& request, MediaStreamType stream_type) { return (request.audio_type == stream_type || request.video_type == stream_type); } // TODO(xians): Merge DeviceRequest with MediaStreamRequest. class MediaStreamManager::DeviceRequest { public: DeviceRequest(MediaStreamRequester* requester, const MediaStreamRequest& request) : requester(requester), request(request), state_(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_NOT_REQUESTED) { } ~DeviceRequest() {} // Update the request state and notify observers. void SetState(MediaStreamType stream_type, MediaRequestState new_state) { if (stream_type == NUM_MEDIA_TYPES) { for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) { const MediaStreamType stream_type = static_cast(i); state_[stream_type] = new_state; } } else { state_[stream_type] = new_state; } if (request.video_type != MEDIA_TAB_VIDEO_CAPTURE && request.audio_type != MEDIA_TAB_AUDIO_CAPTURE && new_state != MEDIA_REQUEST_STATE_CLOSING) { return; } MediaObserver* media_observer = GetContentClient()->browser()->GetMediaObserver(); if (media_observer == NULL) return; // If we appended a device_id scheme, we want to remove it when notifying // observers which may be in different modules since this scheme is only // used internally within the content module. std::string device_id = WebContentsCaptureUtil::StripWebContentsDeviceScheme( request.tab_capture_device_id); media_observer->OnMediaRequestStateChanged( request.render_process_id, request.render_view_id, request.page_request_id, MediaStreamDevice(stream_type, device_id, device_id), new_state); } MediaRequestState state(MediaStreamType stream_type) const { return state_[stream_type]; } MediaStreamRequester* const requester; // Can be NULL. MediaStreamRequest request; StreamDeviceInfoArray devices; // Callback to the requester which audio/video devices have been selected. // It can be null if the requester has no interest to know the result. // Currently it is only used by |DEVICE_ACCESS| type. MediaStreamManager::MediaRequestResponseCallback callback; scoped_ptr ui_proxy; private: std::vector state_; }; MediaStreamManager::EnumerationCache::EnumerationCache() : valid(false) { } MediaStreamManager::EnumerationCache::~EnumerationCache() { } MediaStreamManager::MediaStreamManager() : audio_manager_(NULL), monitoring_started_(false), io_loop_(NULL), use_fake_ui_(false) {} MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager) : audio_manager_(audio_manager), monitoring_started_(false), io_loop_(NULL), use_fake_ui_(false) { 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() { DCHECK(requests_.empty()); DCHECK(!device_thread_.get()); } VideoCaptureManager* MediaStreamManager::video_capture_manager() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(video_capture_manager_.get()); return video_capture_manager_.get(); } AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(audio_input_device_manager_.get()); return audio_input_device_manager_.get(); } std::string MediaStreamManager::MakeMediaAccessRequest( int render_process_id, int render_view_id, int page_request_id, const StreamOptions& options, const GURL& security_origin, const MediaRequestResponseCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Create a new request based on options. MediaStreamRequest stream_request( render_process_id, render_view_id, page_request_id, std::string(), security_origin, MEDIA_DEVICE_ACCESS, std::string(), std::string(), options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(NULL, stream_request); const std::string& label = AddRequest(request); request->callback = callback; HandleRequest(label); return label; } std::string MediaStreamManager::GenerateStream( MediaStreamRequester* requester, int render_process_id, int render_view_id, int page_request_id, const StreamOptions& options, const GURL& security_origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kUseFakeDeviceForMediaStream)) { UseFakeDevice(); } if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kUseFakeUIForMediaStream)) { UseFakeUI(scoped_ptr()); } int target_render_process_id = render_process_id; int target_render_view_id = render_view_id; std::string tab_capture_device_id; // Customize options for a WebContents based capture. if (options.audio_type == MEDIA_TAB_AUDIO_CAPTURE || options.video_type == MEDIA_TAB_VIDEO_CAPTURE) { // TODO(justinlin): Can't plumb audio mirroring using stream type right // now, so plumbing by device_id. Will revisit once it's refactored. // http://crbug.com/163100 tab_capture_device_id = WebContentsCaptureUtil::AppendWebContentsDeviceScheme( !options.video_device_id.empty() ? options.video_device_id : options.audio_device_id); bool has_valid_device_id = WebContentsCaptureUtil::ExtractTabCaptureTarget( tab_capture_device_id, &target_render_process_id, &target_render_view_id); if (!has_valid_device_id || (options.audio_type != MEDIA_TAB_AUDIO_CAPTURE && options.audio_type != MEDIA_NO_SERVICE) || (options.video_type != MEDIA_TAB_VIDEO_CAPTURE && options.video_type != MEDIA_NO_SERVICE)) { LOG(ERROR) << "Invalid request."; return std::string(); } } std::string translated_audio_device_id; std::string translated_video_device_id; if (options.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE) { bool found_match = TranslateGUIDToRawId( MEDIA_DEVICE_AUDIO_CAPTURE, security_origin, options.audio_device_id, &translated_audio_device_id); DCHECK(found_match || translated_audio_device_id.empty()); } if (options.video_type == MEDIA_DEVICE_VIDEO_CAPTURE) { bool found_match = TranslateGUIDToRawId( MEDIA_DEVICE_VIDEO_CAPTURE, security_origin, options.video_device_id, &translated_video_device_id); DCHECK(found_match || translated_video_device_id.empty()); } if (options.video_type == MEDIA_DESKTOP_VIDEO_CAPTURE || options.audio_type == MEDIA_SYSTEM_AUDIO_CAPTURE) { // For screen capture we only support two valid combinations: // (1) screen video capture only, or // (2) screen video capture with system audio capture. if (options.video_type != MEDIA_DESKTOP_VIDEO_CAPTURE || (options.audio_type != MEDIA_NO_SERVICE && options.audio_type != MEDIA_SYSTEM_AUDIO_CAPTURE)) { // TODO(sergeyu): Surface error message to the calling JS code. LOG(ERROR) << "Invalid screen capture request."; return std::string(); } translated_video_device_id = options.video_device_id; } // Create a new request based on options. MediaStreamRequest stream_request( target_render_process_id, target_render_view_id, page_request_id, tab_capture_device_id, security_origin, MEDIA_GENERATE_STREAM, translated_audio_device_id, translated_video_device_id, options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); HandleRequest(label); return label; } void MediaStreamManager::CancelRequest(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequests::iterator it = requests_.find(label); if (it != requests_.end()) { if (!RequestDone(*it->second)) { // TODO(xians): update the |state| to STATE_DONE to trigger a state // changed notification to UI before deleting the request? scoped_ptr request(it->second); RemoveRequest(it); for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) { const MediaStreamType stream_type = static_cast(i); MediaStreamProvider* device_manager = GetDeviceManager(stream_type); if (!device_manager) continue; if (request->state(stream_type) != MEDIA_REQUEST_STATE_OPENING && request->state(stream_type) != MEDIA_REQUEST_STATE_DONE) { continue; } for (StreamDeviceInfoArray::const_iterator device_it = request->devices.begin(); device_it != request->devices.end(); ++device_it) { if (device_it->device.type == stream_type) { device_manager->Close(device_it->session_id); } } } // Cancel the request if still pending at UI side. request->SetState(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_CLOSING); } else { StopGeneratedStream(label); } } } void MediaStreamManager::StopGeneratedStream(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Find the request and close all open devices for the request. DeviceRequests::iterator it = requests_.find(label); if (it != requests_.end()) { if (it->second->request.request_type == MEDIA_ENUMERATE_DEVICES) { StopEnumerateDevices(label); return; } scoped_ptr request(it->second); RemoveRequest(it); for (StreamDeviceInfoArray::const_iterator device_it = request->devices.begin(); device_it != request->devices.end(); ++device_it) { GetDeviceManager(device_it->device.type)->Close(device_it->session_id); } if (request->request.request_type == MEDIA_GENERATE_STREAM && RequestDone(*request)) { // Notify observers that this device is being closed. for (int i = MEDIA_NO_SERVICE + 1; i != NUM_MEDIA_TYPES; ++i) { if (request->state(static_cast(i)) != MEDIA_REQUEST_STATE_NOT_REQUESTED) { request->SetState(static_cast(i), MEDIA_REQUEST_STATE_CLOSING); } } } } } std::string MediaStreamManager::EnumerateDevices( MediaStreamRequester* requester, int render_process_id, int render_view_id, int page_request_id, MediaStreamType type, const GURL& security_origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE || type == MEDIA_DEVICE_VIDEO_CAPTURE); // When the requester is NULL, the request is made by the UI to ensure MSM // starts monitoring devices. if (!requester) { if (!monitoring_started_) StartMonitoring(); return std::string(); } // Create a new request. StreamOptions options; EnumerationCache* cache = NULL; if (type == MEDIA_DEVICE_AUDIO_CAPTURE) { options.audio_type = type; cache = &audio_enumeration_cache_; } else if (type == MEDIA_DEVICE_VIDEO_CAPTURE) { options.video_type = type; cache = &video_enumeration_cache_; } else { NOTREACHED(); return std::string(); } MediaStreamRequest stream_request( render_process_id, render_view_id, page_request_id, std::string(), security_origin, MEDIA_ENUMERATE_DEVICES, std::string(), std::string(), options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); if (cache->valid) { // Cached device list of this type exists. Just send it out. request->SetState(type, MEDIA_REQUEST_STATE_REQUESTED); // Need to post a task since the requester won't have label till // this function returns. BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&MediaStreamManager::SendCachedDeviceList, base::Unretained(this), cache, label)); } else { StartEnumeration(request); } return label; } void MediaStreamManager::StopEnumerateDevices(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequests::iterator it = requests_.find(label); if (it != requests_.end()) { DCHECK_EQ(it->second->request.request_type, MEDIA_ENUMERATE_DEVICES); // Delete the DeviceRequest. scoped_ptr request(it->second); RemoveRequest(it); } } std::string MediaStreamManager::OpenDevice( MediaStreamRequester* requester, int render_process_id, int render_view_id, int page_request_id, const std::string& device_id, MediaStreamType type, const GURL& security_origin) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE || type == MEDIA_DEVICE_VIDEO_CAPTURE); // Create a new request. StreamOptions options; if (IsAudioMediaType(type)) { options.audio_type = type; options.audio_device_id = device_id; } else if (IsVideoMediaType(type)) { options.video_type = type; options.video_device_id = device_id; } else { NOTREACHED(); return std::string(); } MediaStreamRequest stream_request( render_process_id, render_view_id, page_request_id, std::string(), security_origin, MEDIA_OPEN_DEVICE, options.audio_device_id, options.video_device_id, options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); StartEnumeration(request); return label; } void MediaStreamManager::SendCachedDeviceList( EnumerationCache* cache, const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (cache->valid) { DeviceRequests::iterator it = requests_.find(label); if (it != requests_.end()) { it->second->requester->DevicesEnumerated(label, cache->devices); } } } 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 both the audio and video devices to cache the device lists // and send them to media observer. ++active_enumeration_ref_count_[MEDIA_DEVICE_AUDIO_CAPTURE]; audio_input_device_manager_->EnumerateDevices(MEDIA_DEVICE_AUDIO_CAPTURE); ++active_enumeration_ref_count_[MEDIA_DEVICE_VIDEO_CAPTURE]; video_capture_manager_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE); } } void MediaStreamManager::StopMonitoring() { DCHECK_EQ(base::MessageLoop::current(), io_loop_); if (monitoring_started_) { base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); monitoring_started_ = false; ClearEnumerationCache(&audio_enumeration_cache_); ClearEnumerationCache(&video_enumeration_cache_); } } bool MediaStreamManager::TranslateGUIDToRawId(MediaStreamType stream_type, const GURL& security_origin, const std::string& device_guid, std::string* raw_device_id) { DCHECK(stream_type == MEDIA_DEVICE_AUDIO_CAPTURE || stream_type == MEDIA_DEVICE_VIDEO_CAPTURE); if (device_guid.empty()) return false; EnumerationCache* cache = stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ? &audio_enumeration_cache_ : &video_enumeration_cache_; // If device monitoring hasn't started, the |device_guid| is not valid. if (!cache->valid) return false; for (StreamDeviceInfoArray::const_iterator it = cache->devices.begin(); it != cache->devices.end(); ++it) { if (DeviceRequestMessageFilter::DoesRawIdMatchGuid( security_origin, device_guid, it->device.id)) { *raw_device_id = it->device.id; return true; } } return false; } void MediaStreamManager::ClearEnumerationCache(EnumerationCache* cache) { DCHECK_EQ(base::MessageLoop::current(), io_loop_); cache->valid = false; } void MediaStreamManager::StartEnumeration(DeviceRequest* request) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Start monitoring the devices when doing the first enumeration. if (!monitoring_started_ && base::SystemMonitor::Get()) { StartMonitoring(); } // Start enumeration for devices of all requested device types. for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) { const MediaStreamType stream_type = static_cast(i); if (Requested(request->request, stream_type)) { request->SetState(stream_type, MEDIA_REQUEST_STATE_REQUESTED); DCHECK_GE(active_enumeration_ref_count_[stream_type], 0); if (active_enumeration_ref_count_[stream_type] == 0) { ++active_enumeration_ref_count_[stream_type]; GetDeviceManager(stream_type)->EnumerateDevices(stream_type); } } } } std::string MediaStreamManager::AddRequest(DeviceRequest* request) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Create a label for this request and verify it is unique. std::string unique_label; do { unique_label = RandomLabel(); } while (requests_.find(unique_label) != requests_.end()); requests_.insert(std::make_pair(unique_label, request)); return unique_label; } void MediaStreamManager::RemoveRequest(DeviceRequests::iterator it) { requests_.erase(it); } void MediaStreamManager::PostRequestToUI(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequest* request = requests_[label]; if (use_fake_ui_) { if (!fake_ui_) fake_ui_.reset(new FakeMediaStreamUIProxy()); MediaStreamDevices devices; if (audio_enumeration_cache_.valid) { for (StreamDeviceInfoArray::const_iterator it = audio_enumeration_cache_.devices.begin(); it != audio_enumeration_cache_.devices.end(); ++it) { devices.push_back(it->device); } } if (video_enumeration_cache_.valid) { for (StreamDeviceInfoArray::const_iterator it = video_enumeration_cache_.devices.begin(); it != video_enumeration_cache_.devices.end(); ++it) { devices.push_back(it->device); } } fake_ui_->SetAvailableDevices(devices); request->ui_proxy = fake_ui_.Pass(); } else { request->ui_proxy = MediaStreamUIProxy::Create(); } request->ui_proxy->RequestAccess( request->request, base::Bind(&MediaStreamManager::HandleAccessRequestResponse, base::Unretained(this), label)); } void MediaStreamManager::HandleRequest(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequest* request = requests_[label]; const MediaStreamType audio_type = request->request.audio_type; const MediaStreamType video_type = request->request.video_type; bool is_web_contents_capture = audio_type == MEDIA_TAB_AUDIO_CAPTURE || video_type == MEDIA_TAB_VIDEO_CAPTURE; bool is_screen_capture = video_type == MEDIA_DESKTOP_VIDEO_CAPTURE; if (!is_web_contents_capture && !is_screen_capture && ((IsAudioMediaType(audio_type) && !audio_enumeration_cache_.valid) || (IsVideoMediaType(video_type) && !video_enumeration_cache_.valid))) { // Enumerate the devices if there is no valid device lists to be used. StartEnumeration(request); return; } // No need to do new device enumerations, post the request to UI // immediately. if (IsAudioMediaType(audio_type)) request->SetState(audio_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL); if (IsVideoMediaType(video_type)) request->SetState(video_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL); PostRequestToUI(label); } void MediaStreamManager::InitializeDeviceManagersOnIOThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (device_thread_) return; device_thread_.reset(new base::Thread("MediaStreamDeviceThread")); #if defined(OS_WIN) device_thread_->init_com_with_mta(true); #endif CHECK(device_thread_->Start()); audio_input_device_manager_ = new AudioInputDeviceManager(audio_manager_); audio_input_device_manager_->Register( this, device_thread_->message_loop_proxy().get()); video_capture_manager_ = new VideoCaptureManager(); video_capture_manager_->Register(this, device_thread_->message_loop_proxy().get()); // We want to be notified of IO message loop destruction to delete the thread // and the device managers. io_loop_ = base::MessageLoop::current(); io_loop_->AddDestructionObserver(this); } void MediaStreamManager::Opened(MediaStreamType stream_type, int capture_session_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Find the request containing this device and mark it as used. DeviceRequest* request = NULL; StreamDeviceInfoArray* devices = NULL; std::string label; for (DeviceRequests::iterator request_it = requests_.begin(); request_it != requests_.end() && request == NULL; ++request_it) { devices = &(request_it->second->devices); for (StreamDeviceInfoArray::iterator device_it = devices->begin(); device_it != devices->end(); ++device_it) { if (device_it->device.type == stream_type && device_it->session_id == capture_session_id) { // We've found the request. device_it->in_use = true; label = request_it->first; request = request_it->second; break; } } } if (request == NULL) { // The request doesn't exist. return; } DCHECK_NE(request->state(stream_type), MEDIA_REQUEST_STATE_REQUESTED); // Check if all devices for this stream type are opened. Update the state if // they are. for (StreamDeviceInfoArray::iterator device_it = devices->begin(); device_it != devices->end(); ++device_it) { if (device_it->device.type != stream_type) { continue; } if (device_it->in_use == false) { // Wait for more devices to be opened before we're done. return; } } request->SetState(stream_type, MEDIA_REQUEST_STATE_DONE); if (!RequestDone(*request)) { // This stream_type is done, but not the other type. return; } switch (request->request.request_type) { case MEDIA_OPEN_DEVICE: request->requester->DeviceOpened(label, devices->front()); break; case MEDIA_GENERATE_STREAM: { // Partition the array of devices into audio vs video. StreamDeviceInfoArray audio_devices, video_devices; for (StreamDeviceInfoArray::iterator device_it = devices->begin(); device_it != devices->end(); ++device_it) { if (IsAudioMediaType(device_it->device.type)) { // Store the native audio parameters in the device struct. // TODO(xians): Handle the tab capture sample rate/channel layout // in AudioInputDeviceManager::Open(). if (device_it->device.type != content::MEDIA_TAB_AUDIO_CAPTURE) { const StreamDeviceInfo* info = audio_input_device_manager_->GetOpenedDeviceInfoById( device_it->session_id); DCHECK_EQ(info->device.id, device_it->device.id); device_it->device.sample_rate = info->device.sample_rate; device_it->device.channel_layout = info->device.channel_layout; } audio_devices.push_back(*device_it); } else if (IsVideoMediaType(device_it->device.type)) { video_devices.push_back(*device_it); } else { NOTREACHED(); } } request->requester->StreamGenerated(label, audio_devices, video_devices); request->ui_proxy->OnStarted( base::Bind(&MediaStreamManager::StopStreamFromUI, base::Unretained(this), label)); break; } default: NOTREACHED(); break; } } void MediaStreamManager::Closed(MediaStreamType stream_type, int capture_session_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); } void MediaStreamManager::DevicesEnumerated( MediaStreamType stream_type, const StreamDeviceInfoArray& devices) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // 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 (!cache->valid || devices.size() != cache->devices.size() || !std::equal(devices.begin(), devices.end(), cache->devices.begin(), 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 // back from device_settings that will need to iterate through devices. std::list label_list; for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end(); ++it) { if (it->second->state(stream_type) == MEDIA_REQUEST_STATE_REQUESTED && Requested(it->second->request, stream_type)) { if (it->second->request.request_type != MEDIA_ENUMERATE_DEVICES) it->second->SetState(stream_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL); label_list.push_back(it->first); } } for (std::list::iterator it = label_list.begin(); it != label_list.end(); ++it) { DeviceRequest* request = requests_[*it]; switch (request->request.request_type) { case MEDIA_ENUMERATE_DEVICES: if (need_update_clients && request->requester) request->requester->DevicesEnumerated(*it, devices); break; default: if (request->state(request->request.audio_type) == MEDIA_REQUEST_STATE_REQUESTED || request->state(request->request.video_type) == MEDIA_REQUEST_STATE_REQUESTED) { // We are doing enumeration for other type of media, wait until it is // all done before posting the request to UI because UI needs // the device lists to handle the request. break; } // Post the request to UI for permission approval. PostRequestToUI(*it); break; } } label_list.clear(); --active_enumeration_ref_count_[stream_type]; DCHECK_GE(active_enumeration_ref_count_[stream_type], 0); } void MediaStreamManager::Error(MediaStreamType stream_type, int capture_session_id, MediaStreamProviderError error) { // Find the device for the error call. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end(); ++it) { StreamDeviceInfoArray& devices = it->second->devices; // TODO(miu): BUG. It's possible for the audio (or video) device array in // the "requester" to become out-of-sync with the order of devices we have // here. See http://crbug.com/147650 int audio_device_idx = -1; int video_device_idx = -1; for (StreamDeviceInfoArray::iterator device_it = devices.begin(); device_it != devices.end(); ++device_it) { if (IsAudioMediaType(device_it->device.type)) { ++audio_device_idx; } else if (IsVideoMediaType(device_it->device.type)) { ++video_device_idx; } else { NOTREACHED(); continue; } if (device_it->device.type != stream_type || device_it->session_id != capture_session_id) { continue; } // We've found the failing device. Find the error case: // An error should only be reported to the MediaStreamManager if // the request has not been fulfilled yet. DCHECK(it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE); if (it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE) { // Request is not done, devices are not opened in this case. if (devices.size() <= 1) { scoped_ptr request(it->second); // 1. Device not opened and no other devices for this request -> // signal stream error and remove the request. if (request->requester) request->requester->StreamGenerationFailed(it->first); RemoveRequest(it); } else { // 2. Not opened but other devices exists for this request -> remove // device from list, but don't signal an error. devices.erase(device_it); // NOTE: This invalidates device_it! } } return; } } } void MediaStreamManager::HandleAccessRequestResponse( const std::string& label, const MediaStreamDevices& devices) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequests::iterator request_it = requests_.find(label); if (request_it == requests_.end()) { return; } // Handle the case when the request was denied. if (devices.empty()) { // Notify the users about the request result. scoped_ptr request(request_it->second); if (request->requester) request->requester->StreamGenerationFailed(label); if (request->request.request_type == MEDIA_DEVICE_ACCESS && !request->callback.is_null()) { request->callback.Run(MediaStreamDevices(), request->ui_proxy.Pass()); } RemoveRequest(request_it); return; } if (request_it->second->request.request_type == MEDIA_DEVICE_ACCESS) { scoped_ptr request(request_it->second); if (!request->callback.is_null()) request->callback.Run(devices, request->ui_proxy.Pass()); // Delete the request since it is done. RemoveRequest(request_it); return; } // Process all newly-accepted devices for this request. DeviceRequest* request = request_it->second; bool found_audio = false; bool found_video = false; for (MediaStreamDevices::const_iterator device_it = devices.begin(); device_it != devices.end(); ++device_it) { StreamDeviceInfo device_info; device_info.device = *device_it; // TODO(justinlin): Nicer way to do this? // Re-append the device's id since we lost it when posting request to UI. if (device_info.device.type == content::MEDIA_TAB_VIDEO_CAPTURE || device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) { device_info.device.id = request->request.tab_capture_device_id; // Initialize the sample_rate and channel_layout here since for audio // mirroring, we don't go through EnumerateDevices where these are usually // initialized. if (device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) { const media::AudioParameters parameters = audio_manager_->GetDefaultOutputStreamParameters(); int sample_rate = parameters.sample_rate(); // If we weren't able to get the native sampling rate or the sample_rate // is outside the valid range for input devices set reasonable defaults. if (sample_rate <= 0 || sample_rate > 96000) sample_rate = 44100; device_info.device.sample_rate = sample_rate; device_info.device.channel_layout = media::CHANNEL_LAYOUT_STEREO; } } // Set in_use to false to be able to track if this device has been // opened. in_use might be true if the device type can be used in more // than one session. device_info.in_use = false; device_info.session_id = GetDeviceManager(device_info.device.type)->Open(device_info); request->SetState(device_info.device.type, MEDIA_REQUEST_STATE_OPENING); request->devices.push_back(device_info); if (device_info.device.type == request->request.audio_type) { found_audio = true; } else if (device_info.device.type == request->request.video_type) { found_video = true; } } // Check whether we've received all stream types requested. if (!found_audio && IsAudioMediaType(request->request.audio_type)) request->SetState(request->request.audio_type, MEDIA_REQUEST_STATE_ERROR); if (!found_video && IsVideoMediaType(request->request.video_type)) request->SetState(request->request.video_type, MEDIA_REQUEST_STATE_ERROR); } void MediaStreamManager::StopStreamFromUI(const std::string& label) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DeviceRequests::iterator it = requests_.find(label); if (it == requests_.end()) return; // Notify renderers that the stream has been stopped. if (it->second->requester) it->second->requester->StopGeneratedStream(label); StopGeneratedStream(label); } void MediaStreamManager::UseFakeDevice() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); video_capture_manager()->UseFakeDevice(); audio_input_device_manager()->UseFakeDevice(); } void MediaStreamManager::UseFakeUI(scoped_ptr fake_ui) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); use_fake_ui_ = true; fake_ui_ = fake_ui.Pass(); } void MediaStreamManager::WillDestroyCurrentMessageLoop() { DCHECK_EQ(base::MessageLoop::current(), io_loop_); DCHECK(requests_.empty()); if (device_thread_) { StopMonitoring(); video_capture_manager_->Unregister(); audio_input_device_manager_->Unregister(); device_thread_.reset(); } audio_input_device_manager_ = NULL; video_capture_manager_ = NULL; } 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(it->device); } 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)); const bool requested_audio = IsAudioMediaType(request.request.audio_type); const bool requested_video = IsVideoMediaType(request.request.video_type); const bool audio_done = !requested_audio || request.state(request.request.audio_type) == MEDIA_REQUEST_STATE_DONE || request.state(request.request.audio_type) == MEDIA_REQUEST_STATE_ERROR; if (!audio_done) return false; const bool video_done = !requested_video || request.state(request.request.video_type) == MEDIA_REQUEST_STATE_DONE || request.state(request.request.video_type) == MEDIA_REQUEST_STATE_ERROR; if (!video_done) return false; for (StreamDeviceInfoArray::const_iterator it = request.devices.begin(); it != request.devices.end(); ++it) { if (it->in_use == false) return false; } return true; } MediaStreamProvider* MediaStreamManager::GetDeviceManager( MediaStreamType stream_type) { if (IsVideoMediaType(stream_type)) { return video_capture_manager(); } else if (IsAudioMediaType(stream_type)) { return audio_input_device_manager(); } NOTREACHED(); return NULL; } void MediaStreamManager::OnDevicesChanged( base::SystemMonitor::DeviceType device_type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // NOTE: This method is only called in response to physical audio/video device // changes (from the operating system). MediaStreamType stream_type; if (device_type == base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE) { stream_type = MEDIA_DEVICE_AUDIO_CAPTURE; } else if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE) { stream_type = MEDIA_DEVICE_VIDEO_CAPTURE; } else { return; // Uninteresting device change. } // Always do enumeration even though some enumeration is in progress, // because those enumeration commands could be sent before these devices // change. ++active_enumeration_ref_count_[stream_type]; GetDeviceManager(stream_type)->EnumerateDevices(stream_type); } } // namespace content