910e62b5创建于 1月15日历史提交
// 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() {
  // The inspector handler will fail to send events after this, so send queued
  // events now.
  base::AutoLock lock(lock_);
  if (ipc_send_pending_) {
    SendQueuedMediaEvents_Locked();
  }

  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_);

  // This is called as a posted task and another task may
  // decide to immediate emit queued events, so we must
  // check `ipc_send_pending_` first here.
  if (ipc_send_pending_) {
    SendQueuedMediaEvents_Locked();
  }
}

void BatchingMediaLog::SendQueuedMediaEvents_Locked() {
  lock_.AssertAcquired();
  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