| Index: media/filters/pipeline_controller.cc | 
| diff --git a/media/filters/pipeline_controller.cc b/media/filters/pipeline_controller.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..7692c207f280b3db3d3282cde4ff4b6e859430a5 | 
| --- /dev/null | 
| +++ b/media/filters/pipeline_controller.cc | 
| @@ -0,0 +1,223 @@ | 
| +// Copyright 2016 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "media/filters/pipeline_controller.h" | 
| + | 
| +#include "base/bind.h" | 
| +#include "base/bind_helpers.h" | 
| +#include "build/build_config.h" | 
| +#include "media/filters/chunk_demuxer.h" | 
| + | 
| +namespace media { | 
| + | 
| +PipelineController::PipelineController( | 
| +    Pipeline* pipeline, | 
| +    const RendererFactoryCB& renderer_factory_cb, | 
| +    const SeekedCB& seeked_cb, | 
| +    const SuspendedCB& suspended_cb, | 
| +    const PipelineStatusCB& error_cb) | 
| +    : pipeline_(pipeline), | 
| +      renderer_factory_cb_(renderer_factory_cb), | 
| +      seeked_cb_(seeked_cb), | 
| +      suspended_cb_(suspended_cb), | 
| +      error_cb_(error_cb) { | 
| +  DCHECK(pipeline_); | 
| +  DCHECK(!renderer_factory_cb_.is_null()); | 
| +  DCHECK(!seeked_cb_.is_null()); | 
| +  DCHECK(!suspended_cb_.is_null()); | 
| +  DCHECK(!error_cb_.is_null()); | 
| +} | 
| + | 
| +PipelineController::~PipelineController() {} | 
| + | 
| +// TODO(sandersd): Move ChunkDemuxer API to Demuxer so that Pipeline can | 
| +// implement all of this. | 
| +// TODO(sandersd): If there is a pending suspend, don't call pipeline_.Start() | 
| +// until Resume(). | 
| +void PipelineController::Start( | 
| +    ChunkDemuxer* chunk_demuxer, | 
| +    Demuxer* demuxer, | 
| +    const base::Closure& ended_cb, | 
| +    const PipelineMetadataCB& metadata_cb, | 
| +    const BufferingStateCB& buffering_state_cb, | 
| +    const base::Closure& duration_change_cb, | 
| +    const AddTextTrackCB& add_text_track_cb, | 
| +    const base::Closure& waiting_for_decryption_key_cb) { | 
| +  DCHECK(state_ == State::CREATED); | 
| + | 
| +  // Once the pipeline is started, we want to call the seeked callback but | 
| +  // without a time update. | 
| +  pending_seeked_ = true; | 
| +  state_ = State::STARTING; | 
| + | 
| +  chunk_demuxer_ = chunk_demuxer; | 
| +  pipeline_->Start(demuxer, renderer_factory_cb_.Run(), ended_cb, error_cb_, | 
| +                   base::Bind(&PipelineController::OnPipelineStatus, | 
| +                              base::Unretained(this), State::PLAYING), | 
| +                   metadata_cb, buffering_state_cb, duration_change_cb, | 
| +                   add_text_track_cb, waiting_for_decryption_key_cb); | 
| +} | 
| + | 
| +void PipelineController::Seek(base::TimeDelta time, bool time_updated) { | 
| +  // It would be slightly more clear to set this in Dispatch(), but we want to | 
| +  // be sure it gets updated even if the seek is elided. | 
| +  if (time_updated) | 
| +    pending_time_update_ = true; | 
| +  pending_seeked_ = true; | 
| + | 
| +  // If we are already seeking to |time|, just clear any pending seek. This does | 
| +  // not apply to MSE because the underlying buffer could have been changed | 
| +  // between the seek calls. | 
| +  // TODO(sandersd): The underlying buffer could also have changed for | 
| +  // File objects, but WMPI is also broken in that case (because it caches). | 
| +  if ((state_ == State::SEEKING || state_ == State::RESUMING) && | 
| +      seek_time_ == time && !chunk_demuxer_) { | 
| +    pending_seek_ = false; | 
| +    return; | 
| +  } | 
| + | 
| +  pending_seek_time_ = time; | 
| +  pending_seek_ = true; | 
| +  Dispatch(); | 
| +} | 
| + | 
| +// TODO(sandersd): It may be easier to use this interface if |suspended_cb_| is | 
| +// executed when Suspend() is called while already suspended. | 
| +void PipelineController::Suspend() { | 
| +  pending_resume_ = false; | 
| +  if (state_ != State::SUSPENDING && state_ != State::SUSPENDED) { | 
| +    pending_suspend_ = true; | 
| +    Dispatch(); | 
| +  } | 
| +} | 
| + | 
| +void PipelineController::Resume() { | 
| +  pending_suspend_ = false; | 
| +  if (state_ == State::SUSPENDING || state_ == State::SUSPENDED) { | 
| +    pending_resume_ = true; | 
| +    Dispatch(); | 
| +  } | 
| +} | 
| + | 
| +bool PipelineController::IsStable() { | 
| +  return (state_ == State::PLAYING); | 
| +} | 
| + | 
| +bool PipelineController::IsSuspended() { | 
| +  return (state_ == State::SUSPENDED); | 
| +} | 
| + | 
| +void PipelineController::OnPipelineStatus(State state, | 
| +                                          PipelineStatus pipeline_status) { | 
| +  if (pipeline_status != PIPELINE_OK) { | 
| +    error_cb_.Run(pipeline_status); | 
| +    return; | 
| +  } | 
| + | 
| +  state_ = state; | 
| + | 
| +  // Start(), Seek(), or Resume() completed; we can be sure that | 
| +  // |chunk_demuxer_| got the seek it was waiting for. | 
| +  if (state == State::PLAYING) | 
| +    waiting_for_seek_ = false; | 
| + | 
| +  // Sadly we need to signal this state change via a possibly reentrant | 
| +  // callback. Keep in mind that the state may change inside the callback! | 
| +  // (In particular, it must be safe to call Dispatch() twice in a row here.) | 
| +  if (state == State::SUSPENDED) | 
| +    suspended_cb_.Run(); | 
| + | 
| +  Dispatch(); | 
| +} | 
| + | 
| +// Note: Dispatch() may be called re-entrantly (by callbacks internally) or | 
| +// twice in a row (by OnPipelineStatus()). | 
| +void PipelineController::Dispatch() { | 
| +  // Suspend/resume transitions take priority because seeks before a suspend | 
| +  // are wasted, and seeks after can be merged into the resume operation. | 
| +  if (pending_suspend_ && state_ == State::PLAYING) { | 
| +    pending_suspend_ = false; | 
| +    state_ = State::SUSPENDING; | 
| +    pipeline_->Suspend(base::Bind(&PipelineController::OnPipelineStatus, | 
| +                                  base::Unretained(this), State::SUSPENDED)); | 
| +    return; | 
| +  } | 
| + | 
| +  if (pending_resume_ && state_ == State::SUSPENDED) { | 
| +    // If there is a pending seek, resume to that time instead. | 
| +    if (pending_seek_) { | 
| +      seek_time_ = pending_seek_time_; | 
| +      pending_seek_ = false; | 
| +    } else { | 
| +      seek_time_ = pipeline_->GetMediaTime(); | 
| +    } | 
| + | 
| +    // Tell |chunk_demuxer_| to expect our resume. | 
| +    if (chunk_demuxer_) { | 
| +      DCHECK(!waiting_for_seek_); | 
| +      chunk_demuxer_->StartWaitingForSeek(seek_time_); | 
| +      waiting_for_seek_ = true; | 
| +    } | 
| + | 
| +    pending_resume_ = false; | 
| +    state_ = State::RESUMING; | 
| +    pipeline_->Resume(renderer_factory_cb_.Run(), seek_time_, | 
| +                      base::Bind(&PipelineController::OnPipelineStatus, | 
| +                                 base::Unretained(this), State::PLAYING)); | 
| +    return; | 
| +  } | 
| + | 
| +  // |chunk_demuxer_| supports aborting seeks. Make use of that when we have | 
| +  // other pending operations. | 
| +  if ((pending_seek_ || pending_suspend_) && waiting_for_seek_) { | 
| +    CHECK(chunk_demuxer_); | 
| + | 
| +    // If there is no pending seek, return the current seek to pending status. | 
| +    if (!pending_seek_) { | 
| +      pending_seek_time_ = seek_time_; | 
| +      pending_seek_ = true; | 
| +    } | 
| + | 
| +    // CancelPendingSeek() may be reentrant, so update state first and return | 
| +    // immediately. | 
| +    waiting_for_seek_ = false; | 
| +    chunk_demuxer_->CancelPendingSeek(pending_seek_time_); | 
| +    return; | 
| +  } | 
| + | 
| +  // Ordinary seeking. | 
| +  if (pending_seek_ && state_ == State::PLAYING) { | 
| +    seek_time_ = pending_seek_time_; | 
| + | 
| +    // Tell |chunk_demuxer_| to expect our seek. | 
| +    if (chunk_demuxer_) { | 
| +      DCHECK(!waiting_for_seek_); | 
| +      waiting_for_seek_ = true; | 
| +      chunk_demuxer_->StartWaitingForSeek(seek_time_); | 
| +    } | 
| + | 
| +    pending_seek_ = false; | 
| +    state_ = State::SEEKING; | 
| +    pipeline_->Seek(seek_time_, | 
| +                    base::Bind(&PipelineController::OnPipelineStatus, | 
| +                               base::Unretained(this), State::PLAYING)); | 
| +    return; | 
| +  } | 
| + | 
| +  // If |state_| is PLAYING and we didn't trigger an operation above then we | 
| +  // are in a stable state. If there is a seeked callback pending, emit it. | 
| +  if (state_ == State::PLAYING) { | 
| +    if (pending_seeked_) { | 
| +      // |seeked_cb_| may be reentrant, so update state first and return | 
| +      // immediately. | 
| +      pending_seeked_ = false; | 
| +      bool was_pending_time_update = pending_time_update_; | 
| +      pending_time_update_ = false; | 
| +      seeked_cb_.Run(was_pending_time_update); | 
| +      return; | 
| +    } | 
| +  } | 
| +} | 
| + | 
| +}  // namespace media | 
|  |