| Index: media/filters/renderer_impl.cc
|
| diff --git a/media/filters/renderer_impl.cc b/media/filters/renderer_impl.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c4d0f3006b85083e8bc9daefd4a78f3736f5e198
|
| --- /dev/null
|
| +++ b/media/filters/renderer_impl.cc
|
| @@ -0,0 +1,555 @@
|
| +// Copyright 2014 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/filters/renderer_impl.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/callback_helpers.h"
|
| +#include "base/compiler_specific.h"
|
| +#include "base/location.h"
|
| +#include "base/single_thread_task_runner.h"
|
| +#include "media/base/audio_renderer.h"
|
| +#include "media/base/demuxer.h"
|
| +#include "media/base/filter_collection.h"
|
| +#include "media/base/time_delta_interpolator.h"
|
| +#include "media/base/time_source.h"
|
| +#include "media/base/video_renderer.h"
|
| +
|
| +namespace media {
|
| +
|
| +RendererImpl::RendererImpl(
|
| + Demuxer* demuxer,
|
| + scoped_ptr<FilterCollection> filter_collection,
|
| + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
|
| + const TimeDeltaCB& get_duration_cb)
|
| + : state_(kUninitialized),
|
| + task_runner_(task_runner),
|
| + filter_collection_(filter_collection.Pass()),
|
| + demuxer_(demuxer),
|
| + get_duration_cb_(get_duration_cb),
|
| + time_source_(NULL),
|
| + audio_buffering_state_(BUFFERING_HAVE_NOTHING),
|
| + video_buffering_state_(BUFFERING_HAVE_NOTHING),
|
| + audio_ended_(false),
|
| + video_ended_(false),
|
| + underflow_disabled_for_testing_(false),
|
| + interpolator_(new TimeDeltaInterpolator(&default_tick_clock_)),
|
| + interpolation_state_(INTERPOLATION_STOPPED),
|
| + weak_factory_(this),
|
| + weak_this_(weak_factory_.GetWeakPtr()) {
|
| + DVLOG(1) << __FUNCTION__;
|
| + interpolator_->SetBounds(base::TimeDelta(), base::TimeDelta());
|
| +}
|
| +
|
| +RendererImpl::~RendererImpl() {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + audio_renderer_.reset();
|
| + video_renderer_.reset();
|
| +
|
| + if (!init_cb_.is_null())
|
| + base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_ABORT);
|
| +
|
| + if (!flush_cb_.is_null())
|
| + base::ResetAndReturn(&flush_cb_).Run();
|
| +}
|
| +
|
| +void RendererImpl::Initialize(const PipelineStatusCB& init_cb,
|
| + const StatisticsCB& statistics_cb,
|
| + const base::Closure& ended_cb,
|
| + const PipelineStatusCB& error_cb,
|
| + const BufferingStateCB& buffering_state_cb) {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kUninitialized) << state_;
|
| + DCHECK(!init_cb.is_null());
|
| + DCHECK(!statistics_cb.is_null());
|
| + DCHECK(!ended_cb.is_null());
|
| + DCHECK(!error_cb.is_null());
|
| + DCHECK(!buffering_state_cb.is_null());
|
| + DCHECK(demuxer_->GetStream(DemuxerStream::AUDIO) ||
|
| + demuxer_->GetStream(DemuxerStream::VIDEO));
|
| +
|
| + statistics_cb_ = statistics_cb;
|
| + ended_cb_ = ended_cb;
|
| + error_cb_ = error_cb;
|
| + buffering_state_cb_ = buffering_state_cb;
|
| +
|
| + init_cb_ = init_cb;
|
| + state_ = kInitializing;
|
| + InitializeAudioRenderer();
|
| +}
|
| +
|
| +void RendererImpl::Flush(const base::Closure& flush_cb) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kPlaying) << state_;
|
| + DCHECK(flush_cb_.is_null());
|
| +
|
| + {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + PauseClockAndStopTicking_Locked();
|
| + }
|
| +
|
| + flush_cb_ = flush_cb;
|
| + state_ = kFlushing;
|
| + FlushAudioRenderer();
|
| +}
|
| +
|
| +void RendererImpl::StartPlayingFrom(base::TimeDelta timestamp) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kPlaying) << state_;
|
| +
|
| + {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + interpolator_->SetBounds(timestamp, timestamp);
|
| + }
|
| +
|
| + if (time_source_)
|
| + time_source_->SetMediaTime(timestamp);
|
| + if (audio_renderer_)
|
| + audio_renderer_->StartPlaying();
|
| + if (video_renderer_)
|
| + video_renderer_->StartPlaying();
|
| +}
|
| +
|
| +void RendererImpl::SetPlaybackRate(float playback_rate) {
|
| + DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")";
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + // Playback rate changes are only carried out while playing.
|
| + if (state_ != kPlaying)
|
| + return;
|
| +
|
| + {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + interpolator_->SetPlaybackRate(playback_rate);
|
| + }
|
| +
|
| + if (time_source_)
|
| + time_source_->SetPlaybackRate(playback_rate);
|
| +}
|
| +
|
| +void RendererImpl::SetVolume(float volume) {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (audio_renderer_)
|
| + audio_renderer_->SetVolume(volume);
|
| +}
|
| +
|
| +base::TimeDelta RendererImpl::GetMediaTime() const {
|
| + // No BelongsToCurrentThread() checking because this can be called from other
|
| + // threads.
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + return interpolator_->GetInterpolatedTime();
|
| +}
|
| +
|
| +bool RendererImpl::HasAudio() const {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + return audio_renderer_ != NULL;
|
| +}
|
| +
|
| +bool RendererImpl::HasVideo() const {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + return video_renderer_ != NULL;
|
| +}
|
| +
|
| +void RendererImpl::SetCdm(MediaKeys* cdm) {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + // TODO(xhwang): Explore to possibility to move CDM setting from
|
| + // WebMediaPlayerImpl to this class.
|
| + NOTREACHED();
|
| +}
|
| +
|
| +void RendererImpl::DisableUnderflowForTesting() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kUninitialized);
|
| +
|
| + underflow_disabled_for_testing_ = true;
|
| +}
|
| +
|
| +void RendererImpl::SetTimeDeltaInterpolatorForTesting(
|
| + TimeDeltaInterpolator* interpolator) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kUninitialized);
|
| +
|
| + interpolator_.reset(interpolator);
|
| +}
|
| +
|
| +base::TimeDelta RendererImpl::GetMediaDuration() {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + return get_duration_cb_.Run();
|
| +}
|
| +
|
| +void RendererImpl::InitializeAudioRenderer() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kInitializing) << state_;
|
| + DCHECK(!init_cb_.is_null());
|
| + DCHECK(!audio_renderer_);
|
| +
|
| + PipelineStatusCB done_cb =
|
| + base::Bind(&RendererImpl::OnAudioRendererInitializeDone, weak_this_);
|
| +
|
| + if (!demuxer_->GetStream(DemuxerStream::AUDIO)) {
|
| + task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
|
| + return;
|
| + }
|
| +
|
| + audio_renderer_ = filter_collection_->GetAudioRenderer();
|
| + audio_renderer_->Initialize(
|
| + demuxer_->GetStream(DemuxerStream::AUDIO),
|
| + done_cb,
|
| + base::Bind(&RendererImpl::OnUpdateStatistics, weak_this_),
|
| + base::Bind(&RendererImpl::OnAudioTimeUpdate, weak_this_),
|
| + base::Bind(&RendererImpl::OnBufferingStateChanged, weak_this_,
|
| + &audio_buffering_state_),
|
| + base::Bind(&RendererImpl::OnAudioRendererEnded, weak_this_),
|
| + base::Bind(&RendererImpl::OnError, weak_this_));
|
| +}
|
| +
|
| +void RendererImpl::OnAudioRendererInitializeDone(PipelineStatus status) {
|
| + DVLOG(2) << __FUNCTION__ << ": " << status;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kInitializing) << state_;
|
| + DCHECK(!init_cb_.is_null());
|
| +
|
| + if (status != PIPELINE_OK) {
|
| + audio_renderer_.reset();
|
| + state_ = kError;
|
| + base::ResetAndReturn(&init_cb_).Run(status);
|
| + return;
|
| + }
|
| +
|
| + if (audio_renderer_)
|
| + time_source_ = audio_renderer_->GetTimeSource();
|
| +
|
| + InitializeVideoRenderer();
|
| +}
|
| +
|
| +void RendererImpl::InitializeVideoRenderer() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kInitializing) << state_;
|
| + DCHECK(!init_cb_.is_null());
|
| + DCHECK(!video_renderer_);
|
| +
|
| + PipelineStatusCB done_cb =
|
| + base::Bind(&RendererImpl::OnVideoRendererInitializeDone, weak_this_);
|
| +
|
| + if (!demuxer_->GetStream(DemuxerStream::VIDEO)) {
|
| + task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
|
| + return;
|
| + }
|
| +
|
| + video_renderer_ = filter_collection_->GetVideoRenderer();
|
| + video_renderer_->Initialize(
|
| + demuxer_->GetStream(DemuxerStream::VIDEO),
|
| + demuxer_->GetLiveness() == Demuxer::LIVENESS_LIVE,
|
| + done_cb,
|
| + base::Bind(&RendererImpl::OnUpdateStatistics, weak_this_),
|
| + base::Bind(&RendererImpl::OnVideoTimeUpdate, weak_this_),
|
| + base::Bind(&RendererImpl::OnBufferingStateChanged, weak_this_,
|
| + &video_buffering_state_),
|
| + base::Bind(&RendererImpl::OnVideoRendererEnded, weak_this_),
|
| + base::Bind(&RendererImpl::OnError, weak_this_),
|
| + base::Bind(&RendererImpl::GetMediaTime, base::Unretained(this)),
|
| + base::Bind(&RendererImpl::GetMediaDuration, base::Unretained(this)));
|
| +}
|
| +
|
| +void RendererImpl::OnVideoRendererInitializeDone(PipelineStatus status) {
|
| + DVLOG(2) << __FUNCTION__ << ": " << status;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kInitializing) << state_;
|
| + DCHECK(!init_cb_.is_null());
|
| +
|
| + if (status != PIPELINE_OK) {
|
| + audio_renderer_.reset();
|
| + video_renderer_.reset();
|
| + state_ = kError;
|
| + base::ResetAndReturn(&init_cb_).Run(status);
|
| + return;
|
| + }
|
| +
|
| + state_ = kPlaying;
|
| + DCHECK(audio_renderer_ || video_renderer_);
|
| + base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
|
| +}
|
| +
|
| +void RendererImpl::FlushAudioRenderer() {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kFlushing) << state_;
|
| + DCHECK(!flush_cb_.is_null());
|
| +
|
| + base::Closure done_cb =
|
| + base::Bind(&RendererImpl::OnAudioRendererFlushDone, weak_this_);
|
| +
|
| + if (!audio_renderer_) {
|
| + done_cb.Run();
|
| + return;
|
| + }
|
| +
|
| + audio_renderer_->Flush(done_cb);
|
| +}
|
| +
|
| +void RendererImpl::OnAudioRendererFlushDone() {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kFlushing) << state_;
|
| + DCHECK(!flush_cb_.is_null());
|
| +
|
| + DCHECK_EQ(audio_buffering_state_, BUFFERING_HAVE_NOTHING);
|
| + audio_ended_ = false;
|
| + FlushVideoRenderer();
|
| +}
|
| +
|
| +void RendererImpl::FlushVideoRenderer() {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kFlushing) << state_;
|
| + DCHECK(!flush_cb_.is_null());
|
| +
|
| + base::Closure done_cb =
|
| + base::Bind(&RendererImpl::OnVideoRendererFlushDone, weak_this_);
|
| +
|
| + if (!video_renderer_) {
|
| + done_cb.Run();
|
| + return;
|
| + }
|
| +
|
| + video_renderer_->Flush(done_cb);
|
| +}
|
| +
|
| +void RendererImpl::OnVideoRendererFlushDone() {
|
| + DVLOG(1) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kFlushing) << state_;
|
| + DCHECK(!flush_cb_.is_null());
|
| +
|
| + DCHECK_EQ(video_buffering_state_, BUFFERING_HAVE_NOTHING);
|
| + video_ended_ = false;
|
| + state_ = kPlaying;
|
| + base::ResetAndReturn(&flush_cb_).Run();
|
| +}
|
| +
|
| +void RendererImpl::OnAudioTimeUpdate(base::TimeDelta time,
|
| + base::TimeDelta max_time) {
|
| + DVLOG(3) << __FUNCTION__ << "(" << time.InMilliseconds()
|
| + << ", " << max_time.InMilliseconds() << ")";
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_LE(time.InMicroseconds(), max_time.InMicroseconds());
|
| +
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| +
|
| + if (interpolation_state_ == INTERPOLATION_WAITING_FOR_AUDIO_TIME_UPDATE &&
|
| + time < interpolator_->GetInterpolatedTime()) {
|
| + return;
|
| + }
|
| +
|
| + if (state_ == kFlushing)
|
| + return;
|
| +
|
| + interpolator_->SetBounds(time, max_time);
|
| + StartClockIfWaitingForTimeUpdate_Locked();
|
| +}
|
| +
|
| +void RendererImpl::OnVideoTimeUpdate(base::TimeDelta max_time) {
|
| + DVLOG(3) << __FUNCTION__ << "(" << max_time.InMilliseconds() << ")";
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (audio_renderer_)
|
| + return;
|
| +
|
| + if (state_ == kFlushing)
|
| + return;
|
| +
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + DCHECK_NE(interpolation_state_, INTERPOLATION_WAITING_FOR_AUDIO_TIME_UPDATE);
|
| + interpolator_->SetUpperBound(max_time);
|
| +}
|
| +
|
| +void RendererImpl::OnUpdateStatistics(const PipelineStatistics& stats) {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + statistics_cb_.Run(stats);
|
| +}
|
| +
|
| +void RendererImpl::OnBufferingStateChanged(BufferingState* buffering_state,
|
| + BufferingState new_buffering_state) {
|
| + DVLOG(2) << __FUNCTION__ << "(" << *buffering_state << ", "
|
| + << new_buffering_state << ") "
|
| + << (buffering_state == &audio_buffering_state_ ? "audio" : "video");
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + bool was_waiting_for_enough_data = WaitingForEnoughData();
|
| +
|
| + *buffering_state = new_buffering_state;
|
| +
|
| + // Disable underflow by ignoring updates that renderers have ran out of data.
|
| + if (state_ == kPlaying && new_buffering_state == BUFFERING_HAVE_NOTHING &&
|
| + underflow_disabled_for_testing_) {
|
| + return;
|
| + }
|
| +
|
| + // Renderer underflowed.
|
| + if (!was_waiting_for_enough_data && WaitingForEnoughData()) {
|
| + PausePlayback();
|
| +
|
| + // TODO(scherkus): Fire BUFFERING_HAVE_NOTHING callback to alert clients of
|
| + // underflow state http://crbug.com/144683
|
| + return;
|
| + }
|
| +
|
| + // Renderer prerolled.
|
| + if (was_waiting_for_enough_data && !WaitingForEnoughData()) {
|
| + StartPlayback();
|
| + buffering_state_cb_.Run(BUFFERING_HAVE_ENOUGH);
|
| + return;
|
| + }
|
| +}
|
| +
|
| +bool RendererImpl::WaitingForEnoughData() const {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + if (state_ != kPlaying)
|
| + return false;
|
| + if (audio_renderer_ && audio_buffering_state_ != BUFFERING_HAVE_ENOUGH)
|
| + return true;
|
| + if (video_renderer_ && video_buffering_state_ != BUFFERING_HAVE_ENOUGH)
|
| + return true;
|
| + return false;
|
| +}
|
| +
|
| +void RendererImpl::PausePlayback() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kPlaying);
|
| + DCHECK(WaitingForEnoughData());
|
| +
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + PauseClockAndStopTicking_Locked();
|
| +}
|
| +
|
| +void RendererImpl::StartPlayback() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kPlaying);
|
| + DCHECK_EQ(interpolation_state_, INTERPOLATION_STOPPED);
|
| + DCHECK(!WaitingForEnoughData());
|
| +
|
| + if (time_source_) {
|
| + // We use audio stream to update the interpolator. So if there is such a
|
| + // stream, we pause the interpolator until we receive a valid timestamp.
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + interpolation_state_ = INTERPOLATION_WAITING_FOR_AUDIO_TIME_UPDATE;
|
| + time_source_->StartTicking();
|
| + } else {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + interpolation_state_ = INTERPOLATION_STARTED;
|
| + interpolator_->SetUpperBound(get_duration_cb_.Run());
|
| + interpolator_->StartInterpolating();
|
| + }
|
| +}
|
| +
|
| +void RendererImpl::PauseClockAndStopTicking_Locked() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + interpolator_lock_.AssertAcquired();
|
| + switch (interpolation_state_) {
|
| + case INTERPOLATION_STOPPED:
|
| + return;
|
| +
|
| + case INTERPOLATION_WAITING_FOR_AUDIO_TIME_UPDATE:
|
| + time_source_->StopTicking();
|
| + break;
|
| +
|
| + case INTERPOLATION_STARTED:
|
| + if (time_source_)
|
| + time_source_->StopTicking();
|
| + interpolator_->StopInterpolating();
|
| + break;
|
| + }
|
| +
|
| + interpolation_state_ = INTERPOLATION_STOPPED;
|
| +}
|
| +
|
| +void RendererImpl::StartClockIfWaitingForTimeUpdate_Locked() {
|
| + interpolator_lock_.AssertAcquired();
|
| + if (interpolation_state_ != INTERPOLATION_WAITING_FOR_AUDIO_TIME_UPDATE)
|
| + return;
|
| +
|
| + interpolation_state_ = INTERPOLATION_STARTED;
|
| + interpolator_->StartInterpolating();
|
| +}
|
| +
|
| +void RendererImpl::OnAudioRendererEnded() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (state_ != kPlaying)
|
| + return;
|
| +
|
| + DCHECK(!audio_ended_);
|
| + audio_ended_ = true;
|
| +
|
| + // Start clock since there is no more audio to trigger clock updates.
|
| + {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + interpolator_->SetUpperBound(get_duration_cb_.Run());
|
| + StartClockIfWaitingForTimeUpdate_Locked();
|
| + }
|
| +
|
| + RunEndedCallbackIfNeeded();
|
| +}
|
| +
|
| +void RendererImpl::OnVideoRendererEnded() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (state_ != kPlaying)
|
| + return;
|
| +
|
| + DCHECK(!video_ended_);
|
| + video_ended_ = true;
|
| +
|
| + RunEndedCallbackIfNeeded();
|
| +}
|
| +
|
| +void RendererImpl::RunEndedCallbackIfNeeded() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (audio_renderer_ && !audio_ended_)
|
| + return;
|
| +
|
| + if (video_renderer_ && !video_ended_)
|
| + return;
|
| +
|
| + {
|
| + base::AutoLock auto_lock(interpolator_lock_);
|
| + PauseClockAndStopTicking_Locked();
|
| + base::TimeDelta duration = get_duration_cb_.Run();
|
| + interpolator_->SetBounds(duration, duration);
|
| + }
|
| +
|
| + ended_cb_.Run();
|
| +}
|
| +
|
| +void RendererImpl::OnError(PipelineStatus error) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
|
| +
|
| + state_ = kError;
|
| +
|
| + // Pipeline will destroy |this| as the result of error.
|
| + base::ResetAndReturn(&error_cb_).Run(error);
|
| +}
|
| +
|
| +} // namespace media
|
|
|