Chromium Code Reviews| OLD | NEW |
|---|---|
| 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_number_conversions.h" | 16 #include "base/string_number_conversions.h" |
| 17 #include "base/string_util.h" | 17 #include "base/string_util.h" |
| 18 #include "base/synchronization/condition_variable.h" | 18 #include "base/synchronization/condition_variable.h" |
| 19 #include "media/base/audio_decoder.h" | 19 #include "media/base/audio_decoder.h" |
| 20 #include "media/base/audio_renderer.h" | 20 #include "media/base/audio_renderer.h" |
| 21 #include "media/base/buffers.h" | |
| 22 #include "media/base/clock.h" | 21 #include "media/base/clock.h" |
| 23 #include "media/base/filter_collection.h" | 22 #include "media/base/filter_collection.h" |
| 24 #include "media/base/media_log.h" | 23 #include "media/base/media_log.h" |
| 25 #include "media/base/video_decoder.h" | 24 #include "media/base/video_decoder.h" |
| 26 #include "media/base/video_decoder_config.h" | 25 #include "media/base/video_decoder_config.h" |
| 27 #include "media/base/video_renderer.h" | 26 #include "media/base/video_renderer.h" |
| 28 | 27 |
| 29 using base::TimeDelta; | 28 using base::TimeDelta; |
| 30 | 29 |
| 31 namespace media { | 30 namespace media { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 56 while (!notified_) | 55 while (!notified_) |
| 57 cv_.Wait(); | 56 cv_.Wait(); |
| 58 } | 57 } |
| 59 | 58 |
| 60 media::PipelineStatus PipelineStatusNotification::status() { | 59 media::PipelineStatus PipelineStatusNotification::status() { |
| 61 base::AutoLock auto_lock(lock_); | 60 base::AutoLock auto_lock(lock_); |
| 62 DCHECK(notified_); | 61 DCHECK(notified_); |
| 63 return status_; | 62 return status_; |
| 64 } | 63 } |
| 65 | 64 |
| 66 struct Pipeline::PipelineInitState { | |
| 67 scoped_refptr<AudioDecoder> audio_decoder; | |
| 68 }; | |
| 69 | |
| 70 Pipeline::Pipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop, | 65 Pipeline::Pipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop, |
| 71 MediaLog* media_log) | 66 MediaLog* media_log) |
| 72 : message_loop_(message_loop), | 67 : message_loop_(message_loop), |
| 73 media_log_(media_log), | 68 media_log_(media_log), |
| 74 running_(false), | 69 running_(false), |
| 75 seek_pending_(false), | |
| 76 tearing_down_(false), | |
| 77 playback_rate_change_pending_(false), | |
| 78 did_loading_progress_(false), | 70 did_loading_progress_(false), |
| 79 total_bytes_(0), | 71 total_bytes_(0), |
| 80 natural_size_(0, 0), | 72 natural_size_(0, 0), |
| 81 volume_(1.0f), | 73 volume_(1.0f), |
| 82 playback_rate_(0.0f), | 74 playback_rate_(0.0f), |
| 83 pending_playback_rate_(0.0f), | |
| 84 clock_(new Clock(&base::Time::Now)), | 75 clock_(new Clock(&base::Time::Now)), |
| 85 waiting_for_clock_update_(false), | 76 waiting_for_clock_update_(false), |
| 86 status_(PIPELINE_OK), | 77 status_(PIPELINE_OK), |
| 87 has_audio_(false), | 78 has_audio_(false), |
| 88 has_video_(false), | 79 has_video_(false), |
| 89 state_(kCreated), | 80 state_(kCreated), |
| 90 seek_timestamp_(kNoTimestamp()), | |
| 91 audio_ended_(false), | 81 audio_ended_(false), |
| 92 video_ended_(false), | 82 video_ended_(false), |
| 93 audio_disabled_(false), | 83 audio_disabled_(false), |
| 94 creation_time_(base::Time::Now()) { | 84 creation_time_(base::Time::Now()) { |
| 95 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); | 85 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); |
| 96 media_log_->AddEvent( | 86 media_log_->AddEvent( |
| 97 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); | 87 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); |
| 98 } | 88 } |
| 99 | 89 |
| 100 Pipeline::~Pipeline() { | 90 Pipeline::~Pipeline() { |
| 101 base::AutoLock auto_lock(lock_); | |
| 102 DCHECK(!running_) << "Stop() must complete before destroying object"; | 91 DCHECK(!running_) << "Stop() must complete before destroying object"; |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
Should this assert running on a particular thread?
scherkus (not reviewing)
2012/09/06 15:33:20
That would be a nice thing! Here's hoping it doesn
| |
| 103 DCHECK(stop_cb_.is_null()); | 92 DCHECK(stop_cb_.is_null()); |
| 104 DCHECK(!seek_pending_); | 93 DCHECK(seek_cb_.is_null()); |
| 105 | 94 |
| 106 media_log_->AddEvent( | 95 media_log_->AddEvent( |
| 107 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); | 96 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); |
| 108 } | 97 } |
| 109 | 98 |
| 110 void Pipeline::Start(scoped_ptr<FilterCollection> collection, | 99 void Pipeline::Start(scoped_ptr<FilterCollection> collection, |
| 111 const PipelineStatusCB& ended_cb, | 100 const PipelineStatusCB& ended_cb, |
| 112 const PipelineStatusCB& error_cb, | 101 const PipelineStatusCB& error_cb, |
| 113 const PipelineStatusCB& seek_cb, | 102 const PipelineStatusCB& seek_cb, |
| 114 const BufferingStateCB& buffering_state_cb) { | 103 const BufferingStateCB& buffering_state_cb) { |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 249 SetError(status); | 238 SetError(status); |
| 250 } | 239 } |
| 251 | 240 |
| 252 void Pipeline::SetState(State next_state) { | 241 void Pipeline::SetState(State next_state) { |
| 253 if (state_ != kStarted && next_state == kStarted && | 242 if (state_ != kStarted && next_state == kStarted && |
| 254 !creation_time_.is_null()) { | 243 !creation_time_.is_null()) { |
| 255 UMA_HISTOGRAM_TIMES( | 244 UMA_HISTOGRAM_TIMES( |
| 256 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); | 245 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); |
| 257 creation_time_ = base::Time(); | 246 creation_time_ = base::Time(); |
| 258 } | 247 } |
| 248 | |
| 249 DVLOG(2) << GetStateString(state_) << " -> " << GetStateString(next_state); | |
| 250 | |
| 259 state_ = next_state; | 251 state_ = next_state; |
| 260 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); | 252 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); |
| 261 } | 253 } |
| 262 | 254 |
| 263 bool Pipeline::IsPipelineOk() { | 255 #define RETURN_STRING(state) case state: return #state; |
| 264 base::AutoLock auto_lock(lock_); | 256 |
| 265 return status_ == PIPELINE_OK; | 257 const char* Pipeline::GetStateString(State state) { |
| 258 switch (state) { | |
| 259 RETURN_STRING(kCreated); | |
| 260 RETURN_STRING(kInitDemuxer); | |
| 261 RETURN_STRING(kInitAudioDecoder); | |
| 262 RETURN_STRING(kInitAudioRenderer); | |
| 263 RETURN_STRING(kInitVideoRenderer); | |
| 264 RETURN_STRING(kInitPrerolling); | |
| 265 RETURN_STRING(kSeeking); | |
| 266 RETURN_STRING(kStarting); | |
| 267 RETURN_STRING(kStarted); | |
| 268 RETURN_STRING(kStopping); | |
| 269 RETURN_STRING(kStopped); | |
| 270 } | |
| 271 NOTREACHED(); | |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
Is it not possible to omit this and the next lines
scherkus (not reviewing)
2012/09/06 15:33:20
gcc complains about reaching end of non-void funct
| |
| 272 return "INVALID"; | |
| 266 } | 273 } |
| 267 | 274 |
| 268 bool Pipeline::IsPipelineSeeking() { | 275 #undef RETURN_STRING |
| 276 | |
| 277 Pipeline::State Pipeline::GetNextState() const { | |
| 269 DCHECK(message_loop_->BelongsToCurrentThread()); | 278 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 270 if (!seek_pending_) | 279 DCHECK(stop_cb_.is_null()) |
| 271 return false; | 280 << "State transitions don't happen when stopping"; |
| 272 DCHECK(kSeeking == state_ || kPausing == state_ || | 281 DCHECK_EQ(status_, PIPELINE_OK) |
| 273 kFlushing == state_ || kStarting == state_) | 282 << "State transitions don't happen when there's an error: " << status_; |
| 274 << "Current state : " << state_; | |
| 275 return true; | |
| 276 } | |
| 277 | 283 |
| 278 void Pipeline::ReportStatus(const PipelineStatusCB& cb, PipelineStatus status) { | 284 switch (state_) { |
| 279 DCHECK(message_loop_->BelongsToCurrentThread()); | 285 case kCreated: |
| 280 if (cb.is_null()) | 286 return kInitDemuxer; |
| 281 return; | 287 case kInitDemuxer: |
| 282 cb.Run(status); | 288 return kInitAudioDecoder; |
| 283 // Prevent double-reporting of errors to clients. | 289 case kInitAudioDecoder: |
| 284 if (status != PIPELINE_OK) | 290 return kInitAudioRenderer; |
| 285 error_cb_.Reset(); | 291 case kInitAudioRenderer: |
| 286 } | 292 return kInitVideoRenderer; |
| 293 case kInitVideoRenderer: | |
| 294 return kInitPrerolling; | |
| 295 case kInitPrerolling: | |
| 296 return kStarting; | |
| 297 case kSeeking: | |
| 298 return kStarting; | |
| 299 case kStarting: | |
| 300 return kStarted; | |
| 287 | 301 |
| 288 void Pipeline::FinishSeek() { | 302 case kStarted: |
| 289 DCHECK(message_loop_->BelongsToCurrentThread()); | 303 case kStopping: |
| 290 seek_timestamp_ = kNoTimestamp(); | 304 case kStopped: |
| 291 seek_pending_ = false; | 305 NOTREACHED() << "State has no transition: " << state_; |
| 292 | 306 return state_; |
| 293 // Execute the seek callback, if present. Note that this might be the | |
| 294 // initial callback passed into Start(). | |
| 295 ReportStatus(seek_cb_, status_); | |
| 296 seek_cb_.Reset(); | |
| 297 } | |
| 298 | |
| 299 // static | |
| 300 bool Pipeline::TransientState(State state) { | |
| 301 return state == kPausing || | |
| 302 state == kFlushing || | |
| 303 state == kSeeking || | |
| 304 state == kStarting || | |
| 305 state == kStopping; | |
| 306 } | |
| 307 | |
| 308 // static | |
| 309 Pipeline::State Pipeline::FindNextState(State current) { | |
| 310 // TODO(scherkus): refactor InitializeTask() to make use of this function. | |
| 311 if (current == kPausing) { | |
| 312 return kFlushing; | |
| 313 } else if (current == kFlushing) { | |
| 314 // We will always honor Seek() before Stop(). This is based on the | |
| 315 // assumption that we never accept Seek() after Stop(). | |
| 316 DCHECK(IsPipelineSeeking() || | |
| 317 !stop_cb_.is_null() || | |
| 318 tearing_down_); | |
| 319 return IsPipelineSeeking() ? kSeeking : kStopping; | |
| 320 } else if (current == kSeeking) { | |
| 321 return kStarting; | |
| 322 } else if (current == kStarting) { | |
| 323 return kStarted; | |
| 324 } else if (current == kStopping) { | |
| 325 return kStopped; | |
| 326 } else { | |
| 327 return current; | |
| 328 } | 307 } |
| 329 } | 308 } |
| 330 | 309 |
| 331 void Pipeline::OnDemuxerError(PipelineStatus error) { | 310 void Pipeline::OnDemuxerError(PipelineStatus error) { |
| 332 SetError(error); | 311 SetError(error); |
| 333 } | 312 } |
| 334 | 313 |
| 335 void Pipeline::SetError(PipelineStatus error) { | 314 void Pipeline::SetError(PipelineStatus error) { |
| 336 DCHECK(IsRunning()); | 315 DCHECK(IsRunning()); |
| 337 DCHECK_NE(PIPELINE_OK, error); | 316 DCHECK_NE(PIPELINE_OK, error); |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 415 // Since the byte->time calculation is approximate, fudge the beginning & | 394 // Since the byte->time calculation is approximate, fudge the beginning & |
| 416 // ending areas to look better. | 395 // ending areas to look better. |
| 417 TimeDelta epsilon = clock_->Duration() / 100; | 396 TimeDelta epsilon = clock_->Duration() / 100; |
| 418 if (time_offset < epsilon) | 397 if (time_offset < epsilon) |
| 419 return TimeDelta(); | 398 return TimeDelta(); |
| 420 if (time_offset + epsilon > clock_->Duration()) | 399 if (time_offset + epsilon > clock_->Duration()) |
| 421 return clock_->Duration(); | 400 return clock_->Duration(); |
| 422 return time_offset; | 401 return time_offset; |
| 423 } | 402 } |
| 424 | 403 |
| 425 void Pipeline::DoPause(const PipelineStatusCB& done_cb) { | 404 void Pipeline::OnStateTransition(PipelineStatus status) { |
| 405 // Force post to process state transitions after current execution frame. | |
| 406 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 407 &Pipeline::StateTransitionTask, this, status)); | |
| 408 } | |
| 409 | |
| 410 void Pipeline::StateTransitionTask(PipelineStatus status) { | |
| 411 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 412 | |
| 413 // Preserve existing abnormal status, otherwise update based on the result of | |
| 414 // the previous operation. | |
| 415 status_ = (status_ != PIPELINE_OK ? status_ : status); | |
| 416 | |
| 417 // No-op any state transitions if we're stopping. | |
| 418 if (state_ == kStopping || state_ == kStopped) | |
| 419 return; | |
| 420 | |
| 421 if (status_ != PIPELINE_OK) { | |
| 422 ErrorChangedTask(status_); | |
| 423 return; | |
| 424 } | |
| 425 | |
| 426 // TODO(scherkus): not every state transition uses |pending_callbacks_| today. | |
|
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
nit: I think this comment needs to be changed into
scherkus (not reviewing)
2012/09/06 15:33:20
Done.
| |
| 427 if (state_ == kInitPrerolling || state_ == kStarting || state_ == kSeeking) { | |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
I think what you really want is the single-stateme
scherkus (not reviewing)
2012/09/06 15:33:20
The comment I can agree with but the resulting DCH
| |
| 428 DCHECK(pending_callbacks_.get()) | |
| 429 << "pending_callbacks_ should exist when in state " << state_; | |
| 430 } else { | |
| 431 DCHECK(!pending_callbacks_.get()) | |
| 432 << "pending_callbacks_ should not be used when in state " << state_; | |
| 433 } | |
| 434 pending_callbacks_.reset(); | |
| 435 | |
| 436 PipelineStatusCB done_cb = base::Bind(&Pipeline::OnStateTransition, this); | |
| 437 | |
| 438 SetState(GetNextState()); | |
| 439 switch (state_) { | |
| 440 case kInitDemuxer: | |
| 441 return InitializeDemuxer(done_cb); | |
| 442 | |
| 443 case kInitAudioDecoder: | |
| 444 { | |
| 445 base::AutoLock l(lock_); | |
| 446 // We do not want to start the clock running. We only want to set the | |
| 447 // base media time so our timestamp calculations will be correct. | |
| 448 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime()); | |
| 449 } | |
| 450 return InitializeAudioDecoder(done_cb); | |
| 451 | |
| 452 case kInitAudioRenderer: | |
| 453 return InitializeAudioRenderer(done_cb); | |
| 454 | |
| 455 case kInitVideoRenderer: | |
| 456 return InitializeVideoRenderer(done_cb); | |
| 457 | |
| 458 case kInitPrerolling: | |
| 459 filter_collection_.reset(); | |
| 460 { | |
| 461 base::AutoLock l(lock_); | |
| 462 // TODO(scherkus): |has_audio_| should be true no matter what -- | |
| 463 // otherwise people with muted/disabled sound cards will make our | |
| 464 // default controls look as if every video doesn't contain an audio | |
| 465 // track. | |
| 466 has_audio_ = audio_renderer_ && !audio_disabled_; | |
| 467 has_video_ = video_renderer_; | |
|
scherkus (not reviewing)
2012/09/05 15:19:30
FYI:
E:\b\build\slave\win\build\src\media\base\pip
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
I'd prefer video_renderer_ != NULL since that matc
scherkus (not reviewing)
2012/09/06 15:33:20
Done.
| |
| 468 } | |
| 469 if (!audio_renderer_ && !video_renderer_) { | |
| 470 done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER); | |
| 471 return; | |
| 472 } | |
| 473 | |
| 474 buffering_state_cb_.Run(kHaveMetadata); | |
| 475 | |
| 476 return DoInitialPreroll(done_cb); | |
| 477 | |
| 478 case kStarting: | |
| 479 return DoPlay(done_cb); | |
| 480 | |
| 481 case kStarted: | |
| 482 { | |
| 483 base::AutoLock l(lock_); | |
| 484 // We use audio stream to update the clock. So if there is such a | |
| 485 // stream, we pause the clock until we receive a valid timestamp. | |
| 486 waiting_for_clock_update_ = true; | |
| 487 if (!has_audio_) { | |
| 488 clock_->SetMaxTime(clock_->Duration()); | |
| 489 StartClockIfWaitingForTimeUpdate_Locked(); | |
| 490 } | |
| 491 } | |
| 492 | |
| 493 // Fire canplaythrough immediately after playback begins because of | |
| 494 // crbug.com/106480. | |
| 495 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. | |
| 496 if (status_ == PIPELINE_OK) | |
|
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
Based on the DCHECK on line 500, I believe this co
scherkus (not reviewing)
2012/09/06 15:33:20
Done.
| |
| 497 buffering_state_cb_.Run(kPrerollCompleted); | |
| 498 | |
| 499 DCHECK(!seek_cb_.is_null()); | |
| 500 DCHECK_EQ(status_, PIPELINE_OK); | |
| 501 return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); | |
| 502 | |
| 503 case kStopping: | |
| 504 case kStopped: | |
| 505 case kCreated: | |
| 506 case kSeeking: | |
| 507 NOTREACHED() << "State has no transition: " << state_; | |
| 508 return; | |
| 509 } | |
| 510 } | |
| 511 | |
| 512 static void SetPlaybackRateForPreroll( | |
| 513 const scoped_refptr<AudioRenderer>& audio_renderer, | |
| 514 float playback_rate, | |
| 515 const base::Closure& done_cb) { | |
| 516 audio_renderer->SetPlaybackRate(playback_rate); | |
|
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
This doesn't seem right. Won't this cause the audi
scherkus (not reviewing)
2012/09/06 15:33:20
Removed entirely -- see response in pipeline_unitt
| |
| 517 done_cb.Run(); | |
| 518 } | |
| 519 | |
| 520 void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) { | |
| 426 DCHECK(message_loop_->BelongsToCurrentThread()); | 521 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 427 DCHECK(!pending_callbacks_.get()); | 522 DCHECK(!pending_callbacks_.get()); |
| 428 SerialRunner::Queue bound_fns; | 523 SerialRunner::Queue bound_fns; |
| 429 | 524 |
| 430 if (audio_renderer_) | 525 base::TimeDelta seek_timestamp = demuxer_->GetStartTime(); |
| 431 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_)); | |
| 432 | 526 |
| 433 if (video_renderer_) | 527 // Preroll renderers. |
| 434 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_)); | 528 if (audio_renderer_) { |
| 529 bound_fns.Push(base::Bind( | |
| 530 &SetPlaybackRateForPreroll, audio_renderer_, GetPlaybackRate())); | |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
Is there some reason you can't set the playbackrat
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
I don't think this is right. I think you should se
scherkus (not reviewing)
2012/09/06 15:33:20
Removed entirely -- see response in pipeline_unitt
| |
| 531 bound_fns.Push(base::Bind( | |
| 532 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 533 } | |
| 534 | |
| 535 if (video_renderer_) { | |
| 536 bound_fns.Push(base::Bind( | |
| 537 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 538 } | |
| 435 | 539 |
| 436 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 540 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 437 } | 541 } |
| 438 | 542 |
| 439 void Pipeline::DoFlush(const PipelineStatusCB& done_cb) { | 543 void Pipeline::DoSeek( |
| 544 base::TimeDelta seek_timestamp, | |
| 545 const PipelineStatusCB& done_cb) { | |
| 440 DCHECK(message_loop_->BelongsToCurrentThread()); | 546 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 441 DCHECK(!pending_callbacks_.get()); | 547 DCHECK(!pending_callbacks_.get()); |
| 442 SerialRunner::Queue bound_fns; | 548 SerialRunner::Queue bound_fns; |
| 443 | 549 |
| 550 // Pause. | |
| 551 if (audio_renderer_) | |
| 552 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_)); | |
| 553 if (video_renderer_) | |
| 554 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_)); | |
| 555 | |
| 556 // Flush. | |
| 444 if (audio_renderer_) | 557 if (audio_renderer_) |
| 445 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); | 558 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); |
| 446 | |
| 447 if (video_renderer_) | 559 if (video_renderer_) |
| 448 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_)); | 560 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_)); |
| 449 | 561 |
| 562 // Seek demuxer. | |
| 563 bound_fns.Push(base::Bind( | |
| 564 &Demuxer::Seek, demuxer_, seek_timestamp)); | |
| 565 | |
| 566 // Preroll renderers. | |
| 567 if (audio_renderer_) { | |
|
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
How about creating a helper function for DoSeek()
scherkus (not reviewing)
2012/09/06 15:33:20
I think the tiny bit of duplication here is OK --
| |
| 568 bound_fns.Push(base::Bind( | |
| 569 &SetPlaybackRateForPreroll, audio_renderer_, GetPlaybackRate())); | |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
ditto
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
I don't think you want to bind to a static functio
scherkus (not reviewing)
2012/09/06 15:33:20
Removed entirely -- see response in pipeline_unitt
| |
| 570 bound_fns.Push(base::Bind( | |
| 571 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 572 } | |
| 573 | |
| 574 if (video_renderer_) { | |
| 575 bound_fns.Push(base::Bind( | |
| 576 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 577 } | |
| 578 | |
| 450 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 579 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 451 } | 580 } |
| 452 | 581 |
| 453 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { | 582 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { |
| 454 DCHECK(message_loop_->BelongsToCurrentThread()); | 583 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 455 DCHECK(!pending_callbacks_.get()); | 584 DCHECK(!pending_callbacks_.get()); |
| 456 SerialRunner::Queue bound_fns; | 585 SerialRunner::Queue bound_fns; |
| 457 | 586 |
| 587 PlaybackRateChangedTask(GetPlaybackRate()); | |
| 588 VolumeChangedTask(GetVolume()); | |
| 589 | |
| 458 if (audio_renderer_) | 590 if (audio_renderer_) |
| 459 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_)); | 591 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_)); |
| 460 | 592 |
| 461 if (video_renderer_) | 593 if (video_renderer_) |
| 462 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_)); | 594 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_)); |
| 463 | 595 |
| 464 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 596 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 465 } | 597 } |
| 466 | 598 |
| 467 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { | 599 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { |
| 468 DCHECK(message_loop_->BelongsToCurrentThread()); | 600 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 469 DCHECK(!pending_callbacks_.get()); | 601 DCHECK(!pending_callbacks_.get()); |
| 470 SerialRunner::Queue bound_fns; | 602 SerialRunner::Queue bound_fns; |
| 471 | 603 |
| 472 if (demuxer_) | 604 if (demuxer_) |
| 473 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_)); | 605 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_)); |
| 474 | 606 |
| 475 if (audio_renderer_) | 607 if (audio_renderer_) |
| 476 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); | 608 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); |
| 477 | 609 |
| 478 if (video_renderer_) | 610 if (video_renderer_) |
| 479 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_)); | 611 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_)); |
| 480 | 612 |
| 481 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 613 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 482 } | 614 } |
| 483 | 615 |
| 616 void Pipeline::OnStopCompleted(PipelineStatus status) { | |
| 617 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 618 DCHECK_EQ(state_, kStopping); | |
| 619 { | |
| 620 base::AutoLock l(lock_); | |
| 621 running_ = false; | |
| 622 } | |
| 623 | |
| 624 SetState(kStopped); | |
| 625 pending_callbacks_.reset(); | |
| 626 filter_collection_.reset(); | |
| 627 audio_decoder_ = NULL; | |
| 628 audio_renderer_ = NULL; | |
| 629 video_renderer_ = NULL; | |
| 630 demuxer_ = NULL; | |
| 631 | |
| 632 // If we stop during initialization/seeking we want to run |seek_cb_| | |
| 633 // followed by |stop_cb_| so we don't leave outstanding callbacks around. | |
| 634 if (!seek_cb_.is_null()) { | |
| 635 base::ResetAndReturn(&seek_cb_).Run(status_); | |
| 636 error_cb_.Reset(); | |
| 637 } | |
| 638 if (!stop_cb_.is_null()) { | |
| 639 base::ResetAndReturn(&stop_cb_).Run(); | |
| 640 error_cb_.Reset(); | |
| 641 } | |
| 642 if (!error_cb_.is_null()) { | |
| 643 DCHECK_NE(status_, PIPELINE_OK); | |
| 644 base::ResetAndReturn(&error_cb_).Run(status_); | |
| 645 } | |
| 646 } | |
| 647 | |
| 484 void Pipeline::AddBufferedByteRange(int64 start, int64 end) { | 648 void Pipeline::AddBufferedByteRange(int64 start, int64 end) { |
| 485 DCHECK(IsRunning()); | 649 DCHECK(IsRunning()); |
| 486 base::AutoLock auto_lock(lock_); | 650 base::AutoLock auto_lock(lock_); |
| 487 buffered_byte_ranges_.Add(start, end); | 651 buffered_byte_ranges_.Add(start, end); |
| 488 did_loading_progress_ = true; | 652 did_loading_progress_ = true; |
| 489 } | 653 } |
| 490 | 654 |
| 491 void Pipeline::AddBufferedTimeRange(base::TimeDelta start, | 655 void Pipeline::AddBufferedTimeRange(base::TimeDelta start, |
| 492 base::TimeDelta end) { | 656 base::TimeDelta end) { |
| 493 DCHECK(IsRunning()); | 657 DCHECK(IsRunning()); |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 513 } | 677 } |
| 514 | 678 |
| 515 void Pipeline::OnVideoRendererEnded() { | 679 void Pipeline::OnVideoRendererEnded() { |
| 516 // Force post to process ended messages after current execution frame. | 680 // Force post to process ended messages after current execution frame. |
| 517 message_loop_->PostTask(FROM_HERE, base::Bind( | 681 message_loop_->PostTask(FROM_HERE, base::Bind( |
| 518 &Pipeline::DoVideoRendererEnded, this)); | 682 &Pipeline::DoVideoRendererEnded, this)); |
| 519 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED)); | 683 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED)); |
| 520 } | 684 } |
| 521 | 685 |
| 522 // Called from any thread. | 686 // Called from any thread. |
| 523 void Pipeline::OnFilterInitialize(PipelineStatus status) { | |
| 524 // Continue the initialize task by proceeding to the next stage. | |
| 525 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 526 &Pipeline::InitializeTask, this, status)); | |
| 527 } | |
| 528 | |
| 529 // Called from any thread. | |
| 530 // This method makes the PipelineStatusCB behave like a Closure. It | |
| 531 // makes it look like a host()->SetError() call followed by a call to | |
| 532 // OnFilterStateTransition() when errors occur. | |
| 533 // | |
| 534 // TODO(scherkus): Revisit this code when SetError() is removed from FilterHost | |
| 535 // and all the Closures are converted to PipelineStatusCB. | |
| 536 void Pipeline::OnFilterStateTransition(PipelineStatus status) { | |
| 537 if (status != PIPELINE_OK) | |
| 538 SetError(status); | |
| 539 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 540 &Pipeline::FilterStateTransitionTask, this)); | |
| 541 } | |
| 542 | |
| 543 void Pipeline::OnTeardownStateTransition(PipelineStatus status) { | |
| 544 // Ignore any errors during teardown. | |
| 545 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 546 &Pipeline::TeardownStateTransitionTask, this)); | |
| 547 } | |
| 548 | |
| 549 // Called from any thread. | |
| 550 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { | 687 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { |
| 551 base::AutoLock auto_lock(lock_); | 688 base::AutoLock auto_lock(lock_); |
| 552 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; | 689 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; |
| 553 statistics_.video_bytes_decoded += stats.video_bytes_decoded; | 690 statistics_.video_bytes_decoded += stats.video_bytes_decoded; |
| 554 statistics_.video_frames_decoded += stats.video_frames_decoded; | 691 statistics_.video_frames_decoded += stats.video_frames_decoded; |
| 555 statistics_.video_frames_dropped += stats.video_frames_dropped; | 692 statistics_.video_frames_dropped += stats.video_frames_dropped; |
| 556 } | 693 } |
| 557 | 694 |
| 558 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, | 695 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, |
| 559 const PipelineStatusCB& ended_cb, | 696 const PipelineStatusCB& ended_cb, |
| 560 const PipelineStatusCB& error_cb, | 697 const PipelineStatusCB& error_cb, |
| 561 const PipelineStatusCB& seek_cb, | 698 const PipelineStatusCB& seek_cb, |
| 562 const BufferingStateCB& buffering_state_cb) { | 699 const BufferingStateCB& buffering_state_cb) { |
| 563 DCHECK(message_loop_->BelongsToCurrentThread()); | 700 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 564 CHECK_EQ(kCreated, state_) | 701 CHECK_EQ(kCreated, state_) |
| 565 << "Media pipeline cannot be started more than once"; | 702 << "Media pipeline cannot be started more than once"; |
| 566 | 703 |
| 567 filter_collection_ = filter_collection.Pass(); | 704 filter_collection_ = filter_collection.Pass(); |
| 568 ended_cb_ = ended_cb; | 705 ended_cb_ = ended_cb; |
| 569 error_cb_ = error_cb; | 706 error_cb_ = error_cb; |
| 570 seek_cb_ = seek_cb; | 707 seek_cb_ = seek_cb; |
| 571 buffering_state_cb_ = buffering_state_cb; | 708 buffering_state_cb_ = buffering_state_cb; |
| 572 | 709 |
| 573 // Kick off initialization. | 710 StateTransitionTask(PIPELINE_OK); |
| 574 pipeline_init_state_.reset(new PipelineInitState()); | |
| 575 | |
| 576 SetState(kInitDemuxer); | |
| 577 InitializeDemuxer(); | |
| 578 } | 711 } |
| 579 | 712 |
| 580 // Main initialization method called on the pipeline thread. This code attempts | |
| 581 // to use the specified filter factory to build a pipeline. | |
| 582 // Initialization step performed in this method depends on current state of this | |
| 583 // object, indicated by |state_|. After each step of initialization, this | |
| 584 // object transits to the next stage. It starts by creating a Demuxer, and then | |
| 585 // connects the Demuxer's audio stream to an AudioDecoder which is then | |
| 586 // connected to an AudioRenderer. If the media has video, then it connects a | |
| 587 // VideoDecoder to the Demuxer's video stream, and then connects the | |
| 588 // VideoDecoder to a VideoRenderer. | |
| 589 // | |
| 590 // When all required filters have been created and have called their | |
| 591 // FilterHost's InitializationComplete() method, the pipeline will update its | |
| 592 // state to kStarted and |init_cb_|, will be executed. | |
| 593 // | |
| 594 // TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It | |
| 595 // works like a big state change table. If we no longer need to start filters | |
| 596 // in order, we need to get rid of all the state change. | |
| 597 void Pipeline::InitializeTask(PipelineStatus last_stage_status) { | |
| 598 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 599 | |
| 600 if (last_stage_status != PIPELINE_OK) { | |
| 601 SetError(last_stage_status); | |
| 602 return; | |
| 603 } | |
| 604 | |
| 605 // If we have received the stop or error signal, return immediately. | |
| 606 if (!stop_cb_.is_null() || state_ == kStopped || !IsPipelineOk()) | |
| 607 return; | |
| 608 | |
| 609 DCHECK(state_ == kInitDemuxer || | |
| 610 state_ == kInitAudioDecoder || | |
| 611 state_ == kInitAudioRenderer || | |
| 612 state_ == kInitVideoRenderer); | |
| 613 | |
| 614 // Demuxer created, create audio decoder. | |
| 615 if (state_ == kInitDemuxer) { | |
| 616 SetState(kInitAudioDecoder); | |
| 617 // If this method returns false, then there's no audio stream. | |
| 618 if (InitializeAudioDecoder(demuxer_)) | |
| 619 return; | |
| 620 } | |
| 621 | |
| 622 // Assuming audio decoder was created, create audio renderer. | |
| 623 if (state_ == kInitAudioDecoder) { | |
| 624 SetState(kInitAudioRenderer); | |
| 625 | |
| 626 // Returns false if there's no audio stream. | |
| 627 if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder)) { | |
| 628 base::AutoLock auto_lock(lock_); | |
| 629 has_audio_ = true; | |
| 630 return; | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 // Assuming audio renderer was created, create video renderer. | |
| 635 if (state_ == kInitAudioRenderer) { | |
| 636 SetState(kInitVideoRenderer); | |
| 637 scoped_refptr<DemuxerStream> video_stream = | |
| 638 demuxer_->GetStream(DemuxerStream::VIDEO); | |
| 639 if (InitializeVideoRenderer(video_stream)) { | |
| 640 base::AutoLock auto_lock(lock_); | |
| 641 has_video_ = true; | |
| 642 | |
| 643 // Get an initial natural size so we have something when we signal | |
| 644 // the kHaveMetadata buffering state. | |
| 645 natural_size_ = video_stream->video_decoder_config().natural_size(); | |
| 646 return; | |
| 647 } | |
| 648 } | |
| 649 | |
| 650 if (state_ == kInitVideoRenderer) { | |
| 651 if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { | |
| 652 SetError(PIPELINE_ERROR_COULD_NOT_RENDER); | |
| 653 return; | |
| 654 } | |
| 655 | |
| 656 // Clear initialization state now that we're done. | |
| 657 filter_collection_.reset(); | |
| 658 pipeline_init_state_.reset(); | |
| 659 | |
| 660 // Initialization was successful, we are now considered paused, so it's safe | |
| 661 // to set the initial playback rate and volume. | |
| 662 PlaybackRateChangedTask(GetPlaybackRate()); | |
| 663 VolumeChangedTask(GetVolume()); | |
| 664 | |
| 665 buffering_state_cb_.Run(kHaveMetadata); | |
| 666 | |
| 667 // Fire a seek request to get the renderers to preroll. We can skip a seek | |
| 668 // here as the demuxer should be at the start of the stream. | |
| 669 seek_pending_ = true; | |
| 670 SetState(kSeeking); | |
| 671 seek_timestamp_ = demuxer_->GetStartTime(); | |
| 672 DoSeek(seek_timestamp_, true, | |
| 673 base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 674 } | |
| 675 } | |
| 676 | |
| 677 // This method is called as a result of the client calling Pipeline::Stop() or | |
| 678 // as the result of an error condition. | |
| 679 // We stop the filters in the reverse order. | |
| 680 // | |
| 681 // TODO(scherkus): beware! this can get posted multiple times since we post | |
| 682 // Stop() tasks even if we've already stopped. Perhaps this should no-op for | |
| 683 // additional calls, however most of this logic will be changing. | |
| 684 void Pipeline::StopTask(const base::Closure& stop_cb) { | 713 void Pipeline::StopTask(const base::Closure& stop_cb) { |
| 685 DCHECK(message_loop_->BelongsToCurrentThread()); | 714 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 686 DCHECK(stop_cb_.is_null()); | 715 DCHECK(stop_cb_.is_null()); |
| 687 | 716 |
| 688 if (state_ == kStopped) { | 717 if (state_ == kStopped) { |
| 689 stop_cb.Run(); | 718 stop_cb.Run(); |
| 690 return; | 719 return; |
| 691 } | 720 } |
| 692 | 721 |
| 722 // TODO(scherkus): Remove after pipeline state machine refactoring has some | |
| 723 // time to bake http://crbug.com/110228 | |
| 693 if (video_renderer_) | 724 if (video_renderer_) |
| 694 video_renderer_->PrepareForShutdownHack(); | 725 video_renderer_->PrepareForShutdownHack(); |
| 695 | 726 |
| 696 if (tearing_down_ && status_ != PIPELINE_OK) { | 727 SetState(kStopping); |
| 697 // If we are stopping due to SetError(), stop normally instead of | 728 pending_callbacks_.reset(); |
| 698 // going to error state and calling |error_cb_|. This converts | |
| 699 // the teardown in progress from an error teardown into one that acts | |
| 700 // like the error never occurred. | |
| 701 base::AutoLock auto_lock(lock_); | |
| 702 status_ = PIPELINE_OK; | |
| 703 } | |
| 704 | |
| 705 stop_cb_ = stop_cb; | 729 stop_cb_ = stop_cb; |
| 706 | 730 |
| 707 if (!IsPipelineSeeking() && !tearing_down_) { | 731 DoStop(base::Bind(&Pipeline::OnStopCompleted, this)); |
|
acolwell GONE FROM CHROMIUM
2012/09/06 09:19:10
Move the bind into DoStop() since all call sites d
scherkus (not reviewing)
2012/09/06 15:33:20
I prefer to have the resulting callback function d
| |
| 708 // We will tear down pipeline immediately when there is no seek operation | |
| 709 // pending and no teardown in progress. This should include the case where | |
| 710 // we are partially initialized. | |
| 711 TearDownPipeline(); | |
| 712 } | |
| 713 } | 732 } |
| 714 | 733 |
| 715 void Pipeline::ErrorChangedTask(PipelineStatus error) { | 734 void Pipeline::ErrorChangedTask(PipelineStatus error) { |
| 716 DCHECK(message_loop_->BelongsToCurrentThread()); | 735 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 717 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; | 736 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; |
| 718 | 737 |
| 719 // Suppress executing additional error logic. Note that if we are currently | 738 if (state_ == kStopping || state_ == kStopped) |
| 720 // performing a normal stop, then we return immediately and continue the | |
| 721 // normal stop. | |
| 722 if (state_ == kStopped || tearing_down_) { | |
| 723 return; | 739 return; |
| 724 } | |
| 725 | 740 |
| 726 base::AutoLock auto_lock(lock_); | 741 SetState(kStopping); |
| 742 pending_callbacks_.reset(); | |
| 727 status_ = error; | 743 status_ = error; |
| 728 | 744 |
| 729 // Posting TearDownPipeline() to message loop so that we can make sure | 745 DoStop(base::Bind(&Pipeline::OnStopCompleted, this)); |
| 730 // it runs after any pending callbacks that are already queued. | |
| 731 // |tearing_down_| is set early here to make sure that pending callbacks | |
| 732 // don't modify the state before TearDownPipeline() can run. | |
| 733 tearing_down_ = true; | |
| 734 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 735 &Pipeline::TearDownPipeline, this)); | |
| 736 } | 746 } |
| 737 | 747 |
| 738 void Pipeline::PlaybackRateChangedTask(float playback_rate) { | 748 void Pipeline::PlaybackRateChangedTask(float playback_rate) { |
| 739 DCHECK(message_loop_->BelongsToCurrentThread()); | 749 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 740 | 750 |
| 741 if (state_ == kStopped || tearing_down_) | 751 // Playback rate changes are only carried out while playing. |
| 752 if (state_ != kStarting && state_ != kStarted) | |
| 742 return; | 753 return; |
| 743 | 754 |
| 744 // Suppress rate change until after seeking. | |
| 745 if (IsPipelineSeeking()) { | |
| 746 pending_playback_rate_ = playback_rate; | |
| 747 playback_rate_change_pending_ = true; | |
| 748 return; | |
| 749 } | |
| 750 | |
| 751 { | 755 { |
| 752 base::AutoLock auto_lock(lock_); | 756 base::AutoLock auto_lock(lock_); |
| 753 clock_->SetPlaybackRate(playback_rate); | 757 clock_->SetPlaybackRate(playback_rate); |
| 754 } | 758 } |
| 755 | 759 |
| 756 // These will get set after initialization completes in case playback rate is | |
| 757 // set prior to initialization. | |
| 758 if (demuxer_) | 760 if (demuxer_) |
| 759 demuxer_->SetPlaybackRate(playback_rate); | 761 demuxer_->SetPlaybackRate(playback_rate); |
| 760 if (audio_renderer_) | 762 if (audio_renderer_) |
| 761 audio_renderer_->SetPlaybackRate(playback_rate_); | 763 audio_renderer_->SetPlaybackRate(playback_rate_); |
| 762 if (video_renderer_) | 764 if (video_renderer_) |
| 763 video_renderer_->SetPlaybackRate(playback_rate_); | 765 video_renderer_->SetPlaybackRate(playback_rate_); |
| 764 } | 766 } |
| 765 | 767 |
| 766 void Pipeline::VolumeChangedTask(float volume) { | 768 void Pipeline::VolumeChangedTask(float volume) { |
| 767 DCHECK(message_loop_->BelongsToCurrentThread()); | 769 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 768 | 770 |
| 769 if (state_ == kStopped || tearing_down_) | 771 // Volume changes are only carried out while playing. |
| 772 if (state_ != kStarting && state_ != kStarted) | |
| 770 return; | 773 return; |
| 771 | 774 |
| 772 if (audio_renderer_) | 775 if (audio_renderer_) |
| 773 audio_renderer_->SetVolume(volume); | 776 audio_renderer_->SetVolume(volume); |
| 774 } | 777 } |
| 775 | 778 |
| 776 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { | 779 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { |
| 777 DCHECK(message_loop_->BelongsToCurrentThread()); | 780 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 778 DCHECK(stop_cb_.is_null()); | 781 DCHECK(stop_cb_.is_null()); |
| 779 | 782 |
| 780 // Suppress seeking if we're not fully started. | 783 // Suppress seeking if we're not fully started. |
| 781 if (state_ != kStarted) { | 784 if (state_ != kStarted) { |
| 785 DCHECK(state_ == kStopping || state_ == kStopped) | |
| 786 << "Receive extra seek in unexpected state: " << state_; | |
| 787 | |
| 782 // TODO(scherkus): should we run the callback? I'm tempted to say the API | 788 // TODO(scherkus): should we run the callback? I'm tempted to say the API |
| 783 // will only execute the first Seek() request. | 789 // will only execute the first Seek() request. |
| 784 DVLOG(1) << "Media pipeline has not started, ignoring seek to " | 790 DVLOG(1) << "Media pipeline has not started, ignoring seek to " |
| 785 << time.InMicroseconds() << " (current state: " << state_ << ")"; | 791 << time.InMicroseconds() << " (current state: " << state_ << ")"; |
| 786 return; | 792 return; |
| 787 } | 793 } |
| 788 | 794 |
| 789 DCHECK(!seek_pending_); | 795 DCHECK(seek_cb_.is_null()); |
| 790 seek_pending_ = true; | |
| 791 | 796 |
| 792 // We'll need to pause every filter before seeking. The state transition | 797 SetState(kSeeking); |
| 793 // is as follows: | 798 base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime()); |
| 794 // kStarted | 799 seek_cb_ = seek_cb; |
| 795 // kPausing (for each filter) | |
| 796 // kSeeking (for each filter) | |
| 797 // kStarting (for each filter) | |
| 798 // kStarted | |
| 799 SetState(kPausing); | |
| 800 audio_ended_ = false; | 800 audio_ended_ = false; |
| 801 video_ended_ = false; | 801 video_ended_ = false; |
| 802 seek_timestamp_ = std::max(time, demuxer_->GetStartTime()); | |
| 803 seek_cb_ = seek_cb; | |
| 804 | 802 |
| 805 // Kick off seeking! | 803 // Kick off seeking! |
| 806 { | 804 { |
| 807 base::AutoLock auto_lock(lock_); | 805 base::AutoLock auto_lock(lock_); |
| 808 if (clock_->IsPlaying()) | 806 if (clock_->IsPlaying()) |
| 809 clock_->Pause(); | 807 clock_->Pause(); |
| 808 clock_->SetTime(seek_timestamp, seek_timestamp); | |
| 810 } | 809 } |
| 811 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); | 810 DoSeek(seek_timestamp, base::Bind(&Pipeline::OnStateTransition, this)); |
| 812 } | 811 } |
| 813 | 812 |
| 814 void Pipeline::DoAudioRendererEnded() { | 813 void Pipeline::DoAudioRendererEnded() { |
| 815 DCHECK(message_loop_->BelongsToCurrentThread()); | 814 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 816 | 815 |
| 817 if (state_ != kStarted) | 816 if (state_ != kStarted) |
| 818 return; | 817 return; |
| 819 | 818 |
| 820 DCHECK(!audio_ended_); | 819 DCHECK(!audio_ended_); |
| 821 audio_ended_ = true; | 820 audio_ended_ = true; |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 849 return; | 848 return; |
| 850 | 849 |
| 851 if (video_renderer_ && !video_ended_) | 850 if (video_renderer_ && !video_ended_) |
| 852 return; | 851 return; |
| 853 | 852 |
| 854 { | 853 { |
| 855 base::AutoLock auto_lock(lock_); | 854 base::AutoLock auto_lock(lock_); |
| 856 clock_->EndOfStream(); | 855 clock_->EndOfStream(); |
| 857 } | 856 } |
| 858 | 857 |
| 859 ReportStatus(ended_cb_, status_); | 858 // TODO(scherkus): Change |ended_cb_| into a Closure. |
| 859 DCHECK_EQ(status_, PIPELINE_OK); | |
| 860 ended_cb_.Run(status_); | |
| 860 } | 861 } |
| 861 | 862 |
| 862 void Pipeline::AudioDisabledTask() { | 863 void Pipeline::AudioDisabledTask() { |
| 863 DCHECK(message_loop_->BelongsToCurrentThread()); | 864 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 864 | 865 |
| 865 base::AutoLock auto_lock(lock_); | 866 base::AutoLock auto_lock(lock_); |
| 866 has_audio_ = false; | 867 has_audio_ = false; |
| 867 audio_disabled_ = true; | 868 audio_disabled_ = true; |
| 868 | 869 |
| 869 // Notify our demuxer that we're no longer rendering audio. | 870 // Notify our demuxer that we're no longer rendering audio. |
| 870 demuxer_->OnAudioRendererDisabled(); | 871 demuxer_->OnAudioRendererDisabled(); |
| 871 | 872 |
| 872 // Start clock since there is no more audio to | 873 // Start clock since there is no more audio to trigger clock updates. |
| 873 // trigger clock updates. | |
| 874 clock_->SetMaxTime(clock_->Duration()); | 874 clock_->SetMaxTime(clock_->Duration()); |
| 875 StartClockIfWaitingForTimeUpdate_Locked(); | 875 StartClockIfWaitingForTimeUpdate_Locked(); |
| 876 } | 876 } |
| 877 | 877 |
| 878 void Pipeline::FilterStateTransitionTask() { | 878 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) { |
| 879 DCHECK(message_loop_->BelongsToCurrentThread()); | 879 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 880 DCHECK(pending_callbacks_.get()) | |
| 881 << "Filter state transitions must be completed via pending_callbacks_"; | |
| 882 pending_callbacks_.reset(); | |
| 883 | 880 |
| 884 // State transitions while tearing down are handled via | 881 demuxer_ = filter_collection_->GetDemuxer(); |
| 885 // TeardownStateTransitionTask(). | 882 if (!demuxer_) { |
|
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
You said "done" to acolwell's comment about turnin
scherkus (not reviewing)
2012/09/06 15:33:20
Remove this stuff so we crash when faced with miss
| |
| 886 // | 883 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 887 // TODO(scherkus): Merge all state machinery! | |
| 888 if (state_ == kStopped || tearing_down_) { | |
| 889 return; | 884 return; |
| 890 } | 885 } |
| 891 | 886 |
| 892 if (!TransientState(state_)) { | 887 demuxer_->Initialize(this, done_cb); |
| 893 NOTREACHED() << "Invalid current state: " << state_; | 888 } |
| 894 SetError(PIPELINE_ERROR_ABORT); | 889 |
| 890 void Pipeline::InitializeAudioDecoder(const PipelineStatusCB& done_cb) { | |
| 891 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 892 | |
| 893 scoped_refptr<DemuxerStream> stream = | |
| 894 demuxer_->GetStream(DemuxerStream::AUDIO); | |
| 895 | |
| 896 if (!stream) { | |
| 897 done_cb.Run(PIPELINE_OK); | |
| 895 return; | 898 return; |
| 896 } | 899 } |
| 897 | 900 |
| 898 // Decrement the number of remaining transitions, making sure to transition | 901 filter_collection_->SelectAudioDecoder(&audio_decoder_); |
| 899 // to the next state if needed. | |
| 900 SetState(FindNextState(state_)); | |
| 901 if (state_ == kSeeking) { | |
| 902 base::AutoLock auto_lock(lock_); | |
| 903 DCHECK(seek_timestamp_ != kNoTimestamp()); | |
| 904 clock_->SetTime(seek_timestamp_, seek_timestamp_); | |
| 905 } | |
| 906 | 902 |
| 907 // Carry out the action for the current state. | 903 if (!audio_decoder_) { |
| 908 if (TransientState(state_)) { | 904 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 909 if (state_ == kPausing) { | |
| 910 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 911 } else if (state_ == kFlushing) { | |
| 912 DoFlush(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 913 } else if (state_ == kSeeking) { | |
| 914 DoSeek(seek_timestamp_, false, | |
| 915 base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 916 } else if (state_ == kStarting) { | |
| 917 DoPlay(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 918 } else if (state_ == kStopping) { | |
| 919 DoStop(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 920 } else { | |
| 921 NOTREACHED() << "Unexpected state: " << state_; | |
| 922 } | |
| 923 } else if (state_ == kStarted) { | |
| 924 | |
| 925 // Fire canplaythrough immediately after playback begins because of | |
| 926 // crbug.com/106480. | |
| 927 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. | |
| 928 if (status_ == PIPELINE_OK) | |
| 929 buffering_state_cb_.Run(kPrerollCompleted); | |
| 930 | |
| 931 FinishSeek(); | |
| 932 | |
| 933 // If a playback rate change was requested during a seek, do it now that | |
| 934 // the seek has compelted. | |
| 935 if (playback_rate_change_pending_) { | |
| 936 playback_rate_change_pending_ = false; | |
| 937 PlaybackRateChangedTask(pending_playback_rate_); | |
| 938 } | |
| 939 | |
| 940 base::AutoLock auto_lock(lock_); | |
| 941 // We use audio stream to update the clock. So if there is such a stream, | |
| 942 // we pause the clock until we receive a valid timestamp. | |
| 943 waiting_for_clock_update_ = true; | |
| 944 if (!has_audio_) { | |
| 945 clock_->SetMaxTime(clock_->Duration()); | |
| 946 StartClockIfWaitingForTimeUpdate_Locked(); | |
| 947 } | |
| 948 | |
| 949 // Check if we have a pending stop request that needs to be honored. | |
| 950 if (!stop_cb_.is_null()) { | |
| 951 TearDownPipeline(); | |
| 952 } | |
| 953 } else { | |
| 954 NOTREACHED() << "Unexpected state: " << state_; | |
| 955 } | |
| 956 } | |
| 957 | |
| 958 void Pipeline::TeardownStateTransitionTask() { | |
| 959 DCHECK(tearing_down_); | |
| 960 DCHECK(pending_callbacks_.get()) | |
| 961 << "Teardown state transitions must be completed via pending_callbacks_"; | |
| 962 pending_callbacks_.reset(); | |
| 963 | |
| 964 switch (state_) { | |
| 965 case kStopping: | |
| 966 SetState(kStopped); | |
| 967 FinishDestroyingFiltersTask(); | |
| 968 break; | |
| 969 case kPausing: | |
| 970 SetState(kFlushing); | |
| 971 DoFlush(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 972 break; | |
| 973 case kFlushing: | |
| 974 SetState(kStopping); | |
| 975 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 976 break; | |
| 977 | |
| 978 case kCreated: | |
| 979 case kInitDemuxer: | |
| 980 case kInitAudioDecoder: | |
| 981 case kInitAudioRenderer: | |
| 982 case kInitVideoRenderer: | |
| 983 case kSeeking: | |
| 984 case kStarting: | |
| 985 case kStopped: | |
| 986 case kStarted: | |
| 987 NOTREACHED() << "Unexpected state for teardown: " << state_; | |
| 988 break; | |
| 989 // default: intentionally left out to force new states to cause compiler | |
| 990 // errors. | |
| 991 }; | |
| 992 } | |
| 993 | |
| 994 void Pipeline::FinishDestroyingFiltersTask() { | |
| 995 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 996 DCHECK_EQ(state_, kStopped); | |
| 997 | |
| 998 audio_renderer_ = NULL; | |
| 999 video_renderer_ = NULL; | |
| 1000 demuxer_ = NULL; | |
| 1001 tearing_down_ = false; | |
| 1002 { | |
| 1003 base::AutoLock l(lock_); | |
| 1004 running_ = false; | |
| 1005 } | |
| 1006 | |
| 1007 if (!IsPipelineOk() && !error_cb_.is_null()) | |
| 1008 error_cb_.Run(status_); | |
| 1009 | |
| 1010 if (!stop_cb_.is_null()) | |
| 1011 base::ResetAndReturn(&stop_cb_).Run(); | |
| 1012 } | |
| 1013 | |
| 1014 void Pipeline::InitializeDemuxer() { | |
| 1015 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1016 DCHECK(IsPipelineOk()); | |
| 1017 | |
| 1018 demuxer_ = filter_collection_->GetDemuxer(); | |
| 1019 if (!demuxer_) { | |
| 1020 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1021 return; | 905 return; |
| 1022 } | 906 } |
| 1023 | 907 |
| 1024 demuxer_->Initialize(this, base::Bind(&Pipeline::OnDemuxerInitialized, this)); | 908 audio_decoder_->Initialize( |
| 909 stream, done_cb, base::Bind(&Pipeline::OnUpdateStatistics, this)); | |
| 1025 } | 910 } |
| 1026 | 911 |
| 1027 void Pipeline::OnDemuxerInitialized(PipelineStatus status) { | 912 void Pipeline::InitializeAudioRenderer(const PipelineStatusCB& done_cb) { |
| 1028 if (!message_loop_->BelongsToCurrentThread()) { | 913 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 1029 message_loop_->PostTask(FROM_HERE, base::Bind( | 914 |
| 1030 &Pipeline::OnDemuxerInitialized, this, status)); | 915 if (!audio_decoder_) { |
| 916 done_cb.Run(PIPELINE_OK); | |
| 1031 return; | 917 return; |
| 1032 } | 918 } |
| 1033 | 919 |
| 1034 if (status != PIPELINE_OK) { | 920 filter_collection_->SelectAudioRenderer(&audio_renderer_); |
| 1035 SetError(status); | 921 if (!audio_renderer_) { |
| 922 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1036 return; | 923 return; |
| 1037 } | 924 } |
| 1038 | 925 |
| 1039 { | |
| 1040 base::AutoLock auto_lock(lock_); | |
| 1041 // We do not want to start the clock running. We only want to set the base | |
| 1042 // media time so our timestamp calculations will be correct. | |
| 1043 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime()); | |
| 1044 } | |
| 1045 | |
| 1046 OnFilterInitialize(PIPELINE_OK); | |
| 1047 } | |
| 1048 | |
| 1049 bool Pipeline::InitializeAudioDecoder( | |
| 1050 const scoped_refptr<Demuxer>& demuxer) { | |
| 1051 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1052 DCHECK(IsPipelineOk()); | |
| 1053 DCHECK(demuxer); | |
| 1054 | |
| 1055 scoped_refptr<DemuxerStream> stream = | |
| 1056 demuxer->GetStream(DemuxerStream::AUDIO); | |
| 1057 | |
| 1058 if (!stream) | |
| 1059 return false; | |
| 1060 | |
| 1061 filter_collection_->SelectAudioDecoder(&pipeline_init_state_->audio_decoder); | |
| 1062 | |
| 1063 if (!pipeline_init_state_->audio_decoder) { | |
| 1064 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1065 return false; | |
| 1066 } | |
| 1067 | |
| 1068 pipeline_init_state_->audio_decoder->Initialize( | |
| 1069 stream, | |
| 1070 base::Bind(&Pipeline::OnFilterInitialize, this), | |
| 1071 base::Bind(&Pipeline::OnUpdateStatistics, this)); | |
| 1072 return true; | |
| 1073 } | |
| 1074 | |
| 1075 bool Pipeline::InitializeAudioRenderer( | |
| 1076 const scoped_refptr<AudioDecoder>& decoder) { | |
| 1077 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1078 DCHECK(IsPipelineOk()); | |
| 1079 | |
| 1080 if (!decoder) | |
| 1081 return false; | |
| 1082 | |
| 1083 filter_collection_->SelectAudioRenderer(&audio_renderer_); | |
| 1084 if (!audio_renderer_) { | |
| 1085 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1086 return false; | |
| 1087 } | |
| 1088 | |
| 1089 audio_renderer_->Initialize( | 926 audio_renderer_->Initialize( |
| 1090 decoder, | 927 audio_decoder_, |
| 1091 base::Bind(&Pipeline::OnFilterInitialize, this), | 928 done_cb, |
| 1092 base::Bind(&Pipeline::OnAudioUnderflow, this), | 929 base::Bind(&Pipeline::OnAudioUnderflow, this), |
| 1093 base::Bind(&Pipeline::OnAudioTimeUpdate, this), | 930 base::Bind(&Pipeline::OnAudioTimeUpdate, this), |
| 1094 base::Bind(&Pipeline::OnAudioRendererEnded, this), | 931 base::Bind(&Pipeline::OnAudioRendererEnded, this), |
| 1095 base::Bind(&Pipeline::OnAudioDisabled, this), | 932 base::Bind(&Pipeline::OnAudioDisabled, this), |
| 1096 base::Bind(&Pipeline::SetError, this)); | 933 base::Bind(&Pipeline::SetError, this)); |
| 1097 return true; | |
| 1098 } | 934 } |
| 1099 | 935 |
| 1100 bool Pipeline::InitializeVideoRenderer( | 936 void Pipeline::InitializeVideoRenderer(const PipelineStatusCB& done_cb) { |
| 1101 const scoped_refptr<DemuxerStream>& stream) { | |
| 1102 DCHECK(message_loop_->BelongsToCurrentThread()); | 937 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 1103 DCHECK(IsPipelineOk()); | |
| 1104 | 938 |
| 1105 if (!stream) | 939 scoped_refptr<DemuxerStream> stream = |
| 1106 return false; | 940 demuxer_->GetStream(DemuxerStream::VIDEO); |
| 941 | |
| 942 if (!stream) { | |
| 943 done_cb.Run(PIPELINE_OK); | |
| 944 return; | |
| 945 } | |
| 1107 | 946 |
| 1108 filter_collection_->SelectVideoRenderer(&video_renderer_); | 947 filter_collection_->SelectVideoRenderer(&video_renderer_); |
| 1109 if (!video_renderer_) { | 948 if (!video_renderer_) { |
| 1110 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | 949 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 1111 return false; | 950 return; |
| 951 } | |
| 952 | |
| 953 { | |
| 954 // Get an initial natural size so we have something when we signal | |
| 955 // the kHaveMetadata buffering state. | |
| 956 base::AutoLock l(lock_); | |
| 957 natural_size_ = stream->video_decoder_config().natural_size(); | |
| 1112 } | 958 } |
| 1113 | 959 |
| 1114 video_renderer_->Initialize( | 960 video_renderer_->Initialize( |
| 1115 stream, | 961 stream, |
| 1116 *filter_collection_->GetVideoDecoders(), | 962 *filter_collection_->GetVideoDecoders(), |
| 1117 base::Bind(&Pipeline::OnFilterInitialize, this), | 963 done_cb, |
| 1118 base::Bind(&Pipeline::OnUpdateStatistics, this), | 964 base::Bind(&Pipeline::OnUpdateStatistics, this), |
| 1119 base::Bind(&Pipeline::OnVideoTimeUpdate, this), | 965 base::Bind(&Pipeline::OnVideoTimeUpdate, this), |
| 1120 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), | 966 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), |
| 1121 base::Bind(&Pipeline::OnVideoRendererEnded, this), | 967 base::Bind(&Pipeline::OnVideoRendererEnded, this), |
| 1122 base::Bind(&Pipeline::SetError, this), | 968 base::Bind(&Pipeline::SetError, this), |
| 1123 base::Bind(&Pipeline::GetMediaTime, this), | 969 base::Bind(&Pipeline::GetMediaTime, this), |
| 1124 base::Bind(&Pipeline::GetMediaDuration, this)); | 970 base::Bind(&Pipeline::GetMediaDuration, this)); |
| 1125 filter_collection_->GetVideoDecoders()->clear(); | 971 filter_collection_->GetVideoDecoders()->clear(); |
| 1126 return true; | |
| 1127 } | |
| 1128 | |
| 1129 void Pipeline::TearDownPipeline() { | |
| 1130 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1131 DCHECK_NE(kStopped, state_); | |
| 1132 | |
| 1133 // We're either... | |
| 1134 // 1) ...tearing down due to Stop() (it doesn't set tearing_down_) | |
| 1135 // 2) ...tearing down due to an error (it does set tearing_down_) | |
| 1136 // 3) ...tearing down due to an error and Stop() was called during that time | |
| 1137 DCHECK(!tearing_down_ || | |
| 1138 (tearing_down_ && status_ != PIPELINE_OK) || | |
| 1139 (tearing_down_ && !stop_cb_.is_null())); | |
| 1140 | |
| 1141 // Mark that we already start tearing down operation. | |
| 1142 tearing_down_ = true; | |
| 1143 | |
| 1144 // Cancel any pending operation so we can proceed with teardown. | |
| 1145 pending_callbacks_.reset(); | |
| 1146 | |
| 1147 switch (state_) { | |
| 1148 case kCreated: | |
| 1149 SetState(kStopped); | |
| 1150 // Need to put this in the message loop to make sure that it comes | |
| 1151 // after any pending callback tasks that are already queued. | |
| 1152 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 1153 &Pipeline::FinishDestroyingFiltersTask, this)); | |
| 1154 break; | |
| 1155 | |
| 1156 case kInitDemuxer: | |
| 1157 case kInitAudioDecoder: | |
| 1158 case kInitAudioRenderer: | |
| 1159 case kInitVideoRenderer: | |
| 1160 // Make it look like initialization was successful. | |
| 1161 filter_collection_.reset(); | |
| 1162 pipeline_init_state_.reset(); | |
| 1163 | |
| 1164 SetState(kStopping); | |
| 1165 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1166 | |
| 1167 FinishSeek(); | |
| 1168 break; | |
| 1169 | |
| 1170 case kPausing: | |
| 1171 case kSeeking: | |
| 1172 case kFlushing: | |
| 1173 case kStarting: | |
| 1174 SetState(kStopping); | |
| 1175 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1176 | |
| 1177 if (seek_pending_) | |
| 1178 FinishSeek(); | |
| 1179 | |
| 1180 break; | |
| 1181 | |
| 1182 case kStarted: | |
| 1183 SetState(kPausing); | |
| 1184 DoPause(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1185 break; | |
| 1186 | |
| 1187 case kStopping: | |
| 1188 case kStopped: | |
| 1189 NOTREACHED() << "Unexpected state for teardown: " << state_; | |
| 1190 break; | |
| 1191 // default: intentionally left out to force new states to cause compiler | |
| 1192 // errors. | |
| 1193 }; | |
| 1194 } | |
| 1195 | |
| 1196 void Pipeline::DoSeek(base::TimeDelta seek_timestamp, | |
| 1197 bool skip_demuxer_seek, | |
| 1198 const PipelineStatusCB& done_cb) { | |
| 1199 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1200 DCHECK(!pending_callbacks_.get()); | |
| 1201 SerialRunner::Queue bound_fns; | |
| 1202 | |
| 1203 if (!skip_demuxer_seek) { | |
| 1204 bound_fns.Push(base::Bind( | |
| 1205 &Demuxer::Seek, demuxer_, seek_timestamp)); | |
| 1206 } | |
| 1207 | |
| 1208 if (audio_renderer_) { | |
| 1209 bound_fns.Push(base::Bind( | |
| 1210 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 1211 } | |
| 1212 | |
| 1213 if (video_renderer_) { | |
| 1214 bound_fns.Push(base::Bind( | |
| 1215 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 1216 } | |
| 1217 | |
| 1218 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | |
| 1219 } | 972 } |
| 1220 | 973 |
| 1221 void Pipeline::OnAudioUnderflow() { | 974 void Pipeline::OnAudioUnderflow() { |
| 1222 if (!message_loop_->BelongsToCurrentThread()) { | 975 if (!message_loop_->BelongsToCurrentThread()) { |
| 1223 message_loop_->PostTask(FROM_HERE, base::Bind( | 976 message_loop_->PostTask(FROM_HERE, base::Bind( |
| 1224 &Pipeline::OnAudioUnderflow, this)); | 977 &Pipeline::OnAudioUnderflow, this)); |
| 1225 return; | 978 return; |
| 1226 } | 979 } |
| 1227 | 980 |
| 1228 if (state_ != kStarted) | 981 if (state_ != kStarted) |
| 1229 return; | 982 return; |
| 1230 | 983 |
| 1231 if (audio_renderer_) | 984 if (audio_renderer_) |
| 1232 audio_renderer_->ResumeAfterUnderflow(true); | 985 audio_renderer_->ResumeAfterUnderflow(true); |
| 1233 } | 986 } |
| 1234 | 987 |
| 1235 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { | 988 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { |
| 1236 lock_.AssertAcquired(); | 989 lock_.AssertAcquired(); |
| 1237 if (!waiting_for_clock_update_) | 990 if (!waiting_for_clock_update_) |
| 1238 return; | 991 return; |
| 1239 | 992 |
| 1240 waiting_for_clock_update_ = false; | 993 waiting_for_clock_update_ = false; |
| 1241 clock_->Play(); | 994 clock_->Play(); |
| 1242 } | 995 } |
| 1243 | 996 |
| 1244 } // namespace media | 997 } // namespace media |
| OLD | NEW |