#include "media/mojo/clients/win/media_foundation_renderer_client.h"
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "media/base/media_log.h"
#include "media/base/win/mf_feature_checks.h"
#include "media/base/win/mf_helpers.h"
#include "media/mojo/mojom/speech_recognition_service.mojom.h"
#include "media/renderers/win/media_foundation_renderer.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace media {
#define REPORT_ERROR_REASON(reason) \
MediaFoundationRenderer::ReportErrorReason( \
MediaFoundationRenderer::ErrorReason::reason)
MediaFoundationRendererClient::MediaFoundationRendererClient(
scoped_refptr<base::SequencedTaskRunner> media_task_runner,
std::unique_ptr<MediaLog> media_log,
std::unique_ptr<MojoRenderer> mojo_renderer,
mojo::PendingRemote<RendererExtension> pending_renderer_extension,
std::unique_ptr<DCOMPTextureWrapper> dcomp_texture_wrapper,
VideoRendererSink* sink,
mojo::PendingRemote<media::mojom::MediaFoundationRendererObserver>
media_foundation_renderer_observer)
: media_task_runner_(std::move(media_task_runner)),
media_log_(std::move(media_log)),
mojo_renderer_(std::move(mojo_renderer)),
pending_renderer_extension_(std::move(pending_renderer_extension)),
dcomp_texture_wrapper_(std::move(dcomp_texture_wrapper)),
sink_(sink),
pending_media_foundation_renderer_observer_(
std::move(media_foundation_renderer_observer)) {
DVLOG_FUNC(1);
}
MediaFoundationRendererClient::~MediaFoundationRendererClient() {
DVLOG_FUNC(1);
SignalMediaPlayingStateChange(false);
}
void MediaFoundationRendererClient::Initialize(MediaResource* media_resource,
RendererClient* client,
PipelineStatusCallback init_cb) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!init_cb_);
if (!dcomp_texture_wrapper_) {
MEDIA_LOG(ERROR, media_log_) << "Failed to create DCOMPTextureWrapper";
REPORT_ERROR_REASON(kFailedToCreateDCompTextureWrapper);
std::move(init_cb).Run({PIPELINE_ERROR_INITIALIZATION_FAILED,
"DComTextureWrapper creation failed"});
return;
}
renderer_extension_.Bind(std::move(pending_renderer_extension_),
media_task_runner_);
media_foundation_renderer_observer_.Bind(
std::move(pending_media_foundation_renderer_observer_),
media_task_runner_);
renderer_extension_.set_disconnect_handler(
base::BindOnce(&MediaFoundationRendererClient::OnConnectionError,
base::Unretained(this)));
client_ = client;
init_cb_ = std::move(init_cb);
auto media_streams = media_resource->GetAllStreams();
for (DemuxerStream* stream : media_streams) {
if (stream->type() == DemuxerStream::Type::VIDEO) {
has_video_ = true;
break;
}
}
mojo_renderer_->Initialize(
media_resource, this,
base::BindOnce(
&MediaFoundationRendererClient::OnRemoteRendererInitialized,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::SetCdm(CdmContext* cdm_context,
CdmAttachedCB cdm_attached_cb) {
DVLOG_FUNC(1) << "cdm_context=" << cdm_context;
DCHECK(cdm_context);
if (cdm_context_) {
DLOG(ERROR) << "Switching CDM not supported";
std::move(cdm_attached_cb).Run(false);
return;
}
cdm_context_ = cdm_context;
DCHECK(cdm_attached_cb_.is_null());
cdm_attached_cb_ = std::move(cdm_attached_cb);
mojo_renderer_->SetCdm(
cdm_context_,
base::BindOnce(&MediaFoundationRendererClient::OnCdmAttached,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::SetLatencyHint(
std::optional<base::TimeDelta> latency_hint) {
mojo_renderer_->SetLatencyHint(latency_hint);
}
void MediaFoundationRendererClient::Flush(base::OnceClosure flush_cb) {
mojo_renderer_->Flush(std::move(flush_cb));
}
void MediaFoundationRendererClient::StartPlayingFrom(base::TimeDelta time) {
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
SignalMediaPlayingStateChange(true);
mojo_renderer_->StartPlayingFrom(time);
}
void MediaFoundationRendererClient::SetPlaybackRate(double playback_rate) {
mojo_renderer_->SetPlaybackRate(playback_rate);
}
void MediaFoundationRendererClient::SetVolume(float volume) {
mojo_renderer_->SetVolume(volume);
}
base::TimeDelta MediaFoundationRendererClient::GetMediaTime() {
return mojo_renderer_->GetMediaTime();
}
void MediaFoundationRendererClient::OnTracksChanged(
DemuxerStream::Type track_type,
DemuxerStream* enabled_track,
base::OnceClosure change_completed_cb) {
if (track_type != DemuxerStream::VIDEO) {
DLOG(WARNING) << "Audio track changes are not supported.";
std::move(change_completed_cb).Run();
return;
}
renderer_extension_->SetVideoStreamEnabled(enabled_track != nullptr);
std::move(change_completed_cb).Run();
}
RendererType MediaFoundationRendererClient::GetRendererType() {
return RendererType::kMediaFoundation;
}
void MediaFoundationRendererClient::OnError(PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
SignalMediaPlayingStateChange(false);
if (status == PIPELINE_ERROR_HARDWARE_CONTEXT_RESET && dcomp_video_frame_) {
dcomp_video_frame_.reset();
auto black_frame = media::VideoFrame::CreateBlackFrame(natural_size_);
sink_->PaintSingleFrame(black_frame, true);
}
client_->OnError(status);
}
void MediaFoundationRendererClient::OnFallback(PipelineStatus fallback) {
SignalMediaPlayingStateChange(false);
client_->OnFallback(std::move(fallback).AddHere());
}
void MediaFoundationRendererClient::OnEnded() {
SignalMediaPlayingStateChange(false);
client_->OnEnded();
}
void MediaFoundationRendererClient::OnStatisticsUpdate(
const PipelineStatistics& stats) {
client_->OnStatisticsUpdate(stats);
}
void MediaFoundationRendererClient::OnBufferingStateChange(
BufferingState state,
BufferingStateChangeReason reason) {
client_->OnBufferingStateChange(state, reason);
}
void MediaFoundationRendererClient::OnWaiting(WaitingReason reason) {
client_->OnWaiting(reason);
}
void MediaFoundationRendererClient::OnAudioConfigChange(
const AudioDecoderConfig& config) {
client_->OnAudioConfigChange(config);
}
void MediaFoundationRendererClient::OnVideoConfigChange(
const VideoDecoderConfig& config) {
client_->OnVideoConfigChange(config);
}
void MediaFoundationRendererClient::OnVideoNaturalSizeChange(
const gfx::Size& size) {
DVLOG_FUNC(1) << "size=" << size.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
natural_size_ = size;
dcomp_texture_wrapper_->CreateVideoFrame(
natural_size_,
base::BindOnce(&MediaFoundationRendererClient::OnVideoFrameCreated,
weak_factory_.GetWeakPtr()));
client_->OnVideoNaturalSizeChange(natural_size_);
}
void MediaFoundationRendererClient::OnVideoOpacityChange(bool opaque) {
DVLOG_FUNC(1) << "opaque=" << opaque;
DCHECK(has_video_);
client_->OnVideoOpacityChange(opaque);
}
void MediaFoundationRendererClient::OnVideoFrameRateChange(
std::optional<int> fps) {
DVLOG_FUNC(1) << "fps=" << (fps ? *fps : -1);
DCHECK(has_video_);
client_->OnVideoFrameRateChange(fps);
}
void MediaFoundationRendererClient::OnConnectionError() {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
MEDIA_LOG(ERROR, media_log_) << "MediaFoundationRendererClient disconnected";
REPORT_ERROR_REASON(kOnConnectionError);
OnError(PIPELINE_ERROR_DISCONNECTED);
}
void MediaFoundationRendererClient::OnRemoteRendererInitialized(
PipelineStatus status) {
DVLOG_FUNC(1) << "status=" << status;
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(!init_cb_.is_null());
if (status != PIPELINE_OK) {
std::move(init_cb_).Run(status);
return;
}
if (!has_video_) {
std::move(init_cb_).Run(PIPELINE_OK);
return;
}
bool success = dcomp_texture_wrapper_->Initialize(
gfx::Size(1, 1),
base::BindRepeating(&MediaFoundationRendererClient::OnOutputRectChange,
weak_factory_.GetWeakPtr()));
if (!success) {
REPORT_ERROR_REASON(kFailedToInitDCompTextureWrapper);
std::move(init_cb_).Run({PIPELINE_ERROR_INITIALIZATION_FAILED,
"DComTextureWrapper init failed"});
return;
}
if (output_size_.IsEmpty())
dcomp_texture_wrapper_->UpdateTextureSize(gfx::Size(1, 1));
std::move(init_cb_).Run(PIPELINE_OK);
}
void MediaFoundationRendererClient::OnOutputRectChange(gfx::Rect output_rect) {
DVLOG_FUNC(1) << "output_rect=" << output_rect.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
renderer_extension_->SetOutputRect(
output_rect,
base::BindOnce(&MediaFoundationRendererClient::OnSetOutputRectDone,
weak_factory_.GetWeakPtr(), output_rect.size()));
}
void MediaFoundationRendererClient::OnSetOutputRectDone(
const gfx::Size& output_size,
bool success) {
DVLOG_FUNC(1) << "output_size=" << output_size.ToString();
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
if (!success) {
DLOG(ERROR) << "Failed to SetOutputRect";
MEDIA_LOG(WARNING, media_log_) << "Failed to SetOutputRect";
return;
}
output_size_ = output_size;
if (output_size_updated_)
return;
if (!output_size_.IsEmpty() && output_size_ != gfx::Size(1, 1)) {
dcomp_texture_wrapper_->UpdateTextureSize(output_size_);
output_size_updated_ = true;
}
InitializeDCOMPRenderingIfNeeded();
if (dcomp_video_frame_) {
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
}
void MediaFoundationRendererClient::InitializeDCOMPRenderingIfNeeded() {
DVLOG_FUNC(1);
DCHECK(has_video_);
if (dcomp_rendering_initialized_)
return;
dcomp_rendering_initialized_ = true;
renderer_extension_->GetDCOMPSurface(
base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceReceived,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::OnDCOMPSurfaceReceived(
const std::optional<base::UnguessableToken>& token,
const std::string& error) {
DVLOG_FUNC(1);
DCHECK(has_video_);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
if (!token) {
DLOG(ERROR) << "GetDCOMPSurface failed: " + error;
return;
}
dcomp_texture_wrapper_->SetDCOMPSurfaceHandle(
token.value(),
base::BindOnce(&MediaFoundationRendererClient::OnDCOMPSurfaceHandleSet,
weak_factory_.GetWeakPtr()));
}
void MediaFoundationRendererClient::OnDCOMPSurfaceHandleSet(bool success) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
if (!success) {
MEDIA_LOG(ERROR, media_log_) << "Failed to set DCOMP surface handle";
REPORT_ERROR_REASON(kOnDCompSurfaceHandleSetError);
OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
return;
}
if (dcomp_video_frame_) {
sink_->PaintSingleFrame(dcomp_video_frame_,
true);
}
}
void MediaFoundationRendererClient::OnVideoFrameCreated(
scoped_refptr<VideoFrame> video_frame,
const gpu::Mailbox& mailbox) {
DVLOG_FUNC(1);
DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
DCHECK(has_video_);
video_frame->metadata().allow_overlay = true;
if (cdm_context_) {
video_frame->metadata().protected_video = true;
video_frame->metadata().hw_protected = true;
} else {
DCHECK(SupportMediaFoundationClearPlayback());
}
dcomp_video_frame_ = std::move(video_frame);
sink_->PaintSingleFrame(dcomp_video_frame_, true);
}
void MediaFoundationRendererClient::OnCdmAttached(bool success) {
DCHECK(cdm_attached_cb_);
std::move(cdm_attached_cb_).Run(success);
}
void MediaFoundationRendererClient::SignalMediaPlayingStateChange(
bool is_playing) {
if (is_playing == is_playing_) {
return;
}
is_playing_ = is_playing;
}
}