Index: media/filters/video_renderer_impl.cc |
diff --git a/media/filters/video_renderer_impl.cc b/media/filters/video_renderer_impl.cc |
index 7d99dbf14a4bbfda6a7b5c68b8e253ddb46dd4f8..a3589accfc796205eca59d4a20cade9c000c240c 100644 |
--- a/media/filters/video_renderer_impl.cc |
+++ b/media/filters/video_renderer_impl.cc |
@@ -5,74 +5,73 @@ |
#include "media/filters/video_renderer_impl.h" |
#include "base/bind.h" |
-#include "base/callback.h" |
#include "base/callback_helpers.h" |
-#include "base/debug/trace_event.h" |
-#include "base/location.h" |
#include "base/single_thread_task_runner.h" |
-#include "base/threading/platform_thread.h" |
-#include "media/base/buffers.h" |
+#include "base/time/default_tick_clock.h" |
#include "media/base/limits.h" |
-#include "media/base/pipeline.h" |
#include "media/base/video_frame.h" |
namespace media { |
VideoRendererImpl::VideoRendererImpl( |
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
+ scoped_ptr<VideoFrameScheduler> scheduler, |
ScopedVector<VideoDecoder> decoders, |
- const SetDecryptorReadyCB& set_decryptor_ready_cb, |
- const PaintCB& paint_cb, |
- bool drop_frames) |
+ const SetDecryptorReadyCB& set_decryptor_ready_cb) |
: task_runner_(task_runner), |
+ scheduler_(scheduler.Pass()), |
+ tick_clock_(new base::DefaultTickClock()), |
video_frame_stream_(task_runner, decoders.Pass(), set_decryptor_ready_cb), |
+ next_scheduled_frame_id_(0), |
received_end_of_stream_(false), |
- frame_available_(&lock_), |
state_(kUninitialized), |
- thread_(), |
pending_read_(false), |
- drop_frames_(drop_frames), |
playback_rate_(0), |
- paint_cb_(paint_cb), |
- last_timestamp_(kNoTimestamp()), |
frames_decoded_(0), |
frames_dropped_(0), |
weak_factory_(this) { |
- DCHECK(!paint_cb_.is_null()); |
} |
VideoRendererImpl::~VideoRendererImpl() { |
- base::AutoLock auto_lock(lock_); |
CHECK(state_ == kStopped || state_ == kUninitialized) << state_; |
- CHECK(thread_.is_null()); |
} |
void VideoRendererImpl::Play(const base::Closure& callback) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
DCHECK_EQ(kPrerolled, state_); |
state_ = kPlaying; |
+ |
+ ScheduleAllFrames(); |
+ AttemptRead(); |
+ |
+ // Changing from non-playing to playing state can trigger ended. |
+ if (ShouldTransitionToEnded()) |
+ TransitionToEnded(); |
+ |
callback.Run(); |
} |
void VideoRendererImpl::Pause(const base::Closure& callback) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
DCHECK(state_ != kUninitialized || state_ == kError); |
+ |
state_ = kPaused; |
+ ResetScheduler(); |
+ |
callback.Run(); |
} |
void VideoRendererImpl::Flush(const base::Closure& callback) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
DCHECK_EQ(state_, kPaused); |
+ DCHECK_EQ(scheduled_frames_.size(), 0u); |
flush_cb_ = callback; |
state_ = kFlushing; |
// This is necessary if the |video_frame_stream_| has already seen an end of |
// stream and needs to drain it before flushing it. |
- ready_frames_.clear(); |
+ unscheduled_frames_.clear(); |
+ |
received_end_of_stream_ = false; |
video_frame_stream_.Reset( |
base::Bind(&VideoRendererImpl::OnVideoFrameStreamResetDone, |
@@ -81,7 +80,6 @@ void VideoRendererImpl::Flush(const base::Closure& callback) { |
void VideoRendererImpl::Stop(const base::Closure& callback) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
if (state_ == kUninitialized || state_ == kStopped) { |
callback.Run(); |
return; |
@@ -94,44 +92,40 @@ void VideoRendererImpl::Stop(const base::Closure& callback) { |
statistics_cb_.Reset(); |
max_time_cb_.Reset(); |
- DoStopOrError_Locked(); |
- |
- // Clean up our thread if present. |
- base::PlatformThreadHandle thread_to_join = base::PlatformThreadHandle(); |
- if (!thread_.is_null()) { |
- // Signal the thread since it's possible to get stopped with the video |
- // thread waiting for a read to complete. |
- frame_available_.Signal(); |
- std::swap(thread_, thread_to_join); |
- } |
- if (!thread_to_join.is_null()) { |
- base::AutoUnlock auto_unlock(lock_); |
- base::PlatformThread::Join(thread_to_join); |
- } |
+ unscheduled_frames_.clear(); |
+ scheduled_frames_.clear(); |
+ scheduler_->Reset(); |
video_frame_stream_.Stop(callback); |
} |
void VideoRendererImpl::SetPlaybackRate(float playback_rate) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
+ if (playback_rate_ == playback_rate) |
+ return; |
+ |
playback_rate_ = playback_rate; |
+ ResetScheduler(); |
+ |
+ if (state_ == kPlaying) |
+ ScheduleAllFrames(); |
} |
void VideoRendererImpl::Preroll(base::TimeDelta time, |
const PipelineStatusCB& cb) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
DCHECK(!cb.is_null()); |
DCHECK(preroll_cb_.is_null()); |
- DCHECK(state_ == kFlushed || state_== kPaused) << "state_ " << state_; |
+ DCHECK(state_ == kFlushed || state_ == kPaused) << "state_ " << state_; |
if (state_ == kFlushed) { |
DCHECK(time != kNoTimestamp()); |
DCHECK(!pending_read_); |
- DCHECK(ready_frames_.empty()); |
+ DCHECK(unscheduled_frames_.empty()); |
+ DCHECK(scheduled_frames_.empty()); |
} else { |
+ // Prerolling without a timestamp is used for rebuffering. |
DCHECK(time == kNoTimestamp()); |
} |
@@ -139,12 +133,12 @@ void VideoRendererImpl::Preroll(base::TimeDelta time, |
preroll_cb_ = cb; |
preroll_timestamp_ = time; |
- if (ShouldTransitionToPrerolled_Locked()) { |
- TransitionToPrerolled_Locked(); |
+ if (ShouldTransitionToPrerolled()) { |
+ TransitionToPrerolled(); |
return; |
} |
- AttemptRead_Locked(); |
+ AttemptRead(); |
} |
void VideoRendererImpl::Initialize(DemuxerStream* stream, |
@@ -156,7 +150,6 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream, |
const TimeDeltaCB& get_time_cb, |
const TimeDeltaCB& get_duration_cb) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
DCHECK(stream); |
DCHECK_EQ(stream->type(), DemuxerStream::VIDEO); |
DCHECK(!init_cb.is_null()); |
@@ -185,7 +178,6 @@ void VideoRendererImpl::Initialize(DemuxerStream* stream, |
void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- base::AutoLock auto_lock(lock_); |
if (state_ == kStopped) |
return; |
@@ -198,143 +190,14 @@ void VideoRendererImpl::OnVideoFrameStreamInitialized(bool success) { |
return; |
} |
- // We're all good! Consider ourselves flushed. (ThreadMain() should never |
- // see us in the kUninitialized state). |
- // Since we had an initial Preroll(), we consider ourself flushed, because we |
- // have not populated any buffers yet. |
+ // Consider ourselves flushed as no frames have been received yet. |
state_ = kFlushed; |
- // Create our video thread. |
- if (!base::PlatformThread::Create(0, this, &thread_)) { |
- NOTREACHED() << "Video thread creation failed"; |
- state_ = kError; |
- base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); |
- return; |
- } |
- |
-#if defined(OS_WIN) |
- // Bump up our priority so our sleeping is more accurate. |
- // TODO(scherkus): find out if this is necessary, but it seems to help. |
- ::SetThreadPriority(thread_.platform_handle(), THREAD_PRIORITY_ABOVE_NORMAL); |
-#endif // defined(OS_WIN) |
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); |
} |
-// PlatformThread::Delegate implementation. |
-void VideoRendererImpl::ThreadMain() { |
- base::PlatformThread::SetName("CrVideoRenderer"); |
- |
- // The number of milliseconds to idle when we do not have anything to do. |
- // Nothing special about the value, other than we're being more OS-friendly |
- // than sleeping for 1 millisecond. |
- // |
- // TODO(scherkus): switch to pure event-driven frame timing instead of this |
- // kIdleTimeDelta business http://crbug.com/106874 |
- const base::TimeDelta kIdleTimeDelta = |
- base::TimeDelta::FromMilliseconds(10); |
- |
- for (;;) { |
- base::AutoLock auto_lock(lock_); |
- |
- // Thread exit condition. |
- if (state_ == kStopped) |
- return; |
- |
- // Remain idle as long as we're not playing. |
- if (state_ != kPlaying || playback_rate_ == 0) { |
- UpdateStatsAndWait_Locked(kIdleTimeDelta); |
- continue; |
- } |
- |
- // Remain idle until we have the next frame ready for rendering. |
- if (ready_frames_.empty()) { |
- if (received_end_of_stream_) { |
- state_ = kEnded; |
- ended_cb_.Run(); |
- |
- // No need to sleep here as we idle when |state_ != kPlaying|. |
- continue; |
- } |
- |
- UpdateStatsAndWait_Locked(kIdleTimeDelta); |
- continue; |
- } |
- |
- base::TimeDelta remaining_time = |
- CalculateSleepDuration(ready_frames_.front(), playback_rate_); |
- |
- // Sleep up to a maximum of our idle time until we're within the time to |
- // render the next frame. |
- if (remaining_time.InMicroseconds() > 0) { |
- remaining_time = std::min(remaining_time, kIdleTimeDelta); |
- UpdateStatsAndWait_Locked(remaining_time); |
- continue; |
- } |
- |
- // Deadline is defined as the midpoint between this frame and the next |
- // frame, using the delta between this frame and the previous frame as the |
- // assumption for frame duration. |
- // |
- // TODO(scherkus): An improvement over midpoint might be selecting the |
- // minimum and/or maximum between the midpoint and some constants. As a |
- // thought experiment, consider what would be better than the midpoint |
- // for both the 1fps case and 120fps case. |
- // |
- // TODO(scherkus): This can be vastly improved. Use a histogram to measure |
- // the accuracy of our frame timing code. http://crbug.com/149829 |
- if (drop_frames_ && last_timestamp_ != kNoTimestamp()) { |
- base::TimeDelta now = get_time_cb_.Run(); |
- base::TimeDelta deadline = ready_frames_.front()->timestamp() + |
- (ready_frames_.front()->timestamp() - last_timestamp_) / 2; |
- |
- if (now > deadline) { |
- DropNextReadyFrame_Locked(); |
- continue; |
- } |
- } |
- |
- // Congratulations! You've made it past the video frame timing gauntlet. |
- // |
- // At this point enough time has passed that the next frame that ready for |
- // rendering. |
- PaintNextReadyFrame_Locked(); |
- } |
-} |
- |
-void VideoRendererImpl::PaintNextReadyFrame_Locked() { |
- lock_.AssertAcquired(); |
- |
- scoped_refptr<VideoFrame> next_frame = ready_frames_.front(); |
- ready_frames_.pop_front(); |
- frames_decoded_++; |
- |
- last_timestamp_ = next_frame->timestamp(); |
- |
- paint_cb_.Run(next_frame); |
- |
- task_runner_->PostTask( |
- FROM_HERE, |
- base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); |
-} |
- |
-void VideoRendererImpl::DropNextReadyFrame_Locked() { |
- TRACE_EVENT0("media", "VideoRendererImpl:frameDropped"); |
- |
- lock_.AssertAcquired(); |
- |
- last_timestamp_ = ready_frames_.front()->timestamp(); |
- ready_frames_.pop_front(); |
- frames_decoded_++; |
- frames_dropped_++; |
- |
- task_runner_->PostTask( |
- FROM_HERE, |
- base::Bind(&VideoRendererImpl::AttemptRead, weak_factory_.GetWeakPtr())); |
-} |
- |
void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
const scoped_refptr<VideoFrame>& frame) { |
- base::AutoLock auto_lock(lock_); |
DCHECK_NE(state_, kUninitialized); |
DCHECK_NE(state_, kFlushed); |
@@ -367,7 +230,7 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
// A new preroll will be requested after this one completes so there is no |
// point trying to collect more frames. |
if (state_ == kPrerolling) |
- TransitionToPrerolled_Locked(); |
+ TransitionToPrerolled(); |
return; |
} |
@@ -378,7 +241,10 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
max_time_cb_.Run(get_duration_cb_.Run()); |
if (state_ == kPrerolling) |
- TransitionToPrerolled_Locked(); |
+ TransitionToPrerolled(); |
+ |
+ if (ShouldTransitionToEnded()) |
+ TransitionToEnded(); |
return; |
} |
@@ -387,32 +253,9 @@ void VideoRendererImpl::FrameReady(VideoFrameStream::Status status, |
// prerolling has completed. |
if (state_ == kPrerolling && preroll_timestamp_ != kNoTimestamp() && |
frame->timestamp() <= preroll_timestamp_) { |
- ready_frames_.clear(); |
+ unscheduled_frames_.clear(); |
} |
- AddReadyFrame_Locked(frame); |
- |
- if (ShouldTransitionToPrerolled_Locked()) |
- TransitionToPrerolled_Locked(); |
- |
- // Always request more decoded video if we have capacity. This serves two |
- // purposes: |
- // 1) Prerolling while paused |
- // 2) Keeps decoding going if video rendering thread starts falling behind |
- AttemptRead_Locked(); |
-} |
- |
-bool VideoRendererImpl::ShouldTransitionToPrerolled_Locked() { |
- return state_ == kPrerolling && |
- (!video_frame_stream_.CanReadWithoutStalling() || |
- ready_frames_.size() >= static_cast<size_t>(limits::kMaxVideoFrames)); |
-} |
- |
-void VideoRendererImpl::AddReadyFrame_Locked( |
- const scoped_refptr<VideoFrame>& frame) { |
- lock_.AssertAcquired(); |
- DCHECK(!frame->end_of_stream()); |
- |
// Adjust the incoming frame if its rendering stop time is past the duration |
// of the video itself. This is typically the last frame of the video and |
// occurs if the container specifies a duration that isn't a multiple of the |
@@ -423,28 +266,49 @@ void VideoRendererImpl::AddReadyFrame_Locked( |
frame->set_timestamp(duration); |
} |
- ready_frames_.push_back(frame); |
- DCHECK_LE(ready_frames_.size(), |
- static_cast<size_t>(limits::kMaxVideoFrames)); |
+ unscheduled_frames_.push_back(frame); |
+ DCHECK_LE(video_frame_count(), limits::kMaxVideoFrames); |
max_time_cb_.Run(frame->timestamp()); |
- // Avoid needlessly waking up |thread_| unless playing. |
+ if (ShouldTransitionToPrerolled()) |
+ TransitionToPrerolled(); |
+ |
if (state_ == kPlaying) |
- frame_available_.Signal(); |
+ ScheduleAllFrames(); |
+ |
+ if (ShouldTransitionToEnded()) |
+ TransitionToEnded(); |
+ |
+ // Always request more decoded video if we have capacity. This serves two |
+ // purposes: |
+ // 1) Prerolling while paused |
+ // 2) Keeps decoding going if video rendering thread starts falling behind |
+ AttemptRead(); |
} |
-void VideoRendererImpl::AttemptRead() { |
- base::AutoLock auto_lock(lock_); |
- AttemptRead_Locked(); |
+bool VideoRendererImpl::ShouldTransitionToPrerolled() { |
+ return state_ == kPrerolling && |
+ (!video_frame_stream_.CanReadWithoutStalling() || |
+ video_frame_count() >= limits::kMaxVideoFrames); |
+} |
+ |
+bool VideoRendererImpl::ShouldTransitionToEnded() { |
+ return state_ == kPlaying && unscheduled_frames_.empty() && |
+ scheduled_frames_.empty() && received_end_of_stream_; |
} |
-void VideoRendererImpl::AttemptRead_Locked() { |
+void VideoRendererImpl::TransitionToEnded() { |
+ DCHECK(ShouldTransitionToEnded()); |
+ state_ = kEnded; |
+ ended_cb_.Run(); |
+} |
+ |
+void VideoRendererImpl::AttemptRead() { |
DCHECK(task_runner_->BelongsToCurrentThread()); |
- lock_.AssertAcquired(); |
if (pending_read_ || received_end_of_stream_ || |
- ready_frames_.size() == static_cast<size_t>(limits::kMaxVideoFrames)) { |
+ video_frame_count() == limits::kMaxVideoFrames) { |
return; |
} |
@@ -470,41 +334,20 @@ void VideoRendererImpl::AttemptRead_Locked() { |
} |
void VideoRendererImpl::OnVideoFrameStreamResetDone() { |
- base::AutoLock auto_lock(lock_); |
if (state_ == kStopped) |
return; |
DCHECK_EQ(kFlushing, state_); |
DCHECK(!pending_read_); |
- DCHECK(ready_frames_.empty()); |
+ DCHECK(unscheduled_frames_.empty()); |
+ DCHECK(scheduled_frames_.empty()); |
DCHECK(!received_end_of_stream_); |
state_ = kFlushed; |
- last_timestamp_ = kNoTimestamp(); |
base::ResetAndReturn(&flush_cb_).Run(); |
} |
-base::TimeDelta VideoRendererImpl::CalculateSleepDuration( |
- const scoped_refptr<VideoFrame>& next_frame, |
- float playback_rate) { |
- // Determine the current and next presentation timestamps. |
- base::TimeDelta now = get_time_cb_.Run(); |
- base::TimeDelta next_pts = next_frame->timestamp(); |
- |
- // Scale our sleep based on the playback rate. |
- base::TimeDelta sleep = next_pts - now; |
- return base::TimeDelta::FromMicroseconds( |
- static_cast<int64>(sleep.InMicroseconds() / playback_rate)); |
-} |
- |
-void VideoRendererImpl::DoStopOrError_Locked() { |
- lock_.AssertAcquired(); |
- last_timestamp_ = kNoTimestamp(); |
- ready_frames_.clear(); |
-} |
- |
-void VideoRendererImpl::TransitionToPrerolled_Locked() { |
- lock_.AssertAcquired(); |
+void VideoRendererImpl::TransitionToPrerolled() { |
DCHECK_EQ(state_, kPrerolling); |
state_ = kPrerolled; |
@@ -512,29 +355,113 @@ void VideoRendererImpl::TransitionToPrerolled_Locked() { |
// Because we might remain in the prerolled state for an undetermined amount |
// of time (e.g., we seeked while paused), we'll paint the first prerolled |
// frame. |
- if (!ready_frames_.empty()) |
- PaintNextReadyFrame_Locked(); |
+ if (!unscheduled_frames_.empty()) { |
+ ScheduleFirstFrameForImmediateDisplay(); |
+ AttemptRead(); |
+ } |
base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); |
} |
-void VideoRendererImpl::UpdateStatsAndWait_Locked( |
- base::TimeDelta wait_duration) { |
- lock_.AssertAcquired(); |
- DCHECK_GE(frames_decoded_, 0); |
- DCHECK_LE(frames_dropped_, frames_decoded_); |
+void VideoRendererImpl::ResetScheduler() { |
+ for (VideoFrameIDMap::iterator iter = scheduled_frames_.begin(); |
+ iter != scheduled_frames_.end(); |
+ ++iter) { |
+ unscheduled_frames_.push_back(iter->second); |
+ } |
xhwang
2014/04/25 05:44:47
With all the asyncness, it's not a surprise that a
scherkus (not reviewing)
2014/04/25 18:24:25
It's very possible we'll need to introduce more wa
|
+ scheduled_frames_.clear(); |
+ scheduler_->Reset(); |
+} |
+ |
+void VideoRendererImpl::ScheduleAllFrames() { |
+ DCHECK_EQ(state_, kPlaying); |
+ |
+ if (playback_rate_ == 0) |
+ return; |
+ |
+ base::TimeDelta current_media_time = get_time_cb_.Run(); |
+ base::TimeTicks current_wall_ticks = tick_clock_->NowTicks(); |
+ |
+ while (!unscheduled_frames_.empty()) { |
+ // Negative deltas are permitted as it's up to the scheduler to decide |
+ // what it wants to do with late frames. |
+ base::TimeDelta delta = |
+ unscheduled_frames_.front()->timestamp() - current_media_time; |
+ base::TimeDelta scaled_delta = base::TimeDelta::FromMicroseconds( |
+ static_cast<int64>(delta.InMicroseconds() / playback_rate_)); |
+ base::TimeTicks target_wall_ticks = current_wall_ticks + scaled_delta; |
+ |
+ scoped_refptr<VideoFrame> frame = unscheduled_frames_.front(); |
+ int frame_id = next_scheduled_frame_id_++; |
+ scheduled_frames_[frame_id] = frame; |
+ unscheduled_frames_.pop_front(); |
+ |
+ scheduler_->ScheduleVideoFrame( |
+ frame, |
+ target_wall_ticks, |
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished, |
+ weak_factory_.GetWeakPtr(), |
+ frame_id)); |
+ } |
+} |
+ |
+void VideoRendererImpl::ScheduleFirstFrameForImmediateDisplay() { |
+ DCHECK_EQ(state_, kPrerolled); |
+ |
+ scoped_refptr<VideoFrame> frame = unscheduled_frames_.front(); |
+ int frame_id = next_scheduled_frame_id_++; |
+ scheduled_frames_[frame_id] = frame; |
+ unscheduled_frames_.pop_front(); |
+ |
+ scheduler_->ScheduleVideoFrame( |
+ frame, |
+ tick_clock_->NowTicks(), |
+ base::Bind(&VideoRendererImpl::OnVideoFrameFinished, |
+ weak_factory_.GetWeakPtr(), |
+ frame_id)); |
+} |
+ |
+void VideoRendererImpl::OnVideoFrameFinished( |
+ int frame_id, |
+ const scoped_refptr<VideoFrame>& frame, |
+ VideoFrameScheduler::Reason reason) { |
+ VideoFrameIDMap::iterator iter = scheduled_frames_.find(frame_id); |
+ if (iter == scheduled_frames_.end()) { |
+ LOG(ERROR) << "Ignoring frame_id=" << frame_id |
scherkus (not reviewing)
2014/04/25 02:04:47
note to self: remove
|
+ << ", timestamp=" << frame->timestamp().InMicroseconds(); |
+ return; |
+ } |
+ |
+ CHECK_EQ(iter->second, frame); |
+ scheduled_frames_.erase(iter); |
- if (frames_decoded_) { |
- PipelineStatistics statistics; |
- statistics.video_frames_decoded = frames_decoded_; |
- statistics.video_frames_dropped = frames_dropped_; |
- statistics_cb_.Run(statistics); |
+ switch (reason) { |
+ case VideoFrameScheduler::DISPLAYED: |
+ ++frames_decoded_; |
+ break; |
- frames_decoded_ = 0; |
- frames_dropped_ = 0; |
+ case VideoFrameScheduler::DROPPED: |
+ ++frames_decoded_; |
+ ++frames_dropped_; |
+ break; |
} |
- frame_available_.TimedWait(wait_duration); |
+ PipelineStatistics stats; |
+ stats.video_frames_decoded = frames_decoded_; |
+ stats.video_frames_dropped = frames_dropped_; |
+ statistics_cb_.Run(stats); |
+ |
+ if (ShouldTransitionToEnded()) { |
+ TransitionToEnded(); |
+ return; |
+ } |
+ |
+ AttemptRead(); |
+} |
+ |
+void VideoRendererImpl::SetTickClockForTesting( |
+ scoped_ptr<base::TickClock> tick_clock) { |
+ tick_clock_.swap(tick_clock); |
} |
} // namespace media |