| // 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/video_capture_manager.h" |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/browser/renderer_host/media/video_capture_controller.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/desktop_media_id.h" |
| #include "content/public/common/media_stream_request.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_facing.h" |
| #include "media/capture/video/video_capture_device.h" |
| |
| namespace { |
| |
| // Used for logging capture events. |
| // Elements in this enum should not be deleted or rearranged; the only |
| // permitted operation is to add new elements before NUM_VIDEO_CAPTURE_EVENT. |
| enum VideoCaptureEvent { |
| VIDEO_CAPTURE_START_CAPTURE = 0, |
| VIDEO_CAPTURE_STOP_CAPTURE_OK = 1, |
| VIDEO_CAPTURE_STOP_CAPTURE_DUE_TO_ERROR = 2, |
| VIDEO_CAPTURE_STOP_CAPTURE_OK_NO_FRAMES_PRODUCED_BY_DEVICE = 3, |
| VIDEO_CAPTURE_STOP_CAPTURE_OK_NO_FRAMES_PRODUCED_BY_DESKTOP_OR_TAB = 4, |
| NUM_VIDEO_CAPTURE_EVENT |
| }; |
| |
| void LogVideoCaptureEvent(VideoCaptureEvent event) { |
| UMA_HISTOGRAM_ENUMERATION("Media.VideoCaptureManager.Event", event, |
| NUM_VIDEO_CAPTURE_EVENT); |
| } |
| |
| const media::VideoCaptureSessionId kFakeSessionId = -1; |
| |
| } // namespace |
| |
| namespace content { |
| |
| // Class used for queuing request for starting a device. |
| class VideoCaptureManager::CaptureDeviceStartRequest { |
| public: |
| CaptureDeviceStartRequest(VideoCaptureController* controller, |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params); |
| VideoCaptureController* controller() const { return controller_; } |
| media::VideoCaptureSessionId session_id() const { return session_id_; } |
| media::VideoCaptureParams params() const { return params_; } |
| |
| private: |
| VideoCaptureController* const controller_; |
| const media::VideoCaptureSessionId session_id_; |
| const media::VideoCaptureParams params_; |
| }; |
| |
| VideoCaptureManager::CaptureDeviceStartRequest::CaptureDeviceStartRequest( |
| VideoCaptureController* controller, |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params) |
| : controller_(controller), session_id_(session_id), params_(params) {} |
| |
| VideoCaptureManager::VideoCaptureManager( |
| std::unique_ptr<VideoCaptureProvider> video_capture_provider, |
| base::RepeatingCallback<void(const std::string&)> emit_log_message_cb) |
| : new_capture_session_id_(1), |
| video_capture_provider_(std::move(video_capture_provider)), |
| emit_log_message_cb_(std::move(emit_log_message_cb)) {} |
| |
| VideoCaptureManager::~VideoCaptureManager() { |
| DCHECK(controllers_.empty()); |
| DCHECK(device_start_request_queue_.empty()); |
| } |
| |
| void VideoCaptureManager::AddVideoCaptureObserver( |
| media::VideoCaptureObserver* observer) { |
| DCHECK(observer); |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| capture_observers_.AddObserver(observer); |
| } |
| |
| void VideoCaptureManager::RemoveAllVideoCaptureObservers() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| capture_observers_.Clear(); |
| } |
| |
| void VideoCaptureManager::RegisterListener( |
| MediaStreamProviderListener* listener) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(listener); |
| listeners_.AddObserver(listener); |
| #if defined(OS_ANDROID) |
| application_state_has_running_activities_ = true; |
| app_status_listener_.reset(new base::android::ApplicationStatusListener( |
| base::Bind(&VideoCaptureManager::OnApplicationStateChange, |
| base::Unretained(this)))); |
| #endif |
| } |
| |
| void VideoCaptureManager::UnregisterListener( |
| MediaStreamProviderListener* listener) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| listeners_.RemoveObserver(listener); |
| } |
| |
| void VideoCaptureManager::EnumerateDevices( |
| const EnumerationCallback& client_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| EmitLogMessage("VideoCaptureManager::EnumerateDevices", 1); |
| |
| // Pass a timer for UMA histogram collection. |
| video_capture_provider_->GetDeviceInfosAsync(media::BindToCurrentLoop( |
| base::Bind(&VideoCaptureManager::OnDeviceInfosReceived, this, |
| base::Owned(new base::ElapsedTimer()), client_callback))); |
| } |
| |
| int VideoCaptureManager::Open(const MediaStreamDevice& device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Generate a new id for the session being opened. |
| const media::VideoCaptureSessionId capture_session_id = |
| new_capture_session_id_++; |
| |
| DCHECK(sessions_.find(capture_session_id) == sessions_.end()); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureManager::Open, device.name = " << device.name |
| << ", device.id = " << device.id |
| << ", capture_session_id = " << capture_session_id; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| // We just save the stream info for processing later. |
| sessions_[capture_session_id] = device; |
| |
| // Notify our listener asynchronously; this ensures that we return |
| // |capture_session_id| to the caller of this function before using that |
| // same id in a listener event. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&VideoCaptureManager::OnOpened, this, |
| device.type, capture_session_id)); |
| return capture_session_id; |
| } |
| |
| void VideoCaptureManager::Close(int capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureManager::Close, capture_session_id = " |
| << capture_session_id; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| SessionMap::iterator session_it = sessions_.find(capture_session_id); |
| if (session_it == sessions_.end()) { |
| NOTREACHED(); |
| return; |
| } |
| |
| VideoCaptureController* const existing_device = |
| LookupControllerByMediaTypeAndDeviceId(session_it->second.type, |
| session_it->second.id); |
| if (existing_device) { |
| // Remove any client that is still using the session. This is safe to call |
| // even if there are no clients using the session. |
| existing_device->StopSession(capture_session_id); |
| |
| // StopSession() may have removed the last client, so we might need to |
| // close the device. |
| DestroyControllerIfNoClients(existing_device); |
| } |
| |
| // Notify listeners asynchronously, and forget the session. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&VideoCaptureManager::OnClosed, this, |
| session_it->second.type, capture_session_id)); |
| sessions_.erase(session_it); |
| } |
| |
| void VideoCaptureManager::QueueStartDevice( |
| media::VideoCaptureSessionId session_id, |
| VideoCaptureController* controller, |
| const media::VideoCaptureParams& params) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| device_start_request_queue_.push_back( |
| CaptureDeviceStartRequest(controller, session_id, params)); |
| if (device_start_request_queue_.size() == 1) |
| ProcessDeviceStartRequestQueue(); |
| } |
| |
| void VideoCaptureManager::DoStopDevice(VideoCaptureController* controller) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // TODO(mcasas): use a helper function https://crbug.com/624854. |
| DCHECK(std::find_if( |
| controllers_.begin(), controllers_.end(), |
| [controller]( |
| const scoped_refptr<VideoCaptureController>& device_entry) { |
| return device_entry.get() == controller; |
| }) != controllers_.end()); |
| |
| // If start request has not yet started processing, i.e. if it is not at the |
| // beginning of the queue, remove it from the queue. |
| auto request_iter = device_start_request_queue_.begin(); |
| if (request_iter != device_start_request_queue_.end()) { |
| request_iter = |
| std::find_if(++request_iter, device_start_request_queue_.end(), |
| [controller](const CaptureDeviceStartRequest& request) { |
| return request.controller() == controller; |
| }); |
| if (request_iter != device_start_request_queue_.end()) { |
| device_start_request_queue_.erase(request_iter); |
| return; |
| } |
| } |
| |
| const media::VideoCaptureDeviceInfo* device_info = |
| GetDeviceInfoById(controller->device_id()); |
| if (device_info != nullptr) { |
| for (auto& observer : capture_observers_) |
| observer.OnVideoCaptureStopped(device_info->descriptor.facing); |
| } |
| |
| // Since we may be removing |controller| from |controllers_| while |
| // ReleaseDeviceAsnyc() is executing, we pass it shared ownership to |
| // |controller|. |
| controller->ReleaseDeviceAsync( |
| base::BindOnce([](scoped_refptr<VideoCaptureController>) {}, |
| GetControllerSharedRef(controller))); |
| } |
| |
| void VideoCaptureManager::ProcessDeviceStartRequestQueue() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DeviceStartQueue::iterator request = device_start_request_queue_.begin(); |
| if (request == device_start_request_queue_.end()) |
| return; |
| |
| VideoCaptureController* const controller = request->controller(); |
| |
| EmitLogMessage("VideoCaptureManager::ProcessDeviceStartRequestQueue", 3); |
| // The unit test VideoCaptureManagerTest.OpenNotExisting requires us to fail |
| // synchronously if the stream_type is MEDIA_DEVICE_VIDEO_CAPTURE and no |
| // DeviceInfo matching the requested id is present (which is the case when |
| // requesting a device with a bogus id). Note, that since other types of |
| // failure during startup of the device are allowed to be reported |
| // asynchronously, this requirement is questionable. |
| // TODO(chfremer): Check if any production code actually depends on this |
| // requirement. If not, relax the requirement in the test and remove the below |
| // if block. See crbug.com/708251 |
| if (controller->stream_type() == MEDIA_DEVICE_VIDEO_CAPTURE) { |
| const media::VideoCaptureDeviceInfo* device_info = |
| GetDeviceInfoById(controller->device_id()); |
| if (!device_info) { |
| OnDeviceLaunchFailed(controller); |
| return; |
| } |
| for (auto& observer : capture_observers_) |
| observer.OnVideoCaptureStarted(device_info->descriptor.facing); |
| } |
| |
| // The method CreateAndStartDeviceAsync() is going to run asynchronously. |
| // Since we may be removing the controller while it is executing, we need to |
| // pass it shared ownership to itself so that it stays alive while executing. |
| // And since the execution may make callbacks into |this|, we also need |
| // to pass it shared ownership to |this|. |
| // TODO(chfremer): Check if request->params() can actually be different from |
| // controller->parameters, and simplify if this is not the case. |
| controller->CreateAndStartDeviceAsync( |
| request->params(), static_cast<VideoCaptureDeviceLaunchObserver*>(this), |
| base::BindOnce([](scoped_refptr<VideoCaptureManager>, |
| scoped_refptr<VideoCaptureController>) {}, |
| scoped_refptr<VideoCaptureManager>(this), |
| GetControllerSharedRef(controller))); |
| } |
| |
| void VideoCaptureManager::OnDeviceLaunched(VideoCaptureController* controller) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| std::ostringstream string_stream; |
| string_stream << "Launching device has succeeded. device_id = " |
| << controller->device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| DCHECK(!device_start_request_queue_.empty()); |
| DCHECK_EQ(controller, device_start_request_queue_.begin()->controller()); |
| DCHECK(controller); |
| |
| if (controller->stream_type() == MEDIA_GUM_DESKTOP_VIDEO_CAPTURE) { |
| const media::VideoCaptureSessionId session_id = |
| device_start_request_queue_.front().session_id(); |
| DCHECK(session_id != kFakeSessionId); |
| MaybePostDesktopCaptureWindowId(session_id); |
| } |
| |
| auto it = photo_request_queue_.begin(); |
| while (it != photo_request_queue_.end()) { |
| auto request = it++; |
| VideoCaptureController* maybe_entry = |
| LookupControllerBySessionId(request->first); |
| if (maybe_entry && maybe_entry->IsDeviceAlive()) { |
| request->second.Run(); |
| photo_request_queue_.erase(request); |
| } |
| } |
| |
| device_start_request_queue_.pop_front(); |
| ProcessDeviceStartRequestQueue(); |
| } |
| |
| void VideoCaptureManager::OnDeviceLaunchFailed( |
| VideoCaptureController* controller) { |
| std::ostringstream string_stream; |
| string_stream << "Launching device has failed. device_id = " |
| << controller->device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| controller->OnError(); |
| |
| device_start_request_queue_.pop_front(); |
| ProcessDeviceStartRequestQueue(); |
| } |
| |
| void VideoCaptureManager::OnDeviceLaunchAborted() { |
| EmitLogMessage("Launching device has been aborted.", 1); |
| device_start_request_queue_.pop_front(); |
| ProcessDeviceStartRequestQueue(); |
| } |
| |
| void VideoCaptureManager::OnDeviceConnectionLost( |
| VideoCaptureController* controller) { |
| std::ostringstream string_stream; |
| string_stream << "Lost connection to device. device_id = " |
| << controller->device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| controller->OnError(); |
| } |
| |
| void VideoCaptureManager::ConnectClient( |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params, |
| VideoCaptureControllerID client_id, |
| VideoCaptureControllerEventHandler* client_handler, |
| const DoneCB& done_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| { |
| std::ostringstream string_stream; |
| string_stream << "ConnectClient: session_id = " << session_id |
| << ", request: " |
| << media::VideoCaptureFormat::ToString( |
| params.requested_format); |
| EmitLogMessage(string_stream.str(), 1); |
| } |
| |
| VideoCaptureController* controller = |
| GetOrCreateController(session_id, params); |
| if (!controller) { |
| done_cb.Run(base::WeakPtr<VideoCaptureController>()); |
| return; |
| } |
| |
| LogVideoCaptureEvent(VIDEO_CAPTURE_START_CAPTURE); |
| |
| // First client starts the device. |
| if (!controller->HasActiveClient() && !controller->HasPausedClient()) { |
| std::ostringstream string_stream; |
| string_stream |
| << "VideoCaptureManager queueing device start for device_id = " |
| << controller->device_id(); |
| EmitLogMessage(string_stream.str(), 1); |
| QueueStartDevice(session_id, controller, params); |
| } |
| // Run the callback first, as AddClient() may trigger OnFrameInfo(). |
| done_cb.Run(controller->GetWeakPtrForIOThread()); |
| controller->AddClient(client_id, client_handler, session_id, params); |
| } |
| |
| void VideoCaptureManager::DisconnectClient( |
| VideoCaptureController* controller, |
| VideoCaptureControllerID client_id, |
| VideoCaptureControllerEventHandler* client_handler, |
| bool aborted_due_to_error) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(controller); |
| DCHECK(client_handler); |
| |
| if (!IsControllerPointerValid(controller)) { |
| NOTREACHED(); |
| return; |
| } |
| if (!aborted_due_to_error) { |
| if (controller->has_received_frames()) { |
| LogVideoCaptureEvent(VIDEO_CAPTURE_STOP_CAPTURE_OK); |
| } else if (controller->stream_type() == MEDIA_DEVICE_VIDEO_CAPTURE) { |
| LogVideoCaptureEvent( |
| VIDEO_CAPTURE_STOP_CAPTURE_OK_NO_FRAMES_PRODUCED_BY_DEVICE); |
| } else { |
| LogVideoCaptureEvent( |
| VIDEO_CAPTURE_STOP_CAPTURE_OK_NO_FRAMES_PRODUCED_BY_DESKTOP_OR_TAB); |
| } |
| } else { |
| LogVideoCaptureEvent(VIDEO_CAPTURE_STOP_CAPTURE_DUE_TO_ERROR); |
| for (auto it : sessions_) { |
| if (it.second.type == controller->stream_type() && |
| it.second.id == controller->device_id()) { |
| for (auto& listener : listeners_) |
| listener.Aborted(it.second.type, it.first); |
| // Aborted() call might synchronously destroy |controller|, recheck. |
| if (!IsControllerPointerValid(controller)) |
| return; |
| break; |
| } |
| } |
| } |
| |
| // Detach client from controller. |
| const media::VideoCaptureSessionId session_id = |
| controller->RemoveClient(client_id, client_handler); |
| std::ostringstream string_stream; |
| string_stream << "DisconnectClient: session_id = " << session_id; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| // If controller has no more clients, delete controller and device. |
| DestroyControllerIfNoClients(controller); |
| } |
| |
| void VideoCaptureManager::PauseCaptureForClient( |
| VideoCaptureController* controller, |
| VideoCaptureControllerID client_id, |
| VideoCaptureControllerEventHandler* client_handler) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(controller); |
| DCHECK(client_handler); |
| if (!IsControllerPointerValid(controller)) |
| NOTREACHED() << "Got Null controller while pausing capture"; |
| |
| const bool had_active_client = controller->HasActiveClient(); |
| controller->PauseClient(client_id, client_handler); |
| if (!had_active_client || controller->HasActiveClient()) |
| return; |
| if (!controller->IsDeviceAlive()) |
| return; |
| controller->MaybeSuspend(); |
| } |
| |
| void VideoCaptureManager::ResumeCaptureForClient( |
| media::VideoCaptureSessionId session_id, |
| const media::VideoCaptureParams& params, |
| VideoCaptureController* controller, |
| VideoCaptureControllerID client_id, |
| VideoCaptureControllerEventHandler* client_handler) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(controller); |
| DCHECK(client_handler); |
| |
| if (!IsControllerPointerValid(controller)) |
| NOTREACHED() << "Got Null controller while resuming capture"; |
| |
| const bool had_active_client = controller->HasActiveClient(); |
| controller->ResumeClient(client_id, client_handler); |
| if (had_active_client || !controller->HasActiveClient()) |
| return; |
| if (!controller->IsDeviceAlive()) |
| return; |
| controller->Resume(); |
| } |
| |
| void VideoCaptureManager::RequestRefreshFrameForClient( |
| VideoCaptureController* controller) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (IsControllerPointerValid(controller)) { |
| if (!controller->IsDeviceAlive()) |
| return; |
| controller->RequestRefreshFrame(); |
| } |
| } |
| |
| bool VideoCaptureManager::GetDeviceSupportedFormats( |
| media::VideoCaptureSessionId capture_session_id, |
| media::VideoCaptureFormats* supported_formats) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(supported_formats->empty()); |
| |
| SessionMap::iterator it = sessions_.find(capture_session_id); |
| if (it == sessions_.end()) |
| return false; |
| std::ostringstream string_stream; |
| string_stream << "GetDeviceSupportedFormats for device: " << it->second.name; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| return GetDeviceSupportedFormats(it->second.id, supported_formats); |
| } |
| |
| bool VideoCaptureManager::GetDeviceSupportedFormats( |
| const std::string& device_id, |
| media::VideoCaptureFormats* supported_formats) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(supported_formats->empty()); |
| |
| // Return all available formats of the device, regardless its started state. |
| media::VideoCaptureDeviceInfo* existing_device = GetDeviceInfoById(device_id); |
| if (existing_device) |
| *supported_formats = existing_device->supported_formats; |
| return true; |
| } |
| |
| bool VideoCaptureManager::GetDeviceFormatsInUse( |
| media::VideoCaptureSessionId capture_session_id, |
| media::VideoCaptureFormats* formats_in_use) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(formats_in_use->empty()); |
| |
| SessionMap::iterator it = sessions_.find(capture_session_id); |
| if (it == sessions_.end()) |
| return false; |
| std::ostringstream string_stream; |
| string_stream << "GetDeviceFormatsInUse for device: " << it->second.name; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| base::Optional<media::VideoCaptureFormat> format = |
| GetDeviceFormatInUse(it->second.type, it->second.id); |
| if (format.has_value()) |
| formats_in_use->push_back(format.value()); |
| |
| return true; |
| } |
| |
| base::Optional<media::VideoCaptureFormat> |
| VideoCaptureManager::GetDeviceFormatInUse(MediaStreamType stream_type, |
| const std::string& device_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Return the currently in-use format of the device, if it's started. |
| VideoCaptureController* device_in_use = |
| LookupControllerByMediaTypeAndDeviceId(stream_type, device_id); |
| return device_in_use ? device_in_use->GetVideoCaptureFormat() : base::nullopt; |
| } |
| |
| void VideoCaptureManager::SetDesktopCaptureWindowId( |
| media::VideoCaptureSessionId session_id, |
| gfx::NativeViewId window_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| VLOG(2) << "SetDesktopCaptureWindowId called for session " << session_id; |
| |
| notification_window_ids_[session_id] = window_id; |
| MaybePostDesktopCaptureWindowId(session_id); |
| } |
| |
| void VideoCaptureManager::MaybePostDesktopCaptureWindowId( |
| media::VideoCaptureSessionId session_id) { |
| SessionMap::iterator session_it = sessions_.find(session_id); |
| if (session_it == sessions_.end()) |
| return; |
| |
| VideoCaptureController* const existing_device = |
| LookupControllerByMediaTypeAndDeviceId(session_it->second.type, |
| session_it->second.id); |
| if (!existing_device) { |
| DVLOG(2) << "Failed to find an existing screen capture device."; |
| return; |
| } |
| |
| if (!existing_device->IsDeviceAlive()) { |
| DVLOG(2) << "Screen capture device not yet started."; |
| return; |
| } |
| |
| DCHECK_EQ(MEDIA_GUM_DESKTOP_VIDEO_CAPTURE, existing_device->stream_type()); |
| DesktopMediaID id = DesktopMediaID::Parse(existing_device->device_id()); |
| if (id.is_null()) |
| return; |
| |
| auto window_id_it = notification_window_ids_.find(session_id); |
| if (window_id_it == notification_window_ids_.end()) { |
| DVLOG(2) << "Notification window id not set for screen capture."; |
| return; |
| } |
| |
| existing_device->SetDesktopCaptureWindowIdAsync( |
| window_id_it->second, |
| base::BindOnce([](scoped_refptr<VideoCaptureManager>) {}, |
| scoped_refptr<VideoCaptureManager>(this))); |
| notification_window_ids_.erase(window_id_it); |
| } |
| |
| void VideoCaptureManager::GetPhotoState( |
| int session_id, |
| media::VideoCaptureDevice::GetPhotoStateCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const VideoCaptureController* controller = |
| LookupControllerBySessionId(session_id); |
| if (!controller) |
| return; |
| if (controller->IsDeviceAlive()) { |
| controller->GetPhotoState(std::move(callback)); |
| return; |
| } |
| // Queue up a request for later. |
| photo_request_queue_.emplace_back( |
| session_id, |
| base::Bind(&VideoCaptureController::GetPhotoState, |
| base::Unretained(controller), base::Passed(&callback))); |
| } |
| |
| void VideoCaptureManager::SetPhotoOptions( |
| int session_id, |
| media::mojom::PhotoSettingsPtr settings, |
| media::VideoCaptureDevice::SetPhotoOptionsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| VideoCaptureController* controller = LookupControllerBySessionId(session_id); |
| if (!controller) |
| return; |
| if (controller->IsDeviceAlive()) { |
| controller->SetPhotoOptions(std::move(settings), std::move(callback)); |
| return; |
| } |
| // Queue up a request for later. |
| photo_request_queue_.emplace_back( |
| session_id, base::Bind(&VideoCaptureController::SetPhotoOptions, |
| base::Unretained(controller), |
| base::Passed(&settings), base::Passed(&callback))); |
| } |
| |
| void VideoCaptureManager::TakePhoto( |
| int session_id, |
| media::VideoCaptureDevice::TakePhotoCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| VideoCaptureController* controller = LookupControllerBySessionId(session_id); |
| if (!controller) |
| return; |
| if (controller->IsDeviceAlive()) { |
| controller->TakePhoto(std::move(callback)); |
| return; |
| } |
| // Queue up a request for later. |
| photo_request_queue_.emplace_back( |
| session_id, |
| base::Bind(&VideoCaptureController::TakePhoto, |
| base::Unretained(controller), base::Passed(&callback))); |
| } |
| |
| void VideoCaptureManager::OnOpened( |
| MediaStreamType stream_type, |
| media::VideoCaptureSessionId capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (auto& listener : listeners_) |
| listener.Opened(stream_type, capture_session_id); |
| } |
| |
| void VideoCaptureManager::OnClosed( |
| MediaStreamType stream_type, |
| media::VideoCaptureSessionId capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (auto& listener : listeners_) |
| listener.Closed(stream_type, capture_session_id); |
| } |
| |
| void VideoCaptureManager::OnDeviceInfosReceived( |
| base::ElapsedTimer* timer, |
| const EnumerationCallback& client_callback, |
| const std::vector<media::VideoCaptureDeviceInfo>& device_infos) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| UMA_HISTOGRAM_TIMES( |
| "Media.VideoCaptureManager.GetAvailableDevicesInfoOnDeviceThreadTime", |
| timer->Elapsed()); |
| devices_info_cache_ = device_infos; |
| |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureManager::OnDeviceInfosReceived: Recevied " |
| << device_infos.size() << " device infos."; |
| for (const auto& entry : device_infos) { |
| string_stream << std::endl |
| << "device_id: " << entry.descriptor.device_id |
| << ", display_name: " << entry.descriptor.display_name(); |
| } |
| EmitLogMessage(string_stream.str(), 1); |
| |
| // Walk the |devices_info_cache_| and produce a |
| // media::VideoCaptureDeviceDescriptors for |client_callback|. |
| media::VideoCaptureDeviceDescriptors devices; |
| std::vector<std::tuple<media::VideoCaptureDeviceDescriptor, |
| media::VideoCaptureFormats>> |
| descriptors_and_formats; |
| for (const auto& it : devices_info_cache_) { |
| devices.emplace_back(it.descriptor); |
| descriptors_and_formats.emplace_back(it.descriptor, it.supported_formats); |
| MediaInternals::GetInstance()->UpdateVideoCaptureDeviceCapabilities( |
| descriptors_and_formats); |
| } |
| |
| client_callback.Run(devices); |
| } |
| |
| void VideoCaptureManager::DestroyControllerIfNoClients( |
| VideoCaptureController* controller) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Removal of the last client stops the device. |
| if (!controller->HasActiveClient() && !controller->HasPausedClient()) { |
| std::ostringstream string_stream; |
| string_stream << "VideoCaptureManager stopping device (stream_type = " |
| << controller->stream_type() |
| << ", device_id = " << controller->device_id() << ")"; |
| EmitLogMessage(string_stream.str(), 1); |
| |
| // The VideoCaptureController is removed from |controllers_| immediately. |
| // The controller is deleted immediately, and the device is freed |
| // asynchronously. After this point, subsequent requests to open this same |
| // device ID will create a new VideoCaptureController, |
| // VideoCaptureController, and VideoCaptureDevice. |
| DoStopDevice(controller); |
| // TODO(mcasas): use a helper function https://crbug.com/624854. |
| auto controller_iter = std::find_if( |
| controllers_.begin(), controllers_.end(), |
| [controller]( |
| const scoped_refptr<VideoCaptureController>& device_entry) { |
| return device_entry.get() == controller; |
| }); |
| controllers_.erase(controller_iter); |
| } |
| } |
| |
| VideoCaptureController* VideoCaptureManager::LookupControllerBySessionId( |
| int session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SessionMap::const_iterator session_it = sessions_.find(session_id); |
| if (session_it == sessions_.end()) |
| return nullptr; |
| |
| return LookupControllerByMediaTypeAndDeviceId(session_it->second.type, |
| session_it->second.id); |
| } |
| |
| VideoCaptureController* |
| VideoCaptureManager::LookupControllerByMediaTypeAndDeviceId( |
| MediaStreamType type, |
| const std::string& device_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (const auto& entry : controllers_) { |
| if (type == entry->stream_type() && device_id == entry->device_id()) |
| return entry.get(); |
| } |
| return nullptr; |
| } |
| |
| bool VideoCaptureManager::IsControllerPointerValid( |
| const VideoCaptureController* controller) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return base::ContainsValue(controllers_, controller); |
| } |
| |
| scoped_refptr<VideoCaptureController> |
| VideoCaptureManager::GetControllerSharedRef( |
| VideoCaptureController* controller) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (const auto& entry : controllers_) { |
| if (entry.get() == controller) |
| return entry; |
| } |
| return nullptr; |
| } |
| |
| media::VideoCaptureDeviceInfo* VideoCaptureManager::GetDeviceInfoById( |
| const std::string& id) { |
| for (auto& it : devices_info_cache_) { |
| if (it.descriptor.device_id == id) |
| return ⁢ |
| } |
| return nullptr; |
| } |
| |
| VideoCaptureController* VideoCaptureManager::GetOrCreateController( |
| media::VideoCaptureSessionId capture_session_id, |
| const media::VideoCaptureParams& params) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| SessionMap::iterator session_it = sessions_.find(capture_session_id); |
| if (session_it == sessions_.end()) |
| return nullptr; |
| const MediaStreamDevice& device_info = session_it->second; |
| |
| // Check if another session has already opened this device. If so, just |
| // use that opened device. |
| VideoCaptureController* const existing_device = |
| LookupControllerByMediaTypeAndDeviceId(device_info.type, device_info.id); |
| if (existing_device) { |
| DCHECK_EQ(device_info.type, existing_device->stream_type()); |
| return existing_device; |
| } |
| |
| VideoCaptureController* new_controller = new VideoCaptureController( |
| device_info.id, device_info.type, params, |
| video_capture_provider_->CreateDeviceLauncher(), emit_log_message_cb_); |
| controllers_.emplace_back(new_controller); |
| return new_controller; |
| } |
| |
| base::Optional<CameraCalibration> VideoCaptureManager::GetCameraCalibration( |
| const std::string& device_id) { |
| media::VideoCaptureDeviceInfo* info = GetDeviceInfoById(device_id); |
| if (!info) |
| return base::Optional<CameraCalibration>(); |
| return info->descriptor.camera_calibration; |
| } |
| |
| #if defined(OS_ANDROID) |
| void VideoCaptureManager::OnApplicationStateChange( |
| base::android::ApplicationState state) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Only release/resume devices when the Application state changes from |
| // RUNNING->STOPPED->RUNNING. |
| if (state == base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES && |
| !application_state_has_running_activities_) { |
| ResumeDevices(); |
| application_state_has_running_activities_ = true; |
| } else if (state == base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES) { |
| ReleaseDevices(); |
| application_state_has_running_activities_ = false; |
| } |
| } |
| |
| void VideoCaptureManager::ReleaseDevices() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (auto& controller : controllers_) { |
| // Do not stop Content Video Capture devices, e.g. Tab or Screen capture. |
| if (controller->stream_type() != MEDIA_DEVICE_VIDEO_CAPTURE) |
| continue; |
| |
| DoStopDevice(controller.get()); |
| } |
| } |
| |
| void VideoCaptureManager::ResumeDevices() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (auto& controller : controllers_) { |
| // Do not resume Content Video Capture devices, e.g. Tab or Screen capture. |
| // Do not try to restart already running devices. |
| if (controller->stream_type() != MEDIA_DEVICE_VIDEO_CAPTURE || |
| controller->IsDeviceAlive()) |
| continue; |
| |
| // Check if the device is already in the start queue. |
| bool device_in_queue = false; |
| for (auto& request : device_start_request_queue_) { |
| if (request.controller() == controller.get()) { |
| device_in_queue = true; |
| break; |
| } |
| } |
| |
| if (!device_in_queue) { |
| // Session ID is only valid for Screen capture. So we can fake it to |
| // resume video capture devices here. |
| QueueStartDevice(kFakeSessionId, controller.get(), |
| controller->parameters()); |
| } |
| } |
| } |
| #endif // defined(OS_ANDROID) |
| |
| void VideoCaptureManager::EmitLogMessage(const std::string& message, |
| int verbose_log_level) { |
| DVLOG(verbose_log_level) << message; |
| emit_log_message_cb_.Run(message); |
| } |
| |
| } // namespace content |