| Index: media/base/pipeline_impl.cc
|
| diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
|
| index 36747bd48b3cd7ba73421aa5d2de35966c903170..ddbd0cea8e372c56a6be4492630e9bd0883d0b87 100644
|
| --- a/media/base/pipeline_impl.cc
|
| +++ b/media/base/pipeline_impl.cc
|
| @@ -105,20 +105,25 @@ class PipelineImpl::RendererWrapper : public DemuxerHost,
|
| void OnVideoNaturalSizeChange(const gfx::Size& size) final;
|
| void OnVideoOpacityChange(bool opaque) final;
|
|
|
| + // TextRenderer tasks and notifications.
|
| + void OnTextRendererEnded();
|
| + void AddTextStreamTask(DemuxerStream* text_stream,
|
| + const TextTrackConfig& config);
|
| + void RemoveTextStreamTask(DemuxerStream* text_stream);
|
| +
|
| + // Common handlers for notifications from renderers and demuxer.
|
| + void OnPipelineError(PipelineStatus error);
|
| + void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb,
|
| + CdmContext* cdm_context,
|
| + bool success);
|
| + void CheckPlaybackEnded();
|
| +
|
| + // State transition tasks.
|
| void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb);
|
| void DoStop(const base::Closure& done_cb);
|
| - void OnPipelineError(PipelineStatus error);
|
| - void OnTextRendererEnded();
|
| - void RunEndedCallbackIfNeeded();
|
| void SetState(State next_state);
|
| State GetNextState() const;
|
| void StateTransitionTask(PipelineStatus status);
|
| - void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb,
|
| - CdmContext* cdm_context,
|
| - bool success);
|
| - void AddTextStreamTask(DemuxerStream* text_stream,
|
| - const TextTrackConfig& config);
|
| - void RemoveTextStreamTask(DemuxerStream* text_stream);
|
| void InitializeDemuxer(const PipelineStatusCB& done_cb);
|
| void InitializeRenderer(const PipelineStatusCB& done_cb);
|
| void DestroyRenderer();
|
| @@ -166,310 +171,276 @@ class PipelineImpl::RendererWrapper : public DemuxerHost,
|
| DISALLOW_COPY_AND_ASSIGN(RendererWrapper);
|
| };
|
|
|
| -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),
|
| +PipelineImpl::RendererWrapper::RendererWrapper(
|
| + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
|
| + scoped_refptr<MediaLog> media_log)
|
| + : 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) {
|
| - DVLOG(2) << __FUNCTION__;
|
| - renderer_wrapper_.reset(new RendererWrapper(media_task_runner_, media_log_));
|
| + weak_this_ = weak_factory_.GetWeakPtr();
|
| + media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
|
| }
|
|
|
| -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());
|
| - DCHECK(!weak_factory_.HasWeakPtrs());
|
| -
|
| - // RendererWrapper is deleted on the media thread.
|
| - media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release());
|
| +PipelineImpl::RendererWrapper::~RendererWrapper() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(state_ == kCreated || state_ == kStopped);
|
| }
|
|
|
| -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());
|
| -
|
| - DCHECK(!client_);
|
| - DCHECK(seek_cb_.is_null());
|
| - client_ = client;
|
| - seek_cb_ = seek_cb;
|
| +void PipelineImpl::RendererWrapper::Start(
|
| + Demuxer* demuxer,
|
| + std::unique_ptr<Renderer> renderer,
|
| + std::unique_ptr<TextRenderer> text_renderer,
|
| + base::WeakPtr<PipelineImpl> weak_pipeline) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK_EQ(kCreated, state_) << "Received start in unexpected state: "
|
| + << state_;
|
|
|
| - std::unique_ptr<TextRenderer> text_renderer;
|
| - if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| - switches::kEnableInbandTextTracks)) {
|
| - text_renderer.reset(new TextRenderer(
|
| - media_task_runner_,
|
| - BindToCurrentLoop(base::Bind(&PipelineImpl::OnAddTextTrack,
|
| - weak_factory_.GetWeakPtr()))));
|
| + DCHECK(!demuxer_);
|
| + DCHECK(!shared_state_.renderer);
|
| + DCHECK(!text_renderer_);
|
| + DCHECK(!renderer_ended_);
|
| + DCHECK(!text_renderer_ended_);
|
| + DCHECK(!weak_pipeline_);
|
| + demuxer_ = demuxer;
|
| + {
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + shared_state_.renderer = std::move(renderer);
|
| + }
|
| + text_renderer_ = std::move(text_renderer);
|
| + if (text_renderer_) {
|
| + text_renderer_->Initialize(
|
| + base::Bind(&RendererWrapper::OnTextRendererEnded, weak_this_));
|
| }
|
| + weak_pipeline_ = weak_pipeline;
|
|
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RendererWrapper::Start,
|
| - base::Unretained(renderer_wrapper_.get()), demuxer,
|
| - base::Passed(&renderer), base::Passed(&text_renderer),
|
| - weak_factory_.GetWeakPtr()));
|
| + StateTransitionTask(PIPELINE_OK);
|
| }
|
|
|
| -void PipelineImpl::Stop() {
|
| - DVLOG(2) << __FUNCTION__;
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| +void PipelineImpl::RendererWrapper::Stop(const base::Closure& stop_cb) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(state_ != kStopping && state_ != kStopped);
|
|
|
| - if (!IsRunning()) {
|
| - DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
|
| - return;
|
| - }
|
| + SetState(kStopping);
|
|
|
| - 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));
|
| - // 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(media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RendererWrapper::Stop,
|
| - base::Unretained(renderer_wrapper_.get()), stop_cb)));
|
| - waiter.Wait();
|
| + if (shared_state_.statistics.video_frames_decoded > 0) {
|
| + UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount",
|
| + shared_state_.statistics.video_frames_dropped);
|
| }
|
|
|
| - // 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;
|
| + // 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();
|
|
|
| - // Invalidate self weak pointers effectively canceling all pending
|
| - // notifications in the message queue.
|
| - weak_factory_.InvalidateWeakPtrs();
|
| + DoStop(stop_cb);
|
| }
|
|
|
| -void PipelineImpl::Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) {
|
| - DVLOG(2) << __FUNCTION__;
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK(!seek_cb.is_null());
|
| +void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - if (!IsRunning()) {
|
| - DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
|
| + // 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;
|
| }
|
|
|
| - 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));
|
| -}
|
| + const base::TimeDelta seek_timestamp =
|
| + std::max(time, demuxer_->GetStartTime());
|
|
|
| -bool PipelineImpl::IsRunning() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return !!client_;
|
| -}
|
| + SetState(kSeeking);
|
| + renderer_ended_ = false;
|
| + text_renderer_ended_ = false;
|
| + start_timestamp_ = seek_timestamp;
|
|
|
| -double PipelineImpl::GetPlaybackRate() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return playback_rate_;
|
| + DoSeek(seek_timestamp,
|
| + base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -void PipelineImpl::SetPlaybackRate(double playback_rate) {
|
| - DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")";
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| +void PipelineImpl::RendererWrapper::Suspend() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - if (playback_rate < 0.0)
|
| + // Suppress suspending if we're not playing.
|
| + if (state_ != kPlaying) {
|
| + DCHECK(state_ == kStopping || state_ == kStopped)
|
| + << "Receive suspend in unexpected state: " << state_;
|
| + OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| return;
|
| + }
|
| + DCHECK(shared_state_.renderer);
|
| + DCHECK(!pending_callbacks_.get());
|
|
|
| - playback_rate_ = playback_rate;
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RendererWrapper::SetPlaybackRate,
|
| - base::Unretained(renderer_wrapper_.get()), playback_rate_));
|
| -}
|
| + SetState(kSuspending);
|
|
|
| -void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
|
| - DVLOG(2) << __FUNCTION__;
|
| - DCHECK(!suspend_cb.is_null());
|
| + // Freeze playback and record the media time before flushing. (Flushing clears
|
| + // the value.)
|
| + shared_state_.renderer->SetPlaybackRate(0.0);
|
| + {
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + shared_state_.suspend_timestamp = shared_state_.renderer->GetMediaTime();
|
| + DCHECK(shared_state_.suspend_timestamp != kNoTimestamp());
|
| + }
|
|
|
| - DCHECK(IsRunning());
|
| - DCHECK(suspend_cb_.is_null());
|
| - suspend_cb_ = suspend_cb;
|
| + // Queue the asynchronous actions required to stop playback. (Matches setup in
|
| + // DoSeek().)
|
| + // TODO(sandersd): Share implementation with DoSeek().
|
| + SerialRunner::Queue fns;
|
|
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&RendererWrapper::Suspend,
|
| - base::Unretained(renderer_wrapper_.get())));
|
| -}
|
| + if (text_renderer_) {
|
| + fns.Push(base::Bind(&TextRenderer::Pause,
|
| + base::Unretained(text_renderer_.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());
|
| + fns.Push(base::Bind(&Renderer::Flush,
|
| + base::Unretained(shared_state_.renderer.get())));
|
|
|
| - 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()),
|
| - base::Passed(&renderer), time));
|
| -}
|
| + if (text_renderer_) {
|
| + fns.Push(base::Bind(&TextRenderer::Flush,
|
| + base::Unretained(text_renderer_.get())));
|
| + }
|
|
|
| -float PipelineImpl::GetVolume() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return volume_;
|
| + pending_callbacks_ = SerialRunner::Run(
|
| + fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -void PipelineImpl::SetVolume(float volume) {
|
| - DVLOG(2) << __FUNCTION__ << "(" << volume << ")";
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| +void PipelineImpl::RendererWrapper::Resume(std::unique_ptr<Renderer> renderer,
|
| + base::TimeDelta timestamp) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - if (volume < 0.0f || volume > 1.0f)
|
| + // Suppress resuming if we're not suspended.
|
| + if (state_ != kSuspended) {
|
| + DCHECK(state_ == kStopping || state_ == kStopped)
|
| + << "Receive resume in unexpected state: " << state_;
|
| + OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| return;
|
| + }
|
| + DCHECK(!shared_state_.renderer);
|
| + DCHECK(!pending_callbacks_.get());
|
|
|
| - volume_ = volume;
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RendererWrapper::SetVolume,
|
| - base::Unretained(renderer_wrapper_.get()), volume_));
|
| -}
|
| + SetState(kResuming);
|
|
|
| -base::TimeDelta PipelineImpl::GetMediaTime() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return renderer_wrapper_->GetMediaTime();
|
| -}
|
| + {
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + shared_state_.renderer = std::move(renderer);
|
| + }
|
|
|
| -Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return renderer_wrapper_->GetBufferedTimeRanges();
|
| -}
|
| + // Set up for a seek. (Matches setup in SeekTask().)
|
| + // TODO(sandersd): Share implementation with SeekTask().
|
| + renderer_ended_ = false;
|
| + text_renderer_ended_ = false;
|
| + start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime());
|
|
|
| -base::TimeDelta PipelineImpl::GetMediaDuration() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return duration_;
|
| -}
|
| + // 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;
|
|
|
| -bool PipelineImpl::DidLoadingProgress() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return renderer_wrapper_->DidLoadingProgress();
|
| -}
|
| + fns.Push(
|
| + base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_));
|
|
|
| -PipelineStatistics PipelineImpl::GetStatistics() const {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - return renderer_wrapper_->GetStatistics();
|
| + fns.Push(base::Bind(&RendererWrapper::InitializeRenderer, weak_this_));
|
| +
|
| + pending_callbacks_ = SerialRunner::Run(
|
| + fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| }
|
|
|
| -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());
|
| +void PipelineImpl::RendererWrapper::SetPlaybackRate(double playback_rate) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - media_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&RendererWrapper::SetCdm,
|
| - base::Unretained(renderer_wrapper_.get()), cdm_context,
|
| - media::BindToCurrentLoop(cdm_attached_cb)));
|
| + playback_rate_ = playback_rate;
|
| + if (state_ == kPlaying)
|
| + shared_state_.renderer->SetPlaybackRate(playback_rate_);
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::SetState(State next_state) {
|
| +void PipelineImpl::RendererWrapper::SetVolume(float volume) {
|
| 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));
|
| + volume_ = volume;
|
| + if (state_ == kPlaying)
|
| + shared_state_.renderer->SetVolume(volume_);
|
| }
|
|
|
| -#define RETURN_STRING(state) \
|
| - case state: \
|
| - return #state;
|
| +base::TimeDelta PipelineImpl::RendererWrapper::GetMediaTime() const {
|
| + DCHECK(main_task_runner_->BelongsToCurrentThread());
|
|
|
| -// static
|
| -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";
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + if (shared_state_.suspend_timestamp != kNoTimestamp())
|
| + return shared_state_.suspend_timestamp;
|
| + return shared_state_.renderer ? shared_state_.renderer->GetMediaTime()
|
| + : base::TimeDelta();
|
| }
|
|
|
| -#undef RETURN_STRING
|
| -
|
| -PipelineImpl::State PipelineImpl::RendererWrapper::GetNextState() const {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK_EQ(status_, PIPELINE_OK)
|
| - << "State transitions don't happen when there's an error: " << status_;
|
| +Ranges<base::TimeDelta> PipelineImpl::RendererWrapper::GetBufferedTimeRanges()
|
| + const {
|
| + DCHECK(main_task_runner_->BelongsToCurrentThread());
|
|
|
| - switch (state_) {
|
| - case kCreated:
|
| - return kInitDemuxer;
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + return shared_state_.buffered_time_ranges;
|
| +}
|
|
|
| - case kInitDemuxer:
|
| - return kInitRenderer;
|
| +bool PipelineImpl::RendererWrapper::DidLoadingProgress() {
|
| + DCHECK(main_task_runner_->BelongsToCurrentThread());
|
|
|
| - case kInitRenderer:
|
| - case kSeeking:
|
| - return kPlaying;
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + bool did_progress = shared_state_.did_loading_progress;
|
| + shared_state_.did_loading_progress = false;
|
| + return did_progress;
|
| +}
|
|
|
| - case kSuspending:
|
| - return kSuspended;
|
| +PipelineStatistics PipelineImpl::RendererWrapper::GetStatistics() const {
|
| + DCHECK(main_task_runner_->BelongsToCurrentThread());
|
|
|
| - case kSuspended:
|
| - return kResuming;
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + return shared_state_.statistics;
|
| +}
|
|
|
| - case kResuming:
|
| - return kPlaying;
|
| +void PipelineImpl::RendererWrapper::SetCdm(
|
| + CdmContext* cdm_context,
|
| + const CdmAttachedCB& cdm_attached_cb) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - case kPlaying:
|
| - case kStopping:
|
| - case kStopped:
|
| - break;
|
| + if (!shared_state_.renderer) {
|
| + cdm_context_ = cdm_context;
|
| + cdm_attached_cb.Run(true);
|
| + return;
|
| }
|
| - NOTREACHED() << "State has no transition: " << state_;
|
| - return state_;
|
| +
|
| + shared_state_.renderer->SetCdm(
|
| + cdm_context, base::Bind(&RendererWrapper::OnCdmAttached, weak_this_,
|
| + cdm_attached_cb, cdm_context));
|
| +}
|
| +
|
| +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(shared_state_lock_);
|
| + shared_state_.did_loading_progress = true;
|
| + shared_state_.buffered_time_ranges = ranges;
|
| +}
|
| +
|
| +void PipelineImpl::RendererWrapper::SetDuration(base::TimeDelta duration) {
|
| + // 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);
|
| +
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&PipelineImpl::OnDurationChange, weak_pipeline_, duration));
|
| }
|
|
|
| void PipelineImpl::RendererWrapper::OnDemuxerError(PipelineStatus error) {
|
| @@ -516,7 +487,7 @@ void PipelineImpl::RendererWrapper::OnEnded() {
|
|
|
| DCHECK(!renderer_ended_);
|
| renderer_ended_ = true;
|
| - RunEndedCallbackIfNeeded();
|
| + CheckPlaybackEnded();
|
| }
|
|
|
| void PipelineImpl::RendererWrapper::OnStatisticsUpdate(
|
| @@ -568,98 +539,84 @@ void PipelineImpl::RendererWrapper::OnVideoOpacityChange(bool opaque) {
|
| base::Bind(&PipelineImpl::OnVideoOpacityChange, weak_pipeline_, opaque));
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::SetDuration(base::TimeDelta duration) {
|
| - // 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::RendererWrapper::OnTextRendererEnded() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
|
|
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE,
|
| - base::Bind(&PipelineImpl::OnDurationChange, weak_pipeline_, duration));
|
| + if (state_ != kPlaying)
|
| + return;
|
| +
|
| + DCHECK(!text_renderer_ended_);
|
| + text_renderer_ended_ = true;
|
| + CheckPlaybackEnded();
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::StateTransitionTask(PipelineStatus status) {
|
| +void PipelineImpl::RendererWrapper::AddTextStreamTask(
|
| + DemuxerStream* text_stream,
|
| + const TextTrackConfig& config) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - // No-op any state transitions if we're stopping or already encountered error.
|
| - if (state_ == kStopping || state_ == kStopped || status_ != PIPELINE_OK)
|
| - return;
|
| -
|
| - // Report error from the previous operation.
|
| - if (status != PIPELINE_OK) {
|
| - OnPipelineError(status);
|
| - return;
|
| - }
|
| + // TODO(matthewjheaney): fix up text_ended_ when text stream
|
| + // is added (http://crbug.com/321446).
|
| + if (text_renderer_)
|
| + text_renderer_->AddTextStream(text_stream, config);
|
| +}
|
|
|
| - // 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 PipelineImpl::RendererWrapper::RemoveTextStreamTask(
|
| + DemuxerStream* text_stream) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - pending_callbacks_.reset();
|
| + if (text_renderer_)
|
| + text_renderer_->RemoveTextStream(text_stream);
|
| +}
|
|
|
| - PipelineStatusCB done_cb =
|
| - base::Bind(&RendererWrapper::StateTransitionTask, weak_this_);
|
| +void PipelineImpl::RendererWrapper::OnPipelineError(PipelineStatus error) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
|
|
|
| - // Switch states, performing any entrance actions for the new state as well.
|
| - SetState(GetNextState());
|
| - switch (state_) {
|
| - case kInitDemuxer:
|
| - return InitializeDemuxer(done_cb);
|
| + // Preserve existing abnormal status.
|
| + if (status_ != PIPELINE_OK)
|
| + return;
|
|
|
| - 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();
|
| + // 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;
|
| + }
|
|
|
| - return InitializeRenderer(done_cb);
|
| + status_ = error;
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnError, weak_pipeline_, error));
|
| +}
|
|
|
| - case kPlaying:
|
| - DCHECK(start_timestamp_ >= base::TimeDelta());
|
| - shared_state_.renderer->StartPlayingFrom(start_timestamp_);
|
| - {
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - shared_state_.suspend_timestamp = kNoTimestamp();
|
| - }
|
| +void PipelineImpl::RendererWrapper::OnCdmAttached(
|
| + const CdmAttachedCB& cdm_attached_cb,
|
| + CdmContext* cdm_context,
|
| + bool success) {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - if (text_renderer_)
|
| - text_renderer_->StartPlaying();
|
| + if (success)
|
| + cdm_context_ = cdm_context;
|
| + cdm_attached_cb.Run(success);
|
| +}
|
|
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_,
|
| - start_timestamp_));
|
| +void PipelineImpl::RendererWrapper::CheckPlaybackEnded() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - shared_state_.renderer->SetPlaybackRate(playback_rate_);
|
| - shared_state_.renderer->SetVolume(volume_);
|
| - return;
|
| + if (shared_state_.renderer && !renderer_ended_)
|
| + return;
|
|
|
| - case kSuspended:
|
| - DestroyRenderer();
|
| - {
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - shared_state_.statistics.audio_memory_usage = 0;
|
| - shared_state_.statistics.video_memory_usage = 0;
|
| - }
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_,
|
| - shared_state_.suspend_timestamp));
|
| - return;
|
| + if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_)
|
| + return;
|
|
|
| - case kStopping:
|
| - case kStopped:
|
| - case kCreated:
|
| - case kSeeking:
|
| - case kSuspending:
|
| - case kResuming:
|
| - NOTREACHED() << "State has no transition: " << state_;
|
| - return;
|
| - }
|
| + DCHECK_EQ(status_, PIPELINE_OK);
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnEnded, weak_pipeline_));
|
| }
|
|
|
| // Note that the usage of base::Unretained() with the renderers is considered
|
| @@ -722,368 +679,448 @@ void PipelineImpl::RendererWrapper::DoStop(const base::Closure& done_cb) {
|
| media_task_runner_->PostTask(FROM_HERE, done_cb);
|
| }
|
|
|
| -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(shared_state_lock_);
|
| - shared_state_.did_loading_progress = true;
|
| - shared_state_.buffered_time_ranges = ranges;
|
| +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::Start(
|
| - Demuxer* demuxer,
|
| - std::unique_ptr<Renderer> renderer,
|
| - std::unique_ptr<TextRenderer> text_renderer,
|
| - base::WeakPtr<PipelineImpl> weak_pipeline) {
|
| +PipelineImpl::State PipelineImpl::RendererWrapper::GetNextState() const {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK_EQ(kCreated, state_) << "Received start in unexpected state: "
|
| - << state_;
|
| + DCHECK_EQ(status_, PIPELINE_OK)
|
| + << "State transitions don't happen when there's an error: " << status_;
|
|
|
| - DCHECK(!demuxer_);
|
| - DCHECK(!shared_state_.renderer);
|
| - DCHECK(!text_renderer_);
|
| - DCHECK(!renderer_ended_);
|
| - DCHECK(!text_renderer_ended_);
|
| - DCHECK(!weak_pipeline_);
|
| - demuxer_ = demuxer;
|
| - {
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - shared_state_.renderer = std::move(renderer);
|
| - }
|
| - text_renderer_ = std::move(text_renderer);
|
| - if (text_renderer_) {
|
| - text_renderer_->Initialize(
|
| - base::Bind(&RendererWrapper::OnTextRendererEnded, weak_this_));
|
| - }
|
| - weak_pipeline_ = weak_pipeline;
|
| + switch (state_) {
|
| + case kCreated:
|
| + return kInitDemuxer;
|
|
|
| - StateTransitionTask(PIPELINE_OK);
|
| -}
|
| + case kInitDemuxer:
|
| + return kInitRenderer;
|
|
|
| -void PipelineImpl::RendererWrapper::Stop(const base::Closure& stop_cb) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(state_ != kStopping && state_ != kStopped);
|
| + case kInitRenderer:
|
| + case kSeeking:
|
| + return kPlaying;
|
|
|
| - SetState(kStopping);
|
| + case kSuspending:
|
| + return kSuspended;
|
|
|
| - if (shared_state_.statistics.video_frames_decoded > 0) {
|
| - UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount",
|
| - shared_state_.statistics.video_frames_dropped);
|
| - }
|
| + case kSuspended:
|
| + return kResuming;
|
|
|
| - // 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();
|
| + case kResuming:
|
| + return kPlaying;
|
|
|
| - DoStop(stop_cb);
|
| + case kPlaying:
|
| + case kStopping:
|
| + case kStopped:
|
| + break;
|
| + }
|
| + NOTREACHED() << "State has no transition: " << state_;
|
| + return state_;
|
| }
|
| -
|
| -void PipelineImpl::RendererWrapper::OnPipelineError(PipelineStatus error) {
|
| +void PipelineImpl::RendererWrapper::StateTransitionTask(PipelineStatus status) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
|
|
|
| - // Preserve existing abnormal status.
|
| - if (status_ != PIPELINE_OK)
|
| + // No-op any state transitions if we're stopping or already encountered error.
|
| + if (state_ == kStopping || state_ == kStopped || 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.
|
| - if (state_ == kStopping || state_ == kStopped || state_ == kSuspending ||
|
| - state_ == kSuspended) {
|
| + // Report error from the previous operation.
|
| + if (status != PIPELINE_OK) {
|
| + OnPipelineError(status);
|
| return;
|
| }
|
|
|
| - status_ = error;
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::OnError, weak_pipeline_, error));
|
| -}
|
| + // 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 PipelineImpl::RendererWrapper::SetPlaybackRate(double playback_rate) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + pending_callbacks_.reset();
|
|
|
| - playback_rate_ = playback_rate;
|
| - if (state_ == kPlaying)
|
| - shared_state_.renderer->SetPlaybackRate(playback_rate_);
|
| + PipelineStatusCB done_cb =
|
| + base::Bind(&RendererWrapper::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());
|
| + shared_state_.renderer->StartPlayingFrom(start_timestamp_);
|
| + {
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + shared_state_.suspend_timestamp = kNoTimestamp();
|
| + }
|
| +
|
| + if (text_renderer_)
|
| + text_renderer_->StartPlaying();
|
| +
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_,
|
| + start_timestamp_));
|
| +
|
| + shared_state_.renderer->SetPlaybackRate(playback_rate_);
|
| + shared_state_.renderer->SetVolume(volume_);
|
| + return;
|
| +
|
| + case kSuspended:
|
| + DestroyRenderer();
|
| + {
|
| + base::AutoLock auto_lock(shared_state_lock_);
|
| + shared_state_.statistics.audio_memory_usage = 0;
|
| + shared_state_.statistics.video_memory_usage = 0;
|
| + }
|
| + main_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_,
|
| + shared_state_.suspend_timestamp));
|
| + return;
|
| +
|
| + case kStopping:
|
| + case kStopped:
|
| + case kCreated:
|
| + case kSeeking:
|
| + case kSuspending:
|
| + case kResuming:
|
| + NOTREACHED() << "State has no transition: " << state_;
|
| + return;
|
| + }
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::SetVolume(float volume) {
|
| +void PipelineImpl::RendererWrapper::InitializeDemuxer(
|
| + const PipelineStatusCB& done_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - volume_ = volume;
|
| - if (state_ == kPlaying)
|
| - shared_state_.renderer->SetVolume(volume_);
|
| + demuxer_->Initialize(this, done_cb, !!text_renderer_);
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::Seek(base::TimeDelta time) {
|
| +void PipelineImpl::RendererWrapper::InitializeRenderer(
|
| + const PipelineStatusCB& done_cb) {
|
| DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - // 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);
|
| + if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
|
| + !demuxer_->GetStream(DemuxerStream::VIDEO)) {
|
| + done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
|
| return;
|
| }
|
|
|
| - const base::TimeDelta seek_timestamp =
|
| - std::max(time, demuxer_->GetStartTime());
|
| -
|
| - SetState(kSeeking);
|
| - renderer_ended_ = false;
|
| - text_renderer_ended_ = false;
|
| - start_timestamp_ = seek_timestamp;
|
| + if (cdm_context_)
|
| + shared_state_.renderer->SetCdm(cdm_context_,
|
| + base::Bind(&IgnoreCdmAttached));
|
|
|
| - DoSeek(seek_timestamp,
|
| - base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| + shared_state_.renderer->Initialize(demuxer_, this, done_cb);
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::Suspend() {
|
| +void PipelineImpl::RendererWrapper::DestroyRenderer() {
|
| 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_;
|
| - OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| - return;
|
| - }
|
| - DCHECK(shared_state_.renderer);
|
| - DCHECK(!pending_callbacks_.get());
|
| -
|
| - SetState(kSuspending);
|
| -
|
| - // Freeze playback and record the media time before flushing. (Flushing clears
|
| - // the value.)
|
| - shared_state_.renderer->SetPlaybackRate(0.0);
|
| + // Destroy the renderer outside the lock scope to avoid holding the lock
|
| + // while renderer is being destroyed (in case Renderer destructor is costly).
|
| + std::unique_ptr<Renderer> renderer;
|
| {
|
| base::AutoLock auto_lock(shared_state_lock_);
|
| - shared_state_.suspend_timestamp = shared_state_.renderer->GetMediaTime();
|
| - DCHECK(shared_state_.suspend_timestamp != kNoTimestamp());
|
| + renderer.swap(shared_state_.renderer);
|
| }
|
| +}
|
|
|
| - // Queue the asynchronous actions required to stop playback. (Matches setup in
|
| - // DoSeek().)
|
| - // TODO(sandersd): Share implementation with DoSeek().
|
| - SerialRunner::Queue fns;
|
| +void PipelineImpl::RendererWrapper::ReportMetadata() {
|
| + DCHECK(media_task_runner_->BelongsToCurrentThread());
|
|
|
| - if (text_renderer_) {
|
| - fns.Push(base::Bind(&TextRenderer::Pause,
|
| - base::Unretained(text_renderer_.get())));
|
| + 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();
|
| }
|
| -
|
| - fns.Push(base::Bind(&Renderer::Flush,
|
| - base::Unretained(shared_state_.renderer.get())));
|
| -
|
| - if (text_renderer_) {
|
| - fns.Push(base::Bind(&TextRenderer::Flush,
|
| - base::Unretained(text_renderer_.get())));
|
| + if (demuxer_->GetStream(DemuxerStream::AUDIO)) {
|
| + metadata.has_audio = true;
|
| }
|
|
|
| - pending_callbacks_ = SerialRunner::Run(
|
| - fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| + main_task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::OnMetadata,
|
| + weak_pipeline_, metadata));
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::Resume(std::unique_ptr<Renderer> renderer,
|
| - base::TimeDelta timestamp) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| -
|
| - // Suppress resuming if we're not suspended.
|
| - if (state_ != kSuspended) {
|
| - DCHECK(state_ == kStopping || state_ == kStopped)
|
| - << "Receive resume in unexpected state: " << state_;
|
| - OnPipelineError(PIPELINE_ERROR_INVALID_STATE);
|
| - return;
|
| - }
|
| - DCHECK(!shared_state_.renderer);
|
| - DCHECK(!pending_callbacks_.get());
|
| -
|
| - SetState(kResuming);
|
| +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),
|
| + weak_factory_(this) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + renderer_wrapper_.reset(new RendererWrapper(media_task_runner_, media_log_));
|
| +}
|
|
|
| - {
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - shared_state_.renderer = std::move(renderer);
|
| - }
|
| +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());
|
| + DCHECK(!weak_factory_.HasWeakPtrs());
|
|
|
| - // Set up for a seek. (Matches setup in SeekTask().)
|
| - // TODO(sandersd): Share implementation with SeekTask().
|
| - renderer_ended_ = false;
|
| - text_renderer_ended_ = false;
|
| - start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime());
|
| + // RendererWrapper is deleted on the media thread.
|
| + media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release());
|
| +}
|
|
|
| - // 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::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());
|
|
|
| - fns.Push(
|
| - base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_));
|
| + DCHECK(!client_);
|
| + DCHECK(seek_cb_.is_null());
|
| + client_ = client;
|
| + seek_cb_ = seek_cb;
|
|
|
| - fns.Push(base::Bind(&RendererWrapper::InitializeRenderer, weak_this_));
|
| + std::unique_ptr<TextRenderer> text_renderer;
|
| + if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kEnableInbandTextTracks)) {
|
| + text_renderer.reset(new TextRenderer(
|
| + media_task_runner_,
|
| + BindToCurrentLoop(base::Bind(&PipelineImpl::OnAddTextTrack,
|
| + weak_factory_.GetWeakPtr()))));
|
| + }
|
|
|
| - pending_callbacks_ = SerialRunner::Run(
|
| - fns, base::Bind(&RendererWrapper::StateTransitionTask, weak_this_));
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::Start,
|
| + base::Unretained(renderer_wrapper_.get()), demuxer,
|
| + base::Passed(&renderer), base::Passed(&text_renderer),
|
| + weak_factory_.GetWeakPtr()));
|
| }
|
|
|
| -Ranges<base::TimeDelta> PipelineImpl::RendererWrapper::GetBufferedTimeRanges()
|
| - const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - return shared_state_.buffered_time_ranges;
|
| -}
|
| +void PipelineImpl::Stop() {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| -bool PipelineImpl::RendererWrapper::DidLoadingProgress() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + if (!IsRunning()) {
|
| + DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()";
|
| + return;
|
| + }
|
|
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - bool did_progress = shared_state_.did_loading_progress;
|
| - shared_state_.did_loading_progress = false;
|
| - return did_progress;
|
| -}
|
| + 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));
|
| + // 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(media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::Stop,
|
| + base::Unretained(renderer_wrapper_.get()), stop_cb)));
|
| + waiter.Wait();
|
| + }
|
|
|
| -PipelineStatistics PipelineImpl::RendererWrapper::GetStatistics() const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| + // 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;
|
|
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - return shared_state_.statistics;
|
| + // Invalidate self weak pointers effectively canceling all pending
|
| + // notifications in the message queue.
|
| + weak_factory_.InvalidateWeakPtrs();
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::SetCdm(
|
| - CdmContext* cdm_context,
|
| - const CdmAttachedCB& cdm_attached_cb) {
|
| - 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());
|
|
|
| - if (!shared_state_.renderer) {
|
| - cdm_context_ = cdm_context;
|
| - cdm_attached_cb.Run(true);
|
| + if (!IsRunning()) {
|
| + DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
|
| return;
|
| }
|
|
|
| - shared_state_.renderer->SetCdm(
|
| - cdm_context, base::Bind(&RendererWrapper::OnCdmAttached, weak_this_,
|
| - cdm_attached_cb, cdm_context));
|
| + 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::RendererWrapper::OnCdmAttached(
|
| - const CdmAttachedCB& cdm_attached_cb,
|
| - CdmContext* cdm_context,
|
| - bool success) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) {
|
| + DVLOG(2) << __FUNCTION__;
|
| + DCHECK(!suspend_cb.is_null());
|
|
|
| - if (success)
|
| - cdm_context_ = cdm_context;
|
| - cdm_attached_cb.Run(success);
|
| + 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::RendererWrapper::OnTextRendererEnded() {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
|
| +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());
|
|
|
| - if (state_ != kPlaying)
|
| - return;
|
| + DCHECK(IsRunning());
|
| + DCHECK(seek_cb_.is_null());
|
| + seek_cb_ = seek_cb;
|
|
|
| - DCHECK(!text_renderer_ended_);
|
| - text_renderer_ended_ = true;
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&RendererWrapper::Resume,
|
| + base::Unretained(renderer_wrapper_.get()),
|
| + base::Passed(&renderer), time));
|
| +}
|
|
|
| - RunEndedCallbackIfNeeded();
|
| +bool PipelineImpl::IsRunning() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return !!client_;
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::RunEndedCallbackIfNeeded() {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +double PipelineImpl::GetPlaybackRate() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return playback_rate_;
|
| +}
|
|
|
| - if (shared_state_.renderer && !renderer_ended_)
|
| - return;
|
| +void PipelineImpl::SetPlaybackRate(double playback_rate) {
|
| + DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| - if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_)
|
| + if (playback_rate < 0.0)
|
| return;
|
|
|
| - DCHECK_EQ(status_, PIPELINE_OK);
|
| - main_task_runner_->PostTask(
|
| - FROM_HERE, base::Bind(&PipelineImpl::OnEnded, weak_pipeline_));
|
| + playback_rate_ = playback_rate;
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetPlaybackRate,
|
| + base::Unretained(renderer_wrapper_.get()), playback_rate_));
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::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);
|
| +float PipelineImpl::GetVolume() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return volume_;
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::RemoveTextStreamTask(
|
| - DemuxerStream* text_stream) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +void PipelineImpl::SetVolume(float volume) {
|
| + DVLOG(2) << __FUNCTION__ << "(" << volume << ")";
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
|
|
| - if (text_renderer_)
|
| - text_renderer_->RemoveTextStream(text_stream);
|
| -}
|
| + if (volume < 0.0f || volume > 1.0f)
|
| + return;
|
|
|
| -void PipelineImpl::RendererWrapper::InitializeDemuxer(
|
| - const PipelineStatusCB& done_cb) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| + volume_ = volume;
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetVolume,
|
| + base::Unretained(renderer_wrapper_.get()), volume_));
|
| +}
|
|
|
| - demuxer_->Initialize(this, done_cb, !!text_renderer_);
|
| +base::TimeDelta PipelineImpl::GetMediaTime() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return renderer_wrapper_->GetMediaTime();
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::InitializeRenderer(
|
| - const PipelineStatusCB& done_cb) {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return renderer_wrapper_->GetBufferedTimeRanges();
|
| +}
|
|
|
| - if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
|
| - !demuxer_->GetStream(DemuxerStream::VIDEO)) {
|
| - done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
|
| - return;
|
| - }
|
| +base::TimeDelta PipelineImpl::GetMediaDuration() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return duration_;
|
| +}
|
|
|
| - if (cdm_context_)
|
| - shared_state_.renderer->SetCdm(cdm_context_,
|
| - base::Bind(&IgnoreCdmAttached));
|
| +bool PipelineImpl::DidLoadingProgress() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return renderer_wrapper_->DidLoadingProgress();
|
| +}
|
|
|
| - shared_state_.renderer->Initialize(demuxer_, this, done_cb);
|
| +PipelineStatistics PipelineImpl::GetStatistics() const {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + return renderer_wrapper_->GetStatistics();
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::DestroyRenderer() {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +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());
|
|
|
| - // Destroy the renderer outside the lock scope to avoid holding the lock
|
| - // while renderer is being destroyed (in case Renderer destructor is costly).
|
| - std::unique_ptr<Renderer> renderer;
|
| - {
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - renderer.swap(shared_state_.renderer);
|
| - }
|
| + media_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RendererWrapper::SetCdm,
|
| + base::Unretained(renderer_wrapper_.get()), cdm_context,
|
| + media::BindToCurrentLoop(cdm_attached_cb)));
|
| }
|
|
|
| -void PipelineImpl::RendererWrapper::ReportMetadata() {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| +#define RETURN_STRING(state) \
|
| + case state: \
|
| + return #state;
|
|
|
| - 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;
|
| +// static
|
| +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);
|
| }
|
| -
|
| - main_task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::OnMetadata,
|
| - weak_pipeline_, metadata));
|
| + NOTREACHED();
|
| + return "INVALID";
|
| }
|
|
|
| +#undef RETURN_STRING
|
| +
|
| void PipelineImpl::OnError(PipelineStatus error) {
|
| DVLOG(2) << __FUNCTION__;
|
| DCHECK(thread_checker_.CalledOnValidThread());
|
| @@ -1199,38 +1236,4 @@ void PipelineImpl::OnSuspendDone(base::TimeDelta suspend_time) {
|
| base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK);
|
| }
|
|
|
| -PipelineImpl::RendererWrapper::RendererWrapper(
|
| - scoped_refptr<base::SingleThreadTaskRunner> media_task_runner,
|
| - scoped_refptr<MediaLog> media_log)
|
| - : 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) {
|
| - weak_this_ = weak_factory_.GetWeakPtr();
|
| - media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
|
| -}
|
| -
|
| -PipelineImpl::RendererWrapper::~RendererWrapper() {
|
| - DCHECK(media_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(state_ == kCreated || state_ == kStopped);
|
| -}
|
| -
|
| -base::TimeDelta PipelineImpl::RendererWrapper::GetMediaTime() const {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - base::AutoLock auto_lock(shared_state_lock_);
|
| - if (shared_state_.suspend_timestamp != kNoTimestamp())
|
| - return shared_state_.suspend_timestamp;
|
| - return shared_state_.renderer ? shared_state_.renderer->GetMediaTime()
|
| - : base::TimeDelta();
|
| -}
|
| -
|
| } // namespace media
|
|
|