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 |