Index: media/base/pipeline.cc |
diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc |
index 3cfe296155fd8a9d1b05458e6a3adb7cbe278d05..841a49bab5bf88796ee2b0fa824daa4c4afde2c1 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_) |
+ BufferingStateChanged(&audio_buffering_state_, BUFFERING_HAVE_ENOUGH); |
+ if (video_renderer_) |
+ BufferingStateChanged(&video_buffering_state_, 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,24 @@ 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::BufferingStateChanged, |
+ base::Unretained(this), |
+ &audio_buffering_state_, |
+ 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::BufferingStateChanged, |
+ base::Unretained(this), |
+ &video_buffering_state_, |
+ BUFFERING_HAVE_NOTHING)); |
} |
if (text_renderer_) { |
bound_fns.Push(base::Bind( |
@@ -520,27 +535,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 +705,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 +723,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 +735,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 +769,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 +788,7 @@ void Pipeline::DoAudioRendererEnded() { |
void Pipeline::DoVideoRendererEnded() { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- if (state_ != kStarted) |
+ if (state_ != kPlaying) |
return; |
DCHECK(!video_ended_); |
@@ -801,7 +800,7 @@ void Pipeline::DoVideoRendererEnded() { |
void Pipeline::DoTextRendererEnded() { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- if (state_ != kStarted) |
+ if (state_ != kPlaying) |
return; |
DCHECK(!text_ended_); |
@@ -888,13 +887,79 @@ void Pipeline::OnAudioUnderflow() { |
return; |
} |
- if (state_ != kStarted) |
+ if (state_ != kPlaying) |
return; |
if (audio_renderer_) |
audio_renderer_->ResumeAfterUnderflow(); |
} |
+void Pipeline::BufferingStateChanged(BufferingState* buffering_state, |
+ BufferingState new_buffering_state) { |
+ DVLOG(1) << __FUNCTION__ << "(" << *buffering_state << ", " |
+ << " " << new_buffering_state << ") " |
+ << (buffering_state == &audio_buffering_state_ ? "audio" : "video"); |
+ DCHECK(task_runner_->BelongsToCurrentThread()); |
+ bool was_waiting_for_enough_data = WaitingForEnoughData(); |
+ *buffering_state = new_buffering_state; |
+ |
+ // Renderer underflowed. |
+ if (!was_waiting_for_enough_data && WaitingForEnoughData()) { |
+ StartWaitingForEnoughData(); |
+ return; |
+ } |
+ |
+ // 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 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; |
+ } 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_) |