// Copyright 2022 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/media/capture/video_capture_device_proxy_lacros.h"
#include <memory>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/token.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "chromeos/crosapi/mojom/video_capture.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/browser/desktop_media_id.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/device/public/mojom/wake_lock.mojom.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "services/video_capture/lacros/video_frame_handler_proxy_lacros.h"
#include "services/video_capture/public/cpp/receiver_media_to_mojo_adapter.h"
namespace content {
namespace {
const int kRequestRefreshFrameMinVersion = crosapi::mojom::VideoCaptureDevice::
MethodMinVersions::kRequestRefreshFrameMinVersion;
void BindWakeLockProvider(
mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetDeviceService().BindWakeLockProvider(std::move(receiver));
}
} // namespace
VideoCaptureDeviceProxyLacros::VideoCaptureDeviceProxyLacros(
const DesktopMediaID& device_id)
: capture_id_(device_id) {
CHECK(capture_id_.type == DesktopMediaID::TYPE_SCREEN ||
capture_id_.type == DesktopMediaID::TYPE_WINDOW);
// The LacrosService exists at all times except during early start-up and
// late shut-down. This class should never be used in those two times.
auto* lacros_service = chromeos::LacrosService::Get();
CHECK(lacros_service);
CHECK(lacros_service->IsAvailable<crosapi::mojom::ScreenManager>());
lacros_service->BindScreenManagerReceiver(
screen_manager_.BindNewPipeAndPassReceiver());
screen_manager_.set_disconnect_handler(base::BindOnce(
&VideoCaptureDeviceProxyLacros::OnFatalError, base::Unretained(this),
"Mojo connection to screen manager was closed"));
}
VideoCaptureDeviceProxyLacros::~VideoCaptureDeviceProxyLacros() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!receiver_adapter_)
<< "StopAndDeAllocate() was never called after start.";
}
void VideoCaptureDeviceProxyLacros::AllocateAndStartWithReceiver(
const media::VideoCaptureParams& params,
std::unique_ptr<media::VideoFrameReceiver> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!device_);
// If the device has already ended on a fatal error, or screen_manager_ was
// disconnected, abort immediately.
if (fatal_error_message_) {
receiver->OnLog(*fatal_error_message_);
receiver->OnError(
media::VideoCaptureError::
kLacrosVideoCaptureDeviceProxyAlreadyEndedOnFatalError);
return;
}
switch (capture_id_.type) {
case DesktopMediaID::TYPE_SCREEN:
screen_manager_->GetScreenVideoCapturer(
device_.BindNewPipeAndPassReceiver(), capture_id_.id);
break;
case DesktopMediaID::TYPE_WINDOW:
screen_manager_->GetWindowVideoCapturer(
device_.BindNewPipeAndPassReceiver(), capture_id_.id);
break;
case DesktopMediaID::TYPE_NONE:
case DesktopMediaID::TYPE_WEB_CONTENTS:
LOG(FATAL) << "Unknown Type: " << capture_id_.type;
}
device_.set_disconnect_handler(base::BindOnce(
&VideoCaptureDeviceProxyLacros::OnFatalError, base::Unretained(this),
"Mojom connection to device was closed"));
// Note that currently all versioned calls that we need to make are
// best effort, and can just be dropped if we haven't gotten an updated
// version yet. If that changes, we'll need to track that we have an
// outstanding query and respond accordingly.
device_.QueryVersion(base::DoNothing());
// Adapt the media::VideoFrameReceiver we've received to a
// crosapi::mojom::VideoFrameHandler remote that we can pass over crosapi to
// let ash-chrome pass us captured frames.
mojo::PendingRemote<crosapi::mojom::VideoFrameHandler>
pending_crosapi_remote_proxy;
receiver_adapter_ =
std::make_unique<video_capture::ReceiverMediaToCrosapiAdapter>(
pending_crosapi_remote_proxy.InitWithNewPipeAndPassReceiver(),
std::move(receiver));
device_->Start(params, std::move(pending_crosapi_remote_proxy));
DCHECK(!wake_lock_);
RequestWakeLock();
}
void VideoCaptureDeviceProxyLacros::RequestRefreshFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_ && device_.version() >= kRequestRefreshFrameMinVersion)
device_->RequestRefreshFrame();
}
void VideoCaptureDeviceProxyLacros::MaybeSuspend() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->MaybeSuspend();
}
void VideoCaptureDeviceProxyLacros::Resume() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->Resume();
}
void VideoCaptureDeviceProxyLacros::ApplySubCaptureTarget(
media::mojom::SubCaptureTargetType type,
const base::Token& target,
uint32_t sub_capture_target_version,
base::OnceCallback<void(media::mojom::ApplySubCaptureTargetResult)>
callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
std::move(callback).Run(
media::mojom::ApplySubCaptureTargetResult::kUnsupportedCaptureDevice);
}
void VideoCaptureDeviceProxyLacros::StopAndDeAllocate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (wake_lock_) {
wake_lock_->CancelWakeLock();
wake_lock_.reset();
}
device_.reset();
receiver_adapter_.reset();
}
void VideoCaptureDeviceProxyLacros::GetPhotoState(
GetPhotoStateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->GetPhotoState(std::move(callback));
}
void VideoCaptureDeviceProxyLacros::SetPhotoOptions(
media::mojom::PhotoSettingsPtr settings,
SetPhotoOptionsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->SetPhotoOptions(std::move(settings), std::move(callback));
}
void VideoCaptureDeviceProxyLacros::TakePhoto(TakePhotoCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->TakePhoto(std::move(callback));
}
void VideoCaptureDeviceProxyLacros::OnUtilizationReport(
media::VideoCaptureFeedback feedback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (device_)
device_->ProcessFeedback(std::move(feedback));
}
void VideoCaptureDeviceProxyLacros::OnFatalError(std::string message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
fatal_error_message_ = std::move(message);
if (receiver_adapter_) {
receiver_adapter_->OnLog(*fatal_error_message_);
receiver_adapter_->OnError(
media::VideoCaptureError::
kLacrosVideoCaptureDeviceProxyEncounteredFatalError);
}
StopAndDeAllocate();
}
void VideoCaptureDeviceProxyLacros::RequestWakeLock() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
mojo::Remote<device::mojom::WakeLockProvider> wake_lock_provider;
auto receiver = wake_lock_provider.BindNewPipeAndPassReceiver();
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&BindWakeLockProvider, std::move(receiver)));
wake_lock_provider->GetWakeLockWithoutContext(
device::mojom::WakeLockType::kPreventDisplaySleep,
device::mojom::WakeLockReason::kOther, "screen capture",
wake_lock_.BindNewPipeAndPassReceiver());
wake_lock_->RequestWakeLock();
}
void VideoCaptureDeviceProxyLacros::AllocateAndStart(
const media::VideoCaptureParams& params,
std::unique_ptr<media::VideoCaptureDevice::Client> client) {
// VideoCaptureDeviceProxyLacros does not use a
// VideoCaptureDevice::Client. Instead, it provides frames to a
// VideoFrameReceiver directly.
NOTREACHED_IN_MIGRATION();
}
} // namespace content