blob: 214af2f9e9238c5f2c493aed68b96efd9ca70f36 [file] [log] [blame]
// Copyright 2017 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 "media/mojo/services/media_metrics_provider.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "media/base/key_systems.h"
#include "media/learning/mojo/mojo_learning_task_controller_service.h"
#include "media/mojo/services/video_decode_stats_recorder.h"
#include "media/mojo/services/watch_time_recorder.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#if !defined(OS_ANDROID)
#include "media/filters/decrypting_video_decoder.h"
#endif // !defined(OS_ANDROID)
#if defined(OS_FUCHSIA) || (BUILDFLAG(IS_CHROMECAST) && defined(OS_ANDROID))
#include "media/mojo/services/playback_events_recorder.h"
#endif
namespace media {
constexpr char kInvalidInitialize[] = "Initialize() was not called correctly.";
static uint64_t g_player_id = 0;
MediaMetricsProvider::PipelineInfo::PipelineInfo(bool is_incognito)
: is_incognito(is_incognito) {}
MediaMetricsProvider::PipelineInfo::~PipelineInfo() = default;
MediaMetricsProvider::MediaMetricsProvider(
BrowsingMode is_incognito,
FrameStatus is_top_frame,
ukm::SourceId source_id,
learning::FeatureValue origin,
VideoDecodePerfHistory::SaveCallback save_cb,
GetLearningSessionCallback learning_session_cb,
RecordAggregateWatchTimeCallback record_playback_cb)
: player_id_(g_player_id++),
is_top_frame_(is_top_frame == FrameStatus::kTopFrame),
source_id_(source_id),
origin_(origin),
save_cb_(std::move(save_cb)),
learning_session_cb_(std::move(learning_session_cb)),
record_playback_cb_(std::move(record_playback_cb)),
uma_info_(is_incognito == BrowsingMode::kIncognito) {}
MediaMetricsProvider::~MediaMetricsProvider() {
// These UKM and UMA metrics do not apply to MediaStreams.
if (media_stream_type_ != mojom::MediaStreamType::kNone)
return;
// UKM may be unavailable in content_shell or other non-chrome/ builds; it
// may also be unavailable if browser shutdown has started; so this may be a
// nullptr. If it's unavailable, UKM reporting will be skipped.
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
if (!ukm_recorder || !initialized_)
return;
ukm::builders::Media_WebMediaPlayerState builder(source_id_);
builder.SetPlayerID(player_id_);
builder.SetIsTopFrame(is_top_frame_);
builder.SetIsEME(uma_info_.is_eme);
builder.SetIsMSE(is_mse_);
builder.SetRendererType(static_cast<int>(renderer_type_));
builder.SetKeySystem(GetKeySystemIntForUKM(key_system_));
builder.SetIsHardwareSecure(is_hardware_secure_);
builder.SetAudioEncryptionType(
static_cast<int>(uma_info_.audio_pipeline_info.encryption_type));
builder.SetVideoEncryptionType(
static_cast<int>(uma_info_.video_pipeline_info.encryption_type));
builder.SetFinalPipelineStatus(uma_info_.last_pipeline_status);
if (!is_mse_) {
builder.SetURLScheme(static_cast<int64_t>(url_scheme_));
if (container_name_)
builder.SetContainerName(*container_name_);
}
if (time_to_metadata_ != kNoTimestamp)
builder.SetTimeToMetadata(time_to_metadata_.InMilliseconds());
if (time_to_first_frame_ != kNoTimestamp)
builder.SetTimeToFirstFrame(time_to_first_frame_.InMilliseconds());
if (time_to_play_ready_ != kNoTimestamp)
builder.SetTimeToPlayReady(time_to_play_ready_.InMilliseconds());
builder.Record(ukm_recorder);
ReportPipelineUMA();
}
std::string MediaMetricsProvider::GetUMANameForAVStream(
const PipelineInfo& player_info) {
constexpr char kPipelineUmaPrefix[] = "Media.PipelineStatus.AudioVideo.";
std::string uma_name = kPipelineUmaPrefix;
if (player_info.video_codec == VideoCodec::kVP8)
uma_name += "VP8.";
else if (player_info.video_codec == VideoCodec::kVP9)
uma_name += "VP9.";
else if (player_info.video_codec == VideoCodec::kH264)
uma_name += "H264.";
else if (player_info.video_codec == VideoCodec::kAV1)
uma_name += "AV1.";
else
return uma_name + "Other";
// Add Renderer name when not using the default RendererImpl.
if (renderer_type_ == RendererType::kMediaFoundation) {
return uma_name + GetRendererName(RendererType::kMediaFoundation);
} else if (renderer_type_ != RendererType::kDefault) {
return uma_name + "UnknownRenderer";
}
// Using default RendererImpl. Put more detailed info into the UMA name.
#if !defined(OS_ANDROID)
if (player_info.video_pipeline_info.decoder_type ==
VideoDecoderType::kDecrypting) {
return uma_name + "DVD";
}
#endif
if (player_info.video_pipeline_info.has_decrypting_demuxer_stream)
uma_name += "DDS.";
// Note that HW essentially means 'platform' anyway. MediaCodec has been
// reported as HW forever, regardless of the underlying platform
// implementation.
uma_name += player_info.video_pipeline_info.is_platform_decoder ? "HW" : "SW";
return uma_name;
}
void MediaMetricsProvider::ReportPipelineUMA() {
if (uma_info_.has_video && uma_info_.has_audio) {
base::UmaHistogramExactLinear(GetUMANameForAVStream(uma_info_),
uma_info_.last_pipeline_status,
PIPELINE_STATUS_MAX + 1);
} else if (uma_info_.has_audio) {
base::UmaHistogramExactLinear("Media.PipelineStatus.AudioOnly",
uma_info_.last_pipeline_status,
PIPELINE_STATUS_MAX + 1);
} else if (uma_info_.has_video) {
base::UmaHistogramExactLinear("Media.PipelineStatus.VideoOnly",
uma_info_.last_pipeline_status,
PIPELINE_STATUS_MAX + 1);
} else {
// Note: This metric can be recorded as a result of normal operation with
// Media Source Extensions. If a site creates a MediaSource object but never
// creates a source buffer or appends data, PIPELINE_OK will be recorded.
base::UmaHistogramExactLinear("Media.PipelineStatus.Unsupported",
uma_info_.last_pipeline_status,
PIPELINE_STATUS_MAX + 1);
}
// Report whether video decoder fallback happened, but only if a video decoder
// was reported.
if (uma_info_.video_pipeline_info.decoder_type !=
VideoDecoderType::kUnknown) {
base::UmaHistogramBoolean("Media.VideoDecoderFallback",
uma_info_.video_decoder_changed);
}
// Report whether this player ever saw a playback event. Used to measure the
// effectiveness of efforts to reduce loaded-but-never-used players.
if (uma_info_.has_reached_have_enough)
base::UmaHistogramBoolean("Media.HasEverPlayed", uma_info_.has_ever_played);
// Report whether an encrypted playback is in incognito window, excluding
// never-used players.
if (uma_info_.is_eme && uma_info_.has_ever_played)
base::UmaHistogramBoolean("Media.EME.IsIncognito", uma_info_.is_incognito);
}
// static
void MediaMetricsProvider::Create(
BrowsingMode is_incognito,
FrameStatus is_top_frame,
ukm::SourceId source_id,
learning::FeatureValue origin,
VideoDecodePerfHistory::SaveCallback save_cb,
GetLearningSessionCallback learning_session_cb,
GetRecordAggregateWatchTimeCallback get_record_playback_cb,
mojo::PendingReceiver<mojom::MediaMetricsProvider> receiver) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<MediaMetricsProvider>(
is_incognito, is_top_frame, source_id, origin, std::move(save_cb),
std::move(learning_session_cb),
std::move(get_record_playback_cb).Run()),
std::move(receiver));
}
void MediaMetricsProvider::SetHasPlayed() {
uma_info_.has_ever_played = true;
}
void MediaMetricsProvider::SetHasAudio(AudioCodec audio_codec) {
uma_info_.audio_codec = audio_codec;
uma_info_.has_audio = true;
}
void MediaMetricsProvider::SetHasVideo(VideoCodec video_codec) {
uma_info_.video_codec = video_codec;
uma_info_.has_video = true;
}
void MediaMetricsProvider::SetHaveEnough() {
uma_info_.has_reached_have_enough = true;
}
void MediaMetricsProvider::SetVideoPipelineInfo(const VideoPipelineInfo& info) {
auto old_decoder = uma_info_.video_pipeline_info.decoder_type;
if (old_decoder != VideoDecoderType::kUnknown &&
old_decoder != info.decoder_type)
uma_info_.video_decoder_changed = true;
uma_info_.video_pipeline_info = info;
}
void MediaMetricsProvider::SetAudioPipelineInfo(const AudioPipelineInfo& info) {
uma_info_.audio_pipeline_info = info;
}
void MediaMetricsProvider::Initialize(
bool is_mse,
mojom::MediaURLScheme url_scheme,
mojom::MediaStreamType media_stream_type) {
if (initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
is_mse_ = is_mse;
initialized_ = true;
url_scheme_ = url_scheme;
media_stream_type_ = media_stream_type;
}
void MediaMetricsProvider::OnError(PipelineStatus status) {
DCHECK(initialized_);
uma_info_.last_pipeline_status = status;
}
void MediaMetricsProvider::SetIsEME() {
// This may be called before Initialize().
uma_info_.is_eme = true;
}
void MediaMetricsProvider::SetTimeToMetadata(base::TimeDelta elapsed) {
DCHECK(initialized_);
DCHECK_EQ(time_to_metadata_, kNoTimestamp);
time_to_metadata_ = elapsed;
}
void MediaMetricsProvider::SetTimeToFirstFrame(base::TimeDelta elapsed) {
DCHECK(initialized_);
DCHECK_EQ(time_to_first_frame_, kNoTimestamp);
time_to_first_frame_ = elapsed;
}
void MediaMetricsProvider::SetTimeToPlayReady(base::TimeDelta elapsed) {
DCHECK(initialized_);
DCHECK_EQ(time_to_play_ready_, kNoTimestamp);
time_to_play_ready_ = elapsed;
}
void MediaMetricsProvider::SetContainerName(
container_names::MediaContainerName container_name) {
DCHECK(initialized_);
DCHECK(!container_name_.has_value());
container_name_ = container_name;
}
void MediaMetricsProvider::SetRendererType(RendererType renderer_type) {
renderer_type_ = renderer_type;
}
void MediaMetricsProvider::SetKeySystem(const std::string& key_system) {
key_system_ = key_system;
}
void MediaMetricsProvider::SetIsHardwareSecure() {
is_hardware_secure_ = true;
}
void MediaMetricsProvider::AcquireWatchTimeRecorder(
mojom::PlaybackPropertiesPtr properties,
mojo::PendingReceiver<mojom::WatchTimeRecorder> receiver) {
if (!initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
mojo::MakeSelfOwnedReceiver(
std::make_unique<WatchTimeRecorder>(std::move(properties), source_id_,
is_top_frame_, player_id_,
record_playback_cb_),
std::move(receiver));
}
void MediaMetricsProvider::AcquireVideoDecodeStatsRecorder(
mojo::PendingReceiver<mojom::VideoDecodeStatsRecorder> receiver) {
if (!initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
if (!save_cb_) {
DVLOG(3) << __func__ << " Ignoring request, SaveCallback is null";
return;
}
mojo::MakeSelfOwnedReceiver(
std::make_unique<VideoDecodeStatsRecorder>(save_cb_, source_id_, origin_,
is_top_frame_, player_id_),
std::move(receiver));
}
void MediaMetricsProvider::AcquirePlaybackEventsRecorder(
mojo::PendingReceiver<mojom::PlaybackEventsRecorder> receiver) {
#if defined(OS_FUCHSIA) || (BUILDFLAG(IS_CHROMECAST) && defined(OS_ANDROID))
PlaybackEventsRecorder::Create(std::move(receiver));
#endif
}
void MediaMetricsProvider::AcquireLearningTaskController(
const std::string& taskName,
mojo::PendingReceiver<learning::mojom::LearningTaskController> receiver) {
learning::LearningSession* session = learning_session_cb_.Run();
if (!session) {
DVLOG(3) << __func__ << " Ignoring request, unable to get LearningSession.";
return;
}
auto controller = session->GetController(taskName);
if (!controller) {
DVLOG(3) << __func__ << " Ignoring request, no controller found for task: '"
<< taskName << "'.";
return;
}
mojo::MakeSelfOwnedReceiver(
std::make_unique<learning::MojoLearningTaskControllerService>(
controller->GetLearningTask(), source_id_, std::move(controller)),
std::move(receiver));
}
} // namespace media