blob: 4275536f465bb5c9f56db3e423ee909faf60b85f [file]
// Copyright 2012 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/renderer/media/batching_media_log.h"
#include <sstream>
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_tick_clock.h"
#include "media/base/logging_override_if_enabled.h"
namespace {
// Keep the JSON conversion in one function to prevent LOG and DVLOG calls
// from unnecessarily converting it.
std::string ToJSON(const media::MediaLogRecord& event) {
return base::WriteJson(event.params).value_or("");
}
// Print an event to the chromium log.
// TODO(tmathmeyer) replace this with a log-only EventHandler.
void Log(const media::MediaLogRecord& event) {
if (event.type == media::MediaLogRecord::Type::kMediaStatus) {
DVLOG(1) << "MediaEvent: " << ToJSON(event);
} else if (event.type == media::MediaLogRecord::Type::kMessage &&
event.params.Find("error")) {
DVLOG(1) << "MediaEvent: " << ToJSON(event);
} else if (event.type != media::MediaLogRecord::Type::kMediaPropertyChange) {
DVLOG(1) << "MediaEvent: " << ToJSON(event);
}
}
} // namespace
namespace content {
BatchingMediaLog::BatchingMediaLog(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
std::vector<std::unique_ptr<EventHandler>> event_handlers)
: task_runner_(std::move(task_runner)),
tick_clock_(base::DefaultTickClock::GetInstance()),
last_ipc_send_time_(tick_clock_->NowTicks()),
event_handlers_(std::move(event_handlers)),
ipc_send_pending_(false),
logged_rate_limit_warning_(false) {
// Pre-bind the WeakPtr on the right thread since we'll receive calls from
// other threads and don't want races.
weak_this_ = weak_factory_.GetWeakPtr();
AddEvent<media::MediaLogEvent::kMediaLogCreated>(base::Time::Now());
}
BatchingMediaLog::~BatchingMediaLog() {
DCHECK(task_runner_->BelongsToCurrentThread());
// AddEvent() could be in-flight on some other thread. Wait for it, and make
// sure that nobody else calls it.
InvalidateLog();
// There's no further chance to handle this, so send them now. This should not
// be racy since nothing should have a pointer to the media log on another
// thread by this point.
if (ipc_send_pending_)
SendQueuedMediaEvents();
}
void BatchingMediaLog::Stop() {
base::AutoLock lock(lock_);
event_handlers_.clear();
}
void BatchingMediaLog::OnWebMediaPlayerDestroyedLocked() {
base::AutoLock lock(lock_);
for (const auto& handler : event_handlers_)
handler->OnWebMediaPlayerDestroyed();
}
void BatchingMediaLog::AddLogRecordLocked(
std::unique_ptr<media::MediaLogRecord> event) {
Log(*event);
// For enforcing delay until it's been a second since the last ipc message was
// sent.
base::TimeDelta delay_for_next_ipc_send;
{
base::AutoLock auto_lock(lock_);
switch (event->type) {
// Hold onto the most recent failed status and the first, if any,
// MEDIA_LOG_ERROR_ENTRY for use in GetErrorMessage().
case media::MediaLogRecord::Type::kMediaStatus:
last_pipeline_error_ = *event;
MaybeQueueEvent_Locked(std::move(event));
break;
case media::MediaLogRecord::Type::kMediaEventTriggered: {
const base::Value* event_key = event->params.Find(MediaLog::kEventKey);
DCHECK(event_key);
// These strings come from the TypeName template specialization
// in media_log_type_enforcement.h, it's not encoded anywhere, so it's
// just typed out here.
if (*event_key == "kDurationChanged") {
// This may fire many times for badly muxed media; only keep the last.
last_duration_changed_event_ = *event;
} else if (*event_key == "kBufferingStateChanged") {
// This may fire many times on poor networks; only keep the last.
if (event->params.Find("video_buffering_state")) {
last_video_buffering_state_ = *event;
} else if (event->params.Find("audio_buffering_state")) {
last_audio_buffering_state_ = *event;
} else if (event->params.Find("pipeline_buffering_state")) {
last_pipeline_buffering_state_ = *event;
} else {
NOTREACHED();
}
} else if (*event_key == "kPlay") {
last_play_event_ = *event;
} else if (*event_key == "kPause") {
last_pause_event_ = *event;
} else {
MaybeQueueEvent_Locked(std::move(event));
}
break;
}
case media::MediaLogRecord::Type::kMessage:
if (event->params.Find(media::MediaLogMessageLevelToString(
media::MediaLogMessageLevel::kERROR)) &&
!cached_media_error_for_message_) {
cached_media_error_for_message_ = *event;
}
MaybeQueueEvent_Locked(std::move(event));
break;
default:
MaybeQueueEvent_Locked(std::move(event));
}
if (ipc_send_pending_)
return;
ipc_send_pending_ = true;
delay_for_next_ipc_send =
base::Seconds(1) - (tick_clock_->NowTicks() - last_ipc_send_time_);
}
if (delay_for_next_ipc_send.is_positive()) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BatchingMediaLog::SendQueuedMediaEvents, weak_this_),
delay_for_next_ipc_send);
return;
}
// It's been more than a second so send ASAP.
if (task_runner_->BelongsToCurrentThread()) {
SendQueuedMediaEvents();
return;
}
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BatchingMediaLog::SendQueuedMediaEvents, weak_this_));
}
std::string BatchingMediaLog::GetErrorMessageLocked() {
// Keep message structure in sync with
// HTMLMediaElement::BuildElementErrorMessage().
std::stringstream result;
if (last_pipeline_error_)
result << MediaEventToMessageString(*last_pipeline_error_);
if (cached_media_error_for_message_) {
DCHECK(last_pipeline_error_)
<< "Message with detail should be associated with a pipeline error";
// This ':' lets web apps extract the UA-specific-error-code from the
// MediaError.message prefix.
result << ": "
<< MediaEventToMessageString(*cached_media_error_for_message_);
}
return result.str();
}
std::string BatchingMediaLog::MediaEventToMessageString(
const media::MediaLogRecord& event) {
switch (event.type) {
case media::MediaLogRecord::Type::kMediaStatus: {
const std::string* group =
event.params.FindString(media::StatusConstants::kGroupKey);
auto code =
event.params.FindInt(media::StatusConstants::kCodeKey).value_or(0);
DCHECK_NE(code, 0);
if (group && *group == media::PipelineStatus::Traits::Group()) {
return PipelineStatusToString(
static_cast<media::PipelineStatusCodes>(code));
}
return *group;
}
case media::MediaLogRecord::Type::kMessage: {
const std::string* result = event.params.FindString(
MediaLogMessageLevelToString(media::MediaLogMessageLevel::kERROR));
if (result) {
std::string result_copy;
base::ReplaceChars(*result, "\n", " ", &result_copy);
return result_copy;
}
return "";
}
case media::MediaLogRecord::Type::kMediaPropertyChange:
case media::MediaLogRecord::Type::kMediaEventTriggered:
NOTREACHED();
}
NOTREACHED();
}
void BatchingMediaLog::SendQueuedMediaEvents() {
DCHECK(task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(lock_);
DCHECK(ipc_send_pending_);
ipc_send_pending_ = false;
if (last_duration_changed_event_) {
queued_media_events_.push_back(*last_duration_changed_event_);
last_duration_changed_event_.reset();
}
if (last_video_buffering_state_) {
queued_media_events_.push_back(*last_video_buffering_state_);
last_video_buffering_state_.reset();
}
if (last_audio_buffering_state_) {
queued_media_events_.push_back(*last_audio_buffering_state_);
last_audio_buffering_state_.reset();
}
if (last_pipeline_buffering_state_) {
queued_media_events_.push_back(*last_pipeline_buffering_state_);
last_pipeline_buffering_state_.reset();
}
if (last_play_event_) {
queued_media_events_.push_back(*last_play_event_);
last_play_event_.reset();
}
if (last_pause_event_) {
queued_media_events_.push_back(*last_pause_event_);
last_pause_event_.reset();
}
last_ipc_send_time_ = tick_clock_->NowTicks();
if (queued_media_events_.empty())
return;
for (const auto& handler : event_handlers_)
handler->SendQueuedMediaEvents(queued_media_events_);
queued_media_events_.clear();
}
void BatchingMediaLog::SetTickClockForTesting(
const base::TickClock* tick_clock) {
base::AutoLock auto_lock(lock_);
tick_clock_ = tick_clock;
last_ipc_send_time_ = tick_clock_->NowTicks();
}
void BatchingMediaLog::MaybeQueueEvent_Locked(
std::unique_ptr<media::MediaLogRecord> event) {
lock_.AssertAcquired();
if (queued_media_events_.size() < media::MediaLog::kLogLimit) {
queued_media_events_.emplace_back(*event);
return;
}
if (logged_rate_limit_warning_)
return;
logged_rate_limit_warning_ = true;
auto message = "Log rate exceeds " +
base::NumberToString(media::MediaLog::kLogLimit) +
" messages per second. Futher entries will be dropped.";
DLOG(WARNING) << message;
queued_media_events_.emplace_back();
queued_media_events_.back().id = event->id;
queued_media_events_.back().type = media::MediaLogRecord::Type::kMessage;
queued_media_events_.back().time = base::TimeTicks::Now();
queued_media_events_.back().params.Set(
media::MediaLogMessageLevelToString(
media::MediaLogMessageLevel::kWARNING),
message);
}
} // namespace content