Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Unified Diff: media/base/pipeline_impl.cc

Issue 1999893004: Splits PipelineImpl into main and media thread components. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cleanup Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/base/pipeline_impl.cc
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index ac39834374cad409c77ca023419fc83ee8b5a7ed..92484197256bbd17020f7c9f400e22677cde6754 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -5,956 +5,1047 @@
#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 "base/timer/timer.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;
+namespace {
-namespace media {
+const double kDefaultPlaybackRate = 0.0;
+const float kDefaultVolume = 1.0f;
-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_log_(media_log),
- running_(false),
- 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));
+bool TextTracksEnabled() {
+ static bool enabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableInbandTextTracks);
+ return enabled;
}
-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";
- DCHECK(seek_cb_.is_null());
-}
+} // namespace
-void PipelineImpl::Start(Demuxer* demuxer,
- std::unique_ptr<Renderer> renderer,
- Client* client,
- const PipelineStatusCB& seek_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
- DCHECK(client);
- DCHECK(!seek_cb.is_null());
-
- base::AutoLock auto_lock(lock_);
- CHECK(!running_) << "Media pipeline is already running";
- running_ = true;
-
- 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_));
-}
-
-void PipelineImpl::Stop() {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
- DVLOG(2) << __FUNCTION__;
+namespace media {
- if (media_task_runner_ != main_task_runner_) {
- // 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.
- base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC,
- base::WaitableEvent::InitialState::NOT_SIGNALED);
- base::Closure stop_cb =
- base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter));
- // 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)));
- waiter.Wait();
- } else {
- // This path is executed by unittests that share media and main threads.
- StopTask(base::Bind(&base::DoNothing));
+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)
+ : 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),
+ renderer_ended_(false),
+ text_renderer_ended_(false),
+ weak_factory_(this) {
+ media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
}
- // Invalidate client weak pointer effectively canceling all pending client
- // notifications in the message queue.
- client_weak_factory_.reset();
-}
-
-void PipelineImpl::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
- if (!IsRunning()) {
- DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
- return;
+ ~RendererWrapper() final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DCHECK(state_ == kCreated || state_ == kStopped);
}
- media_task_runner_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::SeekTask, weak_this_, time,
- media::BindToCurrentLoop(seek_cb)));
-}
+ // Note that the usage of base::Unretained() with the renderers and demuxer
+ // is safe as they are owned by |pending_callbacks_| and share the same
+ // lifetime. That said, deleting the renderers while keeping
+ // |pending_callbacks_| running on the media thread would result in crashes.
+
+ void 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_;
+ SetState(kStarting);
+
+ DCHECK(!demuxer_);
+ DCHECK(!renderer_);
+ DCHECK(!text_renderer_);
+ DCHECK(!renderer_ended_);
+ DCHECK(!text_renderer_ended_);
+ demuxer_ = demuxer;
+ {
+ base::AutoLock auto_lock(renderer_lock_);
+ renderer_ = std::move(renderer);
+ }
+ text_renderer_ = std::move(text_renderer);
-bool PipelineImpl::IsRunning() const {
- // TODO(alokp): Add thread DCHECK after removing the internal usage on
- // media thread.
- base::AutoLock auto_lock(lock_);
- return running_;
-}
+ // Initialize text renderer.
+ if (text_renderer_) {
+ text_renderer_->Initialize(base::Bind(
+ &RendererWrapper::OnTextRendererEnded, weak_factory_.GetWeakPtr()));
+ }
-double PipelineImpl::GetPlaybackRate() const {
- // TODO(alokp): Add thread DCHECK after removing the internal usage on
- // media thread.
- base::AutoLock auto_lock(lock_);
- return playback_rate_;
-}
+ // Queue asynchronous actions required to seek.
+ DCHECK(!pending_callbacks_);
+ SerialRunner::Queue fns;
-void PipelineImpl::SetPlaybackRate(double playback_rate) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // Initialize demuxer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeDemuxer,
+ weak_factory_.GetWeakPtr()));
- if (playback_rate < 0.0)
- return;
+ // Once the demuxer is initialized successfully, media metadata must be
+ // available - report the metadata to client.
+ fns.Push(base::Bind(&RendererWrapper::ReportMetadata,
+ weak_factory_.GetWeakPtr()));
- 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));
- }
-}
+ // Initialize renderer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeRenderer,
+ weak_factory_.GetWeakPtr()));
-void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // Run tasks.
+ pending_callbacks_ = SerialRunner::Run(
+ fns, base::Bind(&RendererWrapper::OnSeekDone,
+ weak_factory_.GetWeakPtr(), base::TimeDelta()));
+ }
- media_task_runner_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::SuspendTask, weak_this_,
- media::BindToCurrentLoop(suspend_cb)));
-}
+ void Stop(const base::Closure& stop_cb) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DCHECK(state_ != kStopping && state_ != kStopped);
-void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer,
- base::TimeDelta timestamp,
- const PipelineStatusCB& seek_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ SetState(kStopping);
- media_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&PipelineImpl::ResumeTask, weak_this_, base::Passed(&renderer),
- timestamp, media::BindToCurrentLoop(seek_cb)));
-}
+ // 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();
-float PipelineImpl::GetVolume() const {
- // TODO(alokp): Add thread DCHECK after removing the internal usage on
- // media thread.
- base::AutoLock auto_lock(lock_);
- return volume_;
-}
+ std::unique_ptr<Renderer> renderer;
+ {
+ base::AutoLock auto_lock(renderer_lock_);
+ renderer.swap(renderer_);
+ }
+ renderer.reset();
+ text_renderer_.reset();
-void PipelineImpl::SetVolume(float volume) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ if (demuxer_) {
+ demuxer_->Stop();
+ demuxer_ = nullptr;
+ }
- if (volume < 0.0f || volume > 1.0f)
- return;
+ SetState(kStopped);
- base::AutoLock auto_lock(lock_);
- volume_ = volume;
- if (running_) {
- media_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&PipelineImpl::VolumeChangedTask, weak_this_, volume));
+ // 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, stop_cb);
}
-}
-TimeDelta PipelineImpl::GetMediaTime() const {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ void Seek(base::TimeDelta time) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- base::AutoLock auto_lock(lock_);
- if (suspend_timestamp_ != kNoTimestamp())
- return suspend_timestamp_;
- return renderer_ ? std::min(renderer_->GetMediaTime(), duration_)
- : TimeDelta();
-}
+ // Suppress seeking if we're not fully started.
+ if (state_ != kPlaying) {
+ DCHECK(state_ == kStopping || state_ == kStopped)
+ << "Receive seek in unexpected state: " << state_;
+ OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
+ return;
+ }
-Ranges<TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ SetState(kSeeking);
+ renderer_ended_ = false;
+ text_renderer_ended_ = false;
- base::AutoLock auto_lock(lock_);
- return buffered_time_ranges_;
-}
+ // Queue asynchronous actions required to seek.
+ DCHECK(!pending_callbacks_);
+ SerialRunner::Queue bound_fns;
-TimeDelta PipelineImpl::GetMediaDuration() const {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // Pause.
+ if (text_renderer_) {
+ bound_fns.Push(base::Bind(&TextRenderer::Pause,
+ base::Unretained(text_renderer_.get())));
+ }
- base::AutoLock auto_lock(lock_);
- return duration_;
-}
+ // Flush.
+ DCHECK(renderer_);
+ bound_fns.Push(
+ base::Bind(&Renderer::Flush, base::Unretained(renderer_.get())));
+ if (text_renderer_) {
+ bound_fns.Push(base::Bind(&TextRenderer::Flush,
+ base::Unretained(text_renderer_.get())));
+ }
-bool PipelineImpl::DidLoadingProgress() {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // Seek.
+ bound_fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_),
+ std::max(time, demuxer_->GetStartTime())));
- base::AutoLock auto_lock(lock_);
- bool ret = did_loading_progress_;
- did_loading_progress_ = false;
- return ret;
-}
+ pending_callbacks_ = SerialRunner::Run(
+ bound_fns, base::Bind(&RendererWrapper::OnSeekDone,
+ weak_factory_.GetWeakPtr(), time));
+ }
-PipelineStatistics PipelineImpl::GetStatistics() const {
- // TODO(alokp): Add thread DCHECK after removing the internal usage on
- // media thread.
- base::AutoLock auto_lock(lock_);
- return statistics_;
-}
+ void Suspend() {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
-void PipelineImpl::SetCdm(CdmContext* cdm_context,
- const CdmAttachedCB& cdm_attached_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // Suppress suspending if we're not playing.
+ if (state_ != kPlaying) {
+ DCHECK(state_ == kStopping || state_ == kStopped)
+ << "Received suspend in unexpected state: " << state_;
+ OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
+ return;
+ }
- media_task_runner_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::SetCdmTask, weak_this_, cdm_context,
- cdm_attached_cb));
-}
+ SetState(kSuspending);
+ // Freeze playback and record the media time before flushing.
+ // (Flushing clears the value.)
+ renderer_->SetPlaybackRate(0.0);
+ base::TimeDelta suspend_time = renderer_->GetMediaTime();
+ DCHECK(suspend_time != kNoTimestamp());
+
+ // Queue asynchronous actions required to suspend playback.
+ DCHECK(!pending_callbacks_.get());
+ SerialRunner::Queue fns;
+
+ // Pause.
+ if (text_renderer_) {
+ fns.Push(base::Bind(&TextRenderer::Pause,
+ base::Unretained(text_renderer_.get())));
+ }
-void PipelineImpl::SetErrorForTesting(PipelineStatus status) {
- OnError(status);
-}
+ // Flush.
+ fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get())));
+ if (text_renderer_) {
+ fns.Push(base::Bind(&TextRenderer::Flush,
+ base::Unretained(text_renderer_.get())));
+ }
-bool PipelineImpl::HasWeakPtrsForTesting() const {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- return weak_factory_.HasWeakPtrs();
-}
+ pending_callbacks_ = SerialRunner::Run(
+ fns, base::Bind(&RendererWrapper::OnSuspendDone,
+ weak_factory_.GetWeakPtr(), suspend_time));
+ }
-void PipelineImpl::SetState(State next_state) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state);
+ void Resume(base::TimeDelta time, std::unique_ptr<Renderer> renderer) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- state_ = next_state;
- media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
-}
+ DCHECK_EQ(kSuspended, state_) << "Received resume in unexpected state: "
+ << state_;
-#define RETURN_STRING(state) \
- case state: \
- return #state;
+ SetState(kResuming);
+ {
+ base::AutoLock auto_lock(renderer_lock_);
+ renderer_ = std::move(renderer);
+ }
+ renderer_ended_ = false;
+ text_renderer_ended_ = false;
-const char* PipelineImpl::GetStateString(State state) {
- switch (state) {
- RETURN_STRING(kCreated);
- RETURN_STRING(kInitDemuxer);
- RETURN_STRING(kInitRenderer);
- RETURN_STRING(kSeeking);
- RETURN_STRING(kPlaying);
- RETURN_STRING(kStopping);
- RETURN_STRING(kStopped);
- RETURN_STRING(kSuspending);
- RETURN_STRING(kSuspended);
- RETURN_STRING(kResuming);
- }
- NOTREACHED();
- return "INVALID";
-}
+ // Queue the asynchronous actions required to stop playback.
+ DCHECK(!pending_callbacks_.get());
+ SerialRunner::Queue fns;
-#undef RETURN_STRING
+ // Seek.
+ fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_),
+ std::max(time, demuxer_->GetStartTime())));
-PipelineImpl::State PipelineImpl::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_;
+ // Initialize the new renderer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeRenderer,
+ weak_factory_.GetWeakPtr()));
- switch (state_) {
- case kCreated:
- return kInitDemuxer;
+ // Run tasks.
+ pending_callbacks_ =
+ SerialRunner::Run(fns, base::Bind(&RendererWrapper::OnSeekDone,
+ weak_factory_.GetWeakPtr(), time));
+ }
- case kInitDemuxer:
- return kInitRenderer;
+ void SetPlaybackRate(double playback_rate) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- case kInitRenderer:
- case kSeeking:
- return kPlaying;
+ playback_rate_ = playback_rate;
+ if (state_ == kPlaying)
+ renderer_->SetPlaybackRate(playback_rate_);
+ }
- case kSuspending:
- return kSuspended;
+ void SetVolume(float volume) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- case kSuspended:
- return kResuming;
+ volume_ = volume;
+ if (state_ == kPlaying)
+ renderer_->SetVolume(volume_);
+ }
- case kResuming:
- return kPlaying;
+ base::TimeDelta 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());
- case kPlaying:
- case kStopping:
- case kStopped:
- break;
+ base::AutoLock auto_lock(renderer_lock_);
+ return renderer_ ? renderer_->GetMediaTime() : base::TimeDelta();
}
- NOTREACHED() << "State has no transition: " << state_;
- return state_;
-}
-void PipelineImpl::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));
-}
+ void SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
-void PipelineImpl::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_,
- text_stream, config));
-}
+ cdm_context_ = cdm_context;
+ if (renderer_) {
+ renderer_->SetCdm(
+ cdm_context, base::Bind(&RendererWrapper::OnCdmAttached,
+ weak_factory_.GetWeakPtr(), cdm_attached_cb));
+ } else {
+ cdm_attached_cb.Run(true);
+ }
+ }
-void PipelineImpl::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));
-}
+ private:
+ // DemuxerHost implementaion.
+ void OnBufferedTimeRangesChanged(
+ const Ranges<base::TimeDelta>& ranges) final {
+ // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
+ // implementations call DemuxerHost on the media thread.
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PipelineImpl::OnBufferedTimeRangesChange,
+ weak_pipeline_, ranges));
+ }
+ void SetDuration(base::TimeDelta duration) final {
+ // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer
+ // implementations call DemuxerHost on the media thread.
+ media_log_->AddEvent(media_log_->CreateTimeEvent(
+ MediaLogEvent::DURATION_SET, "duration", duration));
+ UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
-void PipelineImpl::OnError(PipelineStatus error) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK(IsRunning());
- DCHECK_NE(PIPELINE_OK, error);
- VLOG(1) << "Media pipeline error: " << error;
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnDurationChange, weak_pipeline_, duration));
+ }
+ void OnDemuxerError(PipelineStatus error) final {
+ // 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(&RendererWrapper::OnPipelineError,
+ weak_factory_.GetWeakPtr(), error));
+ }
+ void AddTextStream(DemuxerStream* text_stream,
+ const TextTrackConfig& config) final {
+ // 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(&RendererWrapper::AddTextStreamTask,
+ weak_factory_.GetWeakPtr(), text_stream, config));
+ }
+ void RemoveTextStream(DemuxerStream* text_stream) final {
+ // 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(&RendererWrapper::RemoveTextStreamTask,
+ weak_factory_.GetWeakPtr(), text_stream));
+ }
- media_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&PipelineImpl::ErrorChangedTask, weak_this_, error));
-}
+ // RendererClient implementation.
+ void OnError(PipelineStatus error) final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
-void PipelineImpl::OnEnded() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
+ media_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&RendererWrapper::OnPipelineError,
+ weak_factory_.GetWeakPtr(), error));
+ }
+ void OnEnded() final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
- if (state_ != kPlaying)
- return;
+ if (state_ != kPlaying)
+ return;
- DCHECK(!renderer_ended_);
- renderer_ended_ = true;
+ DCHECK(!renderer_ended_);
+ renderer_ended_ = true;
+ RunEndedCallbackIfNeeded();
+ }
+ void OnStatisticsUpdate(const PipelineStatistics& stats) final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- RunEndedCallbackIfNeeded();
-}
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnStatisticsUpdate, weak_pipeline_, stats));
+ }
+ void OnBufferingStateChange(BufferingState state) final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(2) << __FUNCTION__ << "(" << state << ") ";
-void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PipelineImpl::OnBufferingStateChange,
+ weak_pipeline_, state));
+ }
+ void OnWaitingForDecryptionKey() final {
+ 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::OnWaitingForDecryptionKey, weak_pipeline_));
+ }
+ void OnVideoNaturalSizeChange(const gfx::Size& size) final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
-void PipelineImpl::OnBufferingStateChange(BufferingState state) {
- DVLOG(1) << __FUNCTION__ << "(" << state << ") ";
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PipelineImpl::OnVideoNaturalSizeChange,
+ weak_pipeline_, size));
+ }
+ void OnVideoOpacityChange(bool opaque) final {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnBufferingStateChange,
- weak_client_, state));
-}
+ main_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&PipelineImpl::OnVideoOpacityChange,
+ weak_pipeline_, opaque));
+ }
-void PipelineImpl::OnWaitingForDecryptionKey() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ void OnPipelineError(PipelineStatus error) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
- main_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&Pipeline::Client::OnWaitingForDecryptionKey, weak_client_));
-}
+ // Preserve existing abnormal status.
+ if (status_ != PIPELINE_OK)
+ return;
-void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ // 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;
+ }
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnVideoNaturalSizeChange,
- weak_client_, size));
-}
+ status_ = error;
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PipelineImpl::OnError, weak_pipeline_, error));
+ }
-void PipelineImpl::OnVideoOpacityChange(bool opaque) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ void OnTextRendererEnded() {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnVideoOpacityChange,
- weak_client_, opaque));
-}
+ if (state_ != kPlaying)
+ return;
-void PipelineImpl::SetDuration(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);
+ DCHECK(!text_renderer_ended_);
+ text_renderer_ended_ = true;
+ RunEndedCallbackIfNeeded();
+ }
- base::AutoLock auto_lock(lock_);
- duration_ = duration;
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnDurationChange, weak_client_));
-}
+ void RunEndedCallbackIfNeeded() {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
-void PipelineImpl::StateTransitionTask(PipelineStatus status) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ if (renderer_ && !renderer_ended_)
+ return;
- // No-op any state transitions if we're stopping.
- if (state_ == kStopping || state_ == kStopped)
- return;
+ if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_)
+ return;
- // Report error from the previous operation.
- if (status != PIPELINE_OK) {
- ErrorChangedTask(status);
- return;
+ DCHECK_EQ(status_, PIPELINE_OK);
+ main_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&PipelineImpl::OnEnded, weak_pipeline_));
}
- // Guard against accidentally clearing |pending_callbacks_| for states that
- // use it as well as states that should not be using it.
- DCHECK_EQ(pending_callbacks_.get() != NULL,
- state_ == kSeeking || state_ == kSuspending || state_ == kResuming);
+ void 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);
+ }
- pending_callbacks_.reset();
+ void RemoveTextStreamTask(DemuxerStream* text_stream) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ if (text_renderer_)
+ text_renderer_->RemoveTextStream(text_stream);
+ }
- PipelineStatusCB done_cb =
- base::Bind(&PipelineImpl::StateTransitionTask, weak_this_);
+ void SetState(State next_state) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(1) << PipelineImpl::GetStateString(state_) << " -> "
+ << PipelineImpl::GetStateString(next_state);
- // Switch states, performing any entrance actions for the new state as well.
- SetState(GetNextState());
- switch (state_) {
- case kInitDemuxer:
- return InitializeDemuxer(done_cb);
+ state_ = next_state;
+ media_log_->AddEvent(
+ media_log_->CreatePipelineStateChangedEvent(next_state));
+ }
- case kInitRenderer:
- // When the state_ transfers to kInitRenderer, it means the demuxer has
- // finished parsing the init info. It should call ReportMetadata in case
- // meeting 'decode' error when passing media segment but WebMediaPlayer's
- // ready_state_ is still ReadyStateHaveNothing. In that case, it will
- // treat it as NetworkStateFormatError not NetworkStateDecodeError.
- ReportMetadata();
- start_timestamp_ = demuxer_->GetStartTime();
+ void InitializeDemuxer(const PipelineStatusCB& done_cb) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(2) << __FUNCTION__;
- return InitializeRenderer(done_cb);
+ demuxer_->Initialize(this, done_cb, !!text_renderer_);
+ }
- case kPlaying:
- DCHECK(start_timestamp_ >= base::TimeDelta());
- renderer_->StartPlayingFrom(start_timestamp_);
- {
- base::AutoLock auto_lock(lock_);
- suspend_timestamp_ = kNoTimestamp();
- }
+ void ReportMetadata() {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(2) << __FUNCTION__;
+
+ PipelineMetadata metadata;
+ metadata.timeline_offset = demuxer_->GetTimelineOffset();
+ DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
+ if (stream) {
+ metadata.has_video = true;
+ metadata.natural_size = stream->video_decoder_config().natural_size();
+ metadata.video_rotation = stream->video_rotation();
+ }
+ if (demuxer_->GetStream(DemuxerStream::AUDIO)) {
+ metadata.has_audio = true;
+ }
- if (text_renderer_)
- text_renderer_->StartPlaying();
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnMetadata, weak_pipeline_, metadata));
+ }
- base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
+ void InitializeRenderer(const PipelineStatusCB& done_cb) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(2) << __FUNCTION__;
- PlaybackRateChangedTask(GetPlaybackRate());
- VolumeChangedTask(GetVolume());
+ if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
+ !demuxer_->GetStream(DemuxerStream::VIDEO)) {
+ done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
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);
- return;
+ if (cdm_context_) {
+ CdmAttachedCB cdm_attached_cb = base::Bind(&IgnoreCdmAttached);
+ renderer_->SetCdm(
+ cdm_context_,
+ base::Bind(&RendererWrapper::OnCdmAttached,
+ weak_factory_.GetWeakPtr(), cdm_attached_cb));
+ }
- case kStopping:
- case kStopped:
- case kCreated:
- case kSeeking:
- case kSuspending:
- case kResuming:
- NOTREACHED() << "State has no transition: " << state_;
- return;
+ renderer_->Initialize(demuxer_, this, done_cb);
}
-}
-// Note that the usage of base::Unretained() with the renderers is considered
-// safe as they are owned by |pending_callbacks_| and share the same lifetime.
-//
-// 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) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK(!pending_callbacks_.get());
- DCHECK_EQ(state_, kSeeking);
- SerialRunner::Queue bound_fns;
+ void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb, bool success) {
+ if (!success)
+ cdm_context_ = nullptr;
- // Pause.
- if (text_renderer_) {
- bound_fns.Push(base::Bind(&TextRenderer::Pause,
- base::Unretained(text_renderer_.get())));
+ cdm_attached_cb.Run(success);
}
- // Flush.
- DCHECK(renderer_);
- bound_fns.Push(
- base::Bind(&Renderer::Flush, base::Unretained(renderer_.get())));
+ void OnSeekDone(base::TimeDelta start_time, PipelineStatus status) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
- if (text_renderer_) {
- bound_fns.Push(base::Bind(&TextRenderer::Flush,
- base::Unretained(text_renderer_.get())));
- }
+ DCHECK(pending_callbacks_);
+ pending_callbacks_.reset();
+
+ if (status != PIPELINE_OK) {
+ OnPipelineError(status);
+ return;
+ }
- // Seek demuxer.
- bound_fns.Push(
- base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp));
+ start_time = std::max(start_time, demuxer_->GetStartTime());
+ renderer_->StartPlayingFrom(start_time);
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
-}
+ if (text_renderer_)
+ text_renderer_->StartPlaying();
-void PipelineImpl::DoStop() {
- DVLOG(2) << __FUNCTION__;
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kStopping);
- DCHECK(!pending_callbacks_.get());
+ renderer_->SetPlaybackRate(playback_rate_);
+ renderer_->SetVolume(volume_);
- // TODO(scherkus): Enforce that Renderer is only called on a single thread,
- // even for accessing media time http://crbug.com/370634
- std::unique_ptr<Renderer> renderer;
- {
- base::AutoLock auto_lock(lock_);
- renderer.swap(renderer_);
+ SetState(kPlaying);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_, start_time));
}
- renderer.reset();
- text_renderer_.reset();
- if (demuxer_) {
- demuxer_->Stop();
- demuxer_ = NULL;
- }
+ void OnSuspendDone(base::TimeDelta suspend_time, PipelineStatus status) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+
+ DCHECK(pending_callbacks_);
+ pending_callbacks_.reset();
+
+ // 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.
+ LOG_IF(WARNING, status != PIPELINE_OK)
+ << "Encountered pipeline error while suspending: " << status;
- {
- base::AutoLock auto_lock(lock_);
- running_ = false;
+ SetState(kSuspended);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_, suspend_time));
}
- 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();
+ 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_;
- if (!stop_cb_.is_null()) {
- // Invalid all weak pointers so it's safe to destroy |this| on the render
- // main thread.
- weak_factory_.InvalidateWeakPtrs();
+ Demuxer* demuxer_;
+ std::unique_ptr<Renderer> renderer_;
+ std::unique_ptr<TextRenderer> text_renderer_;
+ double playback_rate_;
+ float volume_;
+ CdmContext* cdm_context_;
- // 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_));
- }
-}
+ // Lock used to serialize access for |renderer_|.
+ mutable base::Lock renderer_lock_;
-void PipelineImpl::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;
-}
+ // Current state of the pipeline.
+ State state_;
-void PipelineImpl::StartTask() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ // Last media time reported to the pipeline.
+ base::TimeDelta last_media_time_;
- CHECK_EQ(kCreated, state_)
- << "Media pipeline cannot be started more than once";
+ // 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_;
- text_renderer_ = CreateTextRenderer();
- if (text_renderer_) {
- text_renderer_->Initialize(
- base::Bind(&PipelineImpl::OnTextRendererEnded, weak_this_));
- }
+ // Whether we've received the audio/video/text ended events.
+ bool renderer_ended_;
+ bool text_renderer_ended_;
- StateTransitionTask(PIPELINE_OK);
-}
+ // Series of tasks to Start(), Seek(), and Resume().
+ std::unique_ptr<SerialRunner> pending_callbacks_;
-void PipelineImpl::StopTask(const base::Closure& stop_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK(stop_cb_.is_null());
+ base::WeakPtrFactory<RendererWrapper> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(RendererWrapper);
+};
- if (state_ == kStopped) {
- // Invalid all weak pointers so it's safe to destroy |this| on the render
- // main thread.
- weak_factory_.InvalidateWeakPtrs();
+PipelineImpl::PipelineImpl(
+ const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
+ MediaLog* media_log)
+ : media_task_runner_(media_task_runner),
+ media_log_(media_log),
+ client_(nullptr),
+ playback_rate_(kDefaultPlaybackRate),
+ volume_(kDefaultVolume),
+ suspend_time_(kNoTimestamp()),
+ did_loading_progress_(false),
+ weak_factory_(this) {
+ DVLOG(2) << __FUNCTION__;
+ renderer_wrapper_.reset(new RendererWrapper(weak_factory_.GetWeakPtr(),
+ media_task_runner_, media_log_));
+}
- // NOTE: pipeline may be deleted at this point in time as a result of
- // executing |stop_cb|.
- stop_cb.Run();
+PipelineImpl::~PipelineImpl() {
+ 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());
- return;
- }
+ // Invalidate self weak pointers effectively canceling all pending
+ // notifications in the message queue.
+ weak_factory_.InvalidateWeakPtrs();
- stop_cb_ = stop_cb;
+ // RendererWrapper is deleted on the media thread.
+ media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release());
+}
- // We may already be stopping due to a runtime error.
- if (state_ == kStopping)
- return;
+void PipelineImpl::Start(Demuxer* demuxer,
+ std::unique_ptr<Renderer> renderer,
+ Client* client,
+ const PipelineStatusCB& seek_cb) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(demuxer);
+ DCHECK(renderer);
+ DCHECK(client);
+ DCHECK(!seek_cb.is_null());
- // 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(!client_);
+ DCHECK(seek_cb_.is_null());
+ client_ = client;
+ seek_cb_ = seek_cb;
+
+ std::unique_ptr<TextRenderer> text_renderer;
+ if (TextTracksEnabled()) {
+ text_renderer.reset(new TextRenderer(
+ media_task_runner_,
+ BindToCurrentLoop(base::Bind(&PipelineImpl::OnAddTextTrack,
+ weak_factory_.GetWeakPtr()))));
}
- SetState(kStopping);
- pending_callbacks_.reset();
- DoStop();
+ media_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&RendererWrapper::Start,
+ base::Unretained(renderer_wrapper_.get()), demuxer,
+ base::Passed(&renderer), base::Passed(&text_renderer)));
}
-void PipelineImpl::ErrorChangedTask(PipelineStatus error) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
-
- // Preserve existing abnormal status.
- if (status_ != PIPELINE_OK)
- return;
+void PipelineImpl::Stop() {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- // 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) {
+ if (!IsRunning()) {
+ DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
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(
+ 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): 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 =
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter));
+ media_task_runner_->PostTask(
FROM_HERE,
- base::Bind(&Pipeline::Client::OnError, weak_client_, status_));
+ base::Bind(&RendererWrapper::Stop,
+ base::Unretained(renderer_wrapper_.get()), stop_cb));
+ waiter.Wait();
}
- SetState(kStopping);
- pending_callbacks_.reset();
- DoStop();
+ // 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::PlaybackRateChangedTask(double playback_rate) {
- DCHECK(media_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());
- // Playback rate changes are only carried out while playing.
- if (state_ != kPlaying)
+ if (!IsRunning()) {
+ DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
return;
+ }
- renderer_->SetPlaybackRate(playback_rate);
+ DCHECK(seek_cb_.is_null());
+ seek_cb_ = seek_cb;
+ media_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&RendererWrapper::Seek,
+ base::Unretained(renderer_wrapper_.get()), time));
}
-void PipelineImpl::VolumeChangedTask(float volume) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(!suspend_cb.is_null());
- // Volume changes are only carried out while playing.
- if (state_ != kPlaying)
- return;
+ DCHECK(IsRunning());
+ DCHECK(suspend_cb_.is_null());
+ suspend_cb_ = suspend_cb;
- renderer_->SetVolume(volume);
+ media_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&RendererWrapper::Suspend,
+ base::Unretained(renderer_wrapper_.get())));
}
-void PipelineImpl::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
- 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);
- return;
- }
+void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer,
+ base::TimeDelta time,
+ const PipelineStatusCB& seek_cb) {
+ 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;
- const base::TimeDelta seek_timestamp =
- std::max(time, demuxer_->GetStartTime());
+ media_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&RendererWrapper::Resume,
+ base::Unretained(renderer_wrapper_.get()), time,
+ base::Passed(&renderer)));
+}
- SetState(kSeeking);
- seek_cb_ = seek_cb;
- renderer_ended_ = false;
- text_renderer_ended_ = false;
- start_timestamp_ = seek_timestamp;
+bool PipelineImpl::IsRunning() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return !!client_;
+}
- DoSeek(seek_timestamp,
- base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
+double PipelineImpl::GetPlaybackRate() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return playback_rate_;
}
-void PipelineImpl::SuspendTask(const PipelineStatusCB& suspend_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::SetPlaybackRate(double playback_rate) {
+ DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
- // 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);
+ if (playback_rate < 0.0)
return;
- }
- DCHECK(renderer_);
- DCHECK(!pending_callbacks_.get());
- SetState(kSuspending);
- suspend_cb_ = suspend_cb;
+ playback_rate_ = playback_rate;
+ media_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&RendererWrapper::SetPlaybackRate,
+ base::Unretained(renderer_wrapper_.get()), playback_rate_));
+}
- // Freeze playback and record the media time before flushing. (Flushing clears
- // the value.)
- renderer_->SetPlaybackRate(0.0);
- {
- base::AutoLock auto_lock(lock_);
- suspend_timestamp_ = renderer_->GetMediaTime();
- DCHECK(suspend_timestamp_ != kNoTimestamp());
- }
+float PipelineImpl::GetVolume() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return volume_;
+}
- // Queue the asynchronous actions required to stop playback. (Matches setup in
- // DoSeek().)
- // TODO(sandersd): Share implementation with DoSeek().
- SerialRunner::Queue fns;
+void PipelineImpl::SetVolume(float volume) {
+ DVLOG(2) << __FUNCTION__ << "(" << volume << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
- if (text_renderer_) {
- fns.Push(base::Bind(&TextRenderer::Pause,
- base::Unretained(text_renderer_.get())));
- }
+ if (volume < 0.0f || volume > 1.0f)
+ return;
- fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get())));
+ volume_ = volume;
+ media_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&RendererWrapper::SetVolume,
+ base::Unretained(renderer_wrapper_.get()), volume));
+}
- if (text_renderer_) {
- fns.Push(base::Bind(&TextRenderer::Flush,
- base::Unretained(text_renderer_.get())));
- }
+base::TimeDelta PipelineImpl::GetMediaTime() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
- pending_callbacks_ = SerialRunner::Run(
- fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
+ return suspend_time_ != kNoTimestamp() ? suspend_time_
+ : renderer_wrapper_->GetMediaTime();
}
-void PipelineImpl::ResumeTask(std::unique_ptr<Renderer> renderer,
- base::TimeDelta timestamp,
- const PipelineStatusCB& seek_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return buffered_time_ranges_;
+}
- // 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);
- return;
- }
- DCHECK(!renderer_);
- DCHECK(!pending_callbacks_.get());
+base::TimeDelta PipelineImpl::GetMediaDuration() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return duration_;
+}
- SetState(kResuming);
- renderer_ = std::move(renderer);
+bool PipelineImpl::DidLoadingProgress() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ bool ret = did_loading_progress_;
+ did_loading_progress_ = false;
+ return ret;
+}
- // 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());
+PipelineStatistics PipelineImpl::GetStatistics() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return statistics_;
+}
- // Queue the asynchronous actions required to start playback. Unlike DoSeek(),
- // we need to initialize the renderer ourselves (we don't want to enter state
- // kInitDemuxer, and even if we did the current code would seek to the start
- // instead of |timestamp|).
- SerialRunner::Queue fns;
+void PipelineImpl::SetCdm(CdmContext* cdm_context,
+ const CdmAttachedCB& cdm_attached_cb) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(cdm_context);
+ DCHECK(!cdm_attached_cb.is_null());
- fns.Push(
- base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_));
+ media_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&RendererWrapper::SetCdm,
+ base::Unretained(renderer_wrapper_.get()), cdm_context,
+ media::BindToCurrentLoop(cdm_attached_cb)));
+}
- fns.Push(base::Bind(&PipelineImpl::InitializeRenderer, weak_this_));
+#define RETURN_STRING(state) \
+ case state: \
+ return #state;
- pending_callbacks_ = SerialRunner::Run(
- fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
+// static
+const char* PipelineImpl::GetStateString(State state) {
+ switch (state) {
+ RETURN_STRING(kCreated);
+ RETURN_STRING(kStarting);
+ RETURN_STRING(kSeeking);
+ RETURN_STRING(kPlaying);
+ RETURN_STRING(kStopping);
+ RETURN_STRING(kStopped);
+ RETURN_STRING(kSuspending);
+ RETURN_STRING(kSuspended);
+ RETURN_STRING(kResuming);
+ }
+ NOTREACHED();
+ return "INVALID";
}
-void PipelineImpl::SetCdmTask(CdmContext* cdm_context,
- const CdmAttachedCB& cdm_attached_cb) {
- base::AutoLock auto_lock(lock_);
- if (!renderer_) {
- cdm_context_ = cdm_context;
- cdm_attached_cb.Run(true);
+#undef RETURN_STRING
+
+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);
}
- renderer_->SetCdm(cdm_context,
- base::Bind(&PipelineImpl::OnCdmAttached, weak_this_,
- cdm_attached_cb, cdm_context));
+ // Any kind of error stops the pipeline.
+ Stop();
}
-void PipelineImpl::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::OnEnded() {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnEnded();
+ }
}
-void PipelineImpl::OnTextRendererEnded() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
+void PipelineImpl::OnMetadata(PipelineMetadata metadata) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- if (state_ != kPlaying)
- return;
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnMetadata(metadata);
+ }
+}
- DCHECK(!text_renderer_ended_);
- text_renderer_ended_ = true;
+void PipelineImpl::OnBufferingStateChange(BufferingState state) {
+ DVLOG(2) << __FUNCTION__ << "(" << state << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
- RunEndedCallbackIfNeeded();
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnBufferingStateChange(state);
+ }
}
-void PipelineImpl::RunEndedCallbackIfNeeded() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
-
- if (renderer_ && !renderer_ended_)
- return;
+void PipelineImpl::OnDurationChange(base::TimeDelta duration) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_)
- return;
+ duration_ = duration;
- DCHECK_EQ(status_, PIPELINE_OK);
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnEnded, weak_client_));
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnDurationChange();
+ }
}
-std::unique_ptr<TextRenderer> PipelineImpl::CreateTextRenderer() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+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);
+ }
+}
- const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
- if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks))
- return nullptr;
+void PipelineImpl::OnWaitingForDecryptionKey() {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- return base::WrapUnique(new media::TextRenderer(
- media_task_runner_,
- base::Bind(&PipelineImpl::OnAddTextTrack, weak_this_)));
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnWaitingForDecryptionKey();
+ }
}
-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::OnVideoNaturalSizeChange(const gfx::Size& size) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnVideoNaturalSizeChange(size);
+ }
}
-void PipelineImpl::RemoveTextStreamTask(DemuxerStream* text_stream) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::OnVideoOpacityChange(bool opaque) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- if (text_renderer_)
- text_renderer_->RemoveTextStream(text_stream);
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnVideoOpacityChange(opaque);
+ }
}
-void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config,
- const AddTextTrackDoneCB& done_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::OnBufferedTimeRangesChange(
+ const Ranges<base::TimeDelta>& ranges) {
+ DVLOG(3) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- main_task_runner_->PostTask(
- FROM_HERE, base::Bind(&Pipeline::Client::OnAddTextTrack, weak_client_,
- config, done_cb));
+ buffered_time_ranges_ = ranges;
+ did_loading_progress_ = true;
}
-void PipelineImpl::InitializeDemuxer(const PipelineStatusCB& done_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
+ DVLOG(3) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- demuxer_->Initialize(this, done_cb, !!text_renderer_);
+ 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::InitializeRenderer(const PipelineStatusCB& done_cb) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+void PipelineImpl::OnSeekDone(base::TimeDelta start_time) {
+ DVLOG(3) << __FUNCTION__ << "(" << start_time.InMicroseconds() << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
- if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
- !demuxer_->GetStream(DemuxerStream::VIDEO)) {
- {
- base::AutoLock auto_lock(lock_);
- renderer_.reset();
- }
- OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
- return;
+ // Reset the suspend_time now that the pipeline is playing.
+ // Media time will now be reported by renderer.
+ suspend_time_ = kNoTimestamp();
+
+ if (IsRunning()) {
+ DCHECK(!seek_cb_.is_null());
+ base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
}
+}
- if (cdm_context_)
- renderer_->SetCdm(cdm_context_, base::Bind(&IgnoreCdmAttached));
+void PipelineImpl::OnSuspendDone(base::TimeDelta suspend_time) {
+ DVLOG(3) << __FUNCTION__ << "(" << suspend_time.InMicroseconds() << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
- renderer_->Initialize(demuxer_, this, done_cb);
-}
+ // Cache the time at which pipeline was suspended.
+ // It will be used to report media time while the pipeline is suspended.
+ suspend_time_ = suspend_time;
-void PipelineImpl::ReportMetadata() {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
+ // Reset audio-video memory usage since renderer has been destroyed.
+ statistics_.audio_memory_usage = 0;
+ statistics_.video_memory_usage = 0;
- PipelineMetadata metadata;
- metadata.timeline_offset = demuxer_->GetTimelineOffset();
- DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
- if (stream) {
- metadata.has_video = true;
- metadata.natural_size = stream->video_decoder_config().natural_size();
- metadata.video_rotation = stream->video_rotation();
+ if (IsRunning()) {
+ DCHECK(!suspend_cb_.is_null());
+ base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK);
}
- if (demuxer_->GetStream(DemuxerStream::AUDIO)) {
- metadata.has_audio = true;
- }
-
- main_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&Pipeline::Client::OnMetadata, weak_client_, metadata));
}
} // namespace media

Powered by Google App Engine
This is Rietveld 408576698