| Index: media/base/pipeline_impl.cc
|
| diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
|
| index ac39834374cad409c77ca023419fc83ee8b5a7ed..c5cb4841dc039970d0f8bb3cb8d1c587bcf99b69 100644
|
| --- a/media/base/pipeline_impl.cc
|
| +++ b/media/base/pipeline_impl.cc
|
| @@ -5,96 +5,233 @@
|
| #include "media/base/pipeline_impl.h"
|
|
|
| #include <algorithm>
|
| -#include <utility>
|
|
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/callback.h"
|
| #include "base/callback_helpers.h"
|
| #include "base/command_line.h"
|
| -#include "base/compiler_specific.h"
|
| #include "base/location.h"
|
| -#include "base/memory/ptr_util.h"
|
| #include "base/metrics/histogram.h"
|
| #include "base/single_thread_task_runner.h"
|
| -#include "base/stl_util.h"
|
| -#include "base/strings/string_number_conversions.h"
|
| -#include "base/strings/string_util.h"
|
| +#include "base/synchronization/lock.h"
|
| #include "base/synchronization/waitable_event.h"
|
| #include "base/threading/thread_task_runner_handle.h"
|
| #include "media/base/bind_to_current_loop.h"
|
| +#include "media/base/demuxer.h"
|
| #include "media/base/media_log.h"
|
| #include "media/base/media_switches.h"
|
| #include "media/base/renderer.h"
|
| +#include "media/base/renderer_client.h"
|
| +#include "media/base/serial_runner.h"
|
| #include "media/base/text_renderer.h"
|
| #include "media/base/text_track_config.h"
|
| #include "media/base/timestamp_constants.h"
|
| #include "media/base/video_decoder_config.h"
|
|
|
| -using base::TimeDelta;
|
| +static const double kDefaultPlaybackRate = 0.0;
|
| +static const float kDefaultVolume = 1.0f;
|
|
|
| namespace media {
|
|
|
| +class PipelineImpl::RendererWrapper : public DemuxerHost,
|
| + public RendererClient {
|
| + public:
|
| + RendererWrapper(base::WeakPtr<PipelineImpl> weak_pipeline,
|
| + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
|
| + scoped_refptr<MediaLog> media_log);
|
| + ~RendererWrapper() final;
|
| +
|
| + void Start(Demuxer* demuxer,
|
| + std::unique_ptr<Renderer> renderer,
|
| + std::unique_ptr<TextRenderer> text_renderer);
|
| + void Stop(const base::Closure& stop_cb);
|
| + void Seek(base::TimeDelta time);
|
| + void Suspend();
|
| + void Resume(std::unique_ptr<Renderer> renderer, base::TimeDelta time);
|
| + void SetPlaybackRate(double playback_rate);
|
| + void SetVolume(float volume);
|
| + base::TimeDelta GetMediaTime();
|
| + void SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb);
|
| +
|
| + private:
|
| + // DemuxerHost implementaion.
|
| + void OnBufferedTimeRangesChanged(const Ranges<base::TimeDelta>& ranges) final;
|
| + void SetDuration(base::TimeDelta duration) final;
|
| + void OnDemuxerError(PipelineStatus error) final;
|
| + void AddTextStream(DemuxerStream* text_stream,
|
| + const TextTrackConfig& config) final;
|
| + void RemoveTextStream(DemuxerStream* text_stream) final;
|
| +
|
| + // RendererClient implementation.
|
| + void OnError(PipelineStatus error) final;
|
| + void OnEnded() final;
|
| + void OnStatisticsUpdate(const PipelineStatistics& stats) final;
|
| + void OnBufferingStateChange(BufferingState state) final;
|
| + void OnWaitingForDecryptionKey() final;
|
| + void OnVideoNaturalSizeChange(const gfx::Size& size) final;
|
| + void OnVideoOpacityChange(bool opaque) final;
|
| +
|
| + void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb);
|
| + void DoStop(const base::Closure& done_cb);
|
| + void OnPipelineError(PipelineStatus error);
|
| + void OnTextRendererEnded();
|
| + void RunEndedCallbackIfNeeded();
|
| + void SetState(State next_state);
|
| + State GetNextState() const;
|
| + void StateTransitionTask(PipelineStatus status);
|
| + void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb,
|
| + CdmContext* cdm_context,
|
| + bool success);
|
| + void AddTextStreamTask(DemuxerStream* text_stream,
|
| + const TextTrackConfig& config);
|
| + void RemoveTextStreamTask(DemuxerStream* text_stream);
|
| + void InitializeDemuxer(const PipelineStatusCB& done_cb);
|
| + void InitializeRenderer(const PipelineStatusCB& done_cb);
|
| + void ReportMetadata();
|
| +
|
| + base::WeakPtr<PipelineImpl> weak_pipeline_;
|
| + const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
|
| + const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
|
| + const scoped_refptr<MediaLog> media_log_;
|
| +
|
| + Demuxer* demuxer_;
|
| + std::unique_ptr<Renderer> renderer_;
|
| + std::unique_ptr<TextRenderer> text_renderer_;
|
| + double playback_rate_;
|
| + float volume_;
|
| + CdmContext* cdm_context_;
|
| +
|
| + // Lock used to serialize members - |renderer_| and |suspend_timestamp_| -
|
| + // accessed in GetMediaTime(), which is the only member function called on
|
| + // the main thread.
|
| + mutable base::Lock media_time_lock_;
|
| +
|
| + // Current state of the pipeline.
|
| + State state_;
|
| +
|
| + // Status of the pipeline. Initialized to PIPELINE_OK which indicates that
|
| + // the pipeline is operating correctly. Any other value indicates that the
|
| + // pipeline is stopped or is stopping. Clients can call the Stop() method to
|
| + // reset the pipeline state, and restore this to PIPELINE_OK.
|
| + PipelineStatus status_;
|
| +
|
| + // The timestamp to start playback from after starting/seeking/resuming has
|
| + // completed.
|
| + base::TimeDelta start_timestamp_;
|
| +
|
| + // The media timestamp to return while the pipeline is suspended.
|
| + // Otherwise set to kNoTimestamp().
|
| + base::TimeDelta suspend_timestamp_;
|
| +
|
| + // Whether we've received the audio/video/text ended events.
|
| + bool renderer_ended_;
|
| + bool text_renderer_ended_;
|
| +
|
| + // Series of tasks to Start(), Seek(), and Resume().
|
| + std::unique_ptr<SerialRunner> pending_callbacks_;
|
| +
|
| + base::WeakPtr<RendererWrapper> weak_this_;
|
| + base::WeakPtrFactory<RendererWrapper> weak_factory_;
|
| + DISALLOW_COPY_AND_ASSIGN(RendererWrapper);
|
| +};
|
| +
|
| PipelineImpl::PipelineImpl(
|
| const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
|
| MediaLog* media_log)
|
| - : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
|
| - media_task_runner_(media_task_runner),
|
| + : media_task_runner_(media_task_runner),
|
| media_log_(media_log),
|
| - running_(false),
|
| + client_(nullptr),
|
| + playback_rate_(kDefaultPlaybackRate),
|
| + volume_(kDefaultVolume),
|
| did_loading_progress_(false),
|
| - volume_(1.0f),
|
| - playback_rate_(0.0),
|
| - status_(PIPELINE_OK),
|
| - state_(kCreated),
|
| - suspend_timestamp_(kNoTimestamp()),
|
| - renderer_ended_(false),
|
| - text_renderer_ended_(false),
|
| - demuxer_(NULL),
|
| - cdm_context_(nullptr),
|
| weak_factory_(this) {
|
| - weak_this_ = weak_factory_.GetWeakPtr();
|
| - media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
|
| + DVLOG(2) << __FUNCTION__;
|
| + renderer_wrapper_.reset(new RendererWrapper(weak_factory_.GetWeakPtr(),
|
| + media_task_runner_, media_log_));
|
| }
|
|
|
| PipelineImpl::~PipelineImpl() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread())
|
| - << "Pipeline must be destroyed on same thread that created it";
|
| - DCHECK(!running_) << "Stop() must complete before destroying object";
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(!client_) << "Stop() must complete before destroying object";
|
| DCHECK(seek_cb_.is_null());
|
| + DCHECK(suspend_cb_.is_null());
|
| +
|
| + // Invalidate self weak pointers effectively canceling all pending
|
| + // notifications in the message queue.
|
| + // Do this before posting the DeleteSoon task to the media thread to
|
| + // guarantee that the WeakPtr<PipelineImpl> passed to the RendererWrapper
|
| + // is not invalidated on the media thread.
|
| + weak_factory_.InvalidateWeakPtrs();
|
| +
|
| + // RendererWrapper is deleted on the media thread.
|
| + media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release());
|
| }
|
|
|
| void PipelineImpl::Start(Demuxer* demuxer,
|
| std::unique_ptr<Renderer> renderer,
|
| Client* client,
|
| const PipelineStatusCB& seek_cb) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(demuxer);
|
| + DCHECK(renderer);
|
| DCHECK(client);
|
| DCHECK(!seek_cb.is_null());
|
|
|
| - base::AutoLock auto_lock(lock_);
|
| - CHECK(!running_) << "Media pipeline is already running";
|
| - running_ = true;
|
| + DCHECK(!client_);
|
| + DCHECK(seek_cb_.is_null());
|
| + client_ = client;
|
| + seek_cb_ = seek_cb;
|
| +
|
| + std::unique_ptr<TextRenderer> text_renderer;
|
| + if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kEnableInbandTextTracks)) {
|
| + text_renderer.reset(new TextRenderer(
|
| + media_task_runner_,
|
| + BindToCurrentLoop(base::Bind(&PipelineImpl::OnAddTextTrack,
|
| + weak_factory_.GetWeakPtr()))));
|
| + }
|
|
|
| - demuxer_ = demuxer;
|
| - renderer_ = std::move(renderer);
|
| - client_weak_factory_.reset(new base::WeakPtrFactory<Client>(client));
|
| - weak_client_ = client_weak_factory_->GetWeakPtr();
|
| - seek_cb_ = media::BindToCurrentLoop(seek_cb);
|
| media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::StartTask, weak_this_));
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::Start,
|
| + base::Unretained(renderer_wrapper_.get()), demuxer,
|
| + base::Passed(&renderer), base::Passed(&text_renderer)));
|
| }
|
|
|
| void PipelineImpl::Stop() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (!IsRunning()) {
|
| + DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
|
| + return;
|
| + }
|
|
|
| - if (media_task_runner_ != main_task_runner_) {
|
| + if (statistics_.video_frames_decoded > 0) {
|
| + UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount",
|
| + statistics_.video_frames_dropped);
|
| + }
|
| +
|
| + if (media_task_runner_->BelongsToCurrentThread()) {
|
| + // This path is executed by unittests that share media and main threads.
|
| + base::Closure stop_cb = base::Bind(&base::DoNothing);
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::Stop,
|
| + base::Unretained(renderer_wrapper_.get()), stop_cb));
|
| + } else {
|
| // This path is executed by production code where the two task runners -
|
| // main and media - live on different threads.
|
| - // TODO(alokp): It may be possible to not have to wait for StopTask by
|
| - // moving the members accessed on media thread into a class/struct and
|
| - // DeleteSoon the instance on the media thread.
|
| + //
|
| + // TODO(alokp): We should not have to wait for the RendererWrapper::Stop.
|
| + // RendererWrapper holds a raw reference to Demuxer, which in turn holds a
|
| + // raw reference to DataSource. Both Demuxer and DataSource need to live
|
| + // until RendererWrapper is stopped. If RendererWrapper owned Demuxer and
|
| + // Demuxer owned DataSource, we could simply let RendererWrapper get lazily
|
| + // destroyed on the media thread.
|
| base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
| base::WaitableEvent::InitialState::NOT_SIGNALED);
|
| base::Closure stop_cb =
|
| @@ -102,163 +239,156 @@ void PipelineImpl::Stop() {
|
| // If posting the task fails or the posted task fails to run,
|
| // we will wait here forever. So add a CHECK to make sure we do not run
|
| // into those situations.
|
| - CHECK(weak_factory_.HasWeakPtrs());
|
| CHECK(media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::StopTask, weak_this_, stop_cb)));
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::Stop,
|
| + base::Unretained(renderer_wrapper_.get()), stop_cb)));
|
| waiter.Wait();
|
| - } else {
|
| - // This path is executed by unittests that share media and main threads.
|
| - StopTask(base::Bind(&base::DoNothing));
|
| }
|
| - // Invalidate client weak pointer effectively canceling all pending client
|
| - // notifications in the message queue.
|
| - client_weak_factory_.reset();
|
| +
|
| + // Once the pipeline is stopped, nothing is reported back to the client.
|
| + // Reset all callbacks and client handle.
|
| + seek_cb_.Reset();
|
| + suspend_cb_.Reset();
|
| + client_ = nullptr;
|
| }
|
|
|
| -void PipelineImpl::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +void PipelineImpl::Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(!seek_cb.is_null());
|
|
|
| if (!IsRunning()) {
|
| DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
|
| return;
|
| }
|
|
|
| + DCHECK(seek_cb_.is_null());
|
| + seek_cb_ = seek_cb;
|
| media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::SeekTask, weak_this_, time,
|
| - media::BindToCurrentLoop(seek_cb)));
|
| + FROM_HERE, base::Bind(&RendererWrapper::Seek,
|
| + base::Unretained(renderer_wrapper_.get()), time));
|
| }
|
|
|
| bool PipelineImpl::IsRunning() const {
|
| - // TODO(alokp): Add thread DCHECK after removing the internal usage on
|
| - // media thread.
|
| - base::AutoLock auto_lock(lock_);
|
| - return running_;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return !!client_;
|
| }
|
|
|
| double PipelineImpl::GetPlaybackRate() const {
|
| - // TODO(alokp): Add thread DCHECK after removing the internal usage on
|
| - // media thread.
|
| - base::AutoLock auto_lock(lock_);
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| return playback_rate_;
|
| }
|
|
|
| void PipelineImpl::SetPlaybackRate(double playback_rate) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| if (playback_rate < 0.0)
|
| return;
|
|
|
| - base::AutoLock auto_lock(lock_);
|
| playback_rate_ = playback_rate;
|
| - if (running_) {
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::PlaybackRateChangedTask,
|
| - weak_this_, playback_rate));
|
| - }
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetPlaybackRate,
|
| + base::Unretained(renderer_wrapper_.get()), playback_rate_));
|
| }
|
|
|
| void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(!suspend_cb.is_null());
|
| +
|
| + DCHECK(IsRunning());
|
| + DCHECK(suspend_cb_.is_null());
|
| + suspend_cb_ = suspend_cb;
|
|
|
| media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::SuspendTask, weak_this_,
|
| - media::BindToCurrentLoop(suspend_cb)));
|
| + FROM_HERE, base::Bind(&RendererWrapper::Suspend,
|
| + base::Unretained(renderer_wrapper_.get())));
|
| }
|
|
|
| void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer,
|
| - base::TimeDelta timestamp,
|
| + base::TimeDelta time,
|
| const PipelineStatusCB& seek_cb) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(renderer);
|
| + DCHECK(!seek_cb.is_null());
|
| +
|
| + DCHECK(IsRunning());
|
| + DCHECK(seek_cb_.is_null());
|
| + seek_cb_ = seek_cb;
|
|
|
| media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&PipelineImpl::ResumeTask, weak_this_, base::Passed(&renderer),
|
| - timestamp, media::BindToCurrentLoop(seek_cb)));
|
| + FROM_HERE, base::Bind(&RendererWrapper::Resume,
|
| + base::Unretained(renderer_wrapper_.get()),
|
| + base::Passed(&renderer), time));
|
| }
|
|
|
| float PipelineImpl::GetVolume() const {
|
| - // TODO(alokp): Add thread DCHECK after removing the internal usage on
|
| - // media thread.
|
| - base::AutoLock auto_lock(lock_);
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| return volume_;
|
| }
|
|
|
| void PipelineImpl::SetVolume(float volume) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__ << "(" << volume << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| if (volume < 0.0f || volume > 1.0f)
|
| return;
|
|
|
| - base::AutoLock auto_lock(lock_);
|
| volume_ = volume;
|
| - if (running_) {
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&PipelineImpl::VolumeChangedTask, weak_this_, volume));
|
| - }
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetVolume,
|
| + base::Unretained(renderer_wrapper_.get()), volume_));
|
| }
|
|
|
| -TimeDelta PipelineImpl::GetMediaTime() const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(lock_);
|
| - if (suspend_timestamp_ != kNoTimestamp())
|
| - return suspend_timestamp_;
|
| - return renderer_ ? std::min(renderer_->GetMediaTime(), duration_)
|
| - : TimeDelta();
|
| +base::TimeDelta PipelineImpl::GetMediaTime() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return renderer_wrapper_->GetMediaTime();
|
| }
|
|
|
| -Ranges<TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(lock_);
|
| +Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| return buffered_time_ranges_;
|
| }
|
|
|
| -TimeDelta PipelineImpl::GetMediaDuration() const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(lock_);
|
| +base::TimeDelta PipelineImpl::GetMediaDuration() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| return duration_;
|
| }
|
|
|
| bool PipelineImpl::DidLoadingProgress() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(lock_);
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| bool ret = did_loading_progress_;
|
| did_loading_progress_ = false;
|
| return ret;
|
| }
|
|
|
| PipelineStatistics PipelineImpl::GetStatistics() const {
|
| - // TODO(alokp): Add thread DCHECK after removing the internal usage on
|
| - // media thread.
|
| - base::AutoLock auto_lock(lock_);
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| return statistics_;
|
| }
|
|
|
| void PipelineImpl::SetCdm(CdmContext* cdm_context,
|
| const CdmAttachedCB& cdm_attached_cb) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(cdm_context);
|
| + DCHECK(!cdm_attached_cb.is_null());
|
|
|
| media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::SetCdmTask, weak_this_, cdm_context,
|
| - cdm_attached_cb));
|
| -}
|
| -
|
| -void PipelineImpl::SetErrorForTesting(PipelineStatus status) {
|
| - OnError(status);
|
| -}
|
| -
|
| -bool PipelineImpl::HasWeakPtrsForTesting() const {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - return weak_factory_.HasWeakPtrs();
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetCdm,
|
| + base::Unretained(renderer_wrapper_.get()), cdm_context,
|
| + media::BindToCurrentLoop(cdm_attached_cb)));
|
| }
|
|
|
| -void PipelineImpl::SetState(State next_state) {
|
| +void PipelineImpl::RendererWrapper::SetState(State next_state) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state);
|
| + DVLOG(1) << PipelineImpl::GetStateString(state_) << " -> "
|
| + << PipelineImpl::GetStateString(next_state);
|
|
|
| state_ = next_state;
|
| media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
|
| @@ -268,6 +398,7 @@ void PipelineImpl::SetState(State next_state) {
|
| case state: \
|
| return #state;
|
|
|
| +// static
|
| const char* PipelineImpl::GetStateString(State state) {
|
| switch (state) {
|
| RETURN_STRING(kCreated);
|
| @@ -287,9 +418,8 @@ const char* PipelineImpl::GetStateString(State state) {
|
|
|
| #undef RETURN_STRING
|
|
|
| -PipelineImpl::State PipelineImpl::GetNextState() const {
|
| +PipelineImpl::State PipelineImpl::RendererWrapper::GetNextState() const {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(stop_cb_.is_null()) << "State transitions don't happen when stopping";
|
| DCHECK_EQ(status_, PIPELINE_OK)
|
| << "State transitions don't happen when there's an error: " << status_;
|
|
|
| @@ -322,43 +452,42 @@ PipelineImpl::State PipelineImpl::GetNextState() const {
|
| return state_;
|
| }
|
|
|
| -void PipelineImpl::OnDemuxerError(PipelineStatus error) {
|
| +void PipelineImpl::RendererWrapper::OnDemuxerError(PipelineStatus error) {
|
| // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
|
| // implementations call DemuxerHost on the media thread.
|
| media_task_runner_->PostTask(
|
| FROM_HERE,
|
| - base::Bind(&PipelineImpl::ErrorChangedTask, weak_this_, error));
|
| + base::Bind(&RendererWrapper::OnPipelineError, weak_this_, error));
|
| }
|
|
|
| -void PipelineImpl::AddTextStream(DemuxerStream* text_stream,
|
| - const TextTrackConfig& config) {
|
| +void PipelineImpl::RendererWrapper::AddTextStream(
|
| + DemuxerStream* text_stream,
|
| + const TextTrackConfig& config) {
|
| // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
|
| // implementations call DemuxerHost on the media thread.
|
| media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::AddTextStreamTask, weak_this_,
|
| + FROM_HERE, base::Bind(&RendererWrapper::AddTextStreamTask, weak_this_,
|
| text_stream, config));
|
| }
|
|
|
| -void PipelineImpl::RemoveTextStream(DemuxerStream* text_stream) {
|
| +void PipelineImpl::RendererWrapper::RemoveTextStream(
|
| + DemuxerStream* text_stream) {
|
| // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
|
| // implementations call DemuxerHost on the media thread.
|
| media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&PipelineImpl::RemoveTextStreamTask, weak_this_, text_stream));
|
| + FROM_HERE, base::Bind(&RendererWrapper::RemoveTextStreamTask, weak_this_,
|
| + text_stream));
|
| }
|
|
|
| -void PipelineImpl::OnError(PipelineStatus error) {
|
| +void PipelineImpl::RendererWrapper::OnError(PipelineStatus error) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(IsRunning());
|
| - DCHECK_NE(PIPELINE_OK, error);
|
| - VLOG(1) << "Media pipeline error: " << error;
|
|
|
| media_task_runner_->PostTask(
|
| FROM_HERE,
|
| - base::Bind(&PipelineImpl::ErrorChangedTask, weak_this_, error));
|
| + base::Bind(&RendererWrapper::OnPipelineError, weak_this_, error));
|
| }
|
|
|
| -void PipelineImpl::OnEnded() {
|
| +void PipelineImpl::RendererWrapper::OnEnded() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
|
|
|
| @@ -367,79 +496,75 @@ void PipelineImpl::OnEnded() {
|
|
|
| DCHECK(!renderer_ended_);
|
| renderer_ended_ = true;
|
| -
|
| RunEndedCallbackIfNeeded();
|
| }
|
|
|
| -void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
|
| +void PipelineImpl::RendererWrapper::OnStatisticsUpdate(
|
| + const PipelineStatistics& stats) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - base::AutoLock auto_lock(lock_);
|
| - statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
|
| - statistics_.video_bytes_decoded += stats.video_bytes_decoded;
|
| - statistics_.video_frames_decoded += stats.video_frames_decoded;
|
| - statistics_.video_frames_dropped += stats.video_frames_dropped;
|
| - statistics_.audio_memory_usage += stats.audio_memory_usage;
|
| - statistics_.video_memory_usage += stats.video_memory_usage;
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&PipelineImpl::OnStatisticsUpdate, weak_pipeline_, stats));
|
| }
|
|
|
| -void PipelineImpl::OnBufferingStateChange(BufferingState state) {
|
| - DVLOG(1) << __FUNCTION__ << "(" << state << ") ";
|
| +void PipelineImpl::RendererWrapper::OnBufferingStateChange(
|
| + BufferingState state) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DVLOG(2) << __FUNCTION__ << "(" << state << ") ";
|
|
|
| main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnBufferingStateChange,
|
| - weak_client_, state));
|
| + FROM_HERE,
|
| + base::Bind(&PipelineImpl::OnBufferingStateChange, weak_pipeline_, state));
|
| }
|
|
|
| -void PipelineImpl::OnWaitingForDecryptionKey() {
|
| +void PipelineImpl::RendererWrapper::OnWaitingForDecryptionKey() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| main_task_runner_->PostTask(
|
| FROM_HERE,
|
| - base::Bind(&Pipeline::Client::OnWaitingForDecryptionKey, weak_client_));
|
| + base::Bind(&PipelineImpl::OnWaitingForDecryptionKey, weak_pipeline_));
|
| }
|
|
|
| -void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) {
|
| +void PipelineImpl::RendererWrapper::OnVideoNaturalSizeChange(
|
| + const gfx::Size& size) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnVideoNaturalSizeChange,
|
| - weak_client_, size));
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnVideoNaturalSizeChange,
|
| + weak_pipeline_, size));
|
| }
|
|
|
| -void PipelineImpl::OnVideoOpacityChange(bool opaque) {
|
| +void PipelineImpl::RendererWrapper::OnVideoOpacityChange(bool opaque) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnVideoOpacityChange,
|
| - weak_client_, opaque));
|
| + FROM_HERE,
|
| + base::Bind(&PipelineImpl::OnVideoOpacityChange, weak_pipeline_, opaque));
|
| }
|
|
|
| -void PipelineImpl::SetDuration(TimeDelta duration) {
|
| +void PipelineImpl::RendererWrapper::SetDuration(base::TimeDelta duration) {
|
| // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
|
| // implementations call DemuxerHost on the media thread.
|
| - DCHECK(IsRunning());
|
| media_log_->AddEvent(media_log_->CreateTimeEvent(MediaLogEvent::DURATION_SET,
|
| "duration", duration));
|
| UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
|
|
|
| - base::AutoLock auto_lock(lock_);
|
| - duration_ = duration;
|
| main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnDurationChange, weak_client_));
|
| + FROM_HERE,
|
| + base::Bind(&PipelineImpl::OnDurationChange, weak_pipeline_, duration));
|
| }
|
|
|
| -void PipelineImpl::StateTransitionTask(PipelineStatus status) {
|
| +void PipelineImpl::RendererWrapper::StateTransitionTask(PipelineStatus status) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - // No-op any state transitions if we're stopping.
|
| - if (state_ == kStopping || state_ == kStopped)
|
| + // No-op any state transitions if we're stopping or already encountered error.
|
| + if (state_ == kStopping || state_ == kStopped || status_ != PIPELINE_OK)
|
| return;
|
|
|
| // Report error from the previous operation.
|
| if (status != PIPELINE_OK) {
|
| - ErrorChangedTask(status);
|
| + OnPipelineError(status);
|
| return;
|
| }
|
|
|
| @@ -451,7 +576,7 @@ void PipelineImpl::StateTransitionTask(PipelineStatus status) {
|
| pending_callbacks_.reset();
|
|
|
| PipelineStatusCB done_cb =
|
| - base::Bind(&PipelineImpl::StateTransitionTask, weak_this_);
|
| + base::Bind(&RendererWrapper::StateTransitionTask, weak_this_);
|
|
|
| // Switch states, performing any entrance actions for the new state as well.
|
| SetState(GetNextState());
|
| @@ -474,27 +599,26 @@ void PipelineImpl::StateTransitionTask(PipelineStatus status) {
|
| DCHECK(start_timestamp_ >= base::TimeDelta());
|
| renderer_->StartPlayingFrom(start_timestamp_);
|
| {
|
| - base::AutoLock auto_lock(lock_);
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| suspend_timestamp_ = kNoTimestamp();
|
| }
|
|
|
| if (text_renderer_)
|
| text_renderer_->StartPlaying();
|
|
|
| - base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_,
|
| + start_timestamp_));
|
|
|
| - PlaybackRateChangedTask(GetPlaybackRate());
|
| - VolumeChangedTask(GetVolume());
|
| + renderer_->SetPlaybackRate(playback_rate_);
|
| + renderer_->SetVolume(volume_);
|
| return;
|
|
|
| case kSuspended:
|
| renderer_.reset();
|
| - {
|
| - base::AutoLock auto_lock(lock_);
|
| - statistics_.audio_memory_usage = 0;
|
| - statistics_.video_memory_usage = 0;
|
| - }
|
| - base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK);
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_,
|
| + suspend_timestamp_));
|
| return;
|
|
|
| case kStopping:
|
| @@ -513,8 +637,8 @@ void PipelineImpl::StateTransitionTask(PipelineStatus status) {
|
| //
|
| // That being said, deleting the renderers while keeping |pending_callbacks_|
|
| // running on the media thread would result in crashes.
|
| -void PipelineImpl::DoSeek(TimeDelta seek_timestamp,
|
| - const PipelineStatusCB& done_cb) {
|
| +void PipelineImpl::RendererWrapper::DoSeek(base::TimeDelta seek_timestamp,
|
| + const PipelineStatusCB& done_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| DCHECK(!pending_callbacks_.get());
|
| DCHECK_EQ(state_, kSeeking);
|
| @@ -543,7 +667,7 @@ void PipelineImpl::DoSeek(TimeDelta seek_timestamp,
|
| pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
|
| }
|
|
|
| -void PipelineImpl::DoStop() {
|
| +void PipelineImpl::RendererWrapper::DoStop(const base::Closure& done_cb) {
|
| DVLOG(2) << __FUNCTION__;
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| DCHECK_EQ(state_, kStopping);
|
| @@ -553,7 +677,7 @@ void PipelineImpl::DoStop() {
|
| // even for accessing media time http://crbug.com/370634
|
| std::unique_ptr<Renderer> renderer;
|
| {
|
| - base::AutoLock auto_lock(lock_);
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| renderer.swap(renderer_);
|
| }
|
| renderer.reset();
|
| @@ -564,91 +688,69 @@ void PipelineImpl::DoStop() {
|
| demuxer_ = NULL;
|
| }
|
|
|
| - {
|
| - base::AutoLock auto_lock(lock_);
|
| - running_ = false;
|
| - }
|
| SetState(kStopped);
|
|
|
| - // If we stop during initialization/seeking/suspending we don't want to leave
|
| - // outstanding callbacks around. The callbacks also do not get run if the
|
| - // pipeline is stopped before it had a chance to complete outstanding tasks.
|
| - seek_cb_.Reset();
|
| - suspend_cb_.Reset();
|
| -
|
| - if (!stop_cb_.is_null()) {
|
| - // Invalid all weak pointers so it's safe to destroy |this| on the render
|
| - // main thread.
|
| - weak_factory_.InvalidateWeakPtrs();
|
| -
|
| - // Post the stop callback to enqueue it after the tasks that may have been
|
| - // Demuxer and Renderer during stopping.
|
| - media_task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_));
|
| - }
|
| + // Post the stop callback to enqueue it after the tasks that may have been
|
| + // posted by Demuxer and Renderer during stopping. Note that in theory the
|
| + // tasks posted by Demuxer/Renderer may post even more tasks that will get
|
| + // enqueued after |done_cb|. This may be problematic because Demuxer may
|
| + // get destroyed as soon as |done_cb| is run. In practice this is not a
|
| + // problem, but ideally Demuxer should be destroyed on the media thread.
|
| + media_task_runner_->PostTask(FROM_HERE, done_cb);
|
| }
|
|
|
| -void PipelineImpl::OnBufferedTimeRangesChanged(
|
| +void PipelineImpl::RendererWrapper::OnBufferedTimeRangesChanged(
|
| const Ranges<base::TimeDelta>& ranges) {
|
| // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
|
| // implementations call DemuxerHost on the media thread.
|
| - base::AutoLock auto_lock(lock_);
|
| - buffered_time_ranges_ = ranges;
|
| - did_loading_progress_ = true;
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnBufferedTimeRangesChange,
|
| + weak_pipeline_, ranges));
|
| }
|
|
|
| -void PipelineImpl::StartTask() {
|
| +void PipelineImpl::RendererWrapper::Start(
|
| + Demuxer* demuxer,
|
| + std::unique_ptr<Renderer> renderer,
|
| + std::unique_ptr<TextRenderer> text_renderer) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(kCreated, state_) << "Received start in unexpected state: "
|
| + << state_;
|
|
|
| - CHECK_EQ(kCreated, state_)
|
| - << "Media pipeline cannot be started more than once";
|
| -
|
| - text_renderer_ = CreateTextRenderer();
|
| + DCHECK(!demuxer_);
|
| + DCHECK(!renderer_);
|
| + DCHECK(!text_renderer_);
|
| + DCHECK(!renderer_ended_);
|
| + DCHECK(!text_renderer_ended_);
|
| + demuxer_ = demuxer;
|
| + {
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| + renderer_ = std::move(renderer);
|
| + }
|
| + text_renderer_ = std::move(text_renderer);
|
| if (text_renderer_) {
|
| text_renderer_->Initialize(
|
| - base::Bind(&PipelineImpl::OnTextRendererEnded, weak_this_));
|
| + base::Bind(&RendererWrapper::OnTextRendererEnded, weak_this_));
|
| }
|
|
|
| StateTransitionTask(PIPELINE_OK);
|
| }
|
|
|
| -void PipelineImpl::StopTask(const base::Closure& stop_cb) {
|
| +void PipelineImpl::RendererWrapper::Stop(const base::Closure& stop_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(stop_cb_.is_null());
|
| -
|
| - if (state_ == kStopped) {
|
| - // Invalid all weak pointers so it's safe to destroy |this| on the render
|
| - // main thread.
|
| - weak_factory_.InvalidateWeakPtrs();
|
| -
|
| - // NOTE: pipeline may be deleted at this point in time as a result of
|
| - // executing |stop_cb|.
|
| - stop_cb.Run();
|
| -
|
| - return;
|
| - }
|
| -
|
| - stop_cb_ = stop_cb;
|
| -
|
| - // We may already be stopping due to a runtime error.
|
| - if (state_ == kStopping)
|
| - return;
|
| -
|
| - // Do not report statistics if the pipeline is not fully initialized.
|
| - if (state_ == kSeeking || state_ == kPlaying || state_ == kSuspending ||
|
| - state_ == kSuspended || state_ == kResuming) {
|
| - PipelineStatistics stats = GetStatistics();
|
| - if (stats.video_frames_decoded > 0) {
|
| - UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount",
|
| - stats.video_frames_dropped);
|
| - }
|
| - }
|
| + DCHECK(state_ != kStopping && state_ != kStopped);
|
|
|
| SetState(kStopping);
|
| +
|
| + // If we stop during starting/seeking/suspending/resuming we don't want to
|
| + // leave outstanding callbacks around. The callbacks also do not get run if
|
| + // the pipeline is stopped before it had a chance to complete outstanding
|
| + // tasks.
|
| pending_callbacks_.reset();
|
| - DoStop();
|
| +
|
| + DoStop(stop_cb);
|
| }
|
|
|
| -void PipelineImpl::ErrorChangedTask(PipelineStatus error) {
|
| +void PipelineImpl::RendererWrapper::OnPipelineError(PipelineStatus error) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
|
|
|
| @@ -656,110 +758,81 @@ void PipelineImpl::ErrorChangedTask(PipelineStatus error) {
|
| if (status_ != PIPELINE_OK)
|
| return;
|
|
|
| - // Don't report pipeline error events to the media log here. The embedder will
|
| - // log this when Client::OnError is called. If the pipeline is already stopped
|
| - // or stopping we also don't want to log any event. In case we are suspending
|
| - // or suspended, the error may be recoverable, so don't propagate it now,
|
| - // instead let the subsequent seek during resume propagate it if it's
|
| - // unrecoverable.
|
| + // Don't report pipeline error events to the media log here. The embedder
|
| + // will log this when Client::OnError is called. If the pipeline is already
|
| + // stopped or stopping we also don't want to log any event. In case we are
|
| + // suspending or suspended, the error may be recoverable, so don't propagate
|
| + // it now, instead let the subsequent seek during resume propagate it if
|
| + // it's unrecoverable.
|
| if (state_ == kStopping || state_ == kStopped || state_ == kSuspending ||
|
| state_ == kSuspended) {
|
| return;
|
| }
|
|
|
| - // Once we enter |kStopping| state, nothing is reported back to the client.
|
| - // If we encounter an error during initialization/seeking/suspending,
|
| - // report the error using the completion callbacks for those tasks.
|
| status_ = error;
|
| - bool error_reported = false;
|
| - if (!seek_cb_.is_null()) {
|
| - base::ResetAndReturn(&seek_cb_).Run(status_);
|
| - error_reported = true;
|
| - }
|
| - if (!suspend_cb_.is_null()) {
|
| - base::ResetAndReturn(&suspend_cb_).Run(status_);
|
| - error_reported = true;
|
| - }
|
| - if (!error_reported) {
|
| - DCHECK_NE(status_, PIPELINE_OK);
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&Pipeline::Client::OnError, weak_client_, status_));
|
| - }
|
| -
|
| - SetState(kStopping);
|
| - pending_callbacks_.reset();
|
| - DoStop();
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnError, weak_pipeline_, error));
|
| }
|
|
|
| -void PipelineImpl::PlaybackRateChangedTask(double playback_rate) {
|
| +void PipelineImpl::RendererWrapper::SetPlaybackRate(double playback_rate) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - // Playback rate changes are only carried out while playing.
|
| - if (state_ != kPlaying)
|
| - return;
|
| -
|
| - renderer_->SetPlaybackRate(playback_rate);
|
| + playback_rate_ = playback_rate;
|
| + if (state_ == kPlaying)
|
| + renderer_->SetPlaybackRate(playback_rate_);
|
| }
|
|
|
| -void PipelineImpl::VolumeChangedTask(float volume) {
|
| +void PipelineImpl::RendererWrapper::SetVolume(float volume) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - // Volume changes are only carried out while playing.
|
| - if (state_ != kPlaying)
|
| - return;
|
| -
|
| - renderer_->SetVolume(volume);
|
| + volume_ = volume;
|
| + if (state_ == kPlaying)
|
| + renderer_->SetVolume(volume_);
|
| }
|
|
|
| -void PipelineImpl::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
|
| +void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(stop_cb_.is_null());
|
|
|
| // Suppress seeking if we're not fully started.
|
| if (state_ != kPlaying) {
|
| DCHECK(state_ == kStopping || state_ == kStopped)
|
| << "Receive seek in unexpected state: " << state_;
|
| - seek_cb.Run(PIPELINE_ERROR_INVALID_STATE);
|
| + OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| return;
|
| }
|
|
|
| - DCHECK(seek_cb_.is_null());
|
| -
|
| const base::TimeDelta seek_timestamp =
|
| std::max(time, demuxer_->GetStartTime());
|
|
|
| SetState(kSeeking);
|
| - seek_cb_ = seek_cb;
|
| renderer_ended_ = false;
|
| text_renderer_ended_ = false;
|
| start_timestamp_ = seek_timestamp;
|
|
|
| DoSeek(seek_timestamp,
|
| - base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
|
| + base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -void PipelineImpl::SuspendTask(const PipelineStatusCB& suspend_cb) {
|
| +void PipelineImpl::RendererWrapper::Suspend() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| // Suppress suspending if we're not playing.
|
| if (state_ != kPlaying) {
|
| DCHECK(state_ == kStopping || state_ == kStopped)
|
| << "Receive suspend in unexpected state: " << state_;
|
| - suspend_cb.Run(PIPELINE_ERROR_INVALID_STATE);
|
| + OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| return;
|
| }
|
| DCHECK(renderer_);
|
| DCHECK(!pending_callbacks_.get());
|
|
|
| SetState(kSuspending);
|
| - suspend_cb_ = suspend_cb;
|
|
|
| // Freeze playback and record the media time before flushing. (Flushing clears
|
| // the value.)
|
| renderer_->SetPlaybackRate(0.0);
|
| {
|
| - base::AutoLock auto_lock(lock_);
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| suspend_timestamp_ = renderer_->GetMediaTime();
|
| DCHECK(suspend_timestamp_ != kNoTimestamp());
|
| }
|
| @@ -782,30 +855,32 @@ void PipelineImpl::SuspendTask(const PipelineStatusCB& suspend_cb) {
|
| }
|
|
|
| pending_callbacks_ = SerialRunner::Run(
|
| - fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
|
| + fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -void PipelineImpl::ResumeTask(std::unique_ptr<Renderer> renderer,
|
| - base::TimeDelta timestamp,
|
| - const PipelineStatusCB& seek_cb) {
|
| +void PipelineImpl::RendererWrapper::Resume(std::unique_ptr<Renderer> renderer,
|
| + base::TimeDelta timestamp) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| // Suppress resuming if we're not suspended.
|
| if (state_ != kSuspended) {
|
| DCHECK(state_ == kStopping || state_ == kStopped)
|
| << "Receive resume in unexpected state: " << state_;
|
| - seek_cb.Run(PIPELINE_ERROR_INVALID_STATE);
|
| + OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| return;
|
| }
|
| DCHECK(!renderer_);
|
| DCHECK(!pending_callbacks_.get());
|
|
|
| SetState(kResuming);
|
| - renderer_ = std::move(renderer);
|
| +
|
| + {
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| + renderer_ = std::move(renderer);
|
| + }
|
|
|
| // Set up for a seek. (Matches setup in SeekTask().)
|
| // TODO(sandersd): Share implementation with SeekTask().
|
| - seek_cb_ = seek_cb;
|
| renderer_ended_ = false;
|
| text_renderer_ended_ = false;
|
| start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime());
|
| @@ -819,15 +894,17 @@ void PipelineImpl::ResumeTask(std::unique_ptr<Renderer> renderer,
|
| fns.Push(
|
| base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_));
|
|
|
| - fns.Push(base::Bind(&PipelineImpl::InitializeRenderer, weak_this_));
|
| + fns.Push(base::Bind(&RendererWrapper::InitializeRenderer, weak_this_));
|
|
|
| pending_callbacks_ = SerialRunner::Run(
|
| - fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
|
| + fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -void PipelineImpl::SetCdmTask(CdmContext* cdm_context,
|
| - const CdmAttachedCB& cdm_attached_cb) {
|
| - base::AutoLock auto_lock(lock_);
|
| +void PipelineImpl::RendererWrapper::SetCdm(
|
| + CdmContext* cdm_context,
|
| + const CdmAttachedCB& cdm_attached_cb) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +
|
| if (!renderer_) {
|
| cdm_context_ = cdm_context;
|
| cdm_attached_cb.Run(true);
|
| @@ -835,20 +912,22 @@ void PipelineImpl::SetCdmTask(CdmContext* cdm_context,
|
| }
|
|
|
| renderer_->SetCdm(cdm_context,
|
| - base::Bind(&PipelineImpl::OnCdmAttached, weak_this_,
|
| + base::Bind(&RendererWrapper::OnCdmAttached, weak_this_,
|
| cdm_attached_cb, cdm_context));
|
| }
|
|
|
| -void PipelineImpl::OnCdmAttached(const CdmAttachedCB& cdm_attached_cb,
|
| - CdmContext* cdm_context,
|
| - bool success) {
|
| +void PipelineImpl::RendererWrapper::OnCdmAttached(
|
| + const CdmAttachedCB& cdm_attached_cb,
|
| + CdmContext* cdm_context,
|
| + bool success) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +
|
| if (success)
|
| cdm_context_ = cdm_context;
|
| cdm_attached_cb.Run(success);
|
| }
|
|
|
| -void PipelineImpl::OnTextRendererEnded() {
|
| +void PipelineImpl::RendererWrapper::OnTextRendererEnded() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
|
|
|
| @@ -861,7 +940,7 @@ void PipelineImpl::OnTextRendererEnded() {
|
| RunEndedCallbackIfNeeded();
|
| }
|
|
|
| -void PipelineImpl::RunEndedCallbackIfNeeded() {
|
| +void PipelineImpl::RendererWrapper::RunEndedCallbackIfNeeded() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| if (renderer_ && !renderer_ended_)
|
| @@ -872,62 +951,42 @@ void PipelineImpl::RunEndedCallbackIfNeeded() {
|
|
|
| DCHECK_EQ(status_, PIPELINE_OK);
|
| main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnEnded, weak_client_));
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnEnded, weak_pipeline_));
|
| }
|
|
|
| -std::unique_ptr<TextRenderer> PipelineImpl::CreateTextRenderer() {
|
| +void PipelineImpl::RendererWrapper::AddTextStreamTask(
|
| + DemuxerStream* text_stream,
|
| + const TextTrackConfig& config) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
|
| - if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks))
|
| - return nullptr;
|
| -
|
| - return base::WrapUnique(new media::TextRenderer(
|
| - media_task_runner_,
|
| - base::Bind(&PipelineImpl::OnAddTextTrack, weak_this_)));
|
| -}
|
| -
|
| -void PipelineImpl::AddTextStreamTask(DemuxerStream* text_stream,
|
| - const TextTrackConfig& config) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| // TODO(matthewjheaney): fix up text_ended_ when text stream
|
| // is added (http://crbug.com/321446).
|
| if (text_renderer_)
|
| text_renderer_->AddTextStream(text_stream, config);
|
| }
|
|
|
| -void PipelineImpl::RemoveTextStreamTask(DemuxerStream* text_stream) {
|
| +void PipelineImpl::RendererWrapper::RemoveTextStreamTask(
|
| + DemuxerStream* text_stream) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| if (text_renderer_)
|
| text_renderer_->RemoveTextStream(text_stream);
|
| }
|
|
|
| -void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config,
|
| - const AddTextTrackDoneCB& done_cb) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| -
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&Pipeline::Client::OnAddTextTrack, weak_client_,
|
| - config, done_cb));
|
| -}
|
| -
|
| -void PipelineImpl::InitializeDemuxer(const PipelineStatusCB& done_cb) {
|
| +void PipelineImpl::RendererWrapper::InitializeDemuxer(
|
| + const PipelineStatusCB& done_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| demuxer_->Initialize(this, done_cb, !!text_renderer_);
|
| }
|
|
|
| -void PipelineImpl::InitializeRenderer(const PipelineStatusCB& done_cb) {
|
| +void PipelineImpl::RendererWrapper::InitializeRenderer(
|
| + const PipelineStatusCB& done_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
|
| !demuxer_->GetStream(DemuxerStream::VIDEO)) {
|
| - {
|
| - base::AutoLock auto_lock(lock_);
|
| - renderer_.reset();
|
| - }
|
| - OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
|
| + done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
|
| return;
|
| }
|
|
|
| @@ -937,7 +996,7 @@ void PipelineImpl::InitializeRenderer(const PipelineStatusCB& done_cb) {
|
| renderer_->Initialize(demuxer_, this, done_cb);
|
| }
|
|
|
| -void PipelineImpl::ReportMetadata() {
|
| +void PipelineImpl::RendererWrapper::ReportMetadata() {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| PipelineMetadata metadata;
|
| @@ -952,9 +1011,199 @@ void PipelineImpl::ReportMetadata() {
|
| metadata.has_audio = true;
|
| }
|
|
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&Pipeline::Client::OnMetadata, weak_client_, metadata));
|
| + main_task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::OnMetadata,
|
| + weak_pipeline_, metadata));
|
| +}
|
| +
|
| +void PipelineImpl::OnError(PipelineStatus error) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
|
| +
|
| + if (!IsRunning())
|
| + return;
|
| +
|
| + // If the error happens during starting/seeking/suspending/resuming,
|
| + // report the error via the completion callback for those tasks.
|
| + // Else report error via the client interface.
|
| + if (!seek_cb_.is_null()) {
|
| + base::ResetAndReturn(&seek_cb_).Run(error);
|
| + } else if (!suspend_cb_.is_null()) {
|
| + base::ResetAndReturn(&suspend_cb_).Run(error);
|
| + } else {
|
| + DCHECK(client_);
|
| + client_->OnError(error);
|
| + }
|
| +
|
| + // Any kind of error stops the pipeline.
|
| + Stop();
|
| +}
|
| +
|
| +void PipelineImpl::OnEnded() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnEnded();
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnMetadata(PipelineMetadata metadata) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnMetadata(metadata);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnBufferingStateChange(BufferingState state) {
|
| + DVLOG(2) << __FUNCTION__ << "(" << state << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnBufferingStateChange(state);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnDurationChange(base::TimeDelta duration) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + duration_ = duration;
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnDurationChange();
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config,
|
| + const AddTextTrackDoneCB& done_cb) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnAddTextTrack(config, done_cb);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnWaitingForDecryptionKey() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnWaitingForDecryptionKey();
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnVideoNaturalSizeChange(size);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnVideoOpacityChange(bool opaque) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(client_);
|
| + client_->OnVideoOpacityChange(opaque);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnBufferedTimeRangesChange(
|
| + const Ranges<base::TimeDelta>& ranges) {
|
| + DVLOG(3) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + buffered_time_ranges_ = ranges;
|
| + did_loading_progress_ = true;
|
| +}
|
| +
|
| +void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
|
| + DVLOG(3) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
|
| + statistics_.video_bytes_decoded += stats.video_bytes_decoded;
|
| + statistics_.video_frames_decoded += stats.video_frames_decoded;
|
| + statistics_.video_frames_dropped += stats.video_frames_dropped;
|
| + statistics_.audio_memory_usage += stats.audio_memory_usage;
|
| + statistics_.video_memory_usage += stats.video_memory_usage;
|
| +}
|
| +
|
| +void PipelineImpl::OnSeekDone(base::TimeDelta start_time) {
|
| + DVLOG(3) << __FUNCTION__ << "(" << start_time.InMicroseconds() << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(!seek_cb_.is_null());
|
| + base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
|
| + }
|
| +}
|
| +
|
| +void PipelineImpl::OnSuspendDone(base::TimeDelta suspend_time) {
|
| + DVLOG(3) << __FUNCTION__ << "(" << suspend_time.InMicroseconds() << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Reset audio-video memory usage since renderer has been destroyed.
|
| + statistics_.audio_memory_usage = 0;
|
| + statistics_.video_memory_usage = 0;
|
| +
|
| + if (IsRunning()) {
|
| + DCHECK(!suspend_cb_.is_null());
|
| + base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK);
|
| + }
|
| +}
|
| +
|
| +PipelineImpl::RendererWrapper::RendererWrapper(
|
| + base::WeakPtr<PipelineImpl> weak_pipeline,
|
| + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
|
| + scoped_refptr<MediaLog> media_log)
|
| + : weak_pipeline_(weak_pipeline),
|
| + media_task_runner_(std::move(media_task_runner)),
|
| + main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
|
| + media_log_(std::move(media_log)),
|
| + demuxer_(nullptr),
|
| + playback_rate_(kDefaultPlaybackRate),
|
| + volume_(kDefaultVolume),
|
| + cdm_context_(nullptr),
|
| + state_(kCreated),
|
| + status_(PIPELINE_OK),
|
| + suspend_timestamp_(kNoTimestamp()),
|
| + renderer_ended_(false),
|
| + text_renderer_ended_(false),
|
| + weak_factory_(this) {
|
| + weak_this_ = weak_factory_.GetWeakPtr();
|
| + media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
|
| +}
|
| +
|
| +PipelineImpl::RendererWrapper::~RendererWrapper() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(state_ == kCreated || state_ == kStopped);
|
| +}
|
| +
|
| +base::TimeDelta PipelineImpl::RendererWrapper::GetMediaTime() {
|
| + // This is the only member function that gets called on the main thread.
|
| + // TODO(alokp): Enforce that Renderer is only called on a single thread,
|
| + // even for accessing media time http://crbug.com/370634.
|
| + DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +
|
| + base::AutoLock auto_lock(media_time_lock_);
|
| + if (suspend_timestamp_ != kNoTimestamp())
|
| + return suspend_timestamp_;
|
| + return renderer_ ? renderer_->GetMediaTime() : base::TimeDelta();
|
| }
|
|
|
| } // namespace media
|
|
|