Update V8 to version 4.7.56.
[chromium-blink-merge.git] / media / base / pipeline.cc
blob387470aec070dd242e5486419f9efb15d859a2fa
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/command_line.h"
13 #include "base/compiler_specific.h"
14 #include "base/location.h"
15 #include "base/metrics/histogram.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "base/synchronization/condition_variable.h"
21 #include "media/base/media_log.h"
22 #include "media/base/media_switches.h"
23 #include "media/base/renderer.h"
24 #include "media/base/text_renderer.h"
25 #include "media/base/text_track_config.h"
26 #include "media/base/video_decoder_config.h"
28 using base::TimeDelta;
30 namespace media {
32 Pipeline::Pipeline(
33 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
34 MediaLog* media_log)
35 : task_runner_(task_runner),
36 media_log_(media_log),
37 running_(false),
38 did_loading_progress_(false),
39 volume_(1.0f),
40 playback_rate_(0.0),
41 status_(PIPELINE_OK),
42 state_(kCreated),
43 renderer_ended_(false),
44 text_renderer_ended_(false),
45 demuxer_(NULL),
46 pending_cdm_context_(nullptr),
47 weak_factory_(this) {
48 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated));
51 Pipeline::~Pipeline() {
52 DCHECK(thread_checker_.CalledOnValidThread())
53 << "Pipeline must be destroyed on same thread that created it";
54 DCHECK(!running_) << "Stop() must complete before destroying object";
55 DCHECK(stop_cb_.is_null());
56 DCHECK(seek_cb_.is_null());
59 void Pipeline::Start(Demuxer* demuxer,
60 scoped_ptr<Renderer> renderer,
61 const base::Closure& ended_cb,
62 const PipelineStatusCB& error_cb,
63 const PipelineStatusCB& seek_cb,
64 const PipelineMetadataCB& metadata_cb,
65 const BufferingStateCB& buffering_state_cb,
66 const base::Closure& duration_change_cb,
67 const AddTextTrackCB& add_text_track_cb,
68 const base::Closure& waiting_for_decryption_key_cb) {
69 DCHECK(!ended_cb.is_null());
70 DCHECK(!error_cb.is_null());
71 DCHECK(!seek_cb.is_null());
72 DCHECK(!metadata_cb.is_null());
73 DCHECK(!buffering_state_cb.is_null());
75 base::AutoLock auto_lock(lock_);
76 CHECK(!running_) << "Media pipeline is already running";
77 running_ = true;
79 demuxer_ = demuxer;
80 renderer_ = renderer.Pass();
81 ended_cb_ = ended_cb;
82 error_cb_ = error_cb;
83 seek_cb_ = seek_cb;
84 metadata_cb_ = metadata_cb;
85 buffering_state_cb_ = buffering_state_cb;
86 duration_change_cb_ = duration_change_cb;
87 add_text_track_cb_ = add_text_track_cb;
88 waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb;
90 task_runner_->PostTask(
91 FROM_HERE, base::Bind(&Pipeline::StartTask, weak_factory_.GetWeakPtr()));
94 void Pipeline::Stop(const base::Closure& stop_cb) {
95 DVLOG(2) << __FUNCTION__;
96 task_runner_->PostTask(
97 FROM_HERE,
98 base::Bind(&Pipeline::StopTask, weak_factory_.GetWeakPtr(), stop_cb));
101 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) {
102 base::AutoLock auto_lock(lock_);
103 if (!running_) {
104 DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek().";
105 return;
108 task_runner_->PostTask(
109 FROM_HERE,
110 base::Bind(
111 &Pipeline::SeekTask, weak_factory_.GetWeakPtr(), time, seek_cb));
114 bool Pipeline::IsRunning() const {
115 base::AutoLock auto_lock(lock_);
116 return running_;
119 double Pipeline::GetPlaybackRate() const {
120 base::AutoLock auto_lock(lock_);
121 return playback_rate_;
124 void Pipeline::SetPlaybackRate(double playback_rate) {
125 if (playback_rate < 0.0)
126 return;
128 base::AutoLock auto_lock(lock_);
129 playback_rate_ = playback_rate;
130 if (running_) {
131 task_runner_->PostTask(FROM_HERE,
132 base::Bind(&Pipeline::PlaybackRateChangedTask,
133 weak_factory_.GetWeakPtr(),
134 playback_rate));
138 float Pipeline::GetVolume() const {
139 base::AutoLock auto_lock(lock_);
140 return volume_;
143 void Pipeline::SetVolume(float volume) {
144 if (volume < 0.0f || volume > 1.0f)
145 return;
147 base::AutoLock auto_lock(lock_);
148 volume_ = volume;
149 if (running_) {
150 task_runner_->PostTask(
151 FROM_HERE,
152 base::Bind(
153 &Pipeline::VolumeChangedTask, weak_factory_.GetWeakPtr(), volume));
157 TimeDelta Pipeline::GetMediaTime() const {
158 base::AutoLock auto_lock(lock_);
159 return renderer_ ? std::min(renderer_->GetMediaTime(), duration_)
160 : TimeDelta();
163 Ranges<TimeDelta> Pipeline::GetBufferedTimeRanges() const {
164 base::AutoLock auto_lock(lock_);
165 return buffered_time_ranges_;
168 TimeDelta Pipeline::GetMediaDuration() const {
169 base::AutoLock auto_lock(lock_);
170 return duration_;
173 bool Pipeline::DidLoadingProgress() {
174 base::AutoLock auto_lock(lock_);
175 bool ret = did_loading_progress_;
176 did_loading_progress_ = false;
177 return ret;
180 PipelineStatistics Pipeline::GetStatistics() const {
181 base::AutoLock auto_lock(lock_);
182 return statistics_;
185 void Pipeline::SetCdm(CdmContext* cdm_context,
186 const CdmAttachedCB& cdm_attached_cb) {
187 task_runner_->PostTask(
188 FROM_HERE, base::Bind(&Pipeline::SetCdmTask, weak_factory_.GetWeakPtr(),
189 cdm_context, cdm_attached_cb));
192 void Pipeline::SetErrorForTesting(PipelineStatus status) {
193 OnError(status);
196 bool Pipeline::HasWeakPtrsForTesting() const {
197 DCHECK(task_runner_->BelongsToCurrentThread());
198 return weak_factory_.HasWeakPtrs();
201 void Pipeline::SetState(State next_state) {
202 DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state);
204 state_ = next_state;
205 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
208 #define RETURN_STRING(state) case state: return #state;
210 const char* Pipeline::GetStateString(State state) {
211 switch (state) {
212 RETURN_STRING(kCreated);
213 RETURN_STRING(kInitDemuxer);
214 RETURN_STRING(kInitRenderer);
215 RETURN_STRING(kSeeking);
216 RETURN_STRING(kPlaying);
217 RETURN_STRING(kStopping);
218 RETURN_STRING(kStopped);
220 NOTREACHED();
221 return "INVALID";
224 #undef RETURN_STRING
226 Pipeline::State Pipeline::GetNextState() const {
227 DCHECK(task_runner_->BelongsToCurrentThread());
228 DCHECK(stop_cb_.is_null())
229 << "State transitions don't happen when stopping";
230 DCHECK_EQ(status_, PIPELINE_OK)
231 << "State transitions don't happen when there's an error: " << status_;
233 switch (state_) {
234 case kCreated:
235 return kInitDemuxer;
237 case kInitDemuxer:
238 return kInitRenderer;
240 case kInitRenderer:
241 case kSeeking:
242 return kPlaying;
244 case kPlaying:
245 case kStopping:
246 case kStopped:
247 break;
249 NOTREACHED() << "State has no transition: " << state_;
250 return state_;
253 void Pipeline::OnDemuxerError(PipelineStatus error) {
254 task_runner_->PostTask(FROM_HERE,
255 base::Bind(&Pipeline::ErrorChangedTask,
256 weak_factory_.GetWeakPtr(),
257 error));
260 void Pipeline::AddTextStream(DemuxerStream* text_stream,
261 const TextTrackConfig& config) {
262 task_runner_->PostTask(FROM_HERE,
263 base::Bind(&Pipeline::AddTextStreamTask,
264 weak_factory_.GetWeakPtr(),
265 text_stream,
266 config));
269 void Pipeline::RemoveTextStream(DemuxerStream* text_stream) {
270 task_runner_->PostTask(FROM_HERE,
271 base::Bind(&Pipeline::RemoveTextStreamTask,
272 weak_factory_.GetWeakPtr(),
273 text_stream));
276 void Pipeline::OnError(PipelineStatus error) {
277 DCHECK(task_runner_->BelongsToCurrentThread());
278 DCHECK(IsRunning());
279 DCHECK_NE(PIPELINE_OK, error);
280 VLOG(1) << "Media pipeline error: " << error;
282 task_runner_->PostTask(FROM_HERE, base::Bind(
283 &Pipeline::ErrorChangedTask, weak_factory_.GetWeakPtr(), error));
286 void Pipeline::SetDuration(TimeDelta duration) {
287 DCHECK(IsRunning());
288 media_log_->AddEvent(
289 media_log_->CreateTimeEvent(
290 MediaLogEvent::DURATION_SET, "duration", duration));
291 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
293 base::AutoLock auto_lock(lock_);
294 duration_ = duration;
295 if (!duration_change_cb_.is_null())
296 duration_change_cb_.Run();
299 void Pipeline::StateTransitionTask(PipelineStatus status) {
300 DCHECK(task_runner_->BelongsToCurrentThread());
302 // No-op any state transitions if we're stopping.
303 if (state_ == kStopping || state_ == kStopped)
304 return;
306 // Preserve existing abnormal status, otherwise update based on the result of
307 // the previous operation.
308 status_ = (status_ != PIPELINE_OK ? status_ : status);
310 if (status_ != PIPELINE_OK) {
311 ErrorChangedTask(status_);
312 return;
315 // Guard against accidentally clearing |pending_callbacks_| for states that
316 // use it as well as states that should not be using it.
317 DCHECK_EQ(pending_callbacks_.get() != NULL, state_ == kSeeking);
319 pending_callbacks_.reset();
321 PipelineStatusCB done_cb =
322 base::Bind(&Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr());
324 // Switch states, performing any entrance actions for the new state as well.
325 SetState(GetNextState());
326 switch (state_) {
327 case kInitDemuxer:
328 return InitializeDemuxer(done_cb);
330 case kInitRenderer:
331 // When the state_ transfers to kInitRenderer, it means the demuxer has
332 // finished parsing the init info. It should call ReportMetadata in case
333 // meeting 'decode' error when passing media segment but WebMediaPlayer's
334 // ready_state_ is still ReadyStateHaveNothing. In that case, it will
335 // treat it as NetworkStateFormatError not NetworkStateDecodeError.
336 ReportMetadata();
337 start_timestamp_ = demuxer_->GetStartTime();
339 return InitializeRenderer(done_cb);
341 case kPlaying:
342 DCHECK(start_timestamp_ >= base::TimeDelta());
343 renderer_->StartPlayingFrom(start_timestamp_);
345 if (text_renderer_)
346 text_renderer_->StartPlaying();
348 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
350 PlaybackRateChangedTask(GetPlaybackRate());
351 VolumeChangedTask(GetVolume());
352 return;
354 case kStopping:
355 case kStopped:
356 case kCreated:
357 case kSeeking:
358 NOTREACHED() << "State has no transition: " << state_;
359 return;
363 // Note that the usage of base::Unretained() with the renderers is considered
364 // safe as they are owned by |pending_callbacks_| and share the same lifetime.
366 // That being said, deleting the renderers while keeping |pending_callbacks_|
367 // running on the media thread would result in crashes.
368 void Pipeline::DoSeek(TimeDelta seek_timestamp,
369 const PipelineStatusCB& done_cb) {
370 DCHECK(task_runner_->BelongsToCurrentThread());
371 DCHECK(!pending_callbacks_.get());
372 DCHECK_EQ(state_, kSeeking);
373 SerialRunner::Queue bound_fns;
375 // Pause.
376 if (text_renderer_) {
377 bound_fns.Push(base::Bind(
378 &TextRenderer::Pause, base::Unretained(text_renderer_.get())));
381 // Flush.
382 DCHECK(renderer_);
383 bound_fns.Push(
384 base::Bind(&Renderer::Flush, base::Unretained(renderer_.get())));
386 if (text_renderer_) {
387 bound_fns.Push(base::Bind(
388 &TextRenderer::Flush, base::Unretained(text_renderer_.get())));
391 // Seek demuxer.
392 bound_fns.Push(base::Bind(
393 &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp));
395 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
398 void Pipeline::DoStop(const PipelineStatusCB& done_cb) {
399 DVLOG(2) << __FUNCTION__;
400 DCHECK(task_runner_->BelongsToCurrentThread());
401 DCHECK(!pending_callbacks_.get());
403 // TODO(scherkus): Enforce that Renderer is only called on a single thread,
404 // even for accessing media time http://crbug.com/370634
405 scoped_ptr<Renderer> renderer;
407 base::AutoLock auto_lock(lock_);
408 renderer.swap(renderer_);
410 renderer.reset();
411 text_renderer_.reset();
413 if (demuxer_) {
414 demuxer_->Stop();
415 demuxer_ = NULL;
418 task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
421 void Pipeline::OnStopCompleted(PipelineStatus status) {
422 DVLOG(2) << __FUNCTION__;
423 DCHECK(task_runner_->BelongsToCurrentThread());
424 DCHECK_EQ(state_, kStopping);
425 DCHECK(!renderer_);
426 DCHECK(!text_renderer_);
429 base::AutoLock auto_lock(lock_);
430 running_ = false;
433 SetState(kStopped);
434 demuxer_ = NULL;
436 // If we stop during initialization/seeking we want to run |seek_cb_|
437 // followed by |stop_cb_| so we don't leave outstanding callbacks around.
438 if (!seek_cb_.is_null()) {
439 base::ResetAndReturn(&seek_cb_).Run(status_);
440 error_cb_.Reset();
442 if (!stop_cb_.is_null()) {
443 error_cb_.Reset();
445 // Invalid all weak pointers so it's safe to destroy |this| on the render
446 // main thread.
447 weak_factory_.InvalidateWeakPtrs();
449 base::ResetAndReturn(&stop_cb_).Run();
451 // NOTE: pipeline may be deleted at this point in time as a result of
452 // executing |stop_cb_|.
453 return;
455 if (!error_cb_.is_null()) {
456 DCHECK_NE(status_, PIPELINE_OK);
457 base::ResetAndReturn(&error_cb_).Run(status_);
461 void Pipeline::AddBufferedTimeRange(TimeDelta start, TimeDelta end) {
462 DCHECK(IsRunning());
463 base::AutoLock auto_lock(lock_);
464 buffered_time_ranges_.Add(start, end);
465 did_loading_progress_ = true;
468 // Called from any thread.
469 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) {
470 base::AutoLock auto_lock(lock_);
471 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
472 statistics_.video_bytes_decoded += stats.video_bytes_decoded;
473 statistics_.video_frames_decoded += stats.video_frames_decoded;
474 statistics_.video_frames_dropped += stats.video_frames_dropped;
477 void Pipeline::StartTask() {
478 DCHECK(task_runner_->BelongsToCurrentThread());
480 CHECK_EQ(kCreated, state_)
481 << "Media pipeline cannot be started more than once";
483 text_renderer_ = CreateTextRenderer();
484 if (text_renderer_) {
485 text_renderer_->Initialize(
486 base::Bind(&Pipeline::OnTextRendererEnded, weak_factory_.GetWeakPtr()));
489 // Set CDM early to avoid unnecessary delay in Renderer::Initialize().
490 if (pending_cdm_context_) {
491 renderer_->SetCdm(pending_cdm_context_, base::Bind(&IgnoreCdmAttached));
492 pending_cdm_context_ = nullptr;
495 StateTransitionTask(PIPELINE_OK);
498 void Pipeline::StopTask(const base::Closure& stop_cb) {
499 DCHECK(task_runner_->BelongsToCurrentThread());
500 DCHECK(stop_cb_.is_null());
502 if (state_ == kStopped) {
503 // Invalid all weak pointers so it's safe to destroy |this| on the render
504 // main thread.
505 weak_factory_.InvalidateWeakPtrs();
507 // NOTE: pipeline may be deleted at this point in time as a result of
508 // executing |stop_cb|.
509 stop_cb.Run();
511 return;
514 stop_cb_ = stop_cb;
516 // We may already be stopping due to a runtime error.
517 if (state_ == kStopping)
518 return;
520 // Do not report statistics if the pipeline is not fully initialized.
521 if (state_ == kSeeking || state_ == kPlaying) {
522 PipelineStatistics stats = GetStatistics();
523 if (renderer_->HasVideo() && stats.video_frames_decoded > 0) {
524 UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount",
525 stats.video_frames_dropped);
529 SetState(kStopping);
530 pending_callbacks_.reset();
531 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr()));
534 void Pipeline::ErrorChangedTask(PipelineStatus error) {
535 DCHECK(task_runner_->BelongsToCurrentThread());
536 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
538 media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error));
540 if (state_ == kStopping || state_ == kStopped)
541 return;
543 SetState(kStopping);
544 pending_callbacks_.reset();
545 status_ = error;
547 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr()));
550 void Pipeline::PlaybackRateChangedTask(double playback_rate) {
551 DCHECK(task_runner_->BelongsToCurrentThread());
553 // Playback rate changes are only carried out while playing.
554 if (state_ != kPlaying)
555 return;
557 renderer_->SetPlaybackRate(playback_rate);
560 void Pipeline::VolumeChangedTask(float volume) {
561 DCHECK(task_runner_->BelongsToCurrentThread());
563 // Volume changes are only carried out while playing.
564 if (state_ != kPlaying)
565 return;
567 renderer_->SetVolume(volume);
570 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
571 DCHECK(task_runner_->BelongsToCurrentThread());
572 DCHECK(stop_cb_.is_null());
574 // Suppress seeking if we're not fully started.
575 if (state_ != kPlaying) {
576 DCHECK(state_ == kStopping || state_ == kStopped)
577 << "Receive extra seek in unexpected state: " << state_;
579 // TODO(scherkus): should we run the callback? I'm tempted to say the API
580 // will only execute the first Seek() request.
581 DVLOG(1) << "Media pipeline has not started, ignoring seek to "
582 << time.InMicroseconds() << " (current state: " << state_ << ")";
583 return;
586 DCHECK(seek_cb_.is_null());
588 const base::TimeDelta seek_timestamp =
589 std::max(time, demuxer_->GetStartTime());
591 SetState(kSeeking);
592 seek_cb_ = seek_cb;
593 renderer_ended_ = false;
594 text_renderer_ended_ = false;
595 start_timestamp_ = seek_timestamp;
597 DoSeek(seek_timestamp, base::Bind(&Pipeline::StateTransitionTask,
598 weak_factory_.GetWeakPtr()));
601 void Pipeline::SetCdmTask(CdmContext* cdm_context,
602 const CdmAttachedCB& cdm_attached_cb) {
603 base::AutoLock auto_lock(lock_);
604 if (!renderer_) {
605 pending_cdm_context_ = cdm_context;
606 cdm_attached_cb.Run(true);
607 return;
610 renderer_->SetCdm(cdm_context, cdm_attached_cb);
613 void Pipeline::OnRendererEnded() {
614 DCHECK(task_runner_->BelongsToCurrentThread());
615 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED));
617 if (state_ != kPlaying)
618 return;
620 DCHECK(!renderer_ended_);
621 renderer_ended_ = true;
623 RunEndedCallbackIfNeeded();
626 void Pipeline::OnTextRendererEnded() {
627 DCHECK(task_runner_->BelongsToCurrentThread());
628 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED));
630 if (state_ != kPlaying)
631 return;
633 DCHECK(!text_renderer_ended_);
634 text_renderer_ended_ = true;
636 RunEndedCallbackIfNeeded();
639 void Pipeline::RunEndedCallbackIfNeeded() {
640 DCHECK(task_runner_->BelongsToCurrentThread());
642 if (renderer_ && !renderer_ended_)
643 return;
645 if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_)
646 return;
648 DCHECK_EQ(status_, PIPELINE_OK);
649 ended_cb_.Run();
652 scoped_ptr<TextRenderer> Pipeline::CreateTextRenderer() {
653 DCHECK(task_runner_->BelongsToCurrentThread());
655 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
656 if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks))
657 return scoped_ptr<media::TextRenderer>();
659 return scoped_ptr<media::TextRenderer>(new media::TextRenderer(
660 task_runner_,
661 base::Bind(&Pipeline::OnAddTextTrack, weak_factory_.GetWeakPtr())));
664 void Pipeline::AddTextStreamTask(DemuxerStream* text_stream,
665 const TextTrackConfig& config) {
666 DCHECK(task_runner_->BelongsToCurrentThread());
667 // TODO(matthewjheaney): fix up text_ended_ when text stream
668 // is added (http://crbug.com/321446).
669 if (text_renderer_)
670 text_renderer_->AddTextStream(text_stream, config);
673 void Pipeline::RemoveTextStreamTask(DemuxerStream* text_stream) {
674 DCHECK(task_runner_->BelongsToCurrentThread());
675 if (text_renderer_)
676 text_renderer_->RemoveTextStream(text_stream);
679 void Pipeline::OnAddTextTrack(const TextTrackConfig& config,
680 const AddTextTrackDoneCB& done_cb) {
681 DCHECK(task_runner_->BelongsToCurrentThread());
682 add_text_track_cb_.Run(config, done_cb);
685 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) {
686 DCHECK(task_runner_->BelongsToCurrentThread());
687 demuxer_->Initialize(this, done_cb, text_renderer_);
690 void Pipeline::InitializeRenderer(const PipelineStatusCB& done_cb) {
691 DCHECK(task_runner_->BelongsToCurrentThread());
693 if (!demuxer_->GetStream(DemuxerStream::AUDIO) &&
694 !demuxer_->GetStream(DemuxerStream::VIDEO)) {
696 base::AutoLock auto_lock(lock_);
697 renderer_.reset();
699 OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
700 return;
703 base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr();
704 renderer_->Initialize(
705 demuxer_,
706 done_cb,
707 base::Bind(&Pipeline::OnUpdateStatistics, weak_this),
708 base::Bind(&Pipeline::BufferingStateChanged, weak_this),
709 base::Bind(&Pipeline::OnRendererEnded, weak_this),
710 base::Bind(&Pipeline::OnError, weak_this),
711 waiting_for_decryption_key_cb_);
714 void Pipeline::ReportMetadata() {
715 DCHECK(task_runner_->BelongsToCurrentThread());
716 PipelineMetadata metadata;
717 metadata.timeline_offset = demuxer_->GetTimelineOffset();
718 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO);
719 if (stream) {
720 metadata.has_video = true;
721 metadata.natural_size = stream->video_decoder_config().natural_size();
722 metadata.video_rotation = stream->video_rotation();
724 if (demuxer_->GetStream(DemuxerStream::AUDIO)) {
725 metadata.has_audio = true;
727 metadata_cb_.Run(metadata);
730 void Pipeline::BufferingStateChanged(BufferingState new_buffering_state) {
731 DVLOG(1) << __FUNCTION__ << "(" << new_buffering_state << ") ";
732 DCHECK(task_runner_->BelongsToCurrentThread());
733 buffering_state_cb_.Run(new_buffering_state);
736 } // namespace media