Chromium Code Reviews| Index: media/base/pipeline_impl.cc | 
| diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc | 
| index 532236d14c1b1e85c7ff313cc2c4912147c87822..d7f153d5b009b862ecf71aaf161085fe597f5043 100644 | 
| --- a/media/base/pipeline_impl.cc | 
| +++ b/media/base/pipeline_impl.cc | 
| @@ -349,9 +349,28 @@ bool PipelineImpl::IsPipelineInitializing() { | 
| state_ == kInitVideoRenderer; | 
| } | 
| +bool PipelineImpl::IsPipelineStopped() { | 
| + DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| + return state_ == kStopped || state_ == kError; | 
| +} | 
| + | 
| +void PipelineImpl::FinishInitialization() { | 
| + DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| + // Execute the seek callback, if present. Note that this might be the | 
| + // initial callback passed into Start(). | 
| + if (seek_callback_.get()) { | 
| + seek_callback_->Run(); | 
| + seek_callback_.reset(); | 
| + } | 
| + filter_factory_ = NULL; | 
| +} | 
| + | 
| // static | 
| -bool PipelineImpl::StateTransitionsToStarted(State state) { | 
| - return state == kPausing || state == kSeeking || state == kStarting; | 
| +bool PipelineImpl::TransientState(State state) { | 
| + return state == kPausing || | 
| + state == kSeeking || | 
| + state == kStarting || | 
| + state == kStopping; | 
| } | 
| // static | 
| @@ -363,6 +382,8 @@ PipelineImpl::State PipelineImpl::FindNextState(State current) { | 
| return kStarting; | 
| if (current == kStarting) | 
| return kStarted; | 
| + if (current == kStopping) | 
| + return kStopped; | 
| return current; | 
| } | 
| @@ -371,8 +392,6 @@ void PipelineImpl::SetError(PipelineError error) { | 
| DCHECK(error != PIPELINE_OK) << "PIPELINE_OK isn't an error!"; | 
| LOG(INFO) << "Media pipeline error: " << error; | 
| - AutoLock auto_lock(lock_); | 
| - error_ = error; | 
| message_loop_->PostTask(FROM_HERE, | 
| NewRunnableMethod(this, &PipelineImpl::ErrorChangedTask, error)); | 
| } | 
| @@ -531,7 +550,7 @@ void PipelineImpl::InitializeTask() { | 
| DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| // If we have received the stop or error signal, return immediately. | 
| - if (state_ == kStopped || state_ == kError) | 
| + if (state_ == kStopping || IsPipelineStopped()) | 
| return; | 
| DCHECK(state_ == kCreated || IsPipelineInitializing()); | 
| @@ -591,10 +610,6 @@ void PipelineImpl::InitializeTask() { | 
| return; | 
| } | 
| - // We've successfully created and initialized every filter, so we no longer | 
| - // need the filter factory. | 
| - filter_factory_ = NULL; | 
| - | 
| // Initialization was successful, we are now considered paused, so it's safe | 
| // to set the initial playback rate and volume. | 
| PlaybackRateChangedTask(GetPlaybackRate()); | 
| @@ -610,69 +625,57 @@ void PipelineImpl::InitializeTask() { | 
| } | 
| // This method is called as a result of the client calling Pipeline::Stop() or | 
| -// as the result of an error condition. If there is no error, then set the | 
| -// pipeline's |error_| member to PIPELINE_STOPPING. We stop the filters in the | 
| -// reverse order. | 
| +// as the result of an error condition. | 
| +// We stop the filters in the reverse order. | 
| // | 
| // TODO(scherkus): beware! this can get posted multiple times since we post | 
| // Stop() tasks even if we've already stopped. Perhaps this should no-op for | 
| // additional calls, however most of this logic will be changing. | 
| void PipelineImpl::StopTask(PipelineCallback* stop_callback) { | 
| DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| - stop_callback_.reset(stop_callback); | 
| + PipelineError error = GetError(); | 
| - // If we've already stopped, return immediately. | 
| - if (state_ == kStopped) { | 
| + if (state_ == kStopped || (state_ == kStopping && error == PIPELINE_OK)) { | 
| 
 
scherkus (not reviewing)
2010/06/02 02:13:05
this bit of logic is confusing me...
so if we're
 
 | 
| + // If we are already stopped or stopping normally, return immediately. | 
| + delete stop_callback; | 
| return; | 
| + } else if (state_ == kError || | 
| 
 
scherkus (not reviewing)
2010/06/02 02:13:05
nit: we return, don't need else
 
 | 
| + (state_ == kStopping && error != PIPELINE_OK)) { | 
| + // If we are stopping due to SetError(), stop normally instead of | 
| + // going to error state. | 
| + AutoLock auto_lock(lock_); | 
| + error_ = PIPELINE_OK; | 
| } | 
| - // Carry out setting the error, notifying the client and destroying filters. | 
| - ErrorChangedTask(PIPELINE_STOPPING); | 
| - | 
| - // We no longer need to examine our previous state, set it to stopped. | 
| - state_ = kStopped; | 
| - | 
| - // Reset the pipeline. | 
| - ResetState(); | 
| + stop_callback_.reset(stop_callback); | 
| - // Notify the client that stopping has finished. | 
| - if (stop_callback_.get()) { | 
| - stop_callback_->Run(); | 
| - stop_callback_.reset(); | 
| + if (IsPipelineInitializing()) { | 
| + FinishInitialization(); | 
| } | 
| + | 
| + StartDestroyingFilters(); | 
| } | 
| void PipelineImpl::ErrorChangedTask(PipelineError error) { | 
| DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; | 
| - // Suppress executing additional error logic. | 
| - // TODO(hclam): Remove the condition for kStopped. It is there only because | 
| - // FFmpegDemuxer submits a read error while reading after it is called to | 
| - // stop. After FFmpegDemuxer is cleaned up we should remove this condition | 
| - // and add an extra assert. | 
| - if (state_ == kError || state_ == kStopped) { | 
| + // Suppress executing additional error logic. Note that if we are currently | 
| + // performing a normal stop, then we return immediately and continue the | 
| + // normal stop. | 
| + if (IsPipelineStopped() || state_ == kStopping) { | 
| return; | 
| } | 
| + AutoLock auto_lock(lock_); | 
| + error_ = error; | 
| + | 
| // Notify the client that starting did not complete, if necessary. | 
| - if (IsPipelineInitializing() && seek_callback_.get()) { | 
| - seek_callback_->Run(); | 
| + if (IsPipelineInitializing()) { | 
| + FinishInitialization(); | 
| } | 
| - seek_callback_.reset(); | 
| - filter_factory_ = NULL; | 
| - | 
| - // We no longer need to examine our previous state, set it to stopped. | 
| - state_ = kError; | 
| - // Destroy every filter and reset the pipeline as well. | 
| - DestroyFilters(); | 
| - | 
| - // If our owner has requested to be notified of an error, execute | 
| - // |error_callback_| unless we have a "good" error. | 
| - if (error_callback_.get() && error != PIPELINE_STOPPING) { | 
| - error_callback_->Run(); | 
| - } | 
| + StartDestroyingFilters(); | 
| } | 
| void PipelineImpl::PlaybackRateChangedTask(float playback_rate) { | 
| @@ -790,11 +793,11 @@ void PipelineImpl::FilterStateTransitionTask() { | 
| DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| // No reason transitioning if we've errored or have stopped. | 
| - if (state_ == kError || state_ == kStopped) { | 
| + if (IsPipelineStopped()) { | 
| return; | 
| } | 
| - if (!StateTransitionsToStarted(state_)) { | 
| + if (!TransientState(state_)) { | 
| NOTREACHED() << "Invalid current state: " << state_; | 
| SetError(PIPELINE_ERROR_ABORT); | 
| return; | 
| @@ -811,13 +814,13 @@ void PipelineImpl::FilterStateTransitionTask() { | 
| clock_.SetTime(seek_timestamp_); | 
| } | 
| - if (StateTransitionsToStarted(state_)) { | 
| + if (TransientState(state_)) { | 
| remaining_transitions_ = filters_.size(); | 
| } | 
| } | 
| // Carry out the action for the current state. | 
| - if (StateTransitionsToStarted(state_)) { | 
| + if (TransientState(state_)) { | 
| MediaFilter* filter = filters_[filters_.size() - remaining_transitions_]; | 
| if (state_ == kPausing) { | 
| filter->Pause(NewCallback(this, &PipelineImpl::OnFilterStateTransition)); | 
| @@ -826,16 +829,13 @@ void PipelineImpl::FilterStateTransitionTask() { | 
| NewCallback(this, &PipelineImpl::OnFilterStateTransition)); | 
| } else if (state_ == kStarting) { | 
| filter->Play(NewCallback(this, &PipelineImpl::OnFilterStateTransition)); | 
| + } else if (state_ == kStopping) { | 
| + filter->Stop(NewCallback(this, &PipelineImpl::OnFilterStateTransition)); | 
| } else { | 
| NOTREACHED(); | 
| } | 
| } else if (state_ == kStarted) { | 
| - // Execute the seek callback, if present. Note that this might be the | 
| - // initial callback passed into Start(). | 
| - if (seek_callback_.get()) { | 
| - seek_callback_->Run(); | 
| - seek_callback_.reset(); | 
| - } | 
| + FinishInitialization(); | 
| // Finally, reset our seeking timestamp back to zero. | 
| seek_timestamp_ = base::TimeDelta(); | 
| @@ -848,11 +848,52 @@ void PipelineImpl::FilterStateTransitionTask() { | 
| rendered_mime_types_.end(); | 
| if (!waiting_for_clock_update_) | 
| clock_.Play(); | 
| + } else if (IsPipelineStopped()) { | 
| + FinishDestroyingFiltersTask(); | 
| } else { | 
| NOTREACHED(); | 
| } | 
| } | 
| +void PipelineImpl::FinishDestroyingFiltersTask() { | 
| + DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| + DCHECK(IsPipelineStopped()); | 
| + | 
| + // Stop every running filter thread. | 
| + // | 
| + // TODO(scherkus): can we watchdog this section to detect wedged threads? | 
| + for (FilterThreadVector::iterator iter = filter_threads_.begin(); | 
| + iter != filter_threads_.end(); | 
| + ++iter) { | 
| + (*iter)->Stop(); | 
| + } | 
| + | 
| + // Reset the pipeline, which will decrement a reference to this object. | 
| + // We will get destroyed as soon as the remaining tasks finish executing. | 
| + // To be safe, we'll set our pipeline reference to NULL. | 
| + filters_.clear(); | 
| + filter_types_.clear(); | 
| + STLDeleteElements(&filter_threads_); | 
| + | 
| + if (PIPELINE_OK == GetError()) { | 
| + // Destroying filters due to Stop(). | 
| + ResetState(); | 
| + | 
| + // Notify the client that stopping has finished. | 
| + if (stop_callback_.get()) { | 
| + stop_callback_->Run(); | 
| + stop_callback_.reset(); | 
| + } | 
| + } else { | 
| + // Destroying filters due to SetError(). | 
| + state_ = kError; | 
| + // If our owner has requested to be notified of an error. | 
| + if (error_callback_.get()) { | 
| + error_callback_->Run(); | 
| + } | 
| + } | 
| +} | 
| + | 
| template <class Filter, class Source> | 
| void PipelineImpl::CreateFilter(FilterFactory* filter_factory, | 
| Source source, | 
| @@ -965,54 +1006,24 @@ void PipelineImpl::GetFilter(scoped_refptr<Filter>* filter_out) const { | 
| } | 
| } | 
| -void PipelineImpl::DestroyFilters() { | 
| - // Stop every filter. | 
| - for (FilterVector::iterator iter = filters_.begin(); | 
| - iter != filters_.end(); | 
| - ++iter) { | 
| - (*iter)->Stop(); | 
| - } | 
| - | 
| - // Crude blocking counter implementation. | 
| - Lock lock; | 
| - ConditionVariable wait_for_zero(&lock); | 
| - int count = filter_threads_.size(); | 
| - | 
| - // Post a task to every filter's thread to ensure that they've completed their | 
| - // stopping logic before stopping the threads themselves. | 
| - // | 
| - // TODO(scherkus): again, Stop() should either be synchronous or we should | 
| - // receive a signal from filters that they have indeed stopped. | 
| - for (FilterThreadVector::iterator iter = filter_threads_.begin(); | 
| - iter != filter_threads_.end(); | 
| - ++iter) { | 
| - (*iter)->message_loop()->PostTask(FROM_HERE, | 
| - NewRunnableFunction(&DecrementCounter, &lock, &wait_for_zero, &count)); | 
| - } | 
| +void PipelineImpl::StartDestroyingFilters() { | 
| + DCHECK_EQ(MessageLoop::current(), message_loop_); | 
| + DCHECK_NE(kStopped, state_); | 
| - // Wait on our "blocking counter". | 
| - { | 
| - AutoLock auto_lock(lock); | 
| - while (count > 0) { | 
| - wait_for_zero.Wait(); | 
| - } | 
| + if (state_ == kStopping) { | 
| + return; // Do not call Stop() on filters twice. | 
| 
 
scherkus (not reviewing)
2010/06/02 02:13:05
nit: two spaces between ; and //
 
 | 
| } | 
| - // Stop every running filter thread. | 
| - // | 
| - // TODO(scherkus): can we watchdog this section to detect wedged threads? | 
| - for (FilterThreadVector::iterator iter = filter_threads_.begin(); | 
| - iter != filter_threads_.end(); | 
| - ++iter) { | 
| - (*iter)->Stop(); | 
| + remaining_transitions_ = filters_.size(); | 
| + if (remaining_transitions_ > 0) { | 
| + state_ = kStopping; | 
| + filters_.front()->Stop(NewCallback( | 
| + this, &PipelineImpl::OnFilterStateTransition)); | 
| + } else { | 
| + state_ = kStopped; | 
| + message_loop_->PostTask(FROM_HERE, | 
| + NewRunnableMethod(this, &PipelineImpl::FinishDestroyingFiltersTask)); | 
| } | 
| - | 
| - // Reset the pipeline, which will decrement a reference to this object. | 
| - // We will get destroyed as soon as the remaining tasks finish executing. | 
| - // To be safe, we'll set our pipeline reference to NULL. | 
| - filters_.clear(); | 
| - filter_types_.clear(); | 
| - STLDeleteElements(&filter_threads_); | 
| } | 
| } // namespace media |