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. 4 5 #include "media/base/pipeline.h" 6 7 #include <algorithm> 8 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" 27 28 using base::TimeDelta; 29 30 namespace media { 31 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.0f), 41 status_(PIPELINE_OK), 42 is_initialized_(false), 43 state_(kCreated), 44 renderer_ended_(false), 45 text_renderer_ended_(false), 46 demuxer_(NULL), 47 weak_factory_(this) { 48 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); 49 media_log_->AddEvent( 50 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); 51 } 52 53 Pipeline::~Pipeline() { 54 DCHECK(thread_checker_.CalledOnValidThread()) 55 << "Pipeline must be destroyed on same thread that created it"; 56 DCHECK(!running_) << "Stop() must complete before destroying object"; 57 DCHECK(stop_cb_.is_null()); 58 DCHECK(seek_cb_.is_null()); 59 60 media_log_->AddEvent( 61 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); 62 } 63 64 void Pipeline::Start(Demuxer* demuxer, 65 scoped_ptr<Renderer> renderer, 66 const base::Closure& ended_cb, 67 const PipelineStatusCB& error_cb, 68 const PipelineStatusCB& seek_cb, 69 const PipelineMetadataCB& metadata_cb, 70 const BufferingStateCB& buffering_state_cb, 71 const base::Closure& duration_change_cb, 72 const AddTextTrackCB& add_text_track_cb) { 73 DCHECK(!ended_cb.is_null()); 74 DCHECK(!error_cb.is_null()); 75 DCHECK(!seek_cb.is_null()); 76 DCHECK(!metadata_cb.is_null()); 77 DCHECK(!buffering_state_cb.is_null()); 78 79 base::AutoLock auto_lock(lock_); 80 CHECK(!running_) << "Media pipeline is already running"; 81 running_ = true; 82 83 demuxer_ = demuxer; 84 renderer_ = renderer.Pass(); 85 ended_cb_ = ended_cb; 86 error_cb_ = error_cb; 87 seek_cb_ = seek_cb; 88 metadata_cb_ = metadata_cb; 89 buffering_state_cb_ = buffering_state_cb; 90 duration_change_cb_ = duration_change_cb; 91 add_text_track_cb_ = add_text_track_cb; 92 93 task_runner_->PostTask( 94 FROM_HERE, base::Bind(&Pipeline::StartTask, weak_factory_.GetWeakPtr())); 95 } 96 97 void Pipeline::Stop(const base::Closure& stop_cb) { 98 DVLOG(2) << __FUNCTION__; 99 task_runner_->PostTask( 100 FROM_HERE, 101 base::Bind(&Pipeline::StopTask, weak_factory_.GetWeakPtr(), stop_cb)); 102 } 103 104 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { 105 base::AutoLock auto_lock(lock_); 106 if (!running_) { 107 DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek()."; 108 return; 109 } 110 111 task_runner_->PostTask( 112 FROM_HERE, 113 base::Bind( 114 &Pipeline::SeekTask, weak_factory_.GetWeakPtr(), time, seek_cb)); 115 } 116 117 bool Pipeline::IsRunning() const { 118 base::AutoLock auto_lock(lock_); 119 return running_; 120 } 121 122 float Pipeline::GetPlaybackRate() const { 123 base::AutoLock auto_lock(lock_); 124 return playback_rate_; 125 } 126 127 void Pipeline::SetPlaybackRate(float playback_rate) { 128 if (playback_rate < 0.0f) 129 return; 130 131 base::AutoLock auto_lock(lock_); 132 playback_rate_ = playback_rate; 133 if (running_) { 134 task_runner_->PostTask(FROM_HERE, 135 base::Bind(&Pipeline::PlaybackRateChangedTask, 136 weak_factory_.GetWeakPtr(), 137 playback_rate)); 138 } 139 } 140 141 float Pipeline::GetVolume() const { 142 base::AutoLock auto_lock(lock_); 143 return volume_; 144 } 145 146 void Pipeline::SetVolume(float volume) { 147 if (volume < 0.0f || volume > 1.0f) 148 return; 149 150 base::AutoLock auto_lock(lock_); 151 volume_ = volume; 152 if (running_) { 153 task_runner_->PostTask( 154 FROM_HERE, 155 base::Bind( 156 &Pipeline::VolumeChangedTask, weak_factory_.GetWeakPtr(), volume)); 157 } 158 } 159 160 TimeDelta Pipeline::GetMediaTime() const { 161 base::AutoLock auto_lock(lock_); 162 if (!renderer_) 163 return TimeDelta(); 164 165 TimeDelta media_time = renderer_->GetMediaTime(); 166 return std::min(media_time, duration_); 167 } 168 169 Ranges<TimeDelta> Pipeline::GetBufferedTimeRanges() const { 170 base::AutoLock auto_lock(lock_); 171 return buffered_time_ranges_; 172 } 173 174 TimeDelta Pipeline::GetMediaDuration() const { 175 base::AutoLock auto_lock(lock_); 176 return duration_; 177 } 178 179 bool Pipeline::DidLoadingProgress() { 180 base::AutoLock auto_lock(lock_); 181 bool ret = did_loading_progress_; 182 did_loading_progress_ = false; 183 return ret; 184 } 185 186 PipelineStatistics Pipeline::GetStatistics() const { 187 base::AutoLock auto_lock(lock_); 188 return statistics_; 189 } 190 191 void Pipeline::SetErrorForTesting(PipelineStatus status) { 192 OnError(status); 193 } 194 195 bool Pipeline::HasWeakPtrsForTesting() const { 196 DCHECK(task_runner_->BelongsToCurrentThread()); 197 return weak_factory_.HasWeakPtrs(); 198 } 199 200 void Pipeline::SetState(State next_state) { 201 DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state); 202 203 state_ = next_state; 204 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); 205 } 206 207 #define RETURN_STRING(state) case state: return #state; 208 209 const char* Pipeline::GetStateString(State state) { 210 switch (state) { 211 RETURN_STRING(kCreated); 212 RETURN_STRING(kInitDemuxer); 213 RETURN_STRING(kInitRenderer); 214 RETURN_STRING(kSeeking); 215 RETURN_STRING(kPlaying); 216 RETURN_STRING(kStopping); 217 RETURN_STRING(kStopped); 218 } 219 NOTREACHED(); 220 return "INVALID"; 221 } 222 223 #undef RETURN_STRING 224 225 Pipeline::State Pipeline::GetNextState() const { 226 DCHECK(task_runner_->BelongsToCurrentThread()); 227 DCHECK(stop_cb_.is_null()) 228 << "State transitions don't happen when stopping"; 229 DCHECK_EQ(status_, PIPELINE_OK) 230 << "State transitions don't happen when there's an error: " << status_; 231 232 switch (state_) { 233 case kCreated: 234 return kInitDemuxer; 235 236 case kInitDemuxer: 237 return kInitRenderer; 238 239 case kInitRenderer: 240 case kSeeking: 241 return kPlaying; 242 243 case kPlaying: 244 case kStopping: 245 case kStopped: 246 break; 247 } 248 NOTREACHED() << "State has no transition: " << state_; 249 return state_; 250 } 251 252 void Pipeline::OnDemuxerError(PipelineStatus error) { 253 task_runner_->PostTask(FROM_HERE, 254 base::Bind(&Pipeline::ErrorChangedTask, 255 weak_factory_.GetWeakPtr(), 256 error)); 257 } 258 259 void Pipeline::AddTextStream(DemuxerStream* text_stream, 260 const TextTrackConfig& config) { 261 task_runner_->PostTask(FROM_HERE, 262 base::Bind(&Pipeline::AddTextStreamTask, 263 weak_factory_.GetWeakPtr(), 264 text_stream, 265 config)); 266 } 267 268 void Pipeline::RemoveTextStream(DemuxerStream* text_stream) { 269 task_runner_->PostTask(FROM_HERE, 270 base::Bind(&Pipeline::RemoveTextStreamTask, 271 weak_factory_.GetWeakPtr(), 272 text_stream)); 273 } 274 275 void Pipeline::OnError(PipelineStatus error) { 276 DCHECK(task_runner_->BelongsToCurrentThread()); 277 DCHECK(IsRunning()); 278 DCHECK_NE(PIPELINE_OK, error); 279 VLOG(1) << "Media pipeline error: " << error; 280 281 task_runner_->PostTask(FROM_HERE, base::Bind( 282 &Pipeline::ErrorChangedTask, weak_factory_.GetWeakPtr(), error)); 283 } 284 285 void Pipeline::SetDuration(TimeDelta duration) { 286 DCHECK(IsRunning()); 287 media_log_->AddEvent( 288 media_log_->CreateTimeEvent( 289 MediaLogEvent::DURATION_SET, "duration", duration)); 290 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); 291 292 base::AutoLock auto_lock(lock_); 293 duration_ = duration; 294 if (!duration_change_cb_.is_null()) 295 duration_change_cb_.Run(); 296 } 297 298 void Pipeline::OnStateTransition(PipelineStatus status) { 299 DCHECK(task_runner_->BelongsToCurrentThread()); 300 // Force post to process state transitions after current execution frame. 301 task_runner_->PostTask( 302 FROM_HERE, 303 base::Bind( 304 &Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr(), status)); 305 } 306 307 void Pipeline::StateTransitionTask(PipelineStatus status) { 308 DCHECK(task_runner_->BelongsToCurrentThread()); 309 310 // No-op any state transitions if we're stopping. 311 if (state_ == kStopping || state_ == kStopped) 312 return; 313 314 // Preserve existing abnormal status, otherwise update based on the result of 315 // the previous operation. 316 status_ = (status_ != PIPELINE_OK ? status_ : status); 317 318 if (status_ != PIPELINE_OK) { 319 ErrorChangedTask(status_); 320 return; 321 } 322 323 // Guard against accidentally clearing |pending_callbacks_| for states that 324 // use it as well as states that should not be using it. 325 DCHECK_EQ(pending_callbacks_.get() != NULL, state_ == kSeeking); 326 327 pending_callbacks_.reset(); 328 329 PipelineStatusCB done_cb = 330 base::Bind(&Pipeline::OnStateTransition, weak_factory_.GetWeakPtr()); 331 332 // Switch states, performing any entrance actions for the new state as well. 333 SetState(GetNextState()); 334 switch (state_) { 335 case kInitDemuxer: 336 return InitializeDemuxer(done_cb); 337 338 case kInitRenderer: 339 return InitializeRenderer(base::Bind(done_cb, PIPELINE_OK)); 340 341 case kPlaying: 342 // Report metadata the first time we enter the playing state. 343 if (!is_initialized_) { 344 is_initialized_ = true; 345 ReportMetadata(); 346 start_timestamp_ = demuxer_->GetStartTime(); 347 } 348 349 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); 350 351 DCHECK(start_timestamp_ >= base::TimeDelta()); 352 renderer_->StartPlayingFrom(start_timestamp_); 353 354 if (text_renderer_) 355 text_renderer_->StartPlaying(); 356 357 PlaybackRateChangedTask(GetPlaybackRate()); 358 VolumeChangedTask(GetVolume()); 359 return; 360 361 case kStopping: 362 case kStopped: 363 case kCreated: 364 case kSeeking: 365 NOTREACHED() << "State has no transition: " << state_; 366 return; 367 } 368 } 369 370 // Note that the usage of base::Unretained() with the renderers is considered 371 // safe as they are owned by |pending_callbacks_| and share the same lifetime. 372 // 373 // That being said, deleting the renderers while keeping |pending_callbacks_| 374 // running on the media thread would result in crashes. 375 void Pipeline::DoSeek(TimeDelta seek_timestamp, 376 const PipelineStatusCB& done_cb) { 377 DCHECK(task_runner_->BelongsToCurrentThread()); 378 DCHECK(!pending_callbacks_.get()); 379 DCHECK_EQ(state_, kSeeking); 380 SerialRunner::Queue bound_fns; 381 382 // Pause. 383 if (text_renderer_) { 384 bound_fns.Push(base::Bind( 385 &TextRenderer::Pause, base::Unretained(text_renderer_.get()))); 386 } 387 388 // Flush. 389 DCHECK(renderer_); 390 bound_fns.Push( 391 base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); 392 393 if (text_renderer_) { 394 bound_fns.Push(base::Bind( 395 &TextRenderer::Flush, base::Unretained(text_renderer_.get()))); 396 } 397 398 // Seek demuxer. 399 bound_fns.Push(base::Bind( 400 &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); 401 402 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); 403 } 404 405 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { 406 DVLOG(2) << __FUNCTION__; 407 DCHECK(task_runner_->BelongsToCurrentThread()); 408 DCHECK(!pending_callbacks_.get()); 409 410 // TODO(scherkus): Enforce that Renderer is only called on a single thread, 411 // even for accessing media time http://crbug.com/370634 412 scoped_ptr<Renderer> renderer; 413 { 414 base::AutoLock auto_lock(lock_); 415 renderer.swap(renderer_); 416 } 417 renderer.reset(); 418 text_renderer_.reset(); 419 420 if (demuxer_) { 421 demuxer_->Stop(); 422 demuxer_ = NULL; 423 } 424 425 task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK)); 426 } 427 428 void Pipeline::OnStopCompleted(PipelineStatus status) { 429 DVLOG(2) << __FUNCTION__; 430 DCHECK(task_runner_->BelongsToCurrentThread()); 431 DCHECK_EQ(state_, kStopping); 432 DCHECK(!renderer_); 433 DCHECK(!text_renderer_); 434 435 { 436 base::AutoLock auto_lock(lock_); 437 running_ = false; 438 } 439 440 SetState(kStopped); 441 demuxer_ = NULL; 442 443 // If we stop during initialization/seeking we want to run |seek_cb_| 444 // followed by |stop_cb_| so we don't leave outstanding callbacks around. 445 if (!seek_cb_.is_null()) { 446 base::ResetAndReturn(&seek_cb_).Run(status_); 447 error_cb_.Reset(); 448 } 449 if (!stop_cb_.is_null()) { 450 error_cb_.Reset(); 451 452 // Invalid all weak pointers so it's safe to destroy |this| on the render 453 // main thread. 454 weak_factory_.InvalidateWeakPtrs(); 455 456 base::ResetAndReturn(&stop_cb_).Run(); 457 458 // NOTE: pipeline may be deleted at this point in time as a result of 459 // executing |stop_cb_|. 460 return; 461 } 462 if (!error_cb_.is_null()) { 463 DCHECK_NE(status_, PIPELINE_OK); 464 base::ResetAndReturn(&error_cb_).Run(status_); 465 } 466 } 467 468 void Pipeline::AddBufferedTimeRange(TimeDelta start, TimeDelta end) { 469 DCHECK(IsRunning()); 470 base::AutoLock auto_lock(lock_); 471 buffered_time_ranges_.Add(start, end); 472 did_loading_progress_ = true; 473 } 474 475 // Called from any thread. 476 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { 477 base::AutoLock auto_lock(lock_); 478 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; 479 statistics_.video_bytes_decoded += stats.video_bytes_decoded; 480 statistics_.video_frames_decoded += stats.video_frames_decoded; 481 statistics_.video_frames_dropped += stats.video_frames_dropped; 482 } 483 484 void Pipeline::StartTask() { 485 DCHECK(task_runner_->BelongsToCurrentThread()); 486 487 CHECK_EQ(kCreated, state_) 488 << "Media pipeline cannot be started more than once"; 489 490 text_renderer_ = CreateTextRenderer(); 491 if (text_renderer_) { 492 text_renderer_->Initialize( 493 base::Bind(&Pipeline::OnTextRendererEnded, weak_factory_.GetWeakPtr())); 494 } 495 496 StateTransitionTask(PIPELINE_OK); 497 } 498 499 void Pipeline::StopTask(const base::Closure& stop_cb) { 500 DCHECK(task_runner_->BelongsToCurrentThread()); 501 DCHECK(stop_cb_.is_null()); 502 503 if (state_ == kStopped) { 504 // Invalid all weak pointers so it's safe to destroy |this| on the render 505 // main thread. 506 weak_factory_.InvalidateWeakPtrs(); 507 508 // NOTE: pipeline may be deleted at this point in time as a result of 509 // executing |stop_cb|. 510 stop_cb.Run(); 511 512 return; 513 } 514 515 stop_cb_ = stop_cb; 516 517 // We may already be stopping due to a runtime error. 518 if (state_ == kStopping) 519 return; 520 521 // Do not report statistics if the pipeline is not fully initialized. 522 if (state_ == kSeeking || state_ == kPlaying) { 523 PipelineStatistics stats = GetStatistics(); 524 if (renderer_->HasVideo() && stats.video_frames_decoded > 0) { 525 UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount", 526 stats.video_frames_dropped); 527 } 528 } 529 530 SetState(kStopping); 531 pending_callbacks_.reset(); 532 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); 533 } 534 535 void Pipeline::ErrorChangedTask(PipelineStatus error) { 536 DCHECK(task_runner_->BelongsToCurrentThread()); 537 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; 538 539 media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error)); 540 541 if (state_ == kStopping || state_ == kStopped) 542 return; 543 544 SetState(kStopping); 545 pending_callbacks_.reset(); 546 status_ = error; 547 548 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); 549 } 550 551 void Pipeline::PlaybackRateChangedTask(float playback_rate) { 552 DCHECK(task_runner_->BelongsToCurrentThread()); 553 554 // Playback rate changes are only carried out while playing. 555 if (state_ != kPlaying) 556 return; 557 558 renderer_->SetPlaybackRate(playback_rate); 559 } 560 561 void Pipeline::VolumeChangedTask(float volume) { 562 DCHECK(task_runner_->BelongsToCurrentThread()); 563 564 // Volume changes are only carried out while playing. 565 if (state_ != kPlaying) 566 return; 567 568 renderer_->SetVolume(volume); 569 } 570 571 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { 572 DCHECK(task_runner_->BelongsToCurrentThread()); 573 DCHECK(stop_cb_.is_null()); 574 575 // Suppress seeking if we're not fully started. 576 if (state_ != kPlaying) { 577 DCHECK(state_ == kStopping || state_ == kStopped) 578 << "Receive extra seek in unexpected state: " << state_; 579 580 // TODO(scherkus): should we run the callback? I'm tempted to say the API 581 // will only execute the first Seek() request. 582 DVLOG(1) << "Media pipeline has not started, ignoring seek to " 583 << time.InMicroseconds() << " (current state: " << state_ << ")"; 584 return; 585 } 586 587 DCHECK(seek_cb_.is_null()); 588 589 const base::TimeDelta seek_timestamp = 590 std::max(time, demuxer_->GetStartTime()); 591 592 SetState(kSeeking); 593 seek_cb_ = seek_cb; 594 renderer_ended_ = false; 595 text_renderer_ended_ = false; 596 start_timestamp_ = seek_timestamp; 597 598 DoSeek(seek_timestamp, 599 base::Bind(&Pipeline::OnStateTransition, weak_factory_.GetWeakPtr())); 600 } 601 602 void Pipeline::OnRendererEnded() { 603 DCHECK(task_runner_->BelongsToCurrentThread()); 604 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); 605 606 if (state_ != kPlaying) 607 return; 608 609 DCHECK(!renderer_ended_); 610 renderer_ended_ = true; 611 612 RunEndedCallbackIfNeeded(); 613 } 614 615 void Pipeline::OnTextRendererEnded() { 616 DCHECK(task_runner_->BelongsToCurrentThread()); 617 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); 618 619 if (state_ != kPlaying) 620 return; 621 622 DCHECK(!text_renderer_ended_); 623 text_renderer_ended_ = true; 624 625 RunEndedCallbackIfNeeded(); 626 } 627 628 void Pipeline::RunEndedCallbackIfNeeded() { 629 DCHECK(task_runner_->BelongsToCurrentThread()); 630 631 if (renderer_ && !renderer_ended_) 632 return; 633 634 if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_) 635 return; 636 637 DCHECK_EQ(status_, PIPELINE_OK); 638 ended_cb_.Run(); 639 } 640 641 scoped_ptr<TextRenderer> Pipeline::CreateTextRenderer() { 642 DCHECK(task_runner_->BelongsToCurrentThread()); 643 644 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); 645 if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks)) 646 return scoped_ptr<media::TextRenderer>(); 647 648 return scoped_ptr<media::TextRenderer>(new media::TextRenderer( 649 task_runner_, 650 base::Bind(&Pipeline::OnAddTextTrack, weak_factory_.GetWeakPtr()))); 651 } 652 653 void Pipeline::AddTextStreamTask(DemuxerStream* text_stream, 654 const TextTrackConfig& config) { 655 DCHECK(task_runner_->BelongsToCurrentThread()); 656 // TODO(matthewjheaney): fix up text_ended_ when text stream 657 // is added (http://crbug.com/321446). 658 if (text_renderer_) 659 text_renderer_->AddTextStream(text_stream, config); 660 } 661 662 void Pipeline::RemoveTextStreamTask(DemuxerStream* text_stream) { 663 DCHECK(task_runner_->BelongsToCurrentThread()); 664 if (text_renderer_) 665 text_renderer_->RemoveTextStream(text_stream); 666 } 667 668 void Pipeline::OnAddTextTrack(const TextTrackConfig& config, 669 const AddTextTrackDoneCB& done_cb) { 670 DCHECK(task_runner_->BelongsToCurrentThread()); 671 add_text_track_cb_.Run(config, done_cb); 672 } 673 674 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) { 675 DCHECK(task_runner_->BelongsToCurrentThread()); 676 demuxer_->Initialize(this, done_cb, text_renderer_); 677 } 678 679 void Pipeline::InitializeRenderer(const base::Closure& done_cb) { 680 DCHECK(task_runner_->BelongsToCurrentThread()); 681 682 if (!demuxer_->GetStream(DemuxerStream::AUDIO) && 683 !demuxer_->GetStream(DemuxerStream::VIDEO)) { 684 { 685 base::AutoLock auto_lock(lock_); 686 renderer_.reset(); 687 } 688 OnError(PIPELINE_ERROR_COULD_NOT_RENDER); 689 return; 690 } 691 692 base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr(); 693 renderer_->Initialize( 694 done_cb, 695 base::Bind(&Pipeline::OnUpdateStatistics, weak_this), 696 base::Bind(&Pipeline::OnRendererEnded, weak_this), 697 base::Bind(&Pipeline::OnError, weak_this), 698 base::Bind(&Pipeline::BufferingStateChanged, weak_this)); 699 } 700 701 void Pipeline::ReportMetadata() { 702 DCHECK(task_runner_->BelongsToCurrentThread()); 703 PipelineMetadata metadata; 704 metadata.has_audio = renderer_->HasAudio(); 705 metadata.has_video = renderer_->HasVideo(); 706 metadata.timeline_offset = demuxer_->GetTimelineOffset(); 707 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); 708 if (stream) { 709 metadata.natural_size = stream->video_decoder_config().natural_size(); 710 metadata.video_rotation = stream->video_rotation(); 711 } 712 metadata_cb_.Run(metadata); 713 } 714 715 void Pipeline::BufferingStateChanged(BufferingState new_buffering_state) { 716 DVLOG(1) << __FUNCTION__ << "(" << new_buffering_state << ") "; 717 DCHECK(task_runner_->BelongsToCurrentThread()); 718 buffering_state_cb_.Run(new_buffering_state); 719 } 720 721 } // namespace media 722