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

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: rearranged code for easier review 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
« no previous file with comments | « media/base/pipeline_impl.h ('k') | media/base/pipeline_impl_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/base/pipeline_impl.cc
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index ac39834374cad409c77ca023419fc83ee8b5a7ed..05c46775b522d76f19560a97b01dc9e3380aaccd 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -5,274 +5,386 @@
#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;
+namespace {
+
+const double kDefaultPlaybackRate = 0.0;
+const float kDefaultVolume = 1.0f;
+
+bool TextTracksEnabled() {
+ static bool enabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
alokp 2016/06/08 19:59:48 cached here instead of querying the command line f
+ switches::kEnableInbandTextTracks);
+ return enabled;
+}
+
+} // namespace
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(base::TimeDelta time, std::unique_ptr<Renderer> renderer);
+ 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 OnPipelineError(PipelineStatus error);
+ void OnTextRendererEnded();
+ void RunEndedCallbackIfNeeded();
+ void SetState(State next_state);
+ void OnSeekDone(base::TimeDelta start_time, PipelineStatus status);
+ void OnSuspendDone(base::TimeDelta suspend_time, 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 access for |renderer_|.
+ mutable base::Lock renderer_lock_;
+
+ // Current state of the pipeline.
+ State state_;
+
+ // Last media time reported to the pipeline.
+ base::TimeDelta last_media_time_;
+
+ // 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_;
+
+ // 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::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),
alokp 2016/06/08 19:59:48 no need for running_. We now use client_ != null a
+ client_(nullptr),
+ playback_rate_(kDefaultPlaybackRate),
+ volume_(kDefaultVolume),
+ suspend_time_(kNoTimestamp()),
did_loading_progress_(false),
- volume_(1.0f),
- playback_rate_(0.0),
- status_(PIPELINE_OK),
alokp 2016/06/08 19:59:47 state machine has been moved to RendererWrapper.
- 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.
+ 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;
alokp 2016/06/08 19:59:46 We create TextRenderer on the main thread because:
+ if (TextTracksEnabled()) {
+ 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 (media_task_runner_ != main_task_runner_) {
+ if (!IsRunning()) {
+ DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
+ return;
+ }
+
+ if (media_task_runner_->BelongsToCurrentThread()) {
alokp 2016/06/08 19:59:48 no major change here - just swapped the condition
+ // 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 =
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)));
+ media_task_runner_->PostTask(
+ 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__;
alokp 2016/06/08 19:59:47 These functions just post tasks to the media threa
+ 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));
+}
+
+void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
+ 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(&RendererWrapper::Suspend,
+ base::Unretained(renderer_wrapper_.get())));
+}
+
+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;
+
+ media_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&RendererWrapper::Resume,
+ base::Unretained(renderer_wrapper_.get()), time,
+ base::Passed(&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_;
+ DCHECK(thread_checker_.CalledOnValidThread());
alokp 2016/06/08 19:59:48 client_ is cleared as soon the pipeline stops. So
+ 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));
- }
-}
-
-void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
-
- media_task_runner_->PostTask(
- FROM_HERE, base::Bind(&PipelineImpl::SuspendTask, weak_this_,
- media::BindToCurrentLoop(suspend_cb)));
-}
-
-void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer,
- base::TimeDelta timestamp,
- const PipelineStatusCB& seek_cb) {
- DCHECK(main_task_runner_->BelongsToCurrentThread());
-
media_task_runner_->PostTask(
FROM_HERE,
- base::Bind(&PipelineImpl::ResumeTask, weak_this_, base::Passed(&renderer),
- timestamp, media::BindToCurrentLoop(seek_cb)));
+ base::Bind(&RendererWrapper::SetPlaybackRate,
+ base::Unretained(renderer_wrapper_.get()), playback_rate_));
}
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::TimeDelta PipelineImpl::GetMediaTime() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
- base::AutoLock auto_lock(lock_);
- if (suspend_timestamp_ != kNoTimestamp())
- return suspend_timestamp_;
- return renderer_ ? std::min(renderer_->GetMediaTime(), duration_)
- : TimeDelta();
+ return suspend_time_ != kNoTimestamp() ? suspend_time_
+ : 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();
-}
-
-void PipelineImpl::SetState(State next_state) {
- DCHECK(media_task_runner_->BelongsToCurrentThread());
- DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state);
-
- state_ = next_state;
- media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
+ FROM_HERE,
+ base::Bind(&RendererWrapper::SetCdm,
+ base::Unretained(renderer_wrapper_.get()), cdm_context,
+ media::BindToCurrentLoop(cdm_attached_cb)));
}
#define RETURN_STRING(state) \
case state: \
return #state;
+// static
const char* PipelineImpl::GetStateString(State state) {
switch (state) {
RETURN_STRING(kCreated);
- RETURN_STRING(kInitDemuxer);
- RETURN_STRING(kInitRenderer);
+ RETURN_STRING(kStarting);
RETURN_STRING(kSeeking);
RETURN_STRING(kPlaying);
RETURN_STRING(kStopping);
@@ -287,78 +399,251 @@ const char* PipelineImpl::GetStateString(State state) {
#undef RETURN_STRING
-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_;
+void PipelineImpl::OnError(PipelineStatus error) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
- switch (state_) {
- case kCreated:
- return kInitDemuxer;
+ if (!IsRunning())
+ return;
- case kInitDemuxer:
- return kInitRenderer;
+ // 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);
+ }
- case kInitRenderer:
- case kSeeking:
- return kPlaying;
+ // Any kind of error stops the pipeline.
+ Stop();
+}
- case kSuspending:
- return kSuspended;
+void PipelineImpl::OnEnded() {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- case kSuspended:
- return kResuming;
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnEnded();
+ }
+}
- case kResuming:
- return kPlaying;
+void PipelineImpl::OnMetadata(PipelineMetadata metadata) {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
- case kPlaying:
- case kStopping:
- case kStopped:
- break;
+ if (IsRunning()) {
+ DCHECK(client_);
+ client_->OnMetadata(metadata);
}
- NOTREACHED() << "State has no transition: " << state_;
- return state_;
}
-void PipelineImpl::OnDemuxerError(PipelineStatus error) {
+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());
+
+ // 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);
+ }
+}
+
+void PipelineImpl::OnSuspendDone(base::TimeDelta suspend_time) {
+ DVLOG(3) << __FUNCTION__ << "(" << suspend_time.InMicroseconds() << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // 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;
+
+ // 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),
+ renderer_ended_(false),
+ text_renderer_ended_(false),
+ weak_factory_(this) {
+ media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
+}
+
+PipelineImpl::RendererWrapper::~RendererWrapper() {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DCHECK(state_ == kCreated || state_ == kStopped);
+}
+
+// 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.
+
+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(renderer_lock_);
+ return renderer_ ? renderer_->GetMediaTime() : base::TimeDelta();
+}
+
+void PipelineImpl::RendererWrapper::SetState(State next_state) {
+ DCHECK(media_task_runner_->BelongsToCurrentThread());
+ DVLOG(1) << PipelineImpl::GetStateString(state_) << " -> "
+ << PipelineImpl::GetStateString(next_state);
+
+ state_ = next_state;
+ media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
+}
+
+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));
+ media_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&RendererWrapper::OnPipelineError,
+ weak_factory_.GetWeakPtr(), 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_,
- text_stream, config));
+ FROM_HERE, base::Bind(&RendererWrapper::AddTextStreamTask,
+ weak_factory_.GetWeakPtr(), 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_factory_.GetWeakPtr(), 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));
+ media_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&RendererWrapper::OnPipelineError,
+ weak_factory_.GetWeakPtr(), error));
}
-void PipelineImpl::OnEnded() {
+void PipelineImpl::RendererWrapper::OnEnded() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
@@ -367,288 +652,211 @@ 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::OnSeekDone(base::TimeDelta start_time,
+ PipelineStatus status) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
- // No-op any state transitions if we're stopping.
- if (state_ == kStopping || state_ == kStopped)
- return;
+ DCHECK(pending_callbacks_);
+ pending_callbacks_.reset();
- // Report error from the previous operation.
if (status != PIPELINE_OK) {
- ErrorChangedTask(status);
+ OnPipelineError(status);
return;
}
- // 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);
-
- pending_callbacks_.reset();
+ start_time = std::max(start_time, demuxer_->GetStartTime());
+ renderer_->StartPlayingFrom(start_time);
- PipelineStatusCB done_cb =
- base::Bind(&PipelineImpl::StateTransitionTask, weak_this_);
-
- // Switch states, performing any entrance actions for the new state as well.
- SetState(GetNextState());
- switch (state_) {
- case kInitDemuxer:
- return InitializeDemuxer(done_cb);
-
- 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();
-
- return InitializeRenderer(done_cb);
-
- case kPlaying:
- DCHECK(start_timestamp_ >= base::TimeDelta());
- renderer_->StartPlayingFrom(start_timestamp_);
- {
- base::AutoLock auto_lock(lock_);
- suspend_timestamp_ = kNoTimestamp();
- }
-
- if (text_renderer_)
- text_renderer_->StartPlaying();
-
- base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
-
- PlaybackRateChangedTask(GetPlaybackRate());
- VolumeChangedTask(GetVolume());
- 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;
-
- case kStopping:
- case kStopped:
- case kCreated:
- case kSeeking:
- case kSuspending:
- case kResuming:
- NOTREACHED() << "State has no transition: " << state_;
- return;
- }
-}
-
-// 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;
-
- // Pause.
- if (text_renderer_) {
- bound_fns.Push(base::Bind(&TextRenderer::Pause,
- base::Unretained(text_renderer_.get())));
- }
-
- // 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())));
- }
+ if (text_renderer_)
+ text_renderer_->StartPlaying();
- // Seek demuxer.
- bound_fns.Push(
- base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp));
+ renderer_->SetPlaybackRate(playback_rate_);
+ renderer_->SetVolume(volume_);
- pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
+ SetState(kPlaying);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_, start_time));
}
-void PipelineImpl::DoStop() {
- DVLOG(2) << __FUNCTION__;
+void PipelineImpl::RendererWrapper::OnSuspendDone(base::TimeDelta suspend_time,
+ PipelineStatus status) {
DCHECK(media_task_runner_->BelongsToCurrentThread());
- DCHECK_EQ(state_, kStopping);
- DCHECK(!pending_callbacks_.get());
- // TODO(scherkus): Enforce that Renderer is only called on a single thread,
- // even for accessing media time http://crbug.com/370634
+ // 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;
+
+ DCHECK(pending_callbacks_);
+ pending_callbacks_.reset();
+
std::unique_ptr<Renderer> renderer;
{
- base::AutoLock auto_lock(lock_);
+ base::AutoLock auto_lock(renderer_lock_);
renderer.swap(renderer_);
}
renderer.reset();
- text_renderer_.reset();
-
- if (demuxer_) {
- demuxer_->Stop();
- 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_));
- }
+ SetState(kSuspended);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_, suspend_time));
}
-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());
- CHECK_EQ(kCreated, state_)
- << "Media pipeline cannot be started more than once";
+ DCHECK_EQ(kCreated, state_) << "Received start in unexpected state: "
+ << state_;
+ SetState(kStarting);
- text_renderer_ = CreateTextRenderer();
+ 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);
+
+ // Initialize text renderer.
if (text_renderer_) {
- text_renderer_->Initialize(
- base::Bind(&PipelineImpl::OnTextRendererEnded, weak_this_));
+ text_renderer_->Initialize(base::Bind(&RendererWrapper::OnTextRendererEnded,
+ weak_factory_.GetWeakPtr()));
}
- StateTransitionTask(PIPELINE_OK);
alokp 2016/06/08 19:59:48 Instead of using StateTransitionTask, we use Seria
+ // Queue asynchronous actions required to seek.
+ DCHECK(!pending_callbacks_);
+ SerialRunner::Queue fns;
+
+ // Initialize demuxer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeDemuxer,
+ weak_factory_.GetWeakPtr()));
+
+ // 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()));
+
+ // Initialize renderer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeRenderer,
+ weak_factory_.GetWeakPtr()));
+
+ // Run tasks.
+ pending_callbacks_ = SerialRunner::Run(
+ fns, base::Bind(&RendererWrapper::OnSeekDone, weak_factory_.GetWeakPtr(),
+ base::TimeDelta()));
}
-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());
+ DCHECK(state_ != kStopping && state_ != kStopped);
- if (state_ == kStopped) {
- // Invalid all weak pointers so it's safe to destroy |this| on the render
- // main thread.
- weak_factory_.InvalidateWeakPtrs();
+ SetState(kStopping);
- // NOTE: pipeline may be deleted at this point in time as a result of
- // executing |stop_cb|.
- stop_cb.Run();
+ // 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();
- return;
+ std::unique_ptr<Renderer> renderer;
+ {
+ base::AutoLock auto_lock(renderer_lock_);
+ renderer.swap(renderer_);
}
+ renderer.reset();
+ text_renderer_.reset();
- 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);
- }
+ if (demuxer_) {
+ demuxer_->Stop();
+ demuxer_ = nullptr;
}
- SetState(kStopping);
- pending_callbacks_.reset();
- DoStop();
+ SetState(kStopped);
+
+ // 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);
}
-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,199 +864,190 @@ 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());
+ DCHECK(renderer_);
+ DCHECK(!pending_callbacks_.get());
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_));
+ // Queue asynchronous actions required to seek.
+ SerialRunner::Queue bound_fns;
+
+ // Pause.
+ if (text_renderer_) {
+ bound_fns.Push(base::Bind(&TextRenderer::Pause,
+ base::Unretained(text_renderer_.get())));
+ }
+
+ // Flush.
+ 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())));
+ }
+
+ // Seek.
+ bound_fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_),
+ std::max(time, demuxer_->GetStartTime())));
+
+ pending_callbacks_ = SerialRunner::Run(
+ bound_fns, base::Bind(&RendererWrapper::OnSeekDone,
+ weak_factory_.GetWeakPtr(), time));
}
-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);
+ << "Received suspend in unexpected state: " << 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.)
+ // 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());
- }
+ base::TimeDelta suspend_time = renderer_->GetMediaTime();
+ DCHECK(suspend_time != kNoTimestamp());
- // Queue the asynchronous actions required to stop playback. (Matches setup in
- // DoSeek().)
- // TODO(sandersd): Share implementation with DoSeek().
+ // Queue asynchronous actions required to suspend playback.
SerialRunner::Queue fns;
+ // Pause.
if (text_renderer_) {
fns.Push(base::Bind(&TextRenderer::Pause,
base::Unretained(text_renderer_.get())));
}
+ // 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())));
}
pending_callbacks_ = SerialRunner::Run(
- fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
+ fns, base::Bind(&RendererWrapper::OnSuspendDone,
+ weak_factory_.GetWeakPtr(), suspend_time));
}
-void PipelineImpl::ResumeTask(std::unique_ptr<Renderer> renderer,
- base::TimeDelta timestamp,
- const PipelineStatusCB& seek_cb) {
+void PipelineImpl::RendererWrapper::Resume(base::TimeDelta time,
+ std::unique_ptr<Renderer> renderer) {
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);
+ << "Received resume in unexpected state: " << state_;
+ OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
return;
}
DCHECK(!renderer_);
DCHECK(!pending_callbacks_.get());
SetState(kResuming);
- renderer_ = std::move(renderer);
-
- // Set up for a seek. (Matches setup in SeekTask().)
- // TODO(sandersd): Share implementation with SeekTask().
- seek_cb_ = seek_cb;
+ {
+ base::AutoLock auto_lock(renderer_lock_);
+ renderer_ = std::move(renderer);
+ }
renderer_ended_ = false;
text_renderer_ended_ = false;
- start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime());
- // 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|).
+ // Queue the asynchronous actions required to resume playback.
SerialRunner::Queue fns;
- fns.Push(
- base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_));
+ // Seek.
+ fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_),
+ std::max(time, demuxer_->GetStartTime())));
- fns.Push(base::Bind(&PipelineImpl::InitializeRenderer, weak_this_));
+ // Initialize the new renderer.
+ fns.Push(base::Bind(&RendererWrapper::InitializeRenderer,
+ weak_factory_.GetWeakPtr()));
- pending_callbacks_ = SerialRunner::Run(
- fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_));
+ // Run tasks.
+ pending_callbacks_ =
+ SerialRunner::Run(fns, base::Bind(&RendererWrapper::OnSeekDone,
+ weak_factory_.GetWeakPtr(), time));
}
-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);
return;
}
- renderer_->SetCdm(cdm_context,
- base::Bind(&PipelineImpl::OnCdmAttached, weak_this_,
- cdm_attached_cb, cdm_context));
+ renderer_->SetCdm(cdm_context, base::Bind(&RendererWrapper::OnCdmAttached,
+ weak_factory_.GetWeakPtr(),
+ 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 +1060,7 @@ void PipelineImpl::OnTextRendererEnded() {
RunEndedCallbackIfNeeded();
}
-void PipelineImpl::RunEndedCallbackIfNeeded() {
+void PipelineImpl::RendererWrapper::RunEndedCallbackIfNeeded() {
DCHECK(media_task_runner_->BelongsToCurrentThread());
if (renderer_ && !renderer_ended_)
@@ -872,62 +1071,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 +1116,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 +1131,8 @@ 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));
}
} // namespace media
« no previous file with comments | « media/base/pipeline_impl.h ('k') | media/base/pipeline_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698