Web Speech API: cancel outstanding infobar requests when aborting.
[chromium-blink-merge.git] / media / base / pipeline.cc
blob9790c61cb5ca792c12d44a3f7b1b45392892565d
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
3 // found in the LICENSE file.
5 #include "media/base/pipeline.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/callback_helpers.h"
12 #include "base/compiler_specific.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/histogram.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/synchronization/condition_variable.h"
19 #include "media/base/audio_decoder.h"
20 #include "media/base/audio_renderer.h"
21 #include "media/base/clock.h"
22 #include "media/base/filter_collection.h"
23 #include "media/base/media_log.h"
24 #include "media/base/video_decoder.h"
25 #include "media/base/video_decoder_config.h"
26 #include "media/base/video_renderer.h"
28 using base::TimeDelta;
30 namespace media {
32 Pipeline::Pipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop,
33 MediaLog* media_log)
34 : message_loop_(message_loop),
35 media_log_(media_log),
36 running_(false),
37 did_loading_progress_(false),
38 total_bytes_(0),
39 natural_size_(0, 0),
40 volume_(1.0f),
41 playback_rate_(0.0f),
42 clock_(new Clock(&default_tick_clock_)),
43 waiting_for_clock_update_(false),
44 status_(PIPELINE_OK),
45 has_audio_(false),
46 has_video_(false),
47 state_(kCreated),
48 audio_ended_(false),
49 video_ended_(false),
50 audio_disabled_(false),
51 demuxer_(NULL),
52 creation_time_(default_tick_clock_.NowTicks()) {
53 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
54 media_log_->AddEvent(
55 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED));
58 Pipeline::~Pipeline() {
59 DCHECK(thread_checker_.CalledOnValidThread())
60 << "Pipeline must be destroyed on same thread that created it";
61 DCHECK(!running_) << "Stop() must complete before destroying object";
62 DCHECK(stop_cb_.is_null());
63 DCHECK(seek_cb_.is_null());
65 media_log_->AddEvent(
66 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED));
69 void Pipeline::Start(scoped_ptr<FilterCollection> collection,
70 const base::Closure& ended_cb,
71 const PipelineStatusCB& error_cb,
72 const PipelineStatusCB& seek_cb,
73 const BufferingStateCB& buffering_state_cb,
74 const base::Closure& duration_change_cb) {
75 base::AutoLock auto_lock(lock_);
76 CHECK(!running_) << "Media pipeline is already running";
77 DCHECK(!buffering_state_cb.is_null());
79 running_ = true;
80 message_loop_->PostTask(FROM_HERE, base::Bind(
81 &Pipeline::StartTask, base::Unretained(this), base::Passed(&collection),
82 ended_cb, error_cb, seek_cb, buffering_state_cb, duration_change_cb));
85 void Pipeline::Stop(const base::Closure& stop_cb) {
86 base::AutoLock auto_lock(lock_);
87 message_loop_->PostTask(FROM_HERE, base::Bind(
88 &Pipeline::StopTask, base::Unretained(this), stop_cb));
91 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
92 base::AutoLock auto_lock(lock_);
93 if (!running_) {
94 NOTREACHED() << "Media pipeline isn't running";
95 return;
98 message_loop_->PostTask(FROM_HERE, base::Bind(
99 &Pipeline::SeekTask, base::Unretained(this), time, seek_cb));
102 bool Pipeline::IsRunning() const {
103 base::AutoLock auto_lock(lock_);
104 return running_;
107 bool Pipeline::HasAudio() const {
108 base::AutoLock auto_lock(lock_);
109 return has_audio_;
112 bool Pipeline::HasVideo() const {
113 base::AutoLock auto_lock(lock_);
114 return has_video_;
117 float Pipeline::GetPlaybackRate() const {
118 base::AutoLock auto_lock(lock_);
119 return playback_rate_;
122 void Pipeline::SetPlaybackRate(float playback_rate) {
123 if (playback_rate < 0.0f)
124 return;
126 base::AutoLock auto_lock(lock_);
127 playback_rate_ = playback_rate;
128 if (running_) {
129 message_loop_->PostTask(FROM_HERE, base::Bind(
130 &Pipeline::PlaybackRateChangedTask, base::Unretained(this),
131 playback_rate));
135 float Pipeline::GetVolume() const {
136 base::AutoLock auto_lock(lock_);
137 return volume_;
140 void Pipeline::SetVolume(float volume) {
141 if (volume < 0.0f || volume > 1.0f)
142 return;
144 base::AutoLock auto_lock(lock_);
145 volume_ = volume;
146 if (running_) {
147 message_loop_->PostTask(FROM_HERE, base::Bind(
148 &Pipeline::VolumeChangedTask, base::Unretained(this), volume));
152 TimeDelta Pipeline::GetMediaTime() const {
153 base::AutoLock auto_lock(lock_);
154 return clock_->Elapsed();
157 Ranges<TimeDelta> Pipeline::GetBufferedTimeRanges() {
158 base::AutoLock auto_lock(lock_);
159 Ranges<TimeDelta> time_ranges;
160 for (size_t i = 0; i < buffered_time_ranges_.size(); ++i) {
161 time_ranges.Add(buffered_time_ranges_.start(i),
162 buffered_time_ranges_.end(i));
164 if (clock_->Duration() == TimeDelta() || total_bytes_ == 0)
165 return time_ranges;
166 for (size_t i = 0; i < buffered_byte_ranges_.size(); ++i) {
167 TimeDelta start = TimeForByteOffset_Locked(buffered_byte_ranges_.start(i));
168 TimeDelta end = TimeForByteOffset_Locked(buffered_byte_ranges_.end(i));
169 // Cap approximated buffered time at the length of the video.
170 end = std::min(end, clock_->Duration());
171 time_ranges.Add(start, end);
174 return time_ranges;
177 TimeDelta Pipeline::GetMediaDuration() const {
178 base::AutoLock auto_lock(lock_);
179 return clock_->Duration();
182 int64 Pipeline::GetTotalBytes() const {
183 base::AutoLock auto_lock(lock_);
184 return total_bytes_;
187 void Pipeline::GetNaturalVideoSize(gfx::Size* out_size) const {
188 CHECK(out_size);
189 base::AutoLock auto_lock(lock_);
190 *out_size = natural_size_;
193 bool Pipeline::DidLoadingProgress() const {
194 base::AutoLock auto_lock(lock_);
195 bool ret = did_loading_progress_;
196 did_loading_progress_ = false;
197 return ret;
200 PipelineStatistics Pipeline::GetStatistics() const {
201 base::AutoLock auto_lock(lock_);
202 return statistics_;
205 void Pipeline::SetClockForTesting(Clock* clock) {
206 clock_.reset(clock);
209 void Pipeline::SetErrorForTesting(PipelineStatus status) {
210 SetError(status);
213 void Pipeline::SetState(State next_state) {
214 if (state_ != kStarted && next_state == kStarted &&
215 !creation_time_.is_null()) {
216 UMA_HISTOGRAM_TIMES("Media.TimeToPipelineStarted",
217 default_tick_clock_.NowTicks() - creation_time_);
218 creation_time_ = base::TimeTicks();
221 DVLOG(2) << GetStateString(state_) << " -> " << GetStateString(next_state);
223 state_ = next_state;
224 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
227 #define RETURN_STRING(state) case state: return #state;
229 const char* Pipeline::GetStateString(State state) {
230 switch (state) {
231 RETURN_STRING(kCreated);
232 RETURN_STRING(kInitDemuxer);
233 RETURN_STRING(kInitAudioRenderer);
234 RETURN_STRING(kInitVideoRenderer);
235 RETURN_STRING(kInitPrerolling);
236 RETURN_STRING(kSeeking);
237 RETURN_STRING(kStarting);
238 RETURN_STRING(kStarted);
239 RETURN_STRING(kStopping);
240 RETURN_STRING(kStopped);
242 NOTREACHED();
243 return "INVALID";
246 #undef RETURN_STRING
248 Pipeline::State Pipeline::GetNextState() const {
249 DCHECK(message_loop_->BelongsToCurrentThread());
250 DCHECK(stop_cb_.is_null())
251 << "State transitions don't happen when stopping";
252 DCHECK_EQ(status_, PIPELINE_OK)
253 << "State transitions don't happen when there's an error: " << status_;
255 switch (state_) {
256 case kCreated:
257 return kInitDemuxer;
259 case kInitDemuxer:
260 if (demuxer_->GetStream(DemuxerStream::AUDIO))
261 return kInitAudioRenderer;
262 if (demuxer_->GetStream(DemuxerStream::VIDEO))
263 return kInitVideoRenderer;
264 return kInitPrerolling;
266 case kInitAudioRenderer:
267 if (demuxer_->GetStream(DemuxerStream::VIDEO))
268 return kInitVideoRenderer;
269 return kInitPrerolling;
271 case kInitVideoRenderer:
272 return kInitPrerolling;
274 case kInitPrerolling:
275 return kStarting;
277 case kSeeking:
278 return kStarting;
280 case kStarting:
281 return kStarted;
283 case kStarted:
284 case kStopping:
285 case kStopped:
286 break;
288 NOTREACHED() << "State has no transition: " << state_;
289 return state_;
292 void Pipeline::OnDemuxerError(PipelineStatus error) {
293 SetError(error);
296 void Pipeline::SetError(PipelineStatus error) {
297 DCHECK(IsRunning());
298 DCHECK_NE(PIPELINE_OK, error);
299 VLOG(1) << "Media pipeline error: " << error;
301 message_loop_->PostTask(FROM_HERE, base::Bind(
302 &Pipeline::ErrorChangedTask, base::Unretained(this), error));
304 media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error));
307 void Pipeline::OnAudioDisabled() {
308 DCHECK(IsRunning());
309 message_loop_->PostTask(FROM_HERE, base::Bind(
310 &Pipeline::AudioDisabledTask, base::Unretained(this)));
311 media_log_->AddEvent(
312 media_log_->CreateEvent(MediaLogEvent::AUDIO_RENDERER_DISABLED));
315 void Pipeline::OnAudioTimeUpdate(TimeDelta time, TimeDelta max_time) {
316 DCHECK_LE(time.InMicroseconds(), max_time.InMicroseconds());
317 DCHECK(IsRunning());
318 base::AutoLock auto_lock(lock_);
320 if (!has_audio_)
321 return;
322 if (waiting_for_clock_update_ && time < clock_->Elapsed())
323 return;
325 // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
326 // http://crbug.com/137973
327 if (state_ == kSeeking)
328 return;
330 clock_->SetTime(time, max_time);
331 StartClockIfWaitingForTimeUpdate_Locked();
334 void Pipeline::OnVideoTimeUpdate(TimeDelta max_time) {
335 DCHECK(IsRunning());
336 base::AutoLock auto_lock(lock_);
338 if (has_audio_)
339 return;
341 // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
342 // http://crbug.com/137973
343 if (state_ == kSeeking)
344 return;
346 DCHECK(!waiting_for_clock_update_);
347 clock_->SetMaxTime(max_time);
350 void Pipeline::SetDuration(TimeDelta duration) {
351 DCHECK(IsRunning());
352 media_log_->AddEvent(
353 media_log_->CreateTimeEvent(
354 MediaLogEvent::DURATION_SET, "duration", duration));
355 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
357 base::AutoLock auto_lock(lock_);
358 clock_->SetDuration(duration);
359 if (!duration_change_cb_.is_null())
360 duration_change_cb_.Run();
363 void Pipeline::SetTotalBytes(int64 total_bytes) {
364 DCHECK(IsRunning());
365 media_log_->AddEvent(
366 media_log_->CreateStringEvent(
367 MediaLogEvent::TOTAL_BYTES_SET, "total_bytes",
368 base::Int64ToString(total_bytes)));
369 int64 total_mbytes = total_bytes >> 20;
370 if (total_mbytes > kint32max)
371 total_mbytes = kint32max;
372 UMA_HISTOGRAM_CUSTOM_COUNTS(
373 "Media.TotalMBytes", static_cast<int32>(total_mbytes), 1, kint32max, 50);
375 base::AutoLock auto_lock(lock_);
376 total_bytes_ = total_bytes;
379 TimeDelta Pipeline::TimeForByteOffset_Locked(int64 byte_offset) const {
380 lock_.AssertAcquired();
381 TimeDelta time_offset = byte_offset * clock_->Duration() / total_bytes_;
382 // Since the byte->time calculation is approximate, fudge the beginning &
383 // ending areas to look better.
384 TimeDelta epsilon = clock_->Duration() / 100;
385 if (time_offset < epsilon)
386 return TimeDelta();
387 if (time_offset + epsilon > clock_->Duration())
388 return clock_->Duration();
389 return time_offset;
392 void Pipeline::OnStateTransition(PipelineStatus status) {
393 // Force post to process state transitions after current execution frame.
394 message_loop_->PostTask(FROM_HERE, base::Bind(
395 &Pipeline::StateTransitionTask, base::Unretained(this), status));
398 void Pipeline::StateTransitionTask(PipelineStatus status) {
399 DCHECK(message_loop_->BelongsToCurrentThread());
401 // No-op any state transitions if we're stopping.
402 if (state_ == kStopping || state_ == kStopped)
403 return;
405 // Preserve existing abnormal status, otherwise update based on the result of
406 // the previous operation.
407 status_ = (status_ != PIPELINE_OK ? status_ : status);
409 if (status_ != PIPELINE_OK) {
410 ErrorChangedTask(status_);
411 return;
414 // Guard against accidentally clearing |pending_callbacks_| for states that
415 // use it as well as states that should not be using it.
417 // TODO(scherkus): Make every state transition use |pending_callbacks_|.
418 DCHECK_EQ(pending_callbacks_.get() != NULL,
419 (state_ == kInitPrerolling || state_ == kStarting ||
420 state_ == kSeeking));
421 pending_callbacks_.reset();
423 PipelineStatusCB done_cb = base::Bind(
424 &Pipeline::OnStateTransition, base::Unretained(this));
426 // Switch states, performing any entrance actions for the new state as well.
427 SetState(GetNextState());
428 switch (state_) {
429 case kInitDemuxer:
430 return InitializeDemuxer(done_cb);
432 case kInitAudioRenderer:
433 return InitializeAudioRenderer(done_cb);
435 case kInitVideoRenderer:
436 return InitializeVideoRenderer(done_cb);
438 case kInitPrerolling:
439 filter_collection_.reset();
441 base::AutoLock l(lock_);
442 // We do not want to start the clock running. We only want to set the
443 // base media time so our timestamp calculations will be correct.
444 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime());
446 // TODO(scherkus): |has_audio_| should be true no matter what --
447 // otherwise people with muted/disabled sound cards will make our
448 // default controls look as if every video doesn't contain an audio
449 // track.
450 has_audio_ = audio_renderer_ != NULL && !audio_disabled_;
451 has_video_ = video_renderer_ != NULL;
453 if (!audio_renderer_ && !video_renderer_) {
454 done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
455 return;
458 buffering_state_cb_.Run(kHaveMetadata);
460 return DoInitialPreroll(done_cb);
462 case kStarting:
463 return DoPlay(done_cb);
465 case kStarted:
467 base::AutoLock l(lock_);
468 // We use audio stream to update the clock. So if there is such a
469 // stream, we pause the clock until we receive a valid timestamp.
470 waiting_for_clock_update_ = true;
471 if (!has_audio_) {
472 clock_->SetMaxTime(clock_->Duration());
473 StartClockIfWaitingForTimeUpdate_Locked();
477 DCHECK(!seek_cb_.is_null());
478 DCHECK_EQ(status_, PIPELINE_OK);
480 // Fire canplaythrough immediately after playback begins because of
481 // crbug.com/106480.
482 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
483 buffering_state_cb_.Run(kPrerollCompleted);
484 return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
486 case kStopping:
487 case kStopped:
488 case kCreated:
489 case kSeeking:
490 NOTREACHED() << "State has no transition: " << state_;
491 return;
495 // Note that the usage of base::Unretained() with the audio/video renderers
496 // in the following DoXXX() functions is considered safe as they are owned by
497 // |pending_callbacks_| and share the same lifetime.
499 // That being said, deleting the renderers while keeping |pending_callbacks_|
500 // running on the media thread would result in crashes.
501 void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) {
502 DCHECK(message_loop_->BelongsToCurrentThread());
503 DCHECK(!pending_callbacks_.get());
504 SerialRunner::Queue bound_fns;
506 base::TimeDelta seek_timestamp = demuxer_->GetStartTime();
508 // Preroll renderers.
509 if (audio_renderer_) {
510 bound_fns.Push(base::Bind(
511 &AudioRenderer::Preroll, base::Unretained(audio_renderer_.get()),
512 seek_timestamp));
515 if (video_renderer_) {
516 bound_fns.Push(base::Bind(
517 &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()),
518 seek_timestamp));
521 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
524 void Pipeline::DoSeek(
525 base::TimeDelta seek_timestamp,
526 const PipelineStatusCB& done_cb) {
527 DCHECK(message_loop_->BelongsToCurrentThread());
528 DCHECK(!pending_callbacks_.get());
529 SerialRunner::Queue bound_fns;
531 // Pause.
532 if (audio_renderer_) {
533 bound_fns.Push(base::Bind(
534 &AudioRenderer::Pause, base::Unretained(audio_renderer_.get())));
536 if (video_renderer_) {
537 bound_fns.Push(base::Bind(
538 &VideoRenderer::Pause, base::Unretained(video_renderer_.get())));
541 // Flush.
542 if (audio_renderer_) {
543 bound_fns.Push(base::Bind(
544 &AudioRenderer::Flush, base::Unretained(audio_renderer_.get())));
546 if (video_renderer_) {
547 bound_fns.Push(base::Bind(
548 &VideoRenderer::Flush, base::Unretained(video_renderer_.get())));
551 // Seek demuxer.
552 bound_fns.Push(base::Bind(
553 &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp));
555 // Preroll renderers.
556 if (audio_renderer_) {
557 bound_fns.Push(base::Bind(
558 &AudioRenderer::Preroll, base::Unretained(audio_renderer_.get()),
559 seek_timestamp));
562 if (video_renderer_) {
563 bound_fns.Push(base::Bind(
564 &VideoRenderer::Preroll, base::Unretained(video_renderer_.get()),
565 seek_timestamp));
568 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
571 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) {
572 DCHECK(message_loop_->BelongsToCurrentThread());
573 DCHECK(!pending_callbacks_.get());
574 SerialRunner::Queue bound_fns;
576 PlaybackRateChangedTask(GetPlaybackRate());
577 VolumeChangedTask(GetVolume());
579 if (audio_renderer_) {
580 bound_fns.Push(base::Bind(
581 &AudioRenderer::Play, base::Unretained(audio_renderer_.get())));
584 if (video_renderer_) {
585 bound_fns.Push(base::Bind(
586 &VideoRenderer::Play, base::Unretained(video_renderer_.get())));
589 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
592 void Pipeline::DoStop(const PipelineStatusCB& done_cb) {
593 DCHECK(message_loop_->BelongsToCurrentThread());
594 DCHECK(!pending_callbacks_.get());
595 SerialRunner::Queue bound_fns;
597 if (demuxer_) {
598 bound_fns.Push(base::Bind(
599 &Demuxer::Stop, base::Unretained(demuxer_)));
602 if (audio_renderer_) {
603 bound_fns.Push(base::Bind(
604 &AudioRenderer::Stop, base::Unretained(audio_renderer_.get())));
607 if (video_renderer_) {
608 bound_fns.Push(base::Bind(
609 &VideoRenderer::Stop, base::Unretained(video_renderer_.get())));
612 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
615 void Pipeline::OnStopCompleted(PipelineStatus status) {
616 DCHECK(message_loop_->BelongsToCurrentThread());
617 DCHECK_EQ(state_, kStopping);
619 base::AutoLock l(lock_);
620 running_ = false;
623 SetState(kStopped);
624 pending_callbacks_.reset();
625 filter_collection_.reset();
626 audio_renderer_.reset();
627 video_renderer_.reset();
628 demuxer_ = NULL;
630 // If we stop during initialization/seeking we want to run |seek_cb_|
631 // followed by |stop_cb_| so we don't leave outstanding callbacks around.
632 if (!seek_cb_.is_null()) {
633 base::ResetAndReturn(&seek_cb_).Run(status_);
634 error_cb_.Reset();
636 if (!stop_cb_.is_null()) {
637 error_cb_.Reset();
638 base::ResetAndReturn(&stop_cb_).Run();
640 // NOTE: pipeline may be deleted at this point in time as a result of
641 // executing |stop_cb_|.
642 return;
644 if (!error_cb_.is_null()) {
645 DCHECK_NE(status_, PIPELINE_OK);
646 base::ResetAndReturn(&error_cb_).Run(status_);
650 void Pipeline::AddBufferedByteRange(int64 start, int64 end) {
651 DCHECK(IsRunning());
652 base::AutoLock auto_lock(lock_);
653 buffered_byte_ranges_.Add(start, end);
654 did_loading_progress_ = true;
657 void Pipeline::AddBufferedTimeRange(base::TimeDelta start,
658 base::TimeDelta end) {
659 DCHECK(IsRunning());
660 base::AutoLock auto_lock(lock_);
661 buffered_time_ranges_.Add(start, end);
662 did_loading_progress_ = true;
665 void Pipeline::OnNaturalVideoSizeChanged(const gfx::Size& size) {
666 DCHECK(IsRunning());
667 media_log_->AddEvent(media_log_->CreateVideoSizeSetEvent(
668 size.width(), size.height()));
670 base::AutoLock auto_lock(lock_);
671 natural_size_ = size;
674 void Pipeline::OnAudioRendererEnded() {
675 // Force post to process ended messages after current execution frame.
676 message_loop_->PostTask(FROM_HERE, base::Bind(
677 &Pipeline::DoAudioRendererEnded, base::Unretained(this)));
678 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::AUDIO_ENDED));
681 void Pipeline::OnVideoRendererEnded() {
682 // Force post to process ended messages after current execution frame.
683 message_loop_->PostTask(FROM_HERE, base::Bind(
684 &Pipeline::DoVideoRendererEnded, base::Unretained(this)));
685 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED));
688 // Called from any thread.
689 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) {
690 base::AutoLock auto_lock(lock_);
691 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
692 statistics_.video_bytes_decoded += stats.video_bytes_decoded;
693 statistics_.video_frames_decoded += stats.video_frames_decoded;
694 statistics_.video_frames_dropped += stats.video_frames_dropped;
697 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection,
698 const base::Closure& ended_cb,
699 const PipelineStatusCB& error_cb,
700 const PipelineStatusCB& seek_cb,
701 const BufferingStateCB& buffering_state_cb,
702 const base::Closure& duration_change_cb) {
703 DCHECK(message_loop_->BelongsToCurrentThread());
704 CHECK_EQ(kCreated, state_)
705 << "Media pipeline cannot be started more than once";
707 filter_collection_ = filter_collection.Pass();
708 ended_cb_ = ended_cb;
709 error_cb_ = error_cb;
710 seek_cb_ = seek_cb;
711 buffering_state_cb_ = buffering_state_cb;
712 duration_change_cb_ = duration_change_cb;
714 StateTransitionTask(PIPELINE_OK);
717 void Pipeline::StopTask(const base::Closure& stop_cb) {
718 DCHECK(message_loop_->BelongsToCurrentThread());
719 DCHECK(stop_cb_.is_null());
721 if (state_ == kStopped) {
722 stop_cb.Run();
723 return;
726 stop_cb_ = stop_cb;
728 // We may already be stopping due to a runtime error.
729 if (state_ == kStopping)
730 return;
732 SetState(kStopping);
733 pending_callbacks_.reset();
734 DoStop(base::Bind(&Pipeline::OnStopCompleted, base::Unretained(this)));
737 void Pipeline::ErrorChangedTask(PipelineStatus error) {
738 DCHECK(message_loop_->BelongsToCurrentThread());
739 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
741 if (state_ == kStopping || state_ == kStopped)
742 return;
744 SetState(kStopping);
745 pending_callbacks_.reset();
746 status_ = error;
748 DoStop(base::Bind(&Pipeline::OnStopCompleted, base::Unretained(this)));
751 void Pipeline::PlaybackRateChangedTask(float playback_rate) {
752 DCHECK(message_loop_->BelongsToCurrentThread());
754 // Playback rate changes are only carried out while playing.
755 if (state_ != kStarting && state_ != kStarted)
756 return;
759 base::AutoLock auto_lock(lock_);
760 clock_->SetPlaybackRate(playback_rate);
763 if (demuxer_)
764 demuxer_->SetPlaybackRate(playback_rate);
765 if (audio_renderer_)
766 audio_renderer_->SetPlaybackRate(playback_rate_);
767 if (video_renderer_)
768 video_renderer_->SetPlaybackRate(playback_rate_);
771 void Pipeline::VolumeChangedTask(float volume) {
772 DCHECK(message_loop_->BelongsToCurrentThread());
774 // Volume changes are only carried out while playing.
775 if (state_ != kStarting && state_ != kStarted)
776 return;
778 if (audio_renderer_)
779 audio_renderer_->SetVolume(volume);
782 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
783 DCHECK(message_loop_->BelongsToCurrentThread());
784 DCHECK(stop_cb_.is_null());
786 // Suppress seeking if we're not fully started.
787 if (state_ != kStarted) {
788 DCHECK(state_ == kStopping || state_ == kStopped)
789 << "Receive extra seek in unexpected state: " << state_;
791 // TODO(scherkus): should we run the callback? I'm tempted to say the API
792 // will only execute the first Seek() request.
793 DVLOG(1) << "Media pipeline has not started, ignoring seek to "
794 << time.InMicroseconds() << " (current state: " << state_ << ")";
795 return;
798 DCHECK(seek_cb_.is_null());
800 SetState(kSeeking);
801 base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime());
802 seek_cb_ = seek_cb;
803 audio_ended_ = false;
804 video_ended_ = false;
806 // Kick off seeking!
808 base::AutoLock auto_lock(lock_);
809 if (clock_->IsPlaying())
810 clock_->Pause();
811 clock_->SetTime(seek_timestamp, seek_timestamp);
813 DoSeek(seek_timestamp, base::Bind(
814 &Pipeline::OnStateTransition, base::Unretained(this)));
817 void Pipeline::DoAudioRendererEnded() {
818 DCHECK(message_loop_->BelongsToCurrentThread());
820 if (state_ != kStarted)
821 return;
823 DCHECK(!audio_ended_);
824 audio_ended_ = true;
826 // Start clock since there is no more audio to trigger clock updates.
827 if (!audio_disabled_) {
828 base::AutoLock auto_lock(lock_);
829 clock_->SetMaxTime(clock_->Duration());
830 StartClockIfWaitingForTimeUpdate_Locked();
833 RunEndedCallbackIfNeeded();
836 void Pipeline::DoVideoRendererEnded() {
837 DCHECK(message_loop_->BelongsToCurrentThread());
839 if (state_ != kStarted)
840 return;
842 DCHECK(!video_ended_);
843 video_ended_ = true;
845 RunEndedCallbackIfNeeded();
848 void Pipeline::RunEndedCallbackIfNeeded() {
849 DCHECK(message_loop_->BelongsToCurrentThread());
851 if (audio_renderer_ && !audio_ended_ && !audio_disabled_)
852 return;
854 if (video_renderer_ && !video_ended_)
855 return;
858 base::AutoLock auto_lock(lock_);
859 clock_->EndOfStream();
862 DCHECK_EQ(status_, PIPELINE_OK);
863 ended_cb_.Run();
866 void Pipeline::AudioDisabledTask() {
867 DCHECK(message_loop_->BelongsToCurrentThread());
869 base::AutoLock auto_lock(lock_);
870 has_audio_ = false;
871 audio_disabled_ = true;
873 // Notify our demuxer that we're no longer rendering audio.
874 demuxer_->OnAudioRendererDisabled();
876 // Start clock since there is no more audio to trigger clock updates.
877 clock_->SetMaxTime(clock_->Duration());
878 StartClockIfWaitingForTimeUpdate_Locked();
881 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) {
882 DCHECK(message_loop_->BelongsToCurrentThread());
884 demuxer_ = filter_collection_->GetDemuxer();
885 demuxer_->Initialize(this, done_cb);
888 void Pipeline::InitializeAudioRenderer(const PipelineStatusCB& done_cb) {
889 DCHECK(message_loop_->BelongsToCurrentThread());
891 audio_renderer_ = filter_collection_->GetAudioRenderer();
892 audio_renderer_->Initialize(
893 demuxer_->GetStream(DemuxerStream::AUDIO),
894 done_cb,
895 base::Bind(&Pipeline::OnUpdateStatistics, base::Unretained(this)),
896 base::Bind(&Pipeline::OnAudioUnderflow, base::Unretained(this)),
897 base::Bind(&Pipeline::OnAudioTimeUpdate, base::Unretained(this)),
898 base::Bind(&Pipeline::OnAudioRendererEnded, base::Unretained(this)),
899 base::Bind(&Pipeline::OnAudioDisabled, base::Unretained(this)),
900 base::Bind(&Pipeline::SetError, base::Unretained(this)));
903 void Pipeline::InitializeVideoRenderer(const PipelineStatusCB& done_cb) {
904 DCHECK(message_loop_->BelongsToCurrentThread());
906 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
909 // Get an initial natural size so we have something when we signal
910 // the kHaveMetadata buffering state.
911 base::AutoLock l(lock_);
912 natural_size_ = stream->video_decoder_config().natural_size();
915 video_renderer_ = filter_collection_->GetVideoRenderer();
916 video_renderer_->Initialize(
917 stream,
918 done_cb,
919 base::Bind(&Pipeline::OnUpdateStatistics, base::Unretained(this)),
920 base::Bind(&Pipeline::OnVideoTimeUpdate, base::Unretained(this)),
921 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, base::Unretained(this)),
922 base::Bind(&Pipeline::OnVideoRendererEnded, base::Unretained(this)),
923 base::Bind(&Pipeline::SetError, base::Unretained(this)),
924 base::Bind(&Pipeline::GetMediaTime, base::Unretained(this)),
925 base::Bind(&Pipeline::GetMediaDuration, base::Unretained(this)));
928 void Pipeline::OnAudioUnderflow() {
929 if (!message_loop_->BelongsToCurrentThread()) {
930 message_loop_->PostTask(FROM_HERE, base::Bind(
931 &Pipeline::OnAudioUnderflow, base::Unretained(this)));
932 return;
935 if (state_ != kStarted)
936 return;
938 if (audio_renderer_)
939 audio_renderer_->ResumeAfterUnderflow();
942 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() {
943 lock_.AssertAcquired();
944 if (!waiting_for_clock_update_)
945 return;
947 waiting_for_clock_update_ = false;
948 clock_->Play();
951 } // namespace media