blob: e671c6aed4fba86f2d994a55d3a86748711810ac [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 "media/mojo/services/video_decode_stats_recorder.h"
#include "media/mojo/services/watch_time_recorder.h"
#include "mojo/public/cpp/bindings/strong_binding.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
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)
: 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)),
uma_info_(is_incognito == BrowsingMode::kIncognito) {}
MediaMetricsProvider::~MediaMetricsProvider() {
// 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.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);
// Buffered bytes are reported from a different source for EME/MSE.
std::string suffix;
if (uma_info_.is_eme)
suffix = "EME";
else if (is_mse_)
suffix = "MSE";
else
suffix = "SRC";
base::UmaHistogramMemoryKB("Media.BytesReceived." + suffix,
total_bytes_received_ >> 10);
if (is_ad_media_) {
base::UmaHistogramMemoryKB("Ads.Media.BytesReceived",
total_bytes_received_ >> 10);
base::UmaHistogramMemoryKB("Ads.Media.BytesReceived." + suffix,
total_bytes_received_ >> 10);
}
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 == kCodecVP8) {
uma_name += "VP8.";
} else if (player_info.video_codec == kCodecVP9) {
uma_name += "VP9.";
} else if (player_info.video_codec == kCodecH264) {
uma_name += "H264.";
} else if (player_info.video_codec == kCodecAV1) {
uma_name += "AV1.";
} else {
return uma_name + "Other";
}
#if !defined(OS_ANDROID)
if (player_info.video_pipeline_info.decoder_name ==
media::DecryptingVideoDecoder::kDecoderName) {
return uma_name + "DVD";
}
#endif
if (player_info.video_pipeline_info.is_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.
if (player_info.video_pipeline_info.is_platform_decoder) {
uma_name += "HW";
} else {
uma_name += "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,
media::PIPELINE_STATUS_MAX + 1);
} else if (uma_info_.has_audio) {
base::UmaHistogramExactLinear("Media.PipelineStatus.AudioOnly",
uma_info_.last_pipeline_status,
media::PIPELINE_STATUS_MAX + 1);
} else if (uma_info_.has_video) {
base::UmaHistogramExactLinear("Media.PipelineStatus.VideoOnly",
uma_info_.last_pipeline_status,
media::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,
media::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_name.empty()) {
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,
GetSourceIdCallback get_source_id_cb,
GetOriginCallback get_origin_cb,
VideoDecodePerfHistory::SaveCallback save_cb,
mojom::MediaMetricsProviderRequest request) {
mojo::MakeStrongBinding(
std::make_unique<MediaMetricsProvider>(
is_incognito, is_top_frame, get_source_id_cb.Run(),
get_origin_cb.Run(), std::move(save_cb)),
std::move(request));
}
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 PipelineDecoderInfo& info) {
if (!uma_info_.video_pipeline_info.decoder_name.empty())
uma_info_.video_decoder_changed = true;
uma_info_.video_pipeline_info = info;
}
void MediaMetricsProvider::SetAudioPipelineInfo(
const PipelineDecoderInfo& info) {
uma_info_.audio_pipeline_info = info;
}
void MediaMetricsProvider::Initialize(bool is_mse,
mojom::MediaURLScheme url_scheme) {
if (initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
is_mse_ = is_mse;
initialized_ = true;
url_scheme_ = url_scheme;
}
void MediaMetricsProvider::OnError(PipelineStatus status) {
DCHECK(initialized_);
uma_info_.last_pipeline_status = status;
}
void MediaMetricsProvider::SetIsAdMedia() {
// This may be called before Initialize().
is_ad_media_ = true;
}
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::AcquireWatchTimeRecorder(
mojom::PlaybackPropertiesPtr properties,
mojom::WatchTimeRecorderRequest request) {
if (!initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
mojo::MakeStrongBinding(
std::make_unique<WatchTimeRecorder>(std::move(properties), source_id_,
is_top_frame_, player_id_),
std::move(request));
}
void MediaMetricsProvider::AcquireVideoDecodeStatsRecorder(
mojom::VideoDecodeStatsRecorderRequest request) {
if (!initialized_) {
mojo::ReportBadMessage(kInvalidInitialize);
return;
}
if (!save_cb_) {
DVLOG(3) << __func__ << " Ignoring request, SaveCallback is null";
return;
}
mojo::MakeStrongBinding(
std::make_unique<VideoDecodeStatsRecorder>(save_cb_, source_id_, origin_,
is_top_frame_, player_id_),
std::move(request));
}
void MediaMetricsProvider::AddBytesReceived(uint64_t bytes_received) {
total_bytes_received_ += bytes_received;
}
} // namespace media