Chromium Code Reviews| Index: media/base/pipeline.cc |
| diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc |
| index 3cfe296155fd8a9d1b05458e6a3adb7cbe278d05..f1b1c47b3907e3751c4384a4c79c2d973f7ec5aa 100644 |
| --- a/media/base/pipeline.cc |
| +++ b/media/base/pipeline.cc |
| @@ -48,6 +48,8 @@ Pipeline::Pipeline( |
| audio_ended_(false), |
| video_ended_(false), |
| text_ended_(false), |
| + audio_buffering_state_(BUFFERING_HAVE_NOTHING), |
| + video_buffering_state_(BUFFERING_HAVE_NOTHING), |
| demuxer_(NULL), |
| creation_time_(default_tick_clock_.NowTicks()) { |
| media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); |
| @@ -188,7 +190,7 @@ void Pipeline::SetErrorForTesting(PipelineStatus status) { |
| } |
| void Pipeline::SetState(State next_state) { |
| - if (state_ != kStarted && next_state == kStarted && |
| + if (state_ != kPlaying && next_state == kPlaying && |
| !creation_time_.is_null()) { |
| UMA_HISTOGRAM_TIMES("Media.TimeToPipelineStarted", |
| default_tick_clock_.NowTicks() - creation_time_); |
| @@ -211,8 +213,7 @@ const char* Pipeline::GetStateString(State state) { |
| RETURN_STRING(kInitVideoRenderer); |
| RETURN_STRING(kInitPrerolling); |
| RETURN_STRING(kSeeking); |
| - RETURN_STRING(kStarting); |
| - RETURN_STRING(kStarted); |
| + RETURN_STRING(kPlaying); |
| RETURN_STRING(kStopping); |
| RETURN_STRING(kStopped); |
| } |
| @@ -249,15 +250,12 @@ Pipeline::State Pipeline::GetNextState() const { |
| return kInitPrerolling; |
| case kInitPrerolling: |
| - return kStarting; |
| + return kPlaying; |
| case kSeeking: |
| - return kStarting; |
| + return kPlaying; |
| - case kStarting: |
| - return kStarted; |
| - |
| - case kStarted: |
| + case kPlaying: |
| case kStopping: |
| case kStopped: |
| break; |
| @@ -364,11 +362,9 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { |
| // Guard against accidentally clearing |pending_callbacks_| for states that |
| // use it as well as states that should not be using it. |
| - // |
| - // TODO(scherkus): Make every state transition use |pending_callbacks_|. |
| DCHECK_EQ(pending_callbacks_.get() != NULL, |
| - (state_ == kInitPrerolling || state_ == kStarting || |
| - state_ == kSeeking)); |
| + (state_ == kInitPrerolling || state_ == kSeeking)); |
| + |
| pending_callbacks_.reset(); |
| PipelineStatusCB done_cb = base::Bind( |
| @@ -412,29 +408,24 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { |
| return DoInitialPreroll(done_cb); |
| - case kStarting: |
| - return DoPlay(done_cb); |
| - |
| - case kStarted: |
| - { |
| - base::AutoLock l(lock_); |
| - // We use audio stream to update the clock. So if there is such a |
| - // stream, we pause the clock until we receive a valid timestamp. |
| - waiting_for_clock_update_ = true; |
| - if (!audio_renderer_) { |
| - clock_->SetMaxTime(clock_->Duration()); |
| - StartClockIfWaitingForTimeUpdate_Locked(); |
| - } |
| - } |
| - |
| - DCHECK(!seek_cb_.is_null()); |
| - DCHECK_EQ(status_, PIPELINE_OK); |
| - |
| - // Fire canplaythrough immediately after playback begins because of |
| - // crbug.com/106480. |
| - // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. |
| - preroll_completed_cb_.Run(); |
| - return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| + case kPlaying: |
| + PlaybackRateChangedTask(GetPlaybackRate()); |
| + VolumeChangedTask(GetVolume()); |
| + |
| + // We enter this state from either kInitPrerolling or kSeeking. As of now |
| + // both those states call Preroll(), which means by time we enter this |
| + // state we've already buffered enough data. Forcefully update the |
| + // buffering state, which start the clock and renderers and transition |
| + // into kPlaying state. |
| + // |
| + // TODO(scherkus): Remove after renderers are taught to fire buffering |
| + // state callbacks http://crbug.com/144683 |
| + DCHECK(WaitingForEnoughData()); |
| + if (audio_renderer_) |
| + AudioBufferingStateChanged(BUFFERING_HAVE_ENOUGH); |
| + if (video_renderer_) |
| + VideoBufferingStateChanged(BUFFERING_HAVE_ENOUGH); |
| + return; |
| case kStopping: |
| case kStopped: |
| @@ -469,6 +460,16 @@ void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) { |
| bound_fns.Push(base::Bind( |
| &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()), |
| seek_timestamp)); |
| + |
| + // TODO(scherkus): Remove after VideoRenderer is taught to fire buffering |
| + // state callbacks http://crbug.com/144683 |
| + bound_fns.Push(base::Bind(&VideoRenderer::Play, |
| + base::Unretained(video_renderer_.get()))); |
| + } |
| + |
| + if (text_renderer_) { |
| + bound_fns.Push(base::Bind( |
| + &TextRenderer::Play, base::Unretained(text_renderer_.get()))); |
| } |
| pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| @@ -495,10 +496,22 @@ void Pipeline::DoSeek( |
| if (audio_renderer_) { |
| bound_fns.Push(base::Bind( |
| &AudioRenderer::Flush, base::Unretained(audio_renderer_.get()))); |
| + |
| + // TODO(scherkus): Remove after AudioRenderer is taught to fire buffering |
| + // state callbacks http://crbug.com/144683 |
| + bound_fns.Push(base::Bind(&Pipeline::AudioBufferingStateChanged, |
| + base::Unretained(this), |
| + BUFFERING_HAVE_NOTHING)); |
| } |
| if (video_renderer_) { |
| bound_fns.Push(base::Bind( |
| &VideoRenderer::Flush, base::Unretained(video_renderer_.get()))); |
| + |
| + // TODO(scherkus): Remove after VideoRenderer is taught to fire buffering |
| + // state callbacks http://crbug.com/144683 |
| + bound_fns.Push(base::Bind(&Pipeline::VideoBufferingStateChanged, |
| + base::Unretained(this), |
| + BUFFERING_HAVE_NOTHING)); |
| } |
| if (text_renderer_) { |
| bound_fns.Push(base::Bind( |
| @@ -520,27 +533,11 @@ void Pipeline::DoSeek( |
| bound_fns.Push(base::Bind( |
| &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()), |
| seek_timestamp)); |
| - } |
| - pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| -} |
| - |
| -void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { |
| - DCHECK(task_runner_->BelongsToCurrentThread()); |
| - DCHECK(!pending_callbacks_.get()); |
| - SerialRunner::Queue bound_fns; |
| - |
| - PlaybackRateChangedTask(GetPlaybackRate()); |
| - VolumeChangedTask(GetVolume()); |
| - |
| - if (audio_renderer_) { |
| - bound_fns.Push(base::Bind( |
| - &AudioRenderer::Play, base::Unretained(audio_renderer_.get()))); |
| - } |
| - |
| - if (video_renderer_) { |
| - bound_fns.Push(base::Bind( |
| - &VideoRenderer::Play, base::Unretained(video_renderer_.get()))); |
| + // TODO(scherkus): Remove after renderers are taught to fire buffering |
| + // state callbacks http://crbug.com/144683 |
| + bound_fns.Push(base::Bind(&VideoRenderer::Play, |
| + base::Unretained(video_renderer_.get()))); |
| } |
| if (text_renderer_) { |
| @@ -706,7 +703,7 @@ void Pipeline::PlaybackRateChangedTask(float playback_rate) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| // Playback rate changes are only carried out while playing. |
| - if (state_ != kStarting && state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| { |
| @@ -724,7 +721,7 @@ void Pipeline::VolumeChangedTask(float volume) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| // Volume changes are only carried out while playing. |
| - if (state_ != kStarting && state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| if (audio_renderer_) |
| @@ -736,7 +733,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { |
| DCHECK(stop_cb_.is_null()); |
| // Suppress seeking if we're not fully started. |
| - if (state_ != kStarted) { |
| + if (state_ != kPlaying) { |
| DCHECK(state_ == kStopping || state_ == kStopped) |
| << "Receive extra seek in unexpected state: " << state_; |
| @@ -770,7 +767,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { |
| void Pipeline::DoAudioRendererEnded() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| - if (state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| DCHECK(!audio_ended_); |
| @@ -789,7 +786,7 @@ void Pipeline::DoAudioRendererEnded() { |
| void Pipeline::DoVideoRendererEnded() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| - if (state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| DCHECK(!video_ended_); |
| @@ -801,7 +798,7 @@ void Pipeline::DoVideoRendererEnded() { |
| void Pipeline::DoTextRendererEnded() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| - if (state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| DCHECK(!text_ended_); |
| @@ -888,13 +885,93 @@ void Pipeline::OnAudioUnderflow() { |
| return; |
| } |
| - if (state_ != kStarted) |
| + if (state_ != kPlaying) |
| return; |
| if (audio_renderer_) |
| audio_renderer_->ResumeAfterUnderflow(); |
| } |
| +void Pipeline::AudioBufferingStateChanged(BufferingState buffering_state) { |
|
acolwell GONE FROM CHROMIUM
2014/05/12 13:46:54
nit: You could avoid duplicate code below if you b
scherkus (not reviewing)
2014/05/12 15:22:03
Done.
|
| + DCHECK(task_runner_->BelongsToCurrentThread()); |
| + bool was_waiting_for_enough_data = WaitingForEnoughData(); |
| + audio_buffering_state_ = buffering_state; |
| + |
| + // Audio renderer underflowed. |
| + if (!was_waiting_for_enough_data && WaitingForEnoughData()) { |
| + StartWaitingForEnoughData(); |
| + return; |
| + } |
| + |
| + // Audio renderer prerolled. |
| + if (was_waiting_for_enough_data && !WaitingForEnoughData()) { |
| + StartPlayback(); |
| + return; |
| + } |
| +} |
| + |
| +void Pipeline::VideoBufferingStateChanged(BufferingState buffering_state) { |
| + DCHECK(task_runner_->BelongsToCurrentThread()); |
| + bool was_waiting_for_enough_data = WaitingForEnoughData(); |
| + video_buffering_state_ = buffering_state; |
| + |
| + // Video renderer underflowed. |
| + if (!was_waiting_for_enough_data && WaitingForEnoughData()) { |
| + StartWaitingForEnoughData(); |
| + return; |
| + } |
| + |
| + // Video renderer prerolled. |
| + if (was_waiting_for_enough_data && !WaitingForEnoughData()) { |
| + StartPlayback(); |
| + return; |
| + } |
| +} |
| + |
| +bool Pipeline::WaitingForEnoughData() const { |
| + DCHECK(task_runner_->BelongsToCurrentThread()); |
| + if (state_ != kPlaying) |
| + return false; |
| + if (audio_renderer_ && audio_buffering_state_ != BUFFERING_HAVE_ENOUGH) |
| + return true; |
| + if (video_renderer_ && video_buffering_state_ != BUFFERING_HAVE_ENOUGH) |
| + return true; |
| + return false; |
| +} |
| + |
| +void Pipeline::StartWaitingForEnoughData() { |
| + DCHECK_EQ(state_, kPlaying); |
| + DCHECK(WaitingForEnoughData()); |
| + |
| + if (audio_renderer_) |
| + audio_renderer_->Pause(); |
| + |
| + base::AutoLock auto_lock(lock_); |
| + clock_->Pause(); |
| +} |
| + |
| +void Pipeline::StartPlayback() { |
| + DCHECK_EQ(state_, kPlaying); |
| + DCHECK(!WaitingForEnoughData()); |
| + |
| + if (audio_renderer_) { |
| + audio_renderer_->Play(); |
| + |
| + base::AutoLock auto_lock(lock_); |
| + // We use video stream to update the clock. So if there is such a |
|
acolwell GONE FROM CHROMIUM
2014/05/12 13:46:54
nit: s/video/audio?
scherkus (not reviewing)
2014/05/12 15:22:03
Done.
|
| + // stream, we pause the clock until we receive a valid timestamp. |
| + waiting_for_clock_update_ = true; |
| + } else { |
| + base::AutoLock auto_lock(lock_); |
| + clock_->SetMaxTime(clock_->Duration()); |
| + clock_->Play(); |
| + } |
| + |
| + preroll_completed_cb_.Run(); |
| + if (!seek_cb_.is_null()) |
| + base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
| +} |
| + |
| void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { |
| lock_.AssertAcquired(); |
| if (!waiting_for_clock_update_) |