Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(406)

Side by Side Diff: media/base/pipeline.cc

Issue 10828045: Rewrite media::Pipeline state transition machinery. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src
Patch Set: stuff Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "media/base/pipeline.h" 5 #include "media/base/pipeline.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
11 #include "base/callback_helpers.h" 11 #include "base/callback_helpers.h"
12 #include "base/compiler_specific.h" 12 #include "base/compiler_specific.h"
13 #include "base/metrics/histogram.h" 13 #include "base/metrics/histogram.h"
14 #include "base/message_loop.h" 14 #include "base/message_loop.h"
15 #include "base/stl_util.h" 15 #include "base/stl_util.h"
16 #include "base/string_util.h" 16 #include "base/string_util.h"
17 #include "base/synchronization/condition_variable.h" 17 #include "base/synchronization/condition_variable.h"
18 #include "media/base/audio_decoder.h" 18 #include "media/base/audio_decoder.h"
19 #include "media/base/audio_renderer.h" 19 #include "media/base/audio_renderer.h"
20 #include "media/base/buffers.h"
20 #include "media/base/callback_util.h" 21 #include "media/base/callback_util.h"
21 #include "media/base/clock.h" 22 #include "media/base/clock.h"
22 #include "media/base/filter_collection.h" 23 #include "media/base/filter_collection.h"
23 #include "media/base/media_log.h" 24 #include "media/base/media_log.h"
24 #include "media/base/video_decoder.h" 25 #include "media/base/video_decoder.h"
25 #include "media/base/video_renderer.h" 26 #include "media/base/video_renderer.h"
26 27
27 using base::TimeDelta; 28 using base::TimeDelta;
28 29
29 namespace media { 30 namespace media {
30 31
31 PipelineStatusNotification::PipelineStatusNotification() 32 PipelineStatusNotification::PipelineStatusNotification()
acolwell GONE FROM CHROMIUM 2012/07/30 18:33:10 Shouldn't this be in its own file? It isn't even u
32 : cv_(&lock_), status_(PIPELINE_OK), notified_(false) { 33 : cv_(&lock_), status_(PIPELINE_OK), notified_(false) {
33 } 34 }
34 35
35 PipelineStatusNotification::~PipelineStatusNotification() { 36 PipelineStatusNotification::~PipelineStatusNotification() {
36 DCHECK(notified_); 37 DCHECK(notified_);
37 } 38 }
38 39
39 PipelineStatusCB PipelineStatusNotification::Callback() { 40 PipelineStatusCB PipelineStatusNotification::Callback() {
40 return base::Bind(&PipelineStatusNotification::Notify, 41 return base::Bind(&PipelineStatusNotification::Notify,
41 base::Unretained(this)); 42 base::Unretained(this));
(...skipping 20 matching lines...) Expand all
62 } 63 }
63 64
64 struct Pipeline::PipelineInitState { 65 struct Pipeline::PipelineInitState {
65 scoped_refptr<AudioDecoder> audio_decoder; 66 scoped_refptr<AudioDecoder> audio_decoder;
66 scoped_refptr<VideoDecoder> video_decoder; 67 scoped_refptr<VideoDecoder> video_decoder;
67 }; 68 };
68 69
69 Pipeline::Pipeline(MessageLoop* message_loop, MediaLog* media_log) 70 Pipeline::Pipeline(MessageLoop* message_loop, MediaLog* media_log)
70 : message_loop_(message_loop->message_loop_proxy()), 71 : message_loop_(message_loop->message_loop_proxy()),
71 media_log_(media_log), 72 media_log_(media_log),
73 running_(false),
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 I believe it is the case that: running_ == state_
74 did_loading_progress_(false),
75 total_bytes_(0),
76 volume_(1.0f),
77 playback_rate_(0.0f),
72 clock_(new Clock(&base::Time::Now)), 78 clock_(new Clock(&base::Time::Now)),
73 waiting_for_clock_update_(false), 79 waiting_for_clock_update_(false),
80 status_(PIPELINE_OK),
81 has_audio_(false),
82 has_video_(false),
74 state_(kCreated), 83 state_(kCreated),
84 seek_timestamp_(kNoTimestamp()),
85 audio_disabled_(false),
75 creation_time_(base::Time::Now()) { 86 creation_time_(base::Time::Now()) {
76 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); 87 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
77 ResetState();
78 media_log_->AddEvent( 88 media_log_->AddEvent(
79 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); 89 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED));
80 } 90 }
81 91
82 Pipeline::~Pipeline() { 92 Pipeline::~Pipeline() {
83 base::AutoLock auto_lock(lock_); 93 base::AutoLock auto_lock(lock_);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 If you think you need a lock in a dtor, you're gon
84 DCHECK(!running_) << "Stop() must complete before destroying object"; 94 DCHECK(!running_) << "Stop() must complete before destroying object";
85 DCHECK(!stop_pending_); 95 DCHECK(start_cb_.is_null());
86 DCHECK(!seek_pending_); 96 DCHECK(seek_cb_.is_null());
97 DCHECK(stop_cb_.is_null());
87 98
88 media_log_->AddEvent( 99 media_log_->AddEvent(
89 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); 100 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED));
90 } 101 }
91 102
92 void Pipeline::Start(scoped_ptr<FilterCollection> collection, 103 void Pipeline::Start(scoped_ptr<FilterCollection> collection,
93 const PipelineStatusCB& ended_cb, 104 const PipelineStatusCB& ended_cb,
94 const PipelineStatusCB& error_cb, 105 const PipelineStatusCB& error_cb,
95 const PipelineStatusCB& start_cb) { 106 const PipelineStatusCB& start_cb) {
96 base::AutoLock auto_lock(lock_); 107 base::AutoLock auto_lock(lock_);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 replace w/ assertion about running on render threa
97 CHECK(!running_) << "Media pipeline is already running"; 108 CHECK(!running_) << "Media pipeline is already running";
109 running_ = true;
98 110
99 running_ = true;
100 message_loop_->PostTask(FROM_HERE, base::Bind( 111 message_loop_->PostTask(FROM_HERE, base::Bind(
101 &Pipeline::StartTask, this, base::Passed(&collection), 112 &Pipeline::StartTask, this, base::Passed(&collection),
102 ended_cb, error_cb, start_cb)); 113 ended_cb, error_cb, start_cb));
103 } 114 }
104 115
105 void Pipeline::Stop(const base::Closure& stop_cb) { 116 void Pipeline::Stop(const base::Closure& stop_cb) {
106 base::AutoLock auto_lock(lock_); 117 base::AutoLock auto_lock(lock_);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 ditto to asserting on render thread (lock is not o
acolwell GONE FROM CHROMIUM 2012/07/30 18:33:10 Most of the GetXXX methods need locks because they
107 CHECK(running_) << "Media pipeline isn't running";
scherkus (not reviewing) 2012/07/28 02:26:07 it's now always valid to call Stop() -- this avoid
108 118
109 // Stop the pipeline, which will set |running_| to false on our behalf.
110 message_loop_->PostTask(FROM_HERE, base::Bind( 119 message_loop_->PostTask(FROM_HERE, base::Bind(
111 &Pipeline::StopTask, this, stop_cb)); 120 &Pipeline::StopTask, this, stop_cb));
112 } 121 }
113 122
114 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { 123 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
115 base::AutoLock auto_lock(lock_); 124 base::AutoLock auto_lock(lock_);
116 CHECK(running_) << "Media pipeline isn't running"; 125 CHECK(running_) << "Media pipeline isn't running";
117 126
118 message_loop_->PostTask(FROM_HERE, base::Bind( 127 message_loop_->PostTask(FROM_HERE, base::Bind(
119 &Pipeline::SeekTask, this, time, seek_cb)); 128 &Pipeline::SeekTask, this, time, seek_cb));
120 } 129 }
121 130
122 bool Pipeline::IsRunning() const { 131 bool Pipeline::IsRunning() const {
123 base::AutoLock auto_lock(lock_); 132 base::AutoLock auto_lock(lock_);
124 return running_; 133 return running_;
125 } 134 }
126 135
127 bool Pipeline::IsInitialized() const {
128 // TODO(scherkus): perhaps replace this with a bool that is set/get under the
129 // lock, because this is breaching the contract that |state_| is only accessed
130 // on |message_loop_|.
131 base::AutoLock auto_lock(lock_);
132 switch (state_) {
133 case kPausing:
134 case kFlushing:
135 case kSeeking:
136 case kStarting:
137 case kStarted:
138 case kEnded:
139 return true;
140 default:
141 return false;
142 }
143 }
144
145 bool Pipeline::HasAudio() const { 136 bool Pipeline::HasAudio() const {
146 base::AutoLock auto_lock(lock_); 137 base::AutoLock auto_lock(lock_);
147 return has_audio_; 138 return has_audio_;
148 } 139 }
149 140
150 bool Pipeline::HasVideo() const { 141 bool Pipeline::HasVideo() const {
151 base::AutoLock auto_lock(lock_); 142 base::AutoLock auto_lock(lock_);
152 return has_video_; 143 return has_video_;
153 } 144 }
154 145
155 float Pipeline::GetPlaybackRate() const { 146 float Pipeline::GetPlaybackRate() const {
156 base::AutoLock auto_lock(lock_); 147 base::AutoLock auto_lock(lock_);
157 return playback_rate_; 148 return playback_rate_;
158 } 149 }
159 150
160 void Pipeline::SetPlaybackRate(float playback_rate) { 151 void Pipeline::SetPlaybackRate(float playback_rate) {
161 if (playback_rate < 0.0f) 152 if (playback_rate < 0.0f)
162 return; 153 return;
163 154
164 base::AutoLock auto_lock(lock_); 155 base::AutoLock auto_lock(lock_);
165 playback_rate_ = playback_rate; 156 playback_rate_ = playback_rate;
166 if (running_ && !tearing_down_) { 157 if (running_) {
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 couldn't this be an early-return at the top of the
acolwell GONE FROM CHROMIUM 2012/07/30 18:33:10 The running_ check is here because it needs to be
167 message_loop_->PostTask(FROM_HERE, base::Bind( 158 message_loop_->PostTask(FROM_HERE, base::Bind(
168 &Pipeline::PlaybackRateChangedTask, this, playback_rate)); 159 &Pipeline::PlaybackRateChangedTask, this, playback_rate));
169 } 160 }
170 } 161 }
171 162
172 float Pipeline::GetVolume() const { 163 float Pipeline::GetVolume() const {
173 base::AutoLock auto_lock(lock_); 164 base::AutoLock auto_lock(lock_);
174 return volume_; 165 return volume_;
175 } 166 }
176 167
177 void Pipeline::SetVolume(float volume) { 168 void Pipeline::SetVolume(float volume) {
178 if (volume < 0.0f || volume > 1.0f) 169 if (volume < 0.0f || volume > 1.0f)
179 return; 170 return;
180 171
181 base::AutoLock auto_lock(lock_); 172 base::AutoLock auto_lock(lock_);
182 volume_ = volume; 173 volume_ = volume;
183 if (running_ && !tearing_down_) { 174 if (running_) {
184 message_loop_->PostTask(FROM_HERE, base::Bind( 175 message_loop_->PostTask(FROM_HERE, base::Bind(
185 &Pipeline::VolumeChangedTask, this, volume)); 176 &Pipeline::VolumeChangedTask, this, volume));
186 } 177 }
187 } 178 }
188 179
189 TimeDelta Pipeline::GetMediaTime() const { 180 TimeDelta Pipeline::GetMediaTime() const {
190 base::AutoLock auto_lock(lock_); 181 base::AutoLock auto_lock(lock_);
191 return clock_->Elapsed(); 182 return clock_->Elapsed();
192 } 183 }
193 184
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 227
237 PipelineStatistics Pipeline::GetStatistics() const { 228 PipelineStatistics Pipeline::GetStatistics() const {
238 base::AutoLock auto_lock(lock_); 229 base::AutoLock auto_lock(lock_);
239 return statistics_; 230 return statistics_;
240 } 231 }
241 232
242 void Pipeline::SetClockForTesting(Clock* clock) { 233 void Pipeline::SetClockForTesting(Clock* clock) {
243 clock_.reset(clock); 234 clock_.reset(clock);
244 } 235 }
245 236
246 void Pipeline::ResetState() { 237 void Pipeline::SetErrorForTesting(PipelineStatus status) {
247 base::AutoLock auto_lock(lock_); 238 SetError(status);
248 const TimeDelta kZero;
249 running_ = false;
250 stop_pending_ = false;
251 seek_pending_ = false;
252 tearing_down_ = false;
253 error_caused_teardown_ = false;
254 playback_rate_change_pending_ = false;
255 buffered_byte_ranges_.clear();
256 did_loading_progress_ = false;
257 total_bytes_ = 0;
258 natural_size_.SetSize(0, 0);
259 volume_ = 1.0f;
260 playback_rate_ = 0.0f;
261 pending_playback_rate_ = 0.0f;
262 status_ = PIPELINE_OK;
263 has_audio_ = false;
264 has_video_ = false;
265 waiting_for_clock_update_ = false;
266 audio_disabled_ = false;
267 clock_->Reset();
268 } 239 }
269 240
270 void Pipeline::SetState(State next_state) { 241 void Pipeline::SetState(State next_state) {
271 if (state_ != kStarted && next_state == kStarted && 242 if (state_ != kStarted && next_state == kStarted &&
272 !creation_time_.is_null()) { 243 !creation_time_.is_null()) {
273 UMA_HISTOGRAM_TIMES( 244 UMA_HISTOGRAM_TIMES(
274 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); 245 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_);
275 creation_time_ = base::Time(); 246 creation_time_ = base::Time();
276 } 247 }
277 state_ = next_state; 248 state_ = next_state;
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 can you assert a correct thread or lock is held?
278 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); 249 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
279 } 250 }
280 251
281 bool Pipeline::IsPipelineOk() { 252 bool Pipeline::IsPipelineOk() {
282 base::AutoLock auto_lock(lock_); 253 base::AutoLock auto_lock(lock_);
283 return status_ == PIPELINE_OK; 254 return status_ == PIPELINE_OK;
284 } 255 }
285 256
286 bool Pipeline::IsPipelineStopped() {
287 DCHECK(message_loop_->BelongsToCurrentThread());
288 return state_ == kStopped || state_ == kError;
289 }
290
291 bool Pipeline::IsPipelineTearingDown() {
292 DCHECK(message_loop_->BelongsToCurrentThread());
293 return tearing_down_;
294 }
295
296 bool Pipeline::IsPipelineStopPending() {
297 DCHECK(message_loop_->BelongsToCurrentThread());
298 return stop_pending_;
299 }
300
301 bool Pipeline::IsPipelineSeeking() {
302 DCHECK(message_loop_->BelongsToCurrentThread());
303 if (!seek_pending_)
304 return false;
305 DCHECK(kSeeking == state_ || kPausing == state_ ||
306 kFlushing == state_ || kStarting == state_)
307 << "Current state : " << state_;
308 return true;
309 }
310
311 void Pipeline::ReportStatus(const PipelineStatusCB& cb, PipelineStatus status) {
312 DCHECK(message_loop_->BelongsToCurrentThread());
313 if (cb.is_null())
314 return;
315 cb.Run(status);
316 // Prevent double-reporting of errors to clients.
317 if (status != PIPELINE_OK)
318 error_cb_.Reset();
319 }
320
321 void Pipeline::FinishInitialization() {
322 DCHECK(message_loop_->BelongsToCurrentThread());
323 // Execute the seek callback, if present. Note that this might be the
324 // initial callback passed into Start().
325 ReportStatus(seek_cb_, status_);
326 seek_cb_.Reset();
327 }
328
329 // static
330 bool Pipeline::TransientState(State state) {
331 return state == kPausing ||
332 state == kFlushing ||
333 state == kSeeking ||
334 state == kStarting ||
335 state == kStopping;
336 }
337
338 // static
339 Pipeline::State Pipeline::FindNextState(State current) {
340 // TODO(scherkus): refactor InitializeTask() to make use of this function.
341 if (current == kPausing) {
342 return kFlushing;
343 } else if (current == kFlushing) {
344 // We will always honor Seek() before Stop(). This is based on the
345 // assumption that we never accept Seek() after Stop().
346 DCHECK(IsPipelineSeeking() ||
347 IsPipelineStopPending() ||
348 IsPipelineTearingDown());
349 return IsPipelineSeeking() ? kSeeking : kStopping;
350 } else if (current == kSeeking) {
351 return kStarting;
352 } else if (current == kStarting) {
353 return kStarted;
354 } else if (current == kStopping) {
355 return error_caused_teardown_ ? kError : kStopped;
356 } else {
357 return current;
358 }
359 }
360
361 void Pipeline::OnDemuxerError(PipelineStatus error) { 257 void Pipeline::OnDemuxerError(PipelineStatus error) {
362 SetError(error); 258 SetError(error);
363 } 259 }
364 260
365 void Pipeline::SetError(PipelineStatus error) { 261 void Pipeline::SetError(PipelineStatus error) {
366 DCHECK(IsRunning()); 262 DCHECK(IsRunning());
367 DCHECK_NE(PIPELINE_OK, error); 263 DCHECK_NE(PIPELINE_OK, error);
368 VLOG(1) << "Media pipeline error: " << error; 264 VLOG(1) << "Media pipeline error: " << error;
369 265
370 message_loop_->PostTask(FROM_HERE, base::Bind( 266 message_loop_->PostTask(FROM_HERE, base::Bind(
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 TimeDelta epsilon = clock_->Duration() / 100; 342 TimeDelta epsilon = clock_->Duration() / 100;
447 if (time_offset < epsilon) 343 if (time_offset < epsilon)
448 return TimeDelta(); 344 return TimeDelta();
449 if (time_offset + epsilon > clock_->Duration()) 345 if (time_offset + epsilon > clock_->Duration())
450 return clock_->Duration(); 346 return clock_->Duration();
451 return time_offset; 347 return time_offset;
452 } 348 }
453 349
454 void Pipeline::DoPause(const base::Closure& done_cb) { 350 void Pipeline::DoPause(const base::Closure& done_cb) {
455 DCHECK(message_loop_->BelongsToCurrentThread()); 351 DCHECK(message_loop_->BelongsToCurrentThread());
352 DCHECK_EQ(state_, kPausing);
353 DCHECK(seek_timestamp_ != kNoTimestamp());
354
456 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>); 355 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>);
457 356
458 if (audio_renderer_) 357 if (audio_renderer_)
459 closures->push(base::Bind(&AudioRenderer::Pause, audio_renderer_)); 358 closures->push(base::Bind(&AudioRenderer::Pause, audio_renderer_));
460 359
461 if (video_renderer_) 360 if (video_renderer_)
462 closures->push(base::Bind(&VideoRenderer::Pause, video_renderer_)); 361 closures->push(base::Bind(&VideoRenderer::Pause, video_renderer_));
463 362
464 RunInSeries(closures.Pass(), done_cb); 363 RunInSeries(closures.Pass(), done_cb);
465 } 364 }
466 365
467 void Pipeline::DoFlush(const base::Closure& done_cb) { 366 void Pipeline::DoFlush(const base::Closure& done_cb) {
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Is it crazy to still have this around? Is it not a
468 DCHECK(message_loop_->BelongsToCurrentThread()); 367 DCHECK(message_loop_->BelongsToCurrentThread());
368 DCHECK_EQ(state_, kFlushing);
369 DCHECK(seek_timestamp_ != kNoTimestamp());
370
469 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>); 371 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>);
470 372
471 if (audio_renderer_) 373 if (audio_renderer_)
472 closures->push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); 374 closures->push(base::Bind(&AudioRenderer::Flush, audio_renderer_));
473 375
474 if (video_renderer_) 376 if (video_renderer_)
475 closures->push(base::Bind(&VideoRenderer::Flush, video_renderer_)); 377 closures->push(base::Bind(&VideoRenderer::Flush, video_renderer_));
476 378
477 RunInParallel(closures.Pass(), done_cb); 379 RunInParallel(closures.Pass(), done_cb);
478 } 380 }
479 381
480 void Pipeline::DoPlay(const base::Closure& done_cb) { 382 void Pipeline::DoPlay(const base::Closure& done_cb) {
481 DCHECK(message_loop_->BelongsToCurrentThread()); 383 DCHECK(message_loop_->BelongsToCurrentThread());
384 DCHECK_EQ(state_, kStarting);
385 DCHECK(seek_timestamp_ == kNoTimestamp());
386
387 // Update playback rate and volume in case it changed before we resume
388 // playback.
389 PlaybackRateChangedTask(GetPlaybackRate());
scherkus (not reviewing) 2012/07/28 02:26:07 here's the new method for handling rate/volume cha
390 VolumeChangedTask(GetVolume());
391
482 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>); 392 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>);
483 393
484 if (audio_renderer_) 394 if (audio_renderer_)
485 closures->push(base::Bind(&AudioRenderer::Play, audio_renderer_)); 395 closures->push(base::Bind(&AudioRenderer::Play, audio_renderer_));
486 396
487 if (video_renderer_) 397 if (video_renderer_)
488 closures->push(base::Bind(&VideoRenderer::Play, video_renderer_)); 398 closures->push(base::Bind(&VideoRenderer::Play, video_renderer_));
489 399
490 RunInSeries(closures.Pass(), done_cb); 400 RunInSeries(closures.Pass(), done_cb);
491 } 401 }
492 402
493 void Pipeline::DoStop(const base::Closure& done_cb) { 403 void Pipeline::DoStop(const base::Closure& done_cb) {
494 DCHECK(message_loop_->BelongsToCurrentThread()); 404 DCHECK(message_loop_->BelongsToCurrentThread());
405 DCHECK_EQ(state_, kStopping);
406
407 if (video_decoder_) {
408 video_decoder_->PrepareForShutdownHack();
scherkus (not reviewing) 2012/07/28 02:26:07 this might actually go away now that we don't Paus
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Yes, it can!
409 video_decoder_ = NULL;
410 }
411
495 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>); 412 scoped_ptr<std::queue<ClosureFunc> > closures(new std::queue<ClosureFunc>);
496 413
497 if (demuxer_) 414 if (demuxer_)
498 closures->push(base::Bind(&Demuxer::Stop, demuxer_)); 415 closures->push(base::Bind(&Demuxer::Stop, demuxer_));
499 416
500 if (audio_renderer_) 417 if (audio_renderer_)
501 closures->push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); 418 closures->push(base::Bind(&AudioRenderer::Stop, audio_renderer_));
502 419
503 if (video_renderer_) 420 if (video_renderer_)
504 closures->push(base::Bind(&VideoRenderer::Stop, video_renderer_)); 421 closures->push(base::Bind(&VideoRenderer::Stop, video_renderer_));
(...skipping 26 matching lines...) Expand all
531 } 448 }
532 449
533 void Pipeline::OnRendererEnded() { 450 void Pipeline::OnRendererEnded() {
534 DCHECK(IsRunning()); 451 DCHECK(IsRunning());
535 message_loop_->PostTask(FROM_HERE, base::Bind( 452 message_loop_->PostTask(FROM_HERE, base::Bind(
536 &Pipeline::OnRendererEndedTask, this)); 453 &Pipeline::OnRendererEndedTask, this));
537 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); 454 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
538 } 455 }
539 456
540 // Called from any thread. 457 // Called from any thread.
541 void Pipeline::OnFilterInitialize(PipelineStatus status) {
542 // Continue the initialize task by proceeding to the next stage.
543 message_loop_->PostTask(FROM_HERE, base::Bind(
544 &Pipeline::InitializeTask, this, status));
545 }
546
547 // Called from any thread.
548 void Pipeline::OnFilterStateTransition() {
549 message_loop_->PostTask(FROM_HERE, base::Bind(
550 &Pipeline::FilterStateTransitionTask, this));
551 }
552
553 // Called from any thread.
554 // This method makes the PipelineStatusCB behave like a Closure. It
555 // makes it look like a host()->SetError() call followed by a call to
556 // OnFilterStateTransition() when errors occur.
557 //
558 // TODO: Revisit this code when SetError() is removed from FilterHost and
559 // all the Closures are converted to PipelineStatusCB.
560 void Pipeline::OnFilterStateTransitionWithStatus(PipelineStatus status) {
561 if (status != PIPELINE_OK)
562 SetError(status);
563 OnFilterStateTransition();
564 }
565
566 void Pipeline::OnTeardownStateTransition() {
567 message_loop_->PostTask(FROM_HERE, base::Bind(
568 &Pipeline::TeardownStateTransitionTask, this));
569 }
570
571 // Called from any thread.
572 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { 458 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) {
573 base::AutoLock auto_lock(lock_); 459 base::AutoLock auto_lock(lock_);
574 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; 460 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
575 statistics_.video_bytes_decoded += stats.video_bytes_decoded; 461 statistics_.video_bytes_decoded += stats.video_bytes_decoded;
576 statistics_.video_frames_decoded += stats.video_frames_decoded; 462 statistics_.video_frames_decoded += stats.video_frames_decoded;
577 statistics_.video_frames_dropped += stats.video_frames_dropped; 463 statistics_.video_frames_dropped += stats.video_frames_dropped;
578 } 464 }
579 465
580 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, 466 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection,
581 const PipelineStatusCB& ended_cb, 467 const PipelineStatusCB& ended_cb,
582 const PipelineStatusCB& error_cb, 468 const PipelineStatusCB& error_cb,
583 const PipelineStatusCB& start_cb) { 469 const PipelineStatusCB& start_cb) {
584 DCHECK(message_loop_->BelongsToCurrentThread()); 470 DCHECK(message_loop_->BelongsToCurrentThread());
585 DCHECK_EQ(kCreated, state_); 471 DCHECK_EQ(kCreated, state_);
586 filter_collection_ = filter_collection.Pass(); 472 filter_collection_ = filter_collection.Pass();
587 ended_cb_ = ended_cb; 473 ended_cb_ = ended_cb;
588 error_cb_ = error_cb; 474 error_cb_ = error_cb;
589 seek_cb_ = start_cb; 475 start_cb_ = start_cb;
590 476
591 // Kick off initialization. 477 // Kick off initialization.
592 pipeline_init_state_.reset(new PipelineInitState()); 478 pipeline_init_state_.reset(new PipelineInitState());
593
594 SetState(kInitDemuxer); 479 SetState(kInitDemuxer);
595 InitializeDemuxer(); 480 DoInitDemuxer(base::Bind(&Pipeline::DoStateTransition, this));
596 } 481 }
597 482
598 // Main initialization method called on the pipeline thread. This code attempts 483 bool Pipeline::IsTransitioning() {
599 // to use the specified filter factory to build a pipeline. 484 DCHECK(message_loop_->BelongsToCurrentThread());
600 // Initialization step performed in this method depends on current state of this 485 switch (state_) {
601 // object, indicated by |state_|. After each step of initialization, this 486 case kCreated:
602 // object transits to the next stage. It starts by creating a Demuxer, and then 487 case kStarted:
603 // connects the Demuxer's audio stream to an AudioDecoder which is then 488 case kStopped:
604 // connected to an AudioRenderer. If the media has video, then it connects a 489 return false;
605 // VideoDecoder to the Demuxer's video stream, and then connects the 490
606 // VideoDecoder to a VideoRenderer. 491 case kInitDemuxer:
607 // 492 case kInitAudioDecoder:
608 // When all required filters have been created and have called their 493 case kInitAudioRenderer:
609 // FilterHost's InitializationComplete() method, the pipeline will update its 494 case kInitVideoDecoder:
610 // state to kStarted and |init_cb_|, will be executed. 495 case kInitVideoRenderer:
611 // 496 case kPausing:
612 // TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It 497 case kFlushing:
613 // works like a big state change table. If we no longer need to start filters 498 case kSeeking:
614 // in order, we need to get rid of all the state change. 499 case kStarting:
615 void Pipeline::InitializeTask(PipelineStatus last_stage_status) { 500 case kStopping:
616 DCHECK(message_loop_->BelongsToCurrentThread()); 501 return true;
617 502
618 if (last_stage_status != PIPELINE_OK) { 503 // Catch any left out states.
619 // Currently only VideoDecoders have a recoverable error code. 504 }
620 if (state_ == kInitVideoDecoder && 505 NOTREACHED();
621 last_stage_status == DECODER_ERROR_NOT_SUPPORTED) { 506 return false;
622 state_ = kInitAudioRenderer; 507 }
623 } else { 508
624 SetError(last_stage_status); 509 void Pipeline::OnStateTransition(PipelineStatus status) {
510 // Force-post a task on state transitions since to avoid reentrancy between
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 s/since/
511 // states.
512 message_loop_->PostTask(FROM_HERE, base::Bind(
513 &Pipeline::DoStateTransition, this, status));
514 }
515
516 void Pipeline::DoStateTransition(PipelineStatus status) {
517 DCHECK(message_loop_->BelongsToCurrentThread());
518
519 // Preserve existing abnormal status, otherwise update based on the result of
520 // the previous operation.
521 status_ = (status_ != PIPELINE_OK ? status_ : status);
522
523 PipelineStatusCB done_cb = base::Bind(&Pipeline::OnStateTransition, this);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Can move south of the error-handling case.
524 base::Closure done_cb_no_status = base::Bind(
scherkus (not reviewing) 2012/07/28 02:26:07 now that there's only one entry point for state tr
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 See above; I like the CBing, but I think you can s
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 d_c_no_status is a funny name for a thing that has
525 &Pipeline::OnStateTransition, this, status_);
526
527 // Check if we need to stop due to an error or due to |stop_cb_| being set.
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 I'm pretty sure you dropped the VideoDecoder fallb
528 if (state_ != kStopping && state_ != kStopped &&
529 (status_ != PIPELINE_OK || !stop_cb_.is_null())) {
530 SetState(kStopping);
531 DoStop(done_cb_no_status);
532 return;
533 }
534
535 switch(state_) {
536 case kCreated:
537 NOTREACHED() << "kCreated";
scherkus (not reviewing) 2012/07/28 02:26:07 kCreated, kStarted, kStopped are non transitioning
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 IMO best to keep as-is, and drop the default: case
538 return;
539
540 case kInitDemuxer:
541 {
542 base::AutoLock auto_lock(lock_);
543 // We do not want to start the clock running. We only want to set the
544 // base media time so our timestamp calculations will be correct.
545 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime());
546 }
547 SetState(kInitAudioDecoder);
548 DoInitAudioDecoder(done_cb);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 I'm wondering whether the initialization dance cou
549 return;
550
551 case kInitAudioDecoder:
552 SetState(kInitAudioRenderer);
553 DoInitAudioRenderer(done_cb);
554 return;
555
556 case kInitAudioRenderer:
557 SetState(kInitVideoDecoder);
558 DoInitVideoDecoder(done_cb);
559 return;
560
561 case kInitVideoDecoder:
562 SetState(kInitVideoRenderer);
563 DoInitVideoRenderer(done_cb);
564 return;
565
566 case kInitVideoRenderer: {
567 bool success = true;
568 {
569 base::AutoLock l(lock_);
570 has_audio_ = !!audio_renderer_ && !audio_disabled_;
scherkus (not reviewing) 2012/07/28 02:31:47 hmm... this will actually cause us to not render t
571 has_video_ = !!video_renderer_;
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Is it really worthwhile maintaining members for th
572
573 // We're still successful if we have audio but the sound card is busted.
574 success = !!audio_renderer_ || !!video_renderer_;
575 }
576 if (!success) {
577 DoStateTransition(PIPELINE_ERROR_COULD_NOT_RENDER);
578 return;
579 }
580
581 // Clear initialization state now that we're done.
582 filter_collection_.reset();
583 pipeline_init_state_.reset();
584
585 // Kick off initial preroll.
586 seek_timestamp_ = demuxer_->GetStartTime();
587 SetState(kSeeking);
588 DoSeek(true, done_cb);
589 return;
625 } 590 }
626 } 591
627 592 case kPausing:
628 // If we have received the stop or error signal, return immediately. 593 DCHECK(seek_timestamp_ != kNoTimestamp());
629 if (IsPipelineStopPending() || IsPipelineStopped() || !IsPipelineOk()) 594 SetState(kFlushing);
630 return; 595 DoFlush(done_cb_no_status);
631 596 return;
632 DCHECK(state_ == kInitDemuxer || 597
633 state_ == kInitAudioDecoder || 598 case kFlushing:
634 state_ == kInitAudioRenderer || 599 DCHECK(seek_timestamp_ != kNoTimestamp());
635 state_ == kInitVideoDecoder || 600 SetState(kSeeking);
636 state_ == kInitVideoRenderer); 601 DoSeek(false, done_cb);
637 602 return;
638 // Demuxer created, create audio decoder. 603
639 if (state_ == kInitDemuxer) { 604 case kSeeking:
640 SetState(kInitAudioDecoder); 605 DCHECK(seek_timestamp_ != kNoTimestamp());
641 // If this method returns false, then there's no audio stream. 606 seek_timestamp_ = kNoTimestamp();
642 if (InitializeAudioDecoder(demuxer_)) 607
643 return; 608 SetState(kStarting);
644 } 609 DoPlay(done_cb_no_status);
645 610 return;
646 // Assuming audio decoder was created, create audio renderer. 611
647 if (state_ == kInitAudioDecoder) { 612 case kStarting:
648 SetState(kInitAudioRenderer); 613 {
649 614 base::AutoLock l(lock_);
650 // Returns false if there's no audio stream. 615 // We use audio stream to update the clock. So if there is such a
651 if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder)) { 616 // stream, we pause the clock until we receive a valid timestamp.
652 base::AutoLock auto_lock(lock_); 617 waiting_for_clock_update_ = true;
653 has_audio_ = true; 618 if (!has_audio_) {
654 return; 619 clock_->SetMaxTime(clock_->Duration());
655 } 620 StartClockIfWaitingForTimeUpdate_Locked();
656 } 621 }
657 622 }
658 // Assuming audio renderer was created, create video decoder. 623
659 if (state_ == kInitAudioRenderer) { 624 SetState(kStarted);
660 // Then perform the stage of initialization, i.e. initialize video decoder. 625 if (!start_cb_.is_null()) {
661 SetState(kInitVideoDecoder); 626 DCHECK_EQ(status_, PIPELINE_OK);
662 if (InitializeVideoDecoder(demuxer_)) 627 base::ResetAndReturn(&start_cb_).Run(status_);
663 return; 628 return;
664 } 629 }
665 630 if (!seek_cb_.is_null()) {
666 // Assuming video decoder was created, create video renderer. 631 DCHECK_EQ(status_, PIPELINE_OK);
667 if (state_ == kInitVideoDecoder) { 632 base::ResetAndReturn(&seek_cb_).Run(status_);
668 SetState(kInitVideoRenderer); 633 }
669 if (InitializeVideoRenderer(pipeline_init_state_->video_decoder)) { 634 return;
670 base::AutoLock auto_lock(lock_); 635
671 has_video_ = true; 636 case kStarted:
672 return; 637 NOTREACHED() << "kStarted";
673 } 638 return;
674 } 639
675 640 case kStopping:
676 if (state_ == kInitVideoRenderer) { 641 // Release all references.
677 if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { 642 pipeline_init_state_.reset();
678 SetError(PIPELINE_ERROR_COULD_NOT_RENDER); 643 filter_collection_.reset();
679 return; 644 audio_renderer_ = NULL;
680 } 645 video_renderer_ = NULL;
681 646 demuxer_ = NULL;
682 // Clear initialization state now that we're done. 647 {
683 filter_collection_.reset(); 648 base::AutoLock l(lock_);
684 pipeline_init_state_.reset(); 649 running_ = false;
685 650 }
686 // Initialization was successful, we are now considered paused, so it's safe 651 SetState(kStopped);
687 // to set the initial playback rate and volume. 652
688 PlaybackRateChangedTask(GetPlaybackRate()); 653 // Execute any client-initiated pending callbacks if we've got some,
689 VolumeChangedTask(GetVolume()); 654 // otherwise use the permanent callback |error_cb_|.
690 655 if (!start_cb_.is_null()) {
scherkus (not reviewing) 2012/07/28 02:26:07 put your thinking caps on for this one + look at m
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Can you be more assertive about the possibility of
691 // Fire a seek request to get the renderers to preroll. We can skip a seek 656 base::ResetAndReturn(&start_cb_).Run(status_);
692 // here as the demuxer should be at the start of the stream. 657 error_cb_.Reset();
693 seek_pending_ = true; 658 }
694 SetState(kSeeking); 659 if (!seek_cb_.is_null()) {
695 seek_timestamp_ = demuxer_->GetStartTime(); 660 base::ResetAndReturn(&seek_cb_).Run(status_);
696 DoSeek(seek_timestamp_, true, 661 error_cb_.Reset();
697 base::Bind(&Pipeline::OnFilterStateTransitionWithStatus, this)); 662 }
698 } 663 if (!stop_cb_.is_null()) {
699 } 664 base::ResetAndReturn(&stop_cb_).Run();
700 665 error_cb_.Reset();
701 // This method is called as a result of the client calling Pipeline::Stop() or 666 }
702 // as the result of an error condition. 667 if (!error_cb_.is_null()) {
703 // We stop the filters in the reverse order. 668 DCHECK_NE(status_, PIPELINE_OK);
704 // 669 base::ResetAndReturn(&error_cb_).Run(status_);
705 // TODO(scherkus): beware! this can get posted multiple times since we post 670 }
706 // Stop() tasks even if we've already stopped. Perhaps this should no-op for 671 return;
707 // additional calls, however most of this logic will be changing. 672
673 case kStopped:
674 NOTREACHED() << "kStopped";
675 return;
676
677 default:
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 drop
678 NOTREACHED() << "State has no transition: " << state_;
679 }
680 NOTREACHED() << "You should return, not break!";
681 }
682
708 void Pipeline::StopTask(const base::Closure& stop_cb) { 683 void Pipeline::StopTask(const base::Closure& stop_cb) {
709 DCHECK(message_loop_->BelongsToCurrentThread()); 684 DCHECK(message_loop_->BelongsToCurrentThread());
710 DCHECK(!IsPipelineStopPending()); 685 DCHECK(stop_cb_.is_null());
711 DCHECK_NE(state_, kStopped); 686
712 687 if (state_ == kStopped) {
713 if (video_decoder_) { 688 stop_cb.Run();
714 video_decoder_->PrepareForShutdownHack(); 689 return;
715 video_decoder_ = NULL;
716 }
717
718 if (IsPipelineTearingDown() && error_caused_teardown_) {
719 // If we are stopping due to SetError(), stop normally instead of
720 // going to error state and calling |error_cb_|. This converts
721 // the teardown in progress from an error teardown into one that acts
722 // like the error never occurred.
723 base::AutoLock auto_lock(lock_);
724 status_ = PIPELINE_OK;
725 error_caused_teardown_ = false;
726 } 690 }
727 691
728 stop_cb_ = stop_cb; 692 stop_cb_ = stop_cb;
729 693
730 stop_pending_ = true; 694 if (IsTransitioning())
731 if (!IsPipelineSeeking() && !IsPipelineTearingDown()) { 695 return;
732 // We will tear down pipeline immediately when there is no seek operation 696
733 // pending and no teardown in progress. This should include the case where 697 DoStateTransition(PIPELINE_OK);
scherkus (not reviewing) 2012/07/28 02:26:07 technically I think I can SetState(kStopping) here
734 // we are partially initialized.
735 TearDownPipeline();
736 }
737 } 698 }
738 699
739 void Pipeline::ErrorChangedTask(PipelineStatus error) { 700 void Pipeline::ErrorChangedTask(PipelineStatus error) {
740 DCHECK(message_loop_->BelongsToCurrentThread()); 701 DCHECK(message_loop_->BelongsToCurrentThread());
741 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; 702 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
742 703
743 // Suppress executing additional error logic. Note that if we are currently 704 // Preserve the error code.
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 s/the/an existing/
744 // performing a normal stop, then we return immediately and continue the 705 if (status_ != PIPELINE_OK)
745 // normal stop. 706 return;
746 if (IsPipelineStopped() || IsPipelineTearingDown()) { 707
747 return;
748 }
749
750 base::AutoLock auto_lock(lock_);
751 status_ = error; 708 status_ = error;
752 709
753 error_caused_teardown_ = true; 710 if (IsTransitioning())
754 711 return;
755 // Posting TearDownPipeline() to message loop so that we can make sure 712
756 // it runs after any pending callbacks that are already queued. 713 DoStateTransition(status_);
757 // |tearing_down_| is set early here to make sure that pending callbacks
758 // don't modify the state before TeadDownPipeline() can run.
759 tearing_down_ = true;
760 message_loop_->PostTask(FROM_HERE, base::Bind(
761 &Pipeline::TearDownPipeline, this));
762 } 714 }
763 715
764 void Pipeline::PlaybackRateChangedTask(float playback_rate) { 716 void Pipeline::PlaybackRateChangedTask(float playback_rate) {
765 DCHECK(message_loop_->BelongsToCurrentThread()); 717 DCHECK(message_loop_->BelongsToCurrentThread());
766 718
767 if (!running_ || tearing_down_) 719 // Suppress rate change until we're playing.
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Is it right to drop this? Or does WMPI already co
768 return; 720 if (state_ != kStarting && state_ != kStarted)
769 721 return;
770 // Suppress rate change until after seeking.
771 if (IsPipelineSeeking()) {
772 pending_playback_rate_ = playback_rate;
773 playback_rate_change_pending_ = true;
774 return;
775 }
776 722
777 { 723 {
778 base::AutoLock auto_lock(lock_); 724 base::AutoLock auto_lock(lock_);
779 clock_->SetPlaybackRate(playback_rate); 725 clock_->SetPlaybackRate(playback_rate);
780 } 726 }
781 727
782 // These will get set after initialization completes in case playback rate is
783 // set prior to initialization.
784 if (demuxer_) 728 if (demuxer_)
785 demuxer_->SetPlaybackRate(playback_rate); 729 demuxer_->SetPlaybackRate(playback_rate);
786 if (audio_renderer_) 730 if (audio_renderer_)
787 audio_renderer_->SetPlaybackRate(playback_rate_); 731 audio_renderer_->SetPlaybackRate(playback_rate_);
788 if (video_renderer_) 732 if (video_renderer_)
789 video_renderer_->SetPlaybackRate(playback_rate_); 733 video_renderer_->SetPlaybackRate(playback_rate_);
790 } 734 }
791 735
792 void Pipeline::VolumeChangedTask(float volume) { 736 void Pipeline::VolumeChangedTask(float volume) {
793 DCHECK(message_loop_->BelongsToCurrentThread()); 737 DCHECK(message_loop_->BelongsToCurrentThread());
794 if (!running_ || tearing_down_) 738
739 // Suppress volume change until we're playing.
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 ditto
740 if (state_ != kStarting && state_ != kStarted)
795 return; 741 return;
796 742
797 if (audio_renderer_) 743 if (audio_renderer_)
798 audio_renderer_->SetVolume(volume); 744 audio_renderer_->SetVolume(volume);
799 } 745 }
800 746
801 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { 747 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
802 DCHECK(message_loop_->BelongsToCurrentThread()); 748 DCHECK(message_loop_->BelongsToCurrentThread());
803 DCHECK(!IsPipelineStopPending());
804 749
805 // Suppress seeking if we're not fully started. 750 // Suppress seeking if we're not fully started.
806 if (state_ != kStarted && state_ != kEnded) { 751 if (state_ != kStarted) {
807 // TODO(scherkus): should we run the callback? I'm tempted to say the API 752 // TODO(scherkus): should we run the callback? I'm tempted to say the API
808 // will only execute the first Seek() request. 753 // will only execute the first Seek() request.
809 DVLOG(1) << "Media pipeline has not started, ignoring seek to " 754 DVLOG(1) << "Media pipeline has not started, ignoring seek to "
810 << time.InMicroseconds() << " (current state: " << state_ << ")"; 755 << time.InMicroseconds() << " (current state: " << state_ << ")";
811 return; 756 return;
812 } 757 }
813 758
814 DCHECK(!seek_pending_);
815 seek_pending_ = true;
816
817 // We'll need to pause every filter before seeking. The state transition 759 // We'll need to pause every filter before seeking. The state transition
818 // is as follows: 760 // is as follows:
819 // kStarted/kEnded
820 // kPausing (for each filter)
821 // kSeeking (for each filter)
822 // kStarting (for each filter)
823 // kStarted 761 // kStarted
824 SetState(kPausing); 762 // kPausing
763 // kSeeking
764 // kStarting
765 // kStarted
825 seek_timestamp_ = std::max(time, demuxer_->GetStartTime()); 766 seek_timestamp_ = std::max(time, demuxer_->GetStartTime());
826 seek_cb_ = seek_cb; 767 seek_cb_ = seek_cb;
827 768
828 // Kick off seeking! 769 // Kick off seeking!
829 { 770 {
830 base::AutoLock auto_lock(lock_); 771 base::AutoLock auto_lock(lock_);
831 if (clock_->IsPlaying()) 772 if (clock_->IsPlaying())
832 clock_->Pause(); 773 clock_->Pause();
833 } 774 }
834 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); 775 SetState(kPausing);
776 DoPause(base::Bind(&Pipeline::OnStateTransition, this, PIPELINE_OK));
835 } 777 }
836 778
837 void Pipeline::OnRendererEndedTask() { 779 void Pipeline::OnRendererEndedTask() {
838 DCHECK(message_loop_->BelongsToCurrentThread()); 780 DCHECK(message_loop_->BelongsToCurrentThread());
839 781
840 // We can only end if we were actually playing. 782 // We can only end if we were actually playing.
841 if (state_ != kStarted) { 783 if (state_ != kStarted) {
842 return; 784 return;
843 } 785 }
844 786
845 DCHECK(audio_renderer_ || video_renderer_); 787 DCHECK(audio_renderer_ || video_renderer_);
846 788
847 // Make sure every extant renderer has ended. 789 // Make sure every extant renderer has ended.
848 if (audio_renderer_ && !audio_disabled_) { 790 if (audio_renderer_ && !audio_disabled_) {
849 if (!audio_renderer_->HasEnded()) { 791 if (!audio_renderer_->HasEnded()) {
850 return; 792 return;
851 } 793 }
852 794
853 // Start clock since there is no more audio to 795 // Start clock since there is no more audio to
854 // trigger clock updates. 796 // trigger clock updates.
855 base::AutoLock auto_lock(lock_); 797 base::AutoLock auto_lock(lock_);
856 clock_->SetMaxTime(clock_->Duration()); 798 clock_->SetMaxTime(clock_->Duration());
857 StartClockIfWaitingForTimeUpdate_Locked(); 799 StartClockIfWaitingForTimeUpdate_Locked();
858 } 800 }
859 801
860 if (video_renderer_ && !video_renderer_->HasEnded()) { 802 if (video_renderer_ && !video_renderer_->HasEnded()) {
861 return; 803 return;
862 } 804 }
863 805
864 // Transition to ended, executing the callback if present.
865 SetState(kEnded);
866 { 806 {
867 base::AutoLock auto_lock(lock_); 807 base::AutoLock auto_lock(lock_);
868 clock_->EndOfStream(); 808 clock_->EndOfStream();
869 } 809 }
870 810
871 ReportStatus(ended_cb_, status_); 811 DCHECK_EQ(status_, PIPELINE_OK);
812 ended_cb_.Run(status_);
872 } 813 }
873 814
874 void Pipeline::AudioDisabledTask() { 815 void Pipeline::AudioDisabledTask() {
875 DCHECK(message_loop_->BelongsToCurrentThread()); 816 DCHECK(message_loop_->BelongsToCurrentThread());
876 817
877 base::AutoLock auto_lock(lock_); 818 base::AutoLock auto_lock(lock_);
878 has_audio_ = false; 819 has_audio_ = false;
879 audio_disabled_ = true; 820 audio_disabled_ = true;
880 821
881 // Notify our demuxer that we're no longer rendering audio. 822 // Notify our demuxer that we're no longer rendering audio.
882 demuxer_->OnAudioRendererDisabled(); 823 demuxer_->OnAudioRendererDisabled();
883 824
884 // Start clock since there is no more audio to 825 // Start clock since there is no more audio to
885 // trigger clock updates. 826 // trigger clock updates.
886 clock_->SetMaxTime(clock_->Duration()); 827 clock_->SetMaxTime(clock_->Duration());
887 StartClockIfWaitingForTimeUpdate_Locked(); 828 StartClockIfWaitingForTimeUpdate_Locked();
888 } 829 }
889 830
890 void Pipeline::FilterStateTransitionTask() { 831 void Pipeline::DoInitDemuxer(const PipelineStatusCB& done_cb) {
891 DCHECK(message_loop_->BelongsToCurrentThread()); 832 DCHECK(message_loop_->BelongsToCurrentThread());
833 DCHECK_EQ(state_, kInitDemuxer);
892 834
893 // No reason transitioning if we've errored or have stopped. 835 demuxer_ = filter_collection_->GetDemuxer();
894 if (IsPipelineStopped()) { 836 if (!demuxer_) {
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 is this something other than programming error? C
837 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
895 return; 838 return;
896 } 839 }
897 840
898 // If we are tearing down, don't allow any state changes. Teardown 841 demuxer_->Initialize(this, done_cb);
899 // state changes will come in via TeardownStateTransitionTask(). 842 }
900 if (IsPipelineTearingDown()) { 843
844 void Pipeline::DoInitAudioDecoder(const PipelineStatusCB& done_cb) {
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Is it right that Pipeline still initializes the de
845 DCHECK(message_loop_->BelongsToCurrentThread());
846 DCHECK_EQ(state_, kInitAudioDecoder);
847
848 scoped_refptr<DemuxerStream> stream =
849 demuxer_->GetStream(DemuxerStream::AUDIO);
850
851 if (!stream) {
852 done_cb.Run(PIPELINE_OK);
901 return; 853 return;
902 } 854 }
903 855
904 if (!TransientState(state_)) { 856 filter_collection_->SelectAudioDecoder(&pipeline_init_state_->audio_decoder);
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 This SelectFoo() api is funny in that it doesn't j
905 NOTREACHED() << "Invalid current state: " << state_; 857 if (!pipeline_init_state_->audio_decoder) {
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 ditto (here and below). Promote to DCHECK?
906 SetError(PIPELINE_ERROR_ABORT); 858 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
907 return; 859 return;
908 } 860 }
909 861
910 // Decrement the number of remaining transitions, making sure to transition 862 pipeline_init_state_->audio_decoder->Initialize(
911 // to the next state if needed. 863 stream, done_cb, base::Bind(&Pipeline::OnUpdateStatistics, this));
912 SetState(FindNextState(state_));
913 if (state_ == kSeeking) {
914 base::AutoLock auto_lock(lock_);
915 clock_->SetTime(seek_timestamp_, seek_timestamp_);
916 }
917
918 // Carry out the action for the current state.
919 if (TransientState(state_)) {
920 if (state_ == kPausing) {
921 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this));
922 } else if (state_ == kFlushing) {
923 DoFlush(base::Bind(&Pipeline::OnFilterStateTransition, this));
924 } else if (state_ == kSeeking) {
925 DoSeek(seek_timestamp_, false,
926 base::Bind(&Pipeline::OnFilterStateTransitionWithStatus, this));
927 } else if (state_ == kStarting) {
928 DoPlay(base::Bind(&Pipeline::OnFilterStateTransition, this));
929 } else if (state_ == kStopping) {
930 DoStop(base::Bind(&Pipeline::OnFilterStateTransition, this));
931 } else {
932 NOTREACHED() << "Unexpected state: " << state_;
933 }
934 } else if (state_ == kStarted) {
935 FinishInitialization();
936
937 // Finally, complete the seek.
938 seek_pending_ = false;
939
940 // If a playback rate change was requested during a seek, do it now that
941 // the seek has compelted.
942 if (playback_rate_change_pending_) {
943 playback_rate_change_pending_ = false;
944 PlaybackRateChangedTask(pending_playback_rate_);
945 }
946
947 base::AutoLock auto_lock(lock_);
948 // We use audio stream to update the clock. So if there is such a stream,
949 // we pause the clock until we receive a valid timestamp.
950 waiting_for_clock_update_ = true;
951 if (!has_audio_) {
952 clock_->SetMaxTime(clock_->Duration());
953 StartClockIfWaitingForTimeUpdate_Locked();
954 }
955
956 if (IsPipelineStopPending()) {
957 // We had a pending stop request need to be honored right now.
958 TearDownPipeline();
959 }
960 } else {
961 NOTREACHED() << "Unexpected state: " << state_;
962 }
963 } 864 }
964 865
965 void Pipeline::TeardownStateTransitionTask() { 866 void Pipeline::DoInitAudioRenderer(const PipelineStatusCB& done_cb) {
966 DCHECK(IsPipelineTearingDown()); 867 DCHECK(message_loop_->BelongsToCurrentThread());
967 switch (state_) { 868 DCHECK_EQ(state_, kInitAudioRenderer);
968 case kStopping:
969 SetState(error_caused_teardown_ ? kError : kStopped);
970 FinishDestroyingFiltersTask();
971 break;
972 case kPausing:
973 SetState(kFlushing);
974 DoFlush(base::Bind(&Pipeline::OnTeardownStateTransition, this));
975 break;
976 case kFlushing:
977 SetState(kStopping);
978 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this));
979 break;
980 869
981 case kCreated: 870 if (!pipeline_init_state_->audio_decoder) {
982 case kError: 871 done_cb.Run(PIPELINE_OK);
983 case kInitDemuxer:
984 case kInitAudioDecoder:
985 case kInitAudioRenderer:
986 case kInitVideoDecoder:
987 case kInitVideoRenderer:
988 case kSeeking:
989 case kStarting:
990 case kStopped:
991 case kStarted:
992 case kEnded:
993 NOTREACHED() << "Unexpected state for teardown: " << state_;
994 break;
995 // default: intentionally left out to force new states to cause compiler
996 // errors.
997 };
998 }
999
1000 void Pipeline::FinishDestroyingFiltersTask() {
1001 DCHECK(message_loop_->BelongsToCurrentThread());
1002 DCHECK(IsPipelineStopped());
1003
1004 audio_renderer_ = NULL;
1005 video_renderer_ = NULL;
1006 demuxer_ = NULL;
1007
1008 if (error_caused_teardown_ && !IsPipelineOk() && !error_cb_.is_null())
1009 error_cb_.Run(status_);
1010
1011 if (stop_pending_) {
1012 stop_pending_ = false;
1013 ResetState();
1014 // Notify the client that stopping has finished.
1015 base::ResetAndReturn(&stop_cb_).Run();
1016 }
1017
1018 tearing_down_ = false;
1019 error_caused_teardown_ = false;
1020 }
1021
1022 void Pipeline::InitializeDemuxer() {
1023 DCHECK(message_loop_->BelongsToCurrentThread());
1024 DCHECK(IsPipelineOk());
1025
1026 demuxer_ = filter_collection_->GetDemuxer();
1027 if (!demuxer_) {
1028 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1029 return; 872 return;
1030 } 873 }
1031 874
1032 demuxer_->Initialize(this, base::Bind(&Pipeline::OnDemuxerInitialized, this)); 875 filter_collection_->SelectAudioRenderer(&audio_renderer_);
1033 } 876 if (!audio_renderer_) {
1034 877 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1035 void Pipeline::OnDemuxerInitialized(PipelineStatus status) {
1036 if (!message_loop_->BelongsToCurrentThread()) {
1037 message_loop_->PostTask(FROM_HERE, base::Bind(
1038 &Pipeline::OnDemuxerInitialized, this, status));
1039 return; 878 return;
1040 } 879 }
1041 880
1042 if (status != PIPELINE_OK) {
1043 SetError(status);
1044 return;
1045 }
1046
1047 {
1048 base::AutoLock auto_lock(lock_);
1049 // We do not want to start the clock running. We only want to set the base
1050 // media time so our timestamp calculations will be correct.
1051 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime());
1052 }
1053
1054 OnFilterInitialize(PIPELINE_OK);
1055 }
1056
1057 bool Pipeline::InitializeAudioDecoder(
1058 const scoped_refptr<Demuxer>& demuxer) {
1059 DCHECK(message_loop_->BelongsToCurrentThread());
1060 DCHECK(IsPipelineOk());
1061 DCHECK(demuxer);
1062
1063 scoped_refptr<DemuxerStream> stream =
1064 demuxer->GetStream(DemuxerStream::AUDIO);
1065
1066 if (!stream)
1067 return false;
1068
1069 filter_collection_->SelectAudioDecoder(&pipeline_init_state_->audio_decoder);
1070
1071 if (!pipeline_init_state_->audio_decoder) {
1072 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1073 return false;
1074 }
1075
1076 pipeline_init_state_->audio_decoder->Initialize(
1077 stream,
1078 base::Bind(&Pipeline::OnFilterInitialize, this),
1079 base::Bind(&Pipeline::OnUpdateStatistics, this));
1080 return true;
1081 }
1082
1083 bool Pipeline::InitializeVideoDecoder(
1084 const scoped_refptr<Demuxer>& demuxer) {
1085 DCHECK(message_loop_->BelongsToCurrentThread());
1086 DCHECK(IsPipelineOk());
1087 DCHECK(demuxer);
1088
1089 scoped_refptr<DemuxerStream> stream =
1090 demuxer->GetStream(DemuxerStream::VIDEO);
1091
1092 if (!stream)
1093 return false;
1094
1095 filter_collection_->SelectVideoDecoder(&pipeline_init_state_->video_decoder);
1096
1097 if (!pipeline_init_state_->video_decoder) {
1098 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1099 return false;
1100 }
1101
1102 pipeline_init_state_->video_decoder->Initialize(
1103 stream,
1104 base::Bind(&Pipeline::OnFilterInitialize, this),
1105 base::Bind(&Pipeline::OnUpdateStatistics, this));
1106
1107 video_decoder_ = pipeline_init_state_->video_decoder;
1108 return true;
1109 }
1110
1111 bool Pipeline::InitializeAudioRenderer(
1112 const scoped_refptr<AudioDecoder>& decoder) {
1113 DCHECK(message_loop_->BelongsToCurrentThread());
1114 DCHECK(IsPipelineOk());
1115
1116 if (!decoder)
1117 return false;
1118
1119 filter_collection_->SelectAudioRenderer(&audio_renderer_);
1120 if (!audio_renderer_) {
1121 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1122 return false;
1123 }
1124
1125 audio_renderer_->Initialize( 881 audio_renderer_->Initialize(
1126 decoder, 882 pipeline_init_state_->audio_decoder,
1127 base::Bind(&Pipeline::OnFilterInitialize, this), 883 done_cb,
1128 base::Bind(&Pipeline::OnAudioUnderflow, this), 884 base::Bind(&Pipeline::OnAudioUnderflow, this),
1129 base::Bind(&Pipeline::OnAudioTimeUpdate, this), 885 base::Bind(&Pipeline::OnAudioTimeUpdate, this),
1130 base::Bind(&Pipeline::OnRendererEnded, this), 886 base::Bind(&Pipeline::OnRendererEnded, this),
1131 base::Bind(&Pipeline::OnAudioDisabled, this), 887 base::Bind(&Pipeline::OnAudioDisabled, this),
1132 base::Bind(&Pipeline::SetError, this)); 888 base::Bind(&Pipeline::SetError, this));
1133 return true;
1134 } 889 }
1135 890
1136 bool Pipeline::InitializeVideoRenderer( 891 void Pipeline::DoInitVideoDecoder(const PipelineStatusCB& done_cb) {
1137 const scoped_refptr<VideoDecoder>& decoder) {
1138 DCHECK(message_loop_->BelongsToCurrentThread()); 892 DCHECK(message_loop_->BelongsToCurrentThread());
1139 DCHECK(IsPipelineOk()); 893 DCHECK_EQ(state_, kInitVideoDecoder);
1140 894
1141 if (!decoder) 895 scoped_refptr<DemuxerStream> stream =
1142 return false; 896 demuxer_->GetStream(DemuxerStream::VIDEO);
897
898 if (!stream) {
899 done_cb.Run(PIPELINE_OK);
900 return;
901 }
902
903 filter_collection_->SelectVideoDecoder(&pipeline_init_state_->video_decoder);
904 if (!pipeline_init_state_->video_decoder) {
905 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
906 return;
907 }
908
909 pipeline_init_state_->video_decoder->Initialize(
910 stream, done_cb, base::Bind(&Pipeline::OnUpdateStatistics, this));
911 }
912
913 void Pipeline::DoInitVideoRenderer(const PipelineStatusCB& done_cb) {
914 DCHECK(message_loop_->BelongsToCurrentThread());
915 DCHECK_EQ(state_, kInitVideoRenderer);
916
917 if (!pipeline_init_state_->video_decoder) {
918 done_cb.Run(PIPELINE_OK);
919 return;
920 }
1143 921
1144 filter_collection_->SelectVideoRenderer(&video_renderer_); 922 filter_collection_->SelectVideoRenderer(&video_renderer_);
1145 if (!video_renderer_) { 923 if (!video_renderer_) {
1146 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); 924 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1147 return false; 925 return;
1148 } 926 }
1149 927
928 // TODO(scherkus): This is for PrepareForShutdownHack(), see
929 // http://crbug.com/110228 for details.
930 video_decoder_ = pipeline_init_state_->video_decoder;
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Drop this member.
931
1150 video_renderer_->Initialize( 932 video_renderer_->Initialize(
1151 decoder, 933 video_decoder_,
1152 base::Bind(&Pipeline::OnFilterInitialize, this), 934 done_cb,
1153 base::Bind(&Pipeline::OnUpdateStatistics, this), 935 base::Bind(&Pipeline::OnUpdateStatistics, this),
1154 base::Bind(&Pipeline::OnVideoTimeUpdate, this), 936 base::Bind(&Pipeline::OnVideoTimeUpdate, this),
1155 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), 937 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this),
1156 base::Bind(&Pipeline::OnRendererEnded, this), 938 base::Bind(&Pipeline::OnRendererEnded, this),
1157 base::Bind(&Pipeline::SetError, this), 939 base::Bind(&Pipeline::SetError, this),
1158 base::Bind(&Pipeline::GetMediaTime, this), 940 base::Bind(&Pipeline::GetMediaTime, this),
1159 base::Bind(&Pipeline::GetMediaDuration, this)); 941 base::Bind(&Pipeline::GetMediaDuration, this));
1160 return true;
1161 } 942 }
1162 943
1163 void Pipeline::TearDownPipeline() { 944 void Pipeline::DoSeek(bool skip_demuxer_seek,
1164 DCHECK(message_loop_->BelongsToCurrentThread());
1165 DCHECK_NE(kStopped, state_);
1166
1167 DCHECK(!tearing_down_ || // Teardown on Stop().
1168 (tearing_down_ && error_caused_teardown_) || // Teardown on error.
1169 (tearing_down_ && stop_pending_)); // Stop during teardown by error.
1170
1171 // Mark that we already start tearing down operation.
1172 tearing_down_ = true;
1173
1174 switch (state_) {
1175 case kCreated:
1176 case kError:
1177 SetState(kStopped);
1178 // Need to put this in the message loop to make sure that it comes
1179 // after any pending callback tasks that are already queued.
1180 message_loop_->PostTask(FROM_HERE, base::Bind(
1181 &Pipeline::FinishDestroyingFiltersTask, this));
1182 break;
1183
1184 case kInitDemuxer:
1185 case kInitAudioDecoder:
1186 case kInitAudioRenderer:
1187 case kInitVideoDecoder:
1188 case kInitVideoRenderer:
1189 // Make it look like initialization was successful.
1190 filter_collection_.reset();
1191 pipeline_init_state_.reset();
1192
1193 SetState(kStopping);
1194 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this));
1195
1196 FinishInitialization();
1197 break;
1198
1199 case kPausing:
1200 case kSeeking:
1201 case kFlushing:
1202 case kStarting:
1203 SetState(kStopping);
1204 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this));
1205
1206 if (seek_pending_) {
1207 seek_pending_ = false;
1208 FinishInitialization();
1209 }
1210
1211 break;
1212
1213 case kStarted:
1214 case kEnded:
1215 SetState(kPausing);
1216 DoPause(base::Bind(&Pipeline::OnTeardownStateTransition, this));
1217 break;
1218
1219 case kStopping:
1220 case kStopped:
1221 NOTREACHED() << "Unexpected state for teardown: " << state_;
1222 break;
1223 // default: intentionally left out to force new states to cause compiler
1224 // errors.
1225 };
1226 }
1227
1228 void Pipeline::DoSeek(base::TimeDelta seek_timestamp,
1229 bool skip_demuxer_seek,
1230 const PipelineStatusCB& done_cb) { 945 const PipelineStatusCB& done_cb) {
1231 DCHECK(message_loop_->BelongsToCurrentThread()); 946 DCHECK(message_loop_->BelongsToCurrentThread());
947 DCHECK(state_ == kSeeking);
948 DCHECK(seek_timestamp_ != kNoTimestamp());
949
950 {
951 base::AutoLock l(lock_);
952 clock_->SetTime(seek_timestamp_, seek_timestamp_);
953 }
954
1232 scoped_ptr<std::queue<PipelineStatusCBFunc> > status_cbs( 955 scoped_ptr<std::queue<PipelineStatusCBFunc> > status_cbs(
1233 new std::queue<PipelineStatusCBFunc>()); 956 new std::queue<PipelineStatusCBFunc>());
1234 957
1235 if (!skip_demuxer_seek) 958 if (!skip_demuxer_seek)
Ami GONE FROM CHROMIUM 2012/07/30 04:06:21 Is this really important? What happens if we don'
1236 status_cbs->push(base::Bind(&Demuxer::Seek, demuxer_, seek_timestamp)); 959 status_cbs->push(base::Bind(&Demuxer::Seek, demuxer_, seek_timestamp_));
1237 960
1238 if (audio_renderer_) 961 if (audio_renderer_)
1239 status_cbs->push(base::Bind( 962 status_cbs->push(base::Bind(
1240 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); 963 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp_));
1241 964
1242 if (video_renderer_) 965 if (video_renderer_)
1243 status_cbs->push(base::Bind( 966 status_cbs->push(base::Bind(
1244 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); 967 &VideoRenderer::Preroll, video_renderer_, seek_timestamp_));
1245 968
1246 RunInSeriesWithStatus(status_cbs.Pass(), base::Bind( 969 RunInSeriesWithStatus(status_cbs.Pass(), done_cb);
1247 &Pipeline::ReportStatus, this, done_cb));
1248 } 970 }
1249 971
1250 void Pipeline::OnAudioUnderflow() { 972 void Pipeline::OnAudioUnderflow() {
1251 if (!message_loop_->BelongsToCurrentThread()) { 973 if (!message_loop_->BelongsToCurrentThread()) {
1252 message_loop_->PostTask(FROM_HERE, base::Bind( 974 message_loop_->PostTask(FROM_HERE, base::Bind(
1253 &Pipeline::OnAudioUnderflow, this)); 975 &Pipeline::OnAudioUnderflow, this));
1254 return; 976 return;
1255 } 977 }
1256 978
1257 if (state_ != kStarted) 979 if (state_ != kStarted)
1258 return; 980 return;
1259 981
1260 if (audio_renderer_) 982 if (audio_renderer_)
1261 audio_renderer_->ResumeAfterUnderflow(true); 983 audio_renderer_->ResumeAfterUnderflow(true);
1262 } 984 }
1263 985
1264 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { 986 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() {
1265 lock_.AssertAcquired(); 987 lock_.AssertAcquired();
1266 if (!waiting_for_clock_update_) 988 if (!waiting_for_clock_update_)
1267 return; 989 return;
1268 990
1269 waiting_for_clock_update_ = false; 991 waiting_for_clock_update_ = false;
1270 clock_->Play(); 992 clock_->Play();
1271 } 993 }
1272 994
1273 } // namespace media 995 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698