#include "content/browser/media/media_interface_proxy.h"
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "content/browser/media/cdm_storage_common.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/media_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/cdm_info.h"
#include "content/public/common/content_client.h"
#include "media/base/cdm_context.h"
#include "media/cdm/cdm_type.h"
#include "media/media_buildflags.h"
#include "media/mojo/buildflags.h"
#include "media/mojo/mojom/frame_interface_factory.mojom.h"
#include "media/mojo/mojom/media_service.mojom.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#if BUILDFLAG(ENABLE_MOJO_CDM)
#include "content/public/browser/browser_context.h"
#include "content/public/browser/provision_fetcher_impl.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#endif
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "content/browser/media/cdm_storage_manager.h"
#include "media/base/key_system_names.h"
#include "media/mojo/mojom/cdm_service.mojom.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#endif
#if BUILDFLAG(IS_WIN) || BUILDFLAG(ENABLE_LIBRARY_CDMS)
#include "content/browser/media/cdm_registry_impl.h"
#include "content/browser/media/service_factory.h"
#include "media/base/media_switches.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "content/browser/media/dcomp_surface_registry_broker.h"
#include "media/base/win/mf_feature_checks.h"
#include "media/cdm/win/media_foundation_cdm.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/media/flinging_renderer.h"
#include "media/mojo/services/mojo_renderer_service.h"
#endif
#if BUILDFLAG(ALLOW_OOP_VIDEO_DECODER)
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/message.h"
#endif
#if BUILDFLAG(IS_ARKWEB)
#include "arkweb/chromium_ext/content/browser/media/media_interface_proxy_for_include.cc"
#endif
namespace content {
namespace {
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
bool IsValidCdmDisplayName(const std::string& cdm_name) {
constexpr size_t kMaxCdmNameSize = 256;
return cdm_name.size() <= kMaxCdmNameSize && base::IsStringASCII(cdm_name);
}
#endif
#if BUILDFLAG(IS_CHROMEOS)
enum class CrosCdmType {
kChromeCdm = 0,
kPlatformCdm = 1,
kMaxValue = kPlatformCdm,
};
void ReportCdmTypeUMA(CrosCdmType cdm_type) {
UMA_HISTOGRAM_ENUMERATION("Media.EME.CrosCdmType", cdm_type);
}
#endif
constexpr base::TimeDelta kSecondaryInstanceIdleTimeout = base::Seconds(5);
void MaybeLaunchSecondaryMediaService(
mojo::Remote<media::mojom::MediaService>* remote) {
*remote = GetContentClient()->browser()->RunSecondaryMediaService();
if (*remote) {
remote->reset_on_disconnect();
remote->reset_on_idle_timeout(kSecondaryInstanceIdleTimeout);
} else {
std::ignore = remote->BindNewPipeAndPassReceiver();
}
}
media::mojom::MediaService& GetSecondaryMediaService() {
static base::NoDestructor<mojo::Remote<media::mojom::MediaService>> remote;
if (!*remote)
MaybeLaunchSecondaryMediaService(remote.get());
return *remote->get();
}
class FrameInterfaceFactoryImpl : public media::mojom::FrameInterfaceFactory,
public WebContentsObserver {
public:
FrameInterfaceFactoryImpl(RenderFrameHost* render_frame_host,
const media::CdmType& cdm_type)
: WebContentsObserver(
WebContents::FromRenderFrameHost(render_frame_host)),
render_frame_host_(render_frame_host),
cdm_type_(cdm_type) {}
void CreateProvisionFetcher(
mojo::PendingReceiver<media::mojom::ProvisionFetcher> receiver) override {
#if BUILDFLAG(ENABLE_MOJO_CDM)
ProvisionFetcherImpl::Create(render_frame_host_->GetBrowserContext()
->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess(),
std::move(receiver));
#endif
}
void CreateCdmStorage(
mojo::PendingReceiver<media::mojom::CdmStorage> receiver) override {
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
if (cdm_type_.is_zero())
return;
auto storage_key =
static_cast<RenderFrameHostImpl*>(render_frame_host_)->GetStorageKey();
CdmStorageManager* cdm_storage_manager = static_cast<CdmStorageManager*>(
render_frame_host_->GetStoragePartition()->GetCdmStorageDataModel());
cdm_storage_manager->OpenCdmStorage(
CdmStorageBindingContext(storage_key, cdm_type_), std::move(receiver));
#endif
}
#if BUILDFLAG(IS_WIN)
void RegisterMuteStateObserver(
mojo::PendingRemote<media::mojom::MuteStateObserver> observer) override {
auto remote_id = site_mute_observers_.Add(std::move(observer));
site_mute_observers_.Get(remote_id)->OnMuteStateChange(
WebContents::FromRenderFrameHost(render_frame_host_)->IsAudioMuted());
}
void CreateDCOMPSurfaceRegistry(
mojo::PendingReceiver<media::mojom::DCOMPSurfaceRegistry> receiver)
override {
if (media::SupportMediaFoundationPlayback()) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<DCOMPSurfaceRegistryBroker>(), std::move(receiver));
}
}
#endif
void GetCdmOrigin(GetCdmOriginCallback callback) override {
return std::move(callback).Run(
render_frame_host_->GetLastCommittedOrigin());
}
void BindEmbedderReceiver(mojo::GenericPendingReceiver receiver) override {
GetContentClient()->browser()->BindMediaServiceReceiver(
render_frame_host_, std::move(receiver));
}
#if BUILDFLAG(IS_WIN)
void DidUpdateAudioMutingState(bool muted) override {
for (const auto& observer : site_mute_observers_)
observer->OnMuteStateChange(muted);
}
#endif
private:
const raw_ptr<RenderFrameHost> render_frame_host_;
const media::CdmType cdm_type_;
#if BUILDFLAG(IS_WIN)
mojo::RemoteSet<media::mojom::MuteStateObserver> site_mute_observers_;
#endif
};
}
MediaInterfaceProxy::MediaInterfaceProxy(RenderFrameHost* render_frame_host)
: DocumentUserData(render_frame_host) {
DVLOG(1) << __func__;
media::CdmType cdm_type;
#if BUILDFLAG(IS_CHROMEOS)
cdm_type = kChromeOsCdmType;
#endif
auto frame_factory_getter = base::BindRepeating(
&MediaInterfaceProxy::GetFrameServices, base::Unretained(this), cdm_type);
media_interface_factory_ptr_ = std::make_unique<MediaInterfaceFactoryHolder>(
base::BindRepeating(&GetMediaService), frame_factory_getter);
secondary_interface_factory_ = std::make_unique<MediaInterfaceFactoryHolder>(
base::BindRepeating(&GetSecondaryMediaService), frame_factory_getter);
}
MediaInterfaceProxy::~MediaInterfaceProxy() {
DVLOG(1) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
}
void MediaInterfaceProxy::Bind(
mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) {
receivers_.Add(this, std::move(receiver));
}
void MediaInterfaceProxy::CreateAudioDecoder(
mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
InterfaceFactory* factory = media_interface_factory_ptr_->Get();
if (factory)
factory->CreateAudioDecoder(std::move(receiver));
}
void MediaInterfaceProxy::CreateVideoDecoder(
mojo::PendingReceiver<media::mojom::VideoDecoder> receiver,
mojo::PendingRemote<media::mojom::VideoDecoder> dst_video_decoder) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!dst_video_decoder);
InterfaceFactory* factory = media_interface_factory_ptr_->Get();
if (!factory)
return;
mojo::PendingRemote<media::mojom::VideoDecoder> oop_video_decoder;
#if BUILDFLAG(ALLOW_OOP_VIDEO_DECODER)
if (media::IsOutOfProcessVideoDecodingEnabled()) {
render_frame_host().GetProcess()->CreateOOPVideoDecoder(
oop_video_decoder.InitWithNewPipeAndPassReceiver());
}
#endif
factory->CreateVideoDecoder(std::move(receiver),
std::move(oop_video_decoder));
}
#if BUILDFLAG(ALLOW_OOP_VIDEO_DECODER)
void MediaInterfaceProxy::CreateVideoDecoderWithTracker(
mojo::PendingReceiver<media::mojom::VideoDecoder> receiver,
mojo::PendingRemote<media::mojom::VideoDecoderTracker> tracker) {
CHECK(mojo::IsInMessageDispatch());
mojo::ReportBadMessage("CreateVideoDecoderWithTracker() called unexpectedly");
}
#endif
void MediaInterfaceProxy::CreateAudioEncoder(
mojo::PendingReceiver<media::mojom::AudioEncoder> receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
InterfaceFactory* factory = media_interface_factory_ptr_->Get();
if (factory)
factory->CreateAudioEncoder(std::move(receiver));
}
void MediaInterfaceProxy::CreateDefaultRenderer(
const std::string& audio_device_id,
mojo::PendingReceiver<media::mojom::Renderer> receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
InterfaceFactory* factory = media_interface_factory_ptr_->Get();
if (factory)
factory->CreateDefaultRenderer(audio_device_id, std::move(receiver));
}
#if BUILDFLAG(ENABLE_CAST_RENDERER)
void MediaInterfaceProxy::CreateCastRenderer(
const base::UnguessableToken& overlay_plane_id,
mojo::PendingReceiver<media::mojom::Renderer> receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
InterfaceFactory* factory = secondary_interface_factory_->Get();
if (factory)
factory->CreateCastRenderer(overlay_plane_id, std::move(receiver));
}
#endif
#if BUILDFLAG(IS_ANDROID)
void MediaInterfaceProxy::CreateFlingingRenderer(
const std::string& presentation_id,
mojo::PendingRemote<media::mojom::FlingingRendererClientExtension>
client_extension,
mojo::PendingReceiver<media::mojom::Renderer> receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
std::unique_ptr<FlingingRenderer> flinging_renderer =
FlingingRenderer::Create(&render_frame_host(), presentation_id,
std::move(client_extension));
if (!flinging_renderer)
return;
media::MojoRendererService::Create(nullptr, std::move(flinging_renderer),
std::move(receiver));
}
#endif
#if BUILDFLAG(IS_WIN)
void MediaInterfaceProxy::CreateMediaFoundationRenderer(
mojo::PendingRemote<media::mojom::MediaLog> media_log_remote,
mojo::PendingReceiver<media::mojom::Renderer> receiver,
mojo::PendingReceiver<media::mojom::MediaFoundationRendererExtension>
renderer_extension_receiver) {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << __func__ << ": this=" << this;
auto* factory = GetMediaFoundationServiceInterfaceFactory(base::FilePath());
if (factory) {
factory->CreateMediaFoundationRenderer(
std::move(media_log_remote), std::move(receiver),
std::move(renderer_extension_receiver));
}
}
#endif
void MediaInterfaceProxy::CreateCdm(const media::CdmConfig& cdm_config,
CreateCdmCallback create_cdm_cb) {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << __func__ << ": cdm_config=" << cdm_config;
auto callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(create_cdm_cb), mojo::NullRemote(), nullptr,
media::CreateCdmStatus::kDisconnectionError);
#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
bool enable_cdm_factory_daemon = true;
#if defined(ARCH_CPU_ARM_FAMILY)
if (!base::FeatureList::IsEnabled(media::kEnableArmHwdrm)) {
enable_cdm_factory_daemon = false;
}
#endif
if (enable_cdm_factory_daemon && cdm_config.use_hw_secure_codecs &&
cdm_config.allow_distinctive_identifier) {
auto* factory = media_interface_factory_ptr_->Get();
if (factory) {
factory->CreateCdm(
cdm_config, base::BindOnce(&MediaInterfaceProxy::OnChromeOsCdmCreated,
weak_factory_.GetWeakPtr(), cdm_config,
std::move(callback)));
return;
}
}
ReportCdmTypeUMA(CrosCdmType::kChromeCdm);
#elif BUILDFLAG(IS_WIN)
if (ShouldUseMediaFoundationServiceForCdm(cdm_config)) {
if (!cdm_config.allow_distinctive_identifier ||
!cdm_config.allow_persistent_state) {
DVLOG(2) << "MediaFoundationService requires both distinctive identifier "
"and persistent state";
std::move(callback).Run(mojo::NullRemote(), nullptr,
media::CreateCdmStatus::kInvalidCdmConfig);
return;
}
auto cdm_info = CdmRegistryImpl::GetInstance()->GetCdmInfo(
cdm_config.key_system, CdmInfo::Robustness::kHardwareSecure);
if (cdm_info) {
DVLOG(2) << "Get MediaFoundationService with CDM path " << cdm_info->path;
auto* factory = GetMediaFoundationServiceInterfaceFactory(cdm_info->path);
if (factory) {
factory->CreateCdm(cdm_config, std::move(callback));
return;
}
}
}
#endif
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
auto* factory = GetCdmFactory(cdm_config.key_system);
#elif BUILDFLAG(ENABLE_CAST_RENDERER)
auto* factory = secondary_interface_factory_->Get();
#else
auto* factory = media_interface_factory_ptr_->Get();
#endif
if (!factory) {
std::move(callback).Run(mojo::NullRemote(), nullptr,
media::CreateCdmStatus::kCdmFactoryCreationFailed);
return;
}
factory->CreateCdm(cdm_config, std::move(callback));
}
mojo::PendingRemote<media::mojom::FrameInterfaceFactory>
MediaInterfaceProxy::GetFrameServices(const media::CdmType& cdm_type) {
mojo::PendingRemote<media::mojom::FrameInterfaceFactory> factory;
frame_factories_.Add(
std::make_unique<FrameInterfaceFactoryImpl>(
static_cast<RenderFrameHostImpl*>(&render_frame_host()), cdm_type),
factory.InitWithNewPipeAndPassReceiver());
return factory;
}
#if BUILDFLAG(IS_WIN)
media::mojom::InterfaceFactory*
MediaInterfaceProxy::GetMediaFoundationServiceInterfaceFactory(
const base::FilePath& cdm_path) {
DVLOG(3) << __func__ << ": this=" << this << ", cdm_path=" << cdm_path;
DCHECK(thread_checker_.CalledOnValidThread());
if (!media::SupportMediaFoundationPlayback()) {
DLOG(ERROR) << "Media foundation encrypted or clear playback not supported";
return nullptr;
}
if (!mf_interface_factory_remote_)
ConnectToMediaFoundationService(cdm_path);
return mf_interface_factory_remote_.get();
}
void MediaInterfaceProxy::ConnectToMediaFoundationService(
const base::FilePath& cdm_path) {
DVLOG(1) << __func__ << ": this=" << this << ", cdm_path=" << cdm_path;
DCHECK(!mf_interface_factory_remote_);
auto& mf_service = GetMediaFoundationService(
media::CdmType(), render_frame_host().GetBrowserContext(),
render_frame_host().GetSiteInstance()->GetSiteURL(), cdm_path);
mf_service.CreateInterfaceFactory(
mf_interface_factory_remote_.BindNewPipeAndPassReceiver(),
GetFrameServices(media::CdmType()));
mf_interface_factory_remote_.reset_on_disconnect();
}
bool MediaInterfaceProxy::ShouldUseMediaFoundationServiceForCdm(
const media::CdmConfig& cdm_config) {
DVLOG(1) << __func__ << ": this=" << this << ", cdm_config=" << cdm_config;
return cdm_config.use_hw_secure_codecs;
}
#endif
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
media::mojom::CdmFactory* MediaInterfaceProxy::GetCdmFactory(
const std::string& key_system) {
DCHECK(thread_checker_.CalledOnValidThread());
auto cdm_info = CdmRegistryImpl::GetInstance()->GetCdmInfo(
key_system, CdmInfo::Robustness::kSoftwareSecure);
if (!cdm_info) {
DLOG(ERROR) << "No valid CdmInfo for " << key_system;
return nullptr;
}
if (cdm_info->path.empty()) {
NOTREACHED() << "CDM path for " << key_system << " is empty";
}
if (!IsValidCdmDisplayName(cdm_info->name)) {
NOTREACHED() << "Invalid CDM display name " << cdm_info->name;
}
auto& cdm_type = cdm_info->type;
auto found = cdm_factory_map_.find(cdm_type);
if (found != cdm_factory_map_.end())
return found->second.get();
return ConnectToCdmService(*cdm_info);
}
media::mojom::CdmFactory* MediaInterfaceProxy::ConnectToCdmService(
const CdmInfo& cdm_info) {
DVLOG(1) << __func__ << ": cdm_name = " << cdm_info.name;
DCHECK(!cdm_factory_map_.count(cdm_info.type));
auto* browser_context = render_frame_host().GetBrowserContext();
auto& site = render_frame_host().GetSiteInstance()->GetSiteURL();
auto& cdm_service = GetCdmService(browser_context, site, cdm_info);
mojo::Remote<media::mojom::CdmFactory> cdm_factory_remote;
cdm_service.CreateCdmFactory(cdm_factory_remote.BindNewPipeAndPassReceiver(),
GetFrameServices(cdm_info.type));
cdm_factory_remote.set_disconnect_handler(
base::BindOnce(&MediaInterfaceProxy::OnCdmServiceConnectionError,
base::Unretained(this), cdm_info.type));
auto* cdm_factory = cdm_factory_remote.get();
cdm_factory_map_.emplace(cdm_info.type, std::move(cdm_factory_remote));
return cdm_factory;
}
void MediaInterfaceProxy::OnCdmServiceConnectionError(
const media::CdmType& cdm_type) {
DVLOG(1) << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(cdm_factory_map_.count(cdm_type));
cdm_factory_map_.erase(cdm_type);
}
#endif
#if BUILDFLAG(IS_CHROMEOS)
void MediaInterfaceProxy::OnChromeOsCdmCreated(
const media::CdmConfig& cdm_config,
CreateCdmCallback callback,
mojo::PendingRemote<media::mojom::ContentDecryptionModule> receiver,
media::mojom::CdmContextPtr cdm_context,
media::CreateCdmStatus status) {
if (receiver) {
ReportCdmTypeUMA(CrosCdmType::kPlatformCdm);
std::move(callback).Run(std::move(receiver), std::move(cdm_context),
status);
return;
}
VLOG(1) << "Failed creating Chrome OS CDM, will use library CDM";
auto* factory = GetCdmFactory(cdm_config.key_system);
if (!factory) {
std::move(callback).Run(mojo::NullRemote(), nullptr,
media::CreateCdmStatus::kCdmFactoryCreationFailed);
return;
}
ReportCdmTypeUMA(CrosCdmType::kChromeCdm);
factory->CreateCdm(cdm_config, std::move(callback));
}
#endif
DOCUMENT_USER_DATA_KEY_IMPL(MediaInterfaceProxy);
}