blob: 2949c2abd5f97e66719ec0f77cb23813865bb701 [file] [log] [blame] [edit]
// Copyright 2012 The Chromium Authors
// 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 <memory>
#include <set>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "build/android_buildflags.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/browser/screenlock_monitor/screenlock_monitor.h"
#include "content/common/features.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/common/content_client.h"
#include "media/base/media_switches.h"
#include "media/base/video_facing.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "media/capture/video/video_capture_device.h"
#include "services/video_effects/public/cpp/buildflags.h"
#include "services/video_effects/public/mojom/video_effects_processor.mojom-forward.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
namespace {
void LogVideoCaptureError(media::VideoCaptureError error) {
base::UmaHistogramEnumeration("Media.VideoCapture.Error", error);
}
const base::UnguessableToken& FakeSessionId() {
// TODO(crbug.com/40252973): Investigate whether there's a better way
// to accomplish this (without using UnguessableToken::Deserialize).
static const base::UnguessableToken fake_session_id(
base::UnguessableToken::Deserialize(0xFFFFFFFFFFFFFFFFU,
0xFFFFFFFFFFFFFFFFU)
.value());
return fake_session_id;
}
} // namespace
namespace content {
// Class used for queuing request for starting a device.
class VideoCaptureManager::CaptureDeviceStartRequest {
public:
CaptureDeviceStartRequest(
scoped_refptr<VideoCaptureController> controller,
const media::VideoCaptureSessionId& session_id,
const media::VideoCaptureParams& params,
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor,
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>
readonly_video_effects_manager);
scoped_refptr<VideoCaptureController> controller() const {
return controller_;
}
const base::UnguessableToken& session_id() const { return session_id_; }
media::VideoCaptureParams params() const { return params_; }
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>&&
TakeVideoEffectsProcessor() {
return std::move(video_effects_processor_);
}
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>&&
TakeReadonlyVideoEffectsManager() {
return std::move(readonly_video_effects_manager_);
}
private:
const scoped_refptr<VideoCaptureController> controller_;
const base::UnguessableToken session_id_;
const media::VideoCaptureParams params_;
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor_;
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>
readonly_video_effects_manager_;
};
VideoCaptureManager::CaptureDeviceStartRequest::CaptureDeviceStartRequest(
scoped_refptr<VideoCaptureController> controller,
const media::VideoCaptureSessionId& session_id,
const media::VideoCaptureParams& params,
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor,
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>
readonly_video_effects_manager)
: controller_(std::move(controller)),
session_id_(session_id),
params_(params),
video_effects_processor_(std::move(video_effects_processor)),
readonly_video_effects_manager_(
std::move(readonly_video_effects_manager)) {}
VideoCaptureManager::VideoCaptureManager(
std::unique_ptr<VideoCaptureProvider> video_capture_provider,
base::RepeatingCallback<void(const std::string&)> emit_log_message_cb)
: video_capture_provider_(std::move(video_capture_provider)),
emit_log_message_cb_(std::move(emit_log_message_cb)) {
ScreenlockMonitor* screenlock_monitor = ScreenlockMonitor::Get();
if (screenlock_monitor) {
screenlock_monitor->AddObserver(this);
}
}
VideoCaptureManager::~VideoCaptureManager() {
DCHECK(device_start_request_queue_.empty());
ScreenlockMonitor* screenlock_monitor = ScreenlockMonitor::Get();
if (screenlock_monitor) {
screenlock_monitor->RemoveObserver(this);
}
}
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 BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_DESKTOP_ANDROID)
application_state_has_running_activities_ = true;
app_status_listener_ =
base::android::ApplicationStatusListener::New(base::BindRepeating(
&VideoCaptureManager::OnApplicationStateChange, this));
#endif
}
void VideoCaptureManager::UnregisterListener(
MediaStreamProviderListener* listener) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
listeners_.RemoveObserver(listener);
}
void VideoCaptureManager::EnumerateDevices(
EnumerationCallback client_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::EnumerateDevices");
EmitLogMessage("VideoCaptureManager::EnumerateDevices", 1);
// Pass a timer for UMA histogram collection.
video_capture_provider_->GetDeviceInfosAsync(
base::BindPostTaskToCurrentDefault(
base::BindOnce(&VideoCaptureManager::OnDeviceInfosReceived, this,
base::ElapsedTimer(), std::move(client_callback))));
}
base::UnguessableToken VideoCaptureManager::Open(
const blink::MediaStreamDevice& device) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::Open");
// Generate a new id for the session being opened.
const base::UnguessableToken capture_session_id =
base::UnguessableToken::Create();
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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureManager::OnOpened, this,
device.type, capture_session_id));
return capture_session_id;
}
void VideoCaptureManager::Close(
const base::UnguessableToken& capture_session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::Close");
std::ostringstream string_stream;
string_stream << "VideoCaptureManager::Close, capture_session_id = "
<< capture_session_id;
EmitLogMessage(string_stream.str(), 1);
auto session_it = sessions_.find(capture_session_id);
if (session_it == sessions_.end()) {
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(capture_session_id, existing_device);
}
// Notify listeners asynchronously, and forget the session.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureManager::OnClosed, this,
session_it->second.type, capture_session_id));
if (blink::IsDeviceMediaType(session_it->second.type)) {
auto locked_it = locked_sessions_.find(session_it->first);
const bool was_locked = locked_it != locked_sessions_.end();
if (was_locked)
locked_sessions_.erase(locked_it);
if (locked_sessions_.empty() && !lock_time_.is_null()) {
lock_time_ = base::TimeTicks();
idle_close_timer_.Stop();
}
} else {
DCHECK(!locked_sessions_.contains(session_it->first));
}
sessions_.erase(session_it);
}
void VideoCaptureManager::ApplySubCaptureTarget(
const base::UnguessableToken& session_id,
media::mojom::SubCaptureTargetType type,
const base::Token& target,
uint32_t sub_capture_target_version,
base::OnceCallback<void(media::mojom::ApplySubCaptureTargetResult)>
callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
VideoCaptureController* const controller =
LookupControllerBySessionId(session_id);
if (!controller || !controller->IsDeviceAlive()) {
std::move(callback).Run(
media::mojom::ApplySubCaptureTargetResult::kErrorGeneric);
return;
}
controller->ApplySubCaptureTarget(type, target, sub_capture_target_version,
std::move(callback));
}
void VideoCaptureManager::QueueStartDevice(
const media::VideoCaptureSessionId& session_id,
scoped_refptr<VideoCaptureController> controller,
const media::VideoCaptureParams& params,
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor,
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>
readonly_video_effects_manager) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(lock_time_.is_null());
device_start_request_queue_.push_back(
CaptureDeviceStartRequest(std::move(controller), session_id, params,
std::move(video_effects_processor),
std::move(readonly_video_effects_manager)));
if (device_start_request_queue_.size() == 1)
ProcessDeviceStartRequestQueue();
}
void VideoCaptureManager::DoStopDevice(VideoCaptureController* controller) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::DoStopDevice");
DCHECK(base::Contains(controllers_, controller));
// 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.
if (!device_start_request_queue_.empty()) {
auto second_request = std::next(device_start_request_queue_.begin());
for (auto it = second_request; it != device_start_request_queue_.end();) {
if (it->controller() == controller)
it = device_start_request_queue_.erase(it);
else
++it;
}
}
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::DoNothingWithBoundArgs(GetControllerSharedRef(controller)));
}
void VideoCaptureManager::ProcessDeviceStartRequestQueue() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::ProcessDeviceStartRequestQueue");
auto request = device_start_request_queue_.begin();
if (request == device_start_request_queue_.end())
return;
scoped_refptr<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() ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
const media::VideoCaptureDeviceInfo* device_info =
GetDeviceInfoById(controller->device_id());
if (!device_info) {
OnDeviceLaunchFailed(
controller.get(),
media::VideoCaptureError::
kVideoCaptureManagerProcessDeviceStartQueueDeviceInfoNotFound);
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),
std::move(controller)),
request->TakeVideoEffectsProcessor(),
request->TakeReadonlyVideoEffectsManager());
}
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 (blink::IsVideoDesktopCaptureMediaType(controller->stream_type())) {
const media::VideoCaptureSessionId session_id =
device_start_request_queue_.front().session_id();
DCHECK_NE(session_id, FakeSessionId());
MaybePostDesktopCaptureWindowId(session_id);
}
auto it = photo_request_queue_.begin();
while (it != photo_request_queue_.end()) {
VideoCaptureController* maybe_entry =
LookupControllerBySessionId(it->first);
if (maybe_entry && maybe_entry->IsDeviceAlive()) {
std::move(it->second).Run();
it = photo_request_queue_.erase(it);
} else {
++it;
}
}
device_start_request_queue_.pop_front();
ProcessDeviceStartRequestQueue();
}
void VideoCaptureManager::OnDeviceLaunchFailed(
VideoCaptureController* controller,
media::VideoCaptureError error) {
std::ostringstream string_stream;
string_stream << "Launching device has failed. device_id = "
<< controller->device_id();
EmitLogMessage(string_stream.str(), 1);
controller->OnError(error);
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(
media::VideoCaptureError::kVideoCaptureManagerDeviceConnectionLost);
}
void VideoCaptureManager::OpenNativeScreenCapturePicker(
DesktopMediaID::Type type,
base::OnceCallback<void(DesktopMediaID::Id)> created_callback,
base::OnceCallback<void(webrtc::DesktopCapturer::Source)> picker_callback,
base::OnceCallback<void()> cancel_callback,
base::OnceCallback<void()> error_callback) {
video_capture_provider_->OpenNativeScreenCapturePicker(
type, std::move(created_callback), std::move(picker_callback),
std::move(cancel_callback), std::move(error_callback));
}
void VideoCaptureManager::CloseNativeScreenCapturePicker(
DesktopMediaID device_id) {
video_capture_provider_->CloseNativeScreenCapturePicker(device_id);
}
void VideoCaptureManager::ConnectClient(
const media::VideoCaptureSessionId& session_id,
const media::VideoCaptureParams& params,
VideoCaptureControllerID client_id,
VideoCaptureControllerEventHandler* client_handler,
std::optional<url::Origin> origin,
DoneCB done_cb,
BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::ConnectClient");
{
std::ostringstream string_stream;
string_stream << "ConnectClient: session_id = " << session_id
<< ", request: "
<< media::VideoCaptureFormat::ToString(
params.requested_format);
EmitLogMessage(string_stream.str(), 1);
}
scoped_refptr<VideoCaptureController> controller =
GetOrCreateController(session_id, params);
if (!controller) {
std::move(done_cb).Run(nullptr);
return;
}
bool client_exist =
controller->HasActiveClient() || controller->HasPausedClient();
base::UmaHistogramBoolean("Media.VideoCapture.StreamShared", client_exist);
if (client_exist) {
std::optional<url::Origin> first_client_origin =
controller->GetFirstClientOrigin();
bool same_origin = first_client_origin.has_value() && origin.has_value() &&
*first_client_origin == *origin;
base::UmaHistogramBoolean("Media.VideoCapture.StreamSharedSameOrigin",
same_origin);
}
// First client starts the device. Device can't be started while the screen is
// locked.
if (!client_exist && lock_time_.is_null()) {
std::ostringstream string_stream;
string_stream
<< "VideoCaptureManager queueing device start for device_id = "
<< controller->device_id();
EmitLogMessage(string_stream.str(), 1);
mojo::PendingRemote<video_effects::mojom::VideoEffectsProcessor>
video_effects_processor;
mojo::PendingRemote<media::mojom::ReadonlyVideoEffectsManager>
readonly_video_effects_manager;
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
// Only create the video effects processor for DEVICE_VIDEO_CAPTURE media
// streams, i.e. cameras.
if (base::FeatureList::IsEnabled(media::kCameraMicEffects) &&
controller->stream_type() ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
auto* content_client = GetContentClient();
if (browser_context && content_client && content_client->browser()) {
content_client->browser()->BindVideoEffectsProcessor(
controller->device_id(), browser_context,
video_effects_processor.InitWithNewPipeAndPassReceiver());
}
content_client->browser()->BindReadonlyVideoEffectsManager(
controller->device_id(), browser_context,
readonly_video_effects_manager.InitWithNewPipeAndPassReceiver());
}
#endif // BUILDFLAG(ENABLE_VIDEO_EFFECTS)
QueueStartDevice(session_id, controller, params,
std::move(video_effects_processor),
std::move(readonly_video_effects_manager));
}
// Run the callback first, as AddClient() may trigger OnFrameInfo().
std::move(done_cb).Run(controller->GetWeakPtrForIOThread());
controller->AddClient(client_id, client_handler, session_id, params, origin);
}
void VideoCaptureManager::DisconnectClient(
VideoCaptureController* controller,
VideoCaptureControllerID client_id,
VideoCaptureControllerEventHandler* client_handler,
media::VideoCaptureError error) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::DisconnectClient");
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(controller);
DCHECK(client_handler);
CHECK(IsControllerPointerValid(controller));
if (error != media::VideoCaptureError::kNone) {
LogVideoCaptureError(error);
std::ostringstream string_stream;
string_stream << "Video capture session stopped with error code "
<< static_cast<int>(error);
EmitLogMessage(string_stream.str(), 1);
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(session_id, 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(
const 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";
}
if (!controller->ResumeClient(client_id, client_handler)) {
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(
const media::VideoCaptureSessionId& capture_session_id,
media::VideoCaptureFormats* supported_formats) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(supported_formats->empty());
auto 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(
const media::VideoCaptureSessionId& capture_session_id,
media::VideoCaptureFormats* formats_in_use) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(formats_in_use->empty());
auto 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);
std::optional<media::VideoCaptureFormat> format =
GetDeviceFormatInUse(it->second.type, it->second.id);
if (format.has_value())
formats_in_use->push_back(format.value());
return true;
}
std::optional<media::VideoCaptureFormat>
VideoCaptureManager::GetDeviceFormatInUse(
blink::mojom::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() : std::nullopt;
}
GlobalRenderFrameHostId VideoCaptureManager::GetGlobalRenderFrameHostId(
const base::UnguessableToken& session_id) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
VideoCaptureController* const controller =
LookupControllerBySessionId(session_id);
if (!controller || !controller->IsDeviceAlive() ||
!blink::IsVideoDesktopCaptureMediaType(controller->stream_type())) {
return GlobalRenderFrameHostId();
}
const DesktopMediaID desktop_media_id =
DesktopMediaID::Parse(controller->device_id());
if (desktop_media_id.type != DesktopMediaID::Type::TYPE_WEB_CONTENTS ||
desktop_media_id.web_contents_id.is_null()) {
return GlobalRenderFrameHostId();
}
return GlobalRenderFrameHostId(
desktop_media_id.web_contents_id.render_process_id,
desktop_media_id.web_contents_id.main_render_frame_id);
}
void VideoCaptureManager::SetDesktopCaptureWindowId(
const 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);
if (set_desktop_capture_window_id_callback_for_testing_) {
set_desktop_capture_window_id_callback_for_testing_.Run(session_id,
window_id);
}
}
void VideoCaptureManager::MaybePostDesktopCaptureWindowId(
const media::VideoCaptureSessionId& session_id) {
auto 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(blink::IsVideoDesktopCaptureMediaType(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::DoNothingWithBoundArgs(scoped_refptr<VideoCaptureManager>(this)));
notification_window_ids_.erase(window_id_it);
}
void VideoCaptureManager::GetPhotoState(
const base::UnguessableToken& session_id,
media::VideoCaptureDevice::GetPhotoStateCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
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::BindOnce(&VideoCaptureController::GetPhotoState,
controller->GetWeakPtrForIOThread(), std::move(callback)));
}
void VideoCaptureManager::SetPhotoOptions(
const base::UnguessableToken& 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::BindOnce(&VideoCaptureController::SetPhotoOptions,
controller->GetWeakPtrForIOThread(),
std::move(settings), std::move(callback)));
}
void VideoCaptureManager::TakePhoto(
const base::UnguessableToken& session_id,
media::VideoCaptureDevice::TakePhotoCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::TakePhoto");
VideoCaptureController* controller = LookupControllerBySessionId(session_id);
if (!controller)
return;
if (controller->IsDeviceAlive()) {
controller->TakePhoto(std::move(callback));
return;
}
// Queue up a request for later.
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::TakePhoto enqueuing request");
photo_request_queue_.emplace_back(
session_id,
base::BindOnce(&VideoCaptureController::TakePhoto,
controller->GetWeakPtrForIOThread(), std::move(callback)));
}
void VideoCaptureManager::OnOpened(
blink::mojom::MediaStreamType stream_type,
const media::VideoCaptureSessionId& capture_session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (auto& listener : listeners_)
listener.Opened(stream_type, capture_session_id);
}
void VideoCaptureManager::OnClosed(
blink::mojom::MediaStreamType stream_type,
const 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,
EnumerationCallback client_callback,
media::mojom::DeviceEnumerationResult error_code,
const std::vector<media::VideoCaptureDeviceInfo>& device_infos) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureManager::OnDeviceInfosReceived");
if (error_code != media::mojom::DeviceEnumerationResult::kSuccess) {
EmitLogMessage(
base::StringPrintf("VideoCaptureManager::OnDeviceInfosReceived: Failed "
"to list device infos with error_code %d",
static_cast<int>(error_code)),
0);
std::move(client_callback).Run(error_code, {});
return;
}
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);
}
std::move(client_callback).Run(error_code, devices);
}
void VideoCaptureManager::DestroyControllerIfNoClients(
const base::UnguessableToken& capture_session_id,
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);
// Close the native OS picker as the associated VideoCaptureDevice is being
// closed.
CloseNativeScreenCapturePicker(
DesktopMediaID::Parse(controller->device_id()));
// 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::ranges::find(
controllers_, controller, &scoped_refptr<VideoCaptureController>::get);
controllers_.erase(controller_iter);
// Check if there are any associated pending callbacks and delete them.
base::EraseIf(photo_request_queue_,
[capture_session_id](const auto& request) {
return request.first == capture_session_id;
});
}
}
VideoCaptureController* VideoCaptureManager::LookupControllerBySessionId(
const base::UnguessableToken& session_id) const {
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(
blink::mojom::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::Contains(controllers_, controller,
&scoped_refptr<VideoCaptureController>::get);
}
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 &it;
}
return nullptr;
}
scoped_refptr<VideoCaptureController>
VideoCaptureManager::GetOrCreateController(
const media::VideoCaptureSessionId& capture_session_id,
const media::VideoCaptureParams& params) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto session_it = sessions_.find(capture_session_id);
if (session_it == sessions_.end())
return nullptr;
const blink::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());
if (existing_device->was_crop_ever_called()) {
return nullptr;
}
return existing_device;
}
scoped_refptr<VideoCaptureController> new_controller =
base::MakeRefCounted<VideoCaptureController>(
device_info.id, device_info.type, params,
video_capture_provider_->CreateDeviceLauncher(),
emit_log_message_cb_);
controllers_.push_back(new_controller);
return new_controller;
}
#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_DESKTOP_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;
}
}
#endif
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() !=
blink::mojom::MediaStreamType::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() !=
blink::mojom::MediaStreamType::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(FakeSessionId(), controller.get(),
controller->parameters(), {}, {});
}
}
}
void VideoCaptureManager::OnScreenLocked() {
#if !BUILDFLAG(IS_ANDROID)
// Stop screen sharing when screen is locked on desktop platforms only.
DCHECK_CURRENTLY_ON(BrowserThread::IO);
EmitLogMessage("VideoCaptureManager::OnScreenLocked", 1);
std::vector<media::VideoCaptureSessionId> desktopcapture_session_ids;
for (auto it : sessions_) {
if (blink::IsDesktopCaptureMediaType(it.second.type))
desktopcapture_session_ids.push_back(it.first);
if (blink::IsDeviceMediaType(it.second.type))
locked_sessions_.insert(it.first);
}
if (!locked_sessions_.empty()) {
DCHECK(lock_time_.is_null());
lock_time_ = base::TimeTicks::Now();
idle_close_timer_.Start(FROM_HERE, idle_close_timeout_, this,
&VideoCaptureManager::ReleaseDevices);
}
for (auto session_id : desktopcapture_session_ids) {
Close(session_id);
}
#endif // !BUILDFLAG(IS_ANDROID)
}
void VideoCaptureManager::OnScreenUnlocked() {
EmitLogMessage("VideoCaptureManager::OnScreenUnlocked", 1);
if (lock_time_.is_null())
return;
DCHECK(!locked_sessions_.empty());
lock_time_ = base::TimeTicks();
idle_close_timer_.Stop();
ResumeDevices();
}
void VideoCaptureManager::EmitLogMessage(const std::string& message,
int verbose_log_level) {
DVLOG(verbose_log_level) << message;
emit_log_message_cb_.Run(message);
}
} // namespace content