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_impl.h" | 5 #include "media/base/pipeline_impl.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <utility> | |
9 | 8 |
10 #include "base/bind.h" | 9 #include "base/bind.h" |
11 #include "base/bind_helpers.h" | 10 #include "base/bind_helpers.h" |
12 #include "base/callback.h" | 11 #include "base/callback.h" |
13 #include "base/callback_helpers.h" | 12 #include "base/callback_helpers.h" |
14 #include "base/command_line.h" | 13 #include "base/command_line.h" |
15 #include "base/compiler_specific.h" | |
16 #include "base/location.h" | 14 #include "base/location.h" |
17 #include "base/memory/ptr_util.h" | |
18 #include "base/metrics/histogram.h" | 15 #include "base/metrics/histogram.h" |
19 #include "base/single_thread_task_runner.h" | 16 #include "base/single_thread_task_runner.h" |
20 #include "base/stl_util.h" | 17 #include "base/synchronization/lock.h" |
21 #include "base/strings/string_number_conversions.h" | |
22 #include "base/strings/string_util.h" | |
23 #include "base/synchronization/waitable_event.h" | 18 #include "base/synchronization/waitable_event.h" |
24 #include "base/threading/thread_task_runner_handle.h" | 19 #include "base/threading/thread_task_runner_handle.h" |
| 20 #include "base/timer/timer.h" |
25 #include "media/base/bind_to_current_loop.h" | 21 #include "media/base/bind_to_current_loop.h" |
| 22 #include "media/base/demuxer.h" |
26 #include "media/base/media_log.h" | 23 #include "media/base/media_log.h" |
27 #include "media/base/media_switches.h" | 24 #include "media/base/media_switches.h" |
28 #include "media/base/renderer.h" | 25 #include "media/base/renderer.h" |
| 26 #include "media/base/renderer_client.h" |
| 27 #include "media/base/serial_runner.h" |
29 #include "media/base/text_renderer.h" | 28 #include "media/base/text_renderer.h" |
30 #include "media/base/text_track_config.h" | 29 #include "media/base/text_track_config.h" |
31 #include "media/base/timestamp_constants.h" | 30 #include "media/base/timestamp_constants.h" |
32 #include "media/base/video_decoder_config.h" | 31 #include "media/base/video_decoder_config.h" |
33 | 32 |
34 using base::TimeDelta; | 33 namespace { |
| 34 |
| 35 const double kDefaultPlaybackRate = 0.0; |
| 36 const float kDefaultVolume = 1.0f; |
| 37 |
| 38 bool TextTracksEnabled() { |
| 39 static bool enabled = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 40 switches::kEnableInbandTextTracks); |
| 41 return enabled; |
| 42 } |
| 43 |
| 44 } // namespace |
35 | 45 |
36 namespace media { | 46 namespace media { |
37 | 47 |
| 48 class PipelineImpl::RendererWrapper : public DemuxerHost, |
| 49 public RendererClient { |
| 50 public: |
| 51 RendererWrapper(base::WeakPtr<PipelineImpl> weak_pipeline, |
| 52 scoped_refptr<base::SingleThreadTaskRunner> media_task_runner, |
| 53 scoped_refptr<MediaLog> media_log) |
| 54 : weak_pipeline_(weak_pipeline), |
| 55 media_task_runner_(std::move(media_task_runner)), |
| 56 main_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 57 media_log_(std::move(media_log)), |
| 58 demuxer_(nullptr), |
| 59 playback_rate_(kDefaultPlaybackRate), |
| 60 volume_(kDefaultVolume), |
| 61 cdm_context_(nullptr), |
| 62 state_(kCreated), |
| 63 status_(PIPELINE_OK), |
| 64 renderer_ended_(false), |
| 65 text_renderer_ended_(false), |
| 66 weak_factory_(this) { |
| 67 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); |
| 68 } |
| 69 |
| 70 ~RendererWrapper() final { |
| 71 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 72 DCHECK(state_ == kCreated || state_ == kStopped); |
| 73 } |
| 74 |
| 75 // Note that the usage of base::Unretained() with the renderers and demuxer |
| 76 // is safe as they are owned by |pending_callbacks_| and share the same |
| 77 // lifetime. That said, deleting the renderers while keeping |
| 78 // |pending_callbacks_| running on the media thread would result in crashes. |
| 79 |
| 80 void Start(Demuxer* demuxer, |
| 81 std::unique_ptr<Renderer> renderer, |
| 82 std::unique_ptr<TextRenderer> text_renderer) { |
| 83 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 84 |
| 85 DCHECK_EQ(kCreated, state_) << "Received start in unexpected state: " |
| 86 << state_; |
| 87 SetState(kStarting); |
| 88 |
| 89 DCHECK(!demuxer_); |
| 90 DCHECK(!renderer_); |
| 91 DCHECK(!text_renderer_); |
| 92 DCHECK(!renderer_ended_); |
| 93 DCHECK(!text_renderer_ended_); |
| 94 demuxer_ = demuxer; |
| 95 { |
| 96 base::AutoLock auto_lock(renderer_lock_); |
| 97 renderer_ = std::move(renderer); |
| 98 } |
| 99 text_renderer_ = std::move(text_renderer); |
| 100 |
| 101 // Initialize text renderer. |
| 102 if (text_renderer_) { |
| 103 text_renderer_->Initialize(base::Bind( |
| 104 &RendererWrapper::OnTextRendererEnded, weak_factory_.GetWeakPtr())); |
| 105 } |
| 106 |
| 107 // Queue asynchronous actions required to seek. |
| 108 DCHECK(!pending_callbacks_); |
| 109 SerialRunner::Queue fns; |
| 110 |
| 111 // Initialize demuxer. |
| 112 fns.Push(base::Bind(&RendererWrapper::InitializeDemuxer, |
| 113 weak_factory_.GetWeakPtr())); |
| 114 |
| 115 // Once the demuxer is initialized successfully, media metadata must be |
| 116 // available - report the metadata to client. |
| 117 fns.Push(base::Bind(&RendererWrapper::ReportMetadata, |
| 118 weak_factory_.GetWeakPtr())); |
| 119 |
| 120 // Initialize renderer. |
| 121 fns.Push(base::Bind(&RendererWrapper::InitializeRenderer, |
| 122 weak_factory_.GetWeakPtr())); |
| 123 |
| 124 // Run tasks. |
| 125 pending_callbacks_ = SerialRunner::Run( |
| 126 fns, base::Bind(&RendererWrapper::OnSeekDone, |
| 127 weak_factory_.GetWeakPtr(), base::TimeDelta())); |
| 128 } |
| 129 |
| 130 void Stop(const base::Closure& stop_cb) { |
| 131 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 132 DCHECK(state_ != kStopping && state_ != kStopped); |
| 133 |
| 134 SetState(kStopping); |
| 135 |
| 136 // If we stop during starting/seeking/suspending/resuming we don't want to |
| 137 // leave outstanding callbacks around. The callbacks also do not get run if |
| 138 // the pipeline is stopped before it had a chance to complete outstanding |
| 139 // tasks. |
| 140 pending_callbacks_.reset(); |
| 141 |
| 142 std::unique_ptr<Renderer> renderer; |
| 143 { |
| 144 base::AutoLock auto_lock(renderer_lock_); |
| 145 renderer.swap(renderer_); |
| 146 } |
| 147 renderer.reset(); |
| 148 text_renderer_.reset(); |
| 149 |
| 150 if (demuxer_) { |
| 151 demuxer_->Stop(); |
| 152 demuxer_ = nullptr; |
| 153 } |
| 154 |
| 155 SetState(kStopped); |
| 156 |
| 157 // Post the stop callback to enqueue it after the tasks that may have been |
| 158 // Demuxer and Renderer during stopping. |
| 159 media_task_runner_->PostTask(FROM_HERE, stop_cb); |
| 160 } |
| 161 |
| 162 void Seek(base::TimeDelta time) { |
| 163 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 164 |
| 165 // Suppress seeking if we're not fully started. |
| 166 if (state_ != kPlaying) { |
| 167 DCHECK(state_ == kStopping || state_ == kStopped) |
| 168 << "Receive seek in unexpected state: " << state_; |
| 169 OnPipelineError(PIPELINE_ERROR_INVALID_STATE); |
| 170 return; |
| 171 } |
| 172 |
| 173 SetState(kSeeking); |
| 174 renderer_ended_ = false; |
| 175 text_renderer_ended_ = false; |
| 176 |
| 177 // Queue asynchronous actions required to seek. |
| 178 DCHECK(!pending_callbacks_); |
| 179 SerialRunner::Queue bound_fns; |
| 180 |
| 181 // Pause. |
| 182 if (text_renderer_) { |
| 183 bound_fns.Push(base::Bind(&TextRenderer::Pause, |
| 184 base::Unretained(text_renderer_.get()))); |
| 185 } |
| 186 |
| 187 // Flush. |
| 188 DCHECK(renderer_); |
| 189 bound_fns.Push( |
| 190 base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); |
| 191 if (text_renderer_) { |
| 192 bound_fns.Push(base::Bind(&TextRenderer::Flush, |
| 193 base::Unretained(text_renderer_.get()))); |
| 194 } |
| 195 |
| 196 // Seek. |
| 197 bound_fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), |
| 198 std::max(time, demuxer_->GetStartTime()))); |
| 199 |
| 200 pending_callbacks_ = SerialRunner::Run( |
| 201 bound_fns, base::Bind(&RendererWrapper::OnSeekDone, |
| 202 weak_factory_.GetWeakPtr(), time)); |
| 203 } |
| 204 |
| 205 void Suspend() { |
| 206 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 207 |
| 208 // Suppress suspending if we're not playing. |
| 209 if (state_ != kPlaying) { |
| 210 DCHECK(state_ == kStopping || state_ == kStopped) |
| 211 << "Received suspend in unexpected state: " << state_; |
| 212 OnPipelineError(PIPELINE_ERROR_INVALID_STATE); |
| 213 return; |
| 214 } |
| 215 |
| 216 SetState(kSuspending); |
| 217 // Freeze playback and record the media time before flushing. |
| 218 // (Flushing clears the value.) |
| 219 renderer_->SetPlaybackRate(0.0); |
| 220 base::TimeDelta suspend_time = renderer_->GetMediaTime(); |
| 221 DCHECK(suspend_time != kNoTimestamp()); |
| 222 |
| 223 // Queue asynchronous actions required to suspend playback. |
| 224 DCHECK(!pending_callbacks_.get()); |
| 225 SerialRunner::Queue fns; |
| 226 |
| 227 // Pause. |
| 228 if (text_renderer_) { |
| 229 fns.Push(base::Bind(&TextRenderer::Pause, |
| 230 base::Unretained(text_renderer_.get()))); |
| 231 } |
| 232 |
| 233 // Flush. |
| 234 fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); |
| 235 if (text_renderer_) { |
| 236 fns.Push(base::Bind(&TextRenderer::Flush, |
| 237 base::Unretained(text_renderer_.get()))); |
| 238 } |
| 239 |
| 240 pending_callbacks_ = SerialRunner::Run( |
| 241 fns, base::Bind(&RendererWrapper::OnSuspendDone, |
| 242 weak_factory_.GetWeakPtr(), suspend_time)); |
| 243 } |
| 244 |
| 245 void Resume(base::TimeDelta time, std::unique_ptr<Renderer> renderer) { |
| 246 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 247 |
| 248 DCHECK_EQ(kSuspended, state_) << "Received resume in unexpected state: " |
| 249 << state_; |
| 250 |
| 251 SetState(kResuming); |
| 252 { |
| 253 base::AutoLock auto_lock(renderer_lock_); |
| 254 renderer_ = std::move(renderer); |
| 255 } |
| 256 renderer_ended_ = false; |
| 257 text_renderer_ended_ = false; |
| 258 |
| 259 // Queue the asynchronous actions required to stop playback. |
| 260 DCHECK(!pending_callbacks_.get()); |
| 261 SerialRunner::Queue fns; |
| 262 |
| 263 // Seek. |
| 264 fns.Push(base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), |
| 265 std::max(time, demuxer_->GetStartTime()))); |
| 266 |
| 267 // Initialize the new renderer. |
| 268 fns.Push(base::Bind(&RendererWrapper::InitializeRenderer, |
| 269 weak_factory_.GetWeakPtr())); |
| 270 |
| 271 // Run tasks. |
| 272 pending_callbacks_ = |
| 273 SerialRunner::Run(fns, base::Bind(&RendererWrapper::OnSeekDone, |
| 274 weak_factory_.GetWeakPtr(), time)); |
| 275 } |
| 276 |
| 277 void SetPlaybackRate(double playback_rate) { |
| 278 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 279 |
| 280 playback_rate_ = playback_rate; |
| 281 if (state_ == kPlaying) |
| 282 renderer_->SetPlaybackRate(playback_rate_); |
| 283 } |
| 284 |
| 285 void SetVolume(float volume) { |
| 286 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 287 |
| 288 volume_ = volume; |
| 289 if (state_ == kPlaying) |
| 290 renderer_->SetVolume(volume_); |
| 291 } |
| 292 |
| 293 base::TimeDelta GetMediaTime() { |
| 294 // This is the only member function that gets called on the main thread. |
| 295 // TODO(alokp): Enforce that Renderer is only called on a single thread, |
| 296 // even for accessing media time http://crbug.com/370634. |
| 297 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 298 |
| 299 base::AutoLock auto_lock(renderer_lock_); |
| 300 return renderer_ ? renderer_->GetMediaTime() : base::TimeDelta(); |
| 301 } |
| 302 |
| 303 void SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb) { |
| 304 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 305 |
| 306 cdm_context_ = cdm_context; |
| 307 if (renderer_) { |
| 308 renderer_->SetCdm( |
| 309 cdm_context, base::Bind(&RendererWrapper::OnCdmAttached, |
| 310 weak_factory_.GetWeakPtr(), cdm_attached_cb)); |
| 311 } else { |
| 312 cdm_attached_cb.Run(true); |
| 313 } |
| 314 } |
| 315 |
| 316 private: |
| 317 // DemuxerHost implementaion. |
| 318 void OnBufferedTimeRangesChanged( |
| 319 const Ranges<base::TimeDelta>& ranges) final { |
| 320 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer |
| 321 // implementations call DemuxerHost on the media thread. |
| 322 main_task_runner_->PostTask( |
| 323 FROM_HERE, base::Bind(&PipelineImpl::OnBufferedTimeRangesChange, |
| 324 weak_pipeline_, ranges)); |
| 325 } |
| 326 void SetDuration(base::TimeDelta duration) final { |
| 327 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer |
| 328 // implementations call DemuxerHost on the media thread. |
| 329 media_log_->AddEvent(media_log_->CreateTimeEvent( |
| 330 MediaLogEvent::DURATION_SET, "duration", duration)); |
| 331 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); |
| 332 |
| 333 main_task_runner_->PostTask( |
| 334 FROM_HERE, |
| 335 base::Bind(&PipelineImpl::OnDurationChange, weak_pipeline_, duration)); |
| 336 } |
| 337 void OnDemuxerError(PipelineStatus error) final { |
| 338 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer |
| 339 // implementations call DemuxerHost on the media thread. |
| 340 media_task_runner_->PostTask(FROM_HERE, |
| 341 base::Bind(&RendererWrapper::OnPipelineError, |
| 342 weak_factory_.GetWeakPtr(), error)); |
| 343 } |
| 344 void AddTextStream(DemuxerStream* text_stream, |
| 345 const TextTrackConfig& config) final { |
| 346 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer |
| 347 // implementations call DemuxerHost on the media thread. |
| 348 media_task_runner_->PostTask( |
| 349 FROM_HERE, base::Bind(&RendererWrapper::AddTextStreamTask, |
| 350 weak_factory_.GetWeakPtr(), text_stream, config)); |
| 351 } |
| 352 void RemoveTextStream(DemuxerStream* text_stream) final { |
| 353 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer |
| 354 // implementations call DemuxerHost on the media thread. |
| 355 media_task_runner_->PostTask( |
| 356 FROM_HERE, base::Bind(&RendererWrapper::RemoveTextStreamTask, |
| 357 weak_factory_.GetWeakPtr(), text_stream)); |
| 358 } |
| 359 |
| 360 // RendererClient implementation. |
| 361 void OnError(PipelineStatus error) final { |
| 362 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 363 |
| 364 media_task_runner_->PostTask(FROM_HERE, |
| 365 base::Bind(&RendererWrapper::OnPipelineError, |
| 366 weak_factory_.GetWeakPtr(), error)); |
| 367 } |
| 368 void OnEnded() final { |
| 369 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 370 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); |
| 371 |
| 372 if (state_ != kPlaying) |
| 373 return; |
| 374 |
| 375 DCHECK(!renderer_ended_); |
| 376 renderer_ended_ = true; |
| 377 RunEndedCallbackIfNeeded(); |
| 378 } |
| 379 void OnStatisticsUpdate(const PipelineStatistics& stats) final { |
| 380 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 381 |
| 382 main_task_runner_->PostTask( |
| 383 FROM_HERE, |
| 384 base::Bind(&PipelineImpl::OnStatisticsUpdate, weak_pipeline_, stats)); |
| 385 } |
| 386 void OnBufferingStateChange(BufferingState state) final { |
| 387 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 388 DVLOG(2) << __FUNCTION__ << "(" << state << ") "; |
| 389 |
| 390 main_task_runner_->PostTask( |
| 391 FROM_HERE, base::Bind(&PipelineImpl::OnBufferingStateChange, |
| 392 weak_pipeline_, state)); |
| 393 } |
| 394 void OnWaitingForDecryptionKey() final { |
| 395 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 396 |
| 397 main_task_runner_->PostTask( |
| 398 FROM_HERE, |
| 399 base::Bind(&PipelineImpl::OnWaitingForDecryptionKey, weak_pipeline_)); |
| 400 } |
| 401 void OnVideoNaturalSizeChange(const gfx::Size& size) final { |
| 402 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 403 |
| 404 main_task_runner_->PostTask( |
| 405 FROM_HERE, base::Bind(&PipelineImpl::OnVideoNaturalSizeChange, |
| 406 weak_pipeline_, size)); |
| 407 } |
| 408 void OnVideoOpacityChange(bool opaque) final { |
| 409 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 410 |
| 411 main_task_runner_->PostTask(FROM_HERE, |
| 412 base::Bind(&PipelineImpl::OnVideoOpacityChange, |
| 413 weak_pipeline_, opaque)); |
| 414 } |
| 415 |
| 416 void OnPipelineError(PipelineStatus error) { |
| 417 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 418 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; |
| 419 |
| 420 // Preserve existing abnormal status. |
| 421 if (status_ != PIPELINE_OK) |
| 422 return; |
| 423 |
| 424 // Don't report pipeline error events to the media log here. The embedder |
| 425 // will log this when Client::OnError is called. If the pipeline is already |
| 426 // stopped or stopping we also don't want to log any event. In case we are |
| 427 // suspending or suspended, the error may be recoverable, so don't propagate |
| 428 // it now, instead let the subsequent seek during resume propagate it if |
| 429 // it's unrecoverable. |
| 430 if (state_ == kStopping || state_ == kStopped || state_ == kSuspending || |
| 431 state_ == kSuspended) { |
| 432 return; |
| 433 } |
| 434 |
| 435 status_ = error; |
| 436 main_task_runner_->PostTask( |
| 437 FROM_HERE, base::Bind(&PipelineImpl::OnError, weak_pipeline_, error)); |
| 438 } |
| 439 |
| 440 void OnTextRendererEnded() { |
| 441 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 442 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); |
| 443 |
| 444 if (state_ != kPlaying) |
| 445 return; |
| 446 |
| 447 DCHECK(!text_renderer_ended_); |
| 448 text_renderer_ended_ = true; |
| 449 RunEndedCallbackIfNeeded(); |
| 450 } |
| 451 |
| 452 void RunEndedCallbackIfNeeded() { |
| 453 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 454 |
| 455 if (renderer_ && !renderer_ended_) |
| 456 return; |
| 457 |
| 458 if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_) |
| 459 return; |
| 460 |
| 461 DCHECK_EQ(status_, PIPELINE_OK); |
| 462 main_task_runner_->PostTask( |
| 463 FROM_HERE, base::Bind(&PipelineImpl::OnEnded, weak_pipeline_)); |
| 464 } |
| 465 |
| 466 void AddTextStreamTask(DemuxerStream* text_stream, |
| 467 const TextTrackConfig& config) { |
| 468 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 469 // TODO(matthewjheaney): fix up text_ended_ when text stream |
| 470 // is added (http://crbug.com/321446). |
| 471 if (text_renderer_) |
| 472 text_renderer_->AddTextStream(text_stream, config); |
| 473 } |
| 474 |
| 475 void RemoveTextStreamTask(DemuxerStream* text_stream) { |
| 476 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 477 if (text_renderer_) |
| 478 text_renderer_->RemoveTextStream(text_stream); |
| 479 } |
| 480 |
| 481 void SetState(State next_state) { |
| 482 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 483 DVLOG(1) << PipelineImpl::GetStateString(state_) << " -> " |
| 484 << PipelineImpl::GetStateString(next_state); |
| 485 |
| 486 state_ = next_state; |
| 487 media_log_->AddEvent( |
| 488 media_log_->CreatePipelineStateChangedEvent(next_state)); |
| 489 } |
| 490 |
| 491 void InitializeDemuxer(const PipelineStatusCB& done_cb) { |
| 492 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 493 DVLOG(2) << __FUNCTION__; |
| 494 |
| 495 demuxer_->Initialize(this, done_cb, !!text_renderer_); |
| 496 } |
| 497 |
| 498 void ReportMetadata() { |
| 499 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 500 DVLOG(2) << __FUNCTION__; |
| 501 |
| 502 PipelineMetadata metadata; |
| 503 metadata.timeline_offset = demuxer_->GetTimelineOffset(); |
| 504 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); |
| 505 if (stream) { |
| 506 metadata.has_video = true; |
| 507 metadata.natural_size = stream->video_decoder_config().natural_size(); |
| 508 metadata.video_rotation = stream->video_rotation(); |
| 509 } |
| 510 if (demuxer_->GetStream(DemuxerStream::AUDIO)) { |
| 511 metadata.has_audio = true; |
| 512 } |
| 513 |
| 514 main_task_runner_->PostTask( |
| 515 FROM_HERE, |
| 516 base::Bind(&PipelineImpl::OnMetadata, weak_pipeline_, metadata)); |
| 517 } |
| 518 |
| 519 void InitializeRenderer(const PipelineStatusCB& done_cb) { |
| 520 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 521 DVLOG(2) << __FUNCTION__; |
| 522 |
| 523 if (!demuxer_->GetStream(DemuxerStream::AUDIO) && |
| 524 !demuxer_->GetStream(DemuxerStream::VIDEO)) { |
| 525 done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER); |
| 526 return; |
| 527 } |
| 528 |
| 529 if (cdm_context_) { |
| 530 CdmAttachedCB cdm_attached_cb = base::Bind(&IgnoreCdmAttached); |
| 531 renderer_->SetCdm( |
| 532 cdm_context_, |
| 533 base::Bind(&RendererWrapper::OnCdmAttached, |
| 534 weak_factory_.GetWeakPtr(), cdm_attached_cb)); |
| 535 } |
| 536 |
| 537 renderer_->Initialize(demuxer_, this, done_cb); |
| 538 } |
| 539 |
| 540 void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb, bool success) { |
| 541 if (!success) |
| 542 cdm_context_ = nullptr; |
| 543 |
| 544 cdm_attached_cb.Run(success); |
| 545 } |
| 546 |
| 547 void OnSeekDone(base::TimeDelta start_time, PipelineStatus status) { |
| 548 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 549 |
| 550 DCHECK(pending_callbacks_); |
| 551 pending_callbacks_.reset(); |
| 552 |
| 553 if (status != PIPELINE_OK) { |
| 554 OnPipelineError(status); |
| 555 return; |
| 556 } |
| 557 |
| 558 start_time = std::max(start_time, demuxer_->GetStartTime()); |
| 559 renderer_->StartPlayingFrom(start_time); |
| 560 |
| 561 if (text_renderer_) |
| 562 text_renderer_->StartPlaying(); |
| 563 |
| 564 renderer_->SetPlaybackRate(playback_rate_); |
| 565 renderer_->SetVolume(volume_); |
| 566 |
| 567 SetState(kPlaying); |
| 568 main_task_runner_->PostTask( |
| 569 FROM_HERE, |
| 570 base::Bind(&PipelineImpl::OnSeekDone, weak_pipeline_, start_time)); |
| 571 } |
| 572 |
| 573 void OnSuspendDone(base::TimeDelta suspend_time, PipelineStatus status) { |
| 574 DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| 575 |
| 576 DCHECK(pending_callbacks_); |
| 577 pending_callbacks_.reset(); |
| 578 |
| 579 // In case we are suspending or suspended, the error may be recoverable, |
| 580 // so don't propagate it now, instead let the subsequent seek during resume |
| 581 // propagate it if it's unrecoverable. |
| 582 LOG_IF(WARNING, status != PIPELINE_OK) |
| 583 << "Encountered pipeline error while suspending: " << status; |
| 584 |
| 585 SetState(kSuspended); |
| 586 main_task_runner_->PostTask( |
| 587 FROM_HERE, |
| 588 base::Bind(&PipelineImpl::OnSuspendDone, weak_pipeline_, suspend_time)); |
| 589 } |
| 590 |
| 591 base::WeakPtr<PipelineImpl> weak_pipeline_; |
| 592 const scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; |
| 593 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| 594 const scoped_refptr<MediaLog> media_log_; |
| 595 |
| 596 Demuxer* demuxer_; |
| 597 std::unique_ptr<Renderer> renderer_; |
| 598 std::unique_ptr<TextRenderer> text_renderer_; |
| 599 double playback_rate_; |
| 600 float volume_; |
| 601 CdmContext* cdm_context_; |
| 602 |
| 603 // Lock used to serialize access for |renderer_|. |
| 604 mutable base::Lock renderer_lock_; |
| 605 |
| 606 // Current state of the pipeline. |
| 607 State state_; |
| 608 |
| 609 // Last media time reported to the pipeline. |
| 610 base::TimeDelta last_media_time_; |
| 611 |
| 612 // Status of the pipeline. Initialized to PIPELINE_OK which indicates that |
| 613 // the pipeline is operating correctly. Any other value indicates that the |
| 614 // pipeline is stopped or is stopping. Clients can call the Stop() method to |
| 615 // reset the pipeline state, and restore this to PIPELINE_OK. |
| 616 PipelineStatus status_; |
| 617 |
| 618 // Whether we've received the audio/video/text ended events. |
| 619 bool renderer_ended_; |
| 620 bool text_renderer_ended_; |
| 621 |
| 622 // Series of tasks to Start(), Seek(), and Resume(). |
| 623 std::unique_ptr<SerialRunner> pending_callbacks_; |
| 624 |
| 625 base::WeakPtrFactory<RendererWrapper> weak_factory_; |
| 626 DISALLOW_COPY_AND_ASSIGN(RendererWrapper); |
| 627 }; |
| 628 |
38 PipelineImpl::PipelineImpl( | 629 PipelineImpl::PipelineImpl( |
39 const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, | 630 const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, |
40 MediaLog* media_log) | 631 MediaLog* media_log) |
41 : main_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 632 : media_task_runner_(media_task_runner), |
42 media_task_runner_(media_task_runner), | |
43 media_log_(media_log), | 633 media_log_(media_log), |
44 running_(false), | 634 client_(nullptr), |
| 635 playback_rate_(kDefaultPlaybackRate), |
| 636 volume_(kDefaultVolume), |
| 637 suspend_time_(kNoTimestamp()), |
45 did_loading_progress_(false), | 638 did_loading_progress_(false), |
46 volume_(1.0f), | |
47 playback_rate_(0.0), | |
48 status_(PIPELINE_OK), | |
49 state_(kCreated), | |
50 suspend_timestamp_(kNoTimestamp()), | |
51 renderer_ended_(false), | |
52 text_renderer_ended_(false), | |
53 demuxer_(NULL), | |
54 cdm_context_(nullptr), | |
55 weak_factory_(this) { | 639 weak_factory_(this) { |
56 weak_this_ = weak_factory_.GetWeakPtr(); | 640 DVLOG(2) << __FUNCTION__; |
57 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); | 641 renderer_wrapper_.reset(new RendererWrapper(weak_factory_.GetWeakPtr(), |
| 642 media_task_runner_, media_log_)); |
58 } | 643 } |
59 | 644 |
60 PipelineImpl::~PipelineImpl() { | 645 PipelineImpl::~PipelineImpl() { |
61 DCHECK(main_task_runner_->BelongsToCurrentThread()) | 646 DVLOG(2) << __FUNCTION__; |
62 << "Pipeline must be destroyed on same thread that created it"; | 647 DCHECK(thread_checker_.CalledOnValidThread()); |
63 DCHECK(!running_) << "Stop() must complete before destroying object"; | 648 DCHECK(!client_) << "Stop() must complete before destroying object"; |
64 DCHECK(seek_cb_.is_null()); | 649 DCHECK(seek_cb_.is_null()); |
| 650 DCHECK(suspend_cb_.is_null()); |
| 651 |
| 652 // Invalidate self weak pointers effectively canceling all pending |
| 653 // notifications in the message queue. |
| 654 weak_factory_.InvalidateWeakPtrs(); |
| 655 |
| 656 // RendererWrapper is deleted on the media thread. |
| 657 media_task_runner_->DeleteSoon(FROM_HERE, renderer_wrapper_.release()); |
65 } | 658 } |
66 | 659 |
67 void PipelineImpl::Start(Demuxer* demuxer, | 660 void PipelineImpl::Start(Demuxer* demuxer, |
68 std::unique_ptr<Renderer> renderer, | 661 std::unique_ptr<Renderer> renderer, |
69 Client* client, | 662 Client* client, |
70 const PipelineStatusCB& seek_cb) { | 663 const PipelineStatusCB& seek_cb) { |
71 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 664 DVLOG(2) << __FUNCTION__; |
| 665 DCHECK(thread_checker_.CalledOnValidThread()); |
| 666 DCHECK(demuxer); |
| 667 DCHECK(renderer); |
72 DCHECK(client); | 668 DCHECK(client); |
73 DCHECK(!seek_cb.is_null()); | 669 DCHECK(!seek_cb.is_null()); |
74 | 670 |
75 base::AutoLock auto_lock(lock_); | 671 DCHECK(!client_); |
76 CHECK(!running_) << "Media pipeline is already running"; | 672 DCHECK(seek_cb_.is_null()); |
77 running_ = true; | 673 client_ = client; |
78 | 674 seek_cb_ = seek_cb; |
79 demuxer_ = demuxer; | 675 |
80 renderer_ = std::move(renderer); | 676 std::unique_ptr<TextRenderer> text_renderer; |
81 client_weak_factory_.reset(new base::WeakPtrFactory<Client>(client)); | 677 if (TextTracksEnabled()) { |
82 weak_client_ = client_weak_factory_->GetWeakPtr(); | 678 text_renderer.reset(new TextRenderer( |
83 seek_cb_ = media::BindToCurrentLoop(seek_cb); | 679 media_task_runner_, |
| 680 BindToCurrentLoop(base::Bind(&PipelineImpl::OnAddTextTrack, |
| 681 weak_factory_.GetWeakPtr())))); |
| 682 } |
| 683 |
84 media_task_runner_->PostTask( | 684 media_task_runner_->PostTask( |
85 FROM_HERE, base::Bind(&PipelineImpl::StartTask, weak_this_)); | 685 FROM_HERE, |
| 686 base::Bind(&RendererWrapper::Start, |
| 687 base::Unretained(renderer_wrapper_.get()), demuxer, |
| 688 base::Passed(&renderer), base::Passed(&text_renderer))); |
86 } | 689 } |
87 | 690 |
88 void PipelineImpl::Stop() { | 691 void PipelineImpl::Stop() { |
89 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 692 DVLOG(2) << __FUNCTION__; |
90 DVLOG(2) << __FUNCTION__; | 693 DCHECK(thread_checker_.CalledOnValidThread()); |
91 | 694 |
92 if (media_task_runner_ != main_task_runner_) { | 695 if (!IsRunning()) { |
| 696 DVLOG(2) << "Media pipeline isn't running. Ignoring Stop()"; |
| 697 return; |
| 698 } |
| 699 |
| 700 if (media_task_runner_->BelongsToCurrentThread()) { |
| 701 // This path is executed by unittests that share media and main threads. |
| 702 base::Closure stop_cb = base::Bind(&base::DoNothing); |
| 703 media_task_runner_->PostTask( |
| 704 FROM_HERE, |
| 705 base::Bind(&RendererWrapper::Stop, |
| 706 base::Unretained(renderer_wrapper_.get()), stop_cb)); |
| 707 } else { |
93 // This path is executed by production code where the two task runners - | 708 // This path is executed by production code where the two task runners - |
94 // main and media - live on different threads. | 709 // main and media - live on different threads. |
95 // TODO(alokp): It may be possible to not have to wait for StopTask by | 710 // |
96 // moving the members accessed on media thread into a class/struct and | 711 // TODO(alokp): We should not have to wait for the RendererWrapper::Stop. |
97 // DeleteSoon the instance on the media thread. | 712 // RendererWrapper holds a raw reference to Demuxer, which in turn holds a |
| 713 // raw reference to DataSource. Both Demuxer and DataSource need to live |
| 714 // until RendererWrapper is stopped. If RendererWrapper owned Demuxer and |
| 715 // Demuxer owned DataSource, we could simply let RendererWrapper get lazily |
| 716 // destroyed on the media thread. |
98 base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC, | 717 base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
99 base::WaitableEvent::InitialState::NOT_SIGNALED); | 718 base::WaitableEvent::InitialState::NOT_SIGNALED); |
100 base::Closure stop_cb = | 719 base::Closure stop_cb = |
101 base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)); | 720 base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)); |
102 // If posting the task fails or the posted task fails to run, | 721 media_task_runner_->PostTask( |
103 // we will wait here forever. So add a CHECK to make sure we do not run | 722 FROM_HERE, |
104 // into those situations. | 723 base::Bind(&RendererWrapper::Stop, |
105 CHECK(weak_factory_.HasWeakPtrs()); | 724 base::Unretained(renderer_wrapper_.get()), stop_cb)); |
106 CHECK(media_task_runner_->PostTask( | |
107 FROM_HERE, base::Bind(&PipelineImpl::StopTask, weak_this_, stop_cb))); | |
108 waiter.Wait(); | 725 waiter.Wait(); |
109 } else { | 726 } |
110 // This path is executed by unittests that share media and main threads. | 727 |
111 StopTask(base::Bind(&base::DoNothing)); | 728 // Once the pipeline is stopped, nothing is reported back to the client. |
112 } | 729 // Reset all callbacks and client handle. |
113 // Invalidate client weak pointer effectively canceling all pending client | 730 seek_cb_.Reset(); |
114 // notifications in the message queue. | 731 suspend_cb_.Reset(); |
115 client_weak_factory_.reset(); | 732 client_ = nullptr; |
116 } | 733 } |
117 | 734 |
118 void PipelineImpl::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { | 735 void PipelineImpl::Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) { |
119 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 736 DVLOG(2) << __FUNCTION__; |
| 737 DCHECK(thread_checker_.CalledOnValidThread()); |
| 738 DCHECK(!seek_cb.is_null()); |
120 | 739 |
121 if (!IsRunning()) { | 740 if (!IsRunning()) { |
122 DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek()."; | 741 DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek()."; |
123 return; | 742 return; |
124 } | 743 } |
125 | 744 |
| 745 DCHECK(seek_cb_.is_null()); |
| 746 seek_cb_ = seek_cb; |
126 media_task_runner_->PostTask( | 747 media_task_runner_->PostTask( |
127 FROM_HERE, base::Bind(&PipelineImpl::SeekTask, weak_this_, time, | 748 FROM_HERE, base::Bind(&RendererWrapper::Seek, |
128 media::BindToCurrentLoop(seek_cb))); | 749 base::Unretained(renderer_wrapper_.get()), time)); |
| 750 } |
| 751 |
| 752 void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) { |
| 753 DVLOG(2) << __FUNCTION__; |
| 754 DCHECK(!suspend_cb.is_null()); |
| 755 |
| 756 DCHECK(IsRunning()); |
| 757 DCHECK(suspend_cb_.is_null()); |
| 758 suspend_cb_ = suspend_cb; |
| 759 |
| 760 media_task_runner_->PostTask( |
| 761 FROM_HERE, base::Bind(&RendererWrapper::Suspend, |
| 762 base::Unretained(renderer_wrapper_.get()))); |
| 763 } |
| 764 |
| 765 void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer, |
| 766 base::TimeDelta time, |
| 767 const PipelineStatusCB& seek_cb) { |
| 768 DVLOG(2) << __FUNCTION__; |
| 769 DCHECK(thread_checker_.CalledOnValidThread()); |
| 770 DCHECK(renderer); |
| 771 DCHECK(!seek_cb.is_null()); |
| 772 |
| 773 DCHECK(IsRunning()); |
| 774 DCHECK(seek_cb_.is_null()); |
| 775 seek_cb_ = seek_cb; |
| 776 |
| 777 media_task_runner_->PostTask( |
| 778 FROM_HERE, base::Bind(&RendererWrapper::Resume, |
| 779 base::Unretained(renderer_wrapper_.get()), time, |
| 780 base::Passed(&renderer))); |
129 } | 781 } |
130 | 782 |
131 bool PipelineImpl::IsRunning() const { | 783 bool PipelineImpl::IsRunning() const { |
132 // TODO(alokp): Add thread DCHECK after removing the internal usage on | 784 DCHECK(thread_checker_.CalledOnValidThread()); |
133 // media thread. | 785 return !!client_; |
134 base::AutoLock auto_lock(lock_); | |
135 return running_; | |
136 } | 786 } |
137 | 787 |
138 double PipelineImpl::GetPlaybackRate() const { | 788 double PipelineImpl::GetPlaybackRate() const { |
139 // TODO(alokp): Add thread DCHECK after removing the internal usage on | 789 DCHECK(thread_checker_.CalledOnValidThread()); |
140 // media thread. | |
141 base::AutoLock auto_lock(lock_); | |
142 return playback_rate_; | 790 return playback_rate_; |
143 } | 791 } |
144 | 792 |
145 void PipelineImpl::SetPlaybackRate(double playback_rate) { | 793 void PipelineImpl::SetPlaybackRate(double playback_rate) { |
146 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 794 DVLOG(2) << __FUNCTION__ << "(" << playback_rate << ")"; |
| 795 DCHECK(thread_checker_.CalledOnValidThread()); |
147 | 796 |
148 if (playback_rate < 0.0) | 797 if (playback_rate < 0.0) |
149 return; | 798 return; |
150 | 799 |
151 base::AutoLock auto_lock(lock_); | |
152 playback_rate_ = playback_rate; | 800 playback_rate_ = playback_rate; |
153 if (running_) { | |
154 media_task_runner_->PostTask( | |
155 FROM_HERE, base::Bind(&PipelineImpl::PlaybackRateChangedTask, | |
156 weak_this_, playback_rate)); | |
157 } | |
158 } | |
159 | |
160 void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) { | |
161 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
162 | |
163 media_task_runner_->PostTask( | |
164 FROM_HERE, base::Bind(&PipelineImpl::SuspendTask, weak_this_, | |
165 media::BindToCurrentLoop(suspend_cb))); | |
166 } | |
167 | |
168 void PipelineImpl::Resume(std::unique_ptr<Renderer> renderer, | |
169 base::TimeDelta timestamp, | |
170 const PipelineStatusCB& seek_cb) { | |
171 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
172 | |
173 media_task_runner_->PostTask( | 801 media_task_runner_->PostTask( |
174 FROM_HERE, | 802 FROM_HERE, |
175 base::Bind(&PipelineImpl::ResumeTask, weak_this_, base::Passed(&renderer), | 803 base::Bind(&RendererWrapper::SetPlaybackRate, |
176 timestamp, media::BindToCurrentLoop(seek_cb))); | 804 base::Unretained(renderer_wrapper_.get()), playback_rate_)); |
177 } | 805 } |
178 | 806 |
179 float PipelineImpl::GetVolume() const { | 807 float PipelineImpl::GetVolume() const { |
180 // TODO(alokp): Add thread DCHECK after removing the internal usage on | 808 DCHECK(thread_checker_.CalledOnValidThread()); |
181 // media thread. | |
182 base::AutoLock auto_lock(lock_); | |
183 return volume_; | 809 return volume_; |
184 } | 810 } |
185 | 811 |
186 void PipelineImpl::SetVolume(float volume) { | 812 void PipelineImpl::SetVolume(float volume) { |
187 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 813 DVLOG(2) << __FUNCTION__ << "(" << volume << ")"; |
| 814 DCHECK(thread_checker_.CalledOnValidThread()); |
188 | 815 |
189 if (volume < 0.0f || volume > 1.0f) | 816 if (volume < 0.0f || volume > 1.0f) |
190 return; | 817 return; |
191 | 818 |
192 base::AutoLock auto_lock(lock_); | |
193 volume_ = volume; | 819 volume_ = volume; |
194 if (running_) { | 820 media_task_runner_->PostTask( |
195 media_task_runner_->PostTask( | 821 FROM_HERE, base::Bind(&RendererWrapper::SetVolume, |
196 FROM_HERE, | 822 base::Unretained(renderer_wrapper_.get()), volume)); |
197 base::Bind(&PipelineImpl::VolumeChangedTask, weak_this_, volume)); | 823 } |
198 } | 824 |
199 } | 825 base::TimeDelta PipelineImpl::GetMediaTime() const { |
200 | 826 DCHECK(thread_checker_.CalledOnValidThread()); |
201 TimeDelta PipelineImpl::GetMediaTime() const { | 827 |
202 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 828 return suspend_time_ != kNoTimestamp() ? suspend_time_ |
203 | 829 : renderer_wrapper_->GetMediaTime(); |
204 base::AutoLock auto_lock(lock_); | 830 } |
205 if (suspend_timestamp_ != kNoTimestamp()) | 831 |
206 return suspend_timestamp_; | 832 Ranges<base::TimeDelta> PipelineImpl::GetBufferedTimeRanges() const { |
207 return renderer_ ? std::min(renderer_->GetMediaTime(), duration_) | 833 DCHECK(thread_checker_.CalledOnValidThread()); |
208 : TimeDelta(); | |
209 } | |
210 | |
211 Ranges<TimeDelta> PipelineImpl::GetBufferedTimeRanges() const { | |
212 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
213 | |
214 base::AutoLock auto_lock(lock_); | |
215 return buffered_time_ranges_; | 834 return buffered_time_ranges_; |
216 } | 835 } |
217 | 836 |
218 TimeDelta PipelineImpl::GetMediaDuration() const { | 837 base::TimeDelta PipelineImpl::GetMediaDuration() const { |
219 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 838 DCHECK(thread_checker_.CalledOnValidThread()); |
220 | |
221 base::AutoLock auto_lock(lock_); | |
222 return duration_; | 839 return duration_; |
223 } | 840 } |
224 | 841 |
225 bool PipelineImpl::DidLoadingProgress() { | 842 bool PipelineImpl::DidLoadingProgress() { |
226 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 843 DCHECK(thread_checker_.CalledOnValidThread()); |
227 | |
228 base::AutoLock auto_lock(lock_); | |
229 bool ret = did_loading_progress_; | 844 bool ret = did_loading_progress_; |
230 did_loading_progress_ = false; | 845 did_loading_progress_ = false; |
231 return ret; | 846 return ret; |
232 } | 847 } |
233 | 848 |
234 PipelineStatistics PipelineImpl::GetStatistics() const { | 849 PipelineStatistics PipelineImpl::GetStatistics() const { |
235 // TODO(alokp): Add thread DCHECK after removing the internal usage on | 850 DCHECK(thread_checker_.CalledOnValidThread()); |
236 // media thread. | |
237 base::AutoLock auto_lock(lock_); | |
238 return statistics_; | 851 return statistics_; |
239 } | 852 } |
240 | 853 |
241 void PipelineImpl::SetCdm(CdmContext* cdm_context, | 854 void PipelineImpl::SetCdm(CdmContext* cdm_context, |
242 const CdmAttachedCB& cdm_attached_cb) { | 855 const CdmAttachedCB& cdm_attached_cb) { |
243 DCHECK(main_task_runner_->BelongsToCurrentThread()); | 856 DVLOG(2) << __FUNCTION__; |
| 857 DCHECK(thread_checker_.CalledOnValidThread()); |
| 858 DCHECK(cdm_context); |
| 859 DCHECK(!cdm_attached_cb.is_null()); |
244 | 860 |
245 media_task_runner_->PostTask( | 861 media_task_runner_->PostTask( |
246 FROM_HERE, base::Bind(&PipelineImpl::SetCdmTask, weak_this_, cdm_context, | 862 FROM_HERE, |
247 cdm_attached_cb)); | 863 base::Bind(&RendererWrapper::SetCdm, |
248 } | 864 base::Unretained(renderer_wrapper_.get()), cdm_context, |
249 | 865 media::BindToCurrentLoop(cdm_attached_cb))); |
250 void PipelineImpl::SetErrorForTesting(PipelineStatus status) { | |
251 OnError(status); | |
252 } | |
253 | |
254 bool PipelineImpl::HasWeakPtrsForTesting() const { | |
255 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
256 return weak_factory_.HasWeakPtrs(); | |
257 } | |
258 | |
259 void PipelineImpl::SetState(State next_state) { | |
260 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
261 DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state); | |
262 | |
263 state_ = next_state; | |
264 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); | |
265 } | 866 } |
266 | 867 |
267 #define RETURN_STRING(state) \ | 868 #define RETURN_STRING(state) \ |
268 case state: \ | 869 case state: \ |
269 return #state; | 870 return #state; |
270 | 871 |
| 872 // static |
271 const char* PipelineImpl::GetStateString(State state) { | 873 const char* PipelineImpl::GetStateString(State state) { |
272 switch (state) { | 874 switch (state) { |
273 RETURN_STRING(kCreated); | 875 RETURN_STRING(kCreated); |
274 RETURN_STRING(kInitDemuxer); | 876 RETURN_STRING(kStarting); |
275 RETURN_STRING(kInitRenderer); | |
276 RETURN_STRING(kSeeking); | 877 RETURN_STRING(kSeeking); |
277 RETURN_STRING(kPlaying); | 878 RETURN_STRING(kPlaying); |
278 RETURN_STRING(kStopping); | 879 RETURN_STRING(kStopping); |
279 RETURN_STRING(kStopped); | 880 RETURN_STRING(kStopped); |
280 RETURN_STRING(kSuspending); | 881 RETURN_STRING(kSuspending); |
281 RETURN_STRING(kSuspended); | 882 RETURN_STRING(kSuspended); |
282 RETURN_STRING(kResuming); | 883 RETURN_STRING(kResuming); |
283 } | 884 } |
284 NOTREACHED(); | 885 NOTREACHED(); |
285 return "INVALID"; | 886 return "INVALID"; |
286 } | 887 } |
287 | 888 |
288 #undef RETURN_STRING | 889 #undef RETURN_STRING |
289 | 890 |
290 PipelineImpl::State PipelineImpl::GetNextState() const { | |
291 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
292 DCHECK(stop_cb_.is_null()) << "State transitions don't happen when stopping"; | |
293 DCHECK_EQ(status_, PIPELINE_OK) | |
294 << "State transitions don't happen when there's an error: " << status_; | |
295 | |
296 switch (state_) { | |
297 case kCreated: | |
298 return kInitDemuxer; | |
299 | |
300 case kInitDemuxer: | |
301 return kInitRenderer; | |
302 | |
303 case kInitRenderer: | |
304 case kSeeking: | |
305 return kPlaying; | |
306 | |
307 case kSuspending: | |
308 return kSuspended; | |
309 | |
310 case kSuspended: | |
311 return kResuming; | |
312 | |
313 case kResuming: | |
314 return kPlaying; | |
315 | |
316 case kPlaying: | |
317 case kStopping: | |
318 case kStopped: | |
319 break; | |
320 } | |
321 NOTREACHED() << "State has no transition: " << state_; | |
322 return state_; | |
323 } | |
324 | |
325 void PipelineImpl::OnDemuxerError(PipelineStatus error) { | |
326 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer | |
327 // implementations call DemuxerHost on the media thread. | |
328 media_task_runner_->PostTask( | |
329 FROM_HERE, | |
330 base::Bind(&PipelineImpl::ErrorChangedTask, weak_this_, error)); | |
331 } | |
332 | |
333 void PipelineImpl::AddTextStream(DemuxerStream* text_stream, | |
334 const TextTrackConfig& config) { | |
335 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer | |
336 // implementations call DemuxerHost on the media thread. | |
337 media_task_runner_->PostTask( | |
338 FROM_HERE, base::Bind(&PipelineImpl::AddTextStreamTask, weak_this_, | |
339 text_stream, config)); | |
340 } | |
341 | |
342 void PipelineImpl::RemoveTextStream(DemuxerStream* text_stream) { | |
343 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer | |
344 // implementations call DemuxerHost on the media thread. | |
345 media_task_runner_->PostTask( | |
346 FROM_HERE, | |
347 base::Bind(&PipelineImpl::RemoveTextStreamTask, weak_this_, text_stream)); | |
348 } | |
349 | |
350 void PipelineImpl::OnError(PipelineStatus error) { | 891 void PipelineImpl::OnError(PipelineStatus error) { |
351 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 892 DVLOG(2) << __FUNCTION__; |
352 DCHECK(IsRunning()); | 893 DCHECK(thread_checker_.CalledOnValidThread()); |
353 DCHECK_NE(PIPELINE_OK, error); | 894 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; |
354 VLOG(1) << "Media pipeline error: " << error; | 895 |
355 | 896 if (!IsRunning()) |
356 media_task_runner_->PostTask( | 897 return; |
357 FROM_HERE, | 898 |
358 base::Bind(&PipelineImpl::ErrorChangedTask, weak_this_, error)); | 899 // If the error happens during starting/seeking/suspending/resuming, |
| 900 // report the error via the completion callback for those tasks. |
| 901 // Else report error via the client interface. |
| 902 if (!seek_cb_.is_null()) { |
| 903 base::ResetAndReturn(&seek_cb_).Run(error); |
| 904 } else if (!suspend_cb_.is_null()) { |
| 905 base::ResetAndReturn(&suspend_cb_).Run(error); |
| 906 } else { |
| 907 DCHECK(client_); |
| 908 client_->OnError(error); |
| 909 } |
| 910 |
| 911 // Any kind of error stops the pipeline. |
| 912 Stop(); |
359 } | 913 } |
360 | 914 |
361 void PipelineImpl::OnEnded() { | 915 void PipelineImpl::OnEnded() { |
362 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 916 DVLOG(2) << __FUNCTION__; |
363 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); | 917 DCHECK(thread_checker_.CalledOnValidThread()); |
364 | 918 |
365 if (state_ != kPlaying) | 919 if (IsRunning()) { |
366 return; | 920 DCHECK(client_); |
367 | 921 client_->OnEnded(); |
368 DCHECK(!renderer_ended_); | 922 } |
369 renderer_ended_ = true; | 923 } |
370 | 924 |
371 RunEndedCallbackIfNeeded(); | 925 void PipelineImpl::OnMetadata(PipelineMetadata metadata) { |
| 926 DVLOG(2) << __FUNCTION__; |
| 927 DCHECK(thread_checker_.CalledOnValidThread()); |
| 928 |
| 929 if (IsRunning()) { |
| 930 DCHECK(client_); |
| 931 client_->OnMetadata(metadata); |
| 932 } |
| 933 } |
| 934 |
| 935 void PipelineImpl::OnBufferingStateChange(BufferingState state) { |
| 936 DVLOG(2) << __FUNCTION__ << "(" << state << ")"; |
| 937 DCHECK(thread_checker_.CalledOnValidThread()); |
| 938 |
| 939 if (IsRunning()) { |
| 940 DCHECK(client_); |
| 941 client_->OnBufferingStateChange(state); |
| 942 } |
| 943 } |
| 944 |
| 945 void PipelineImpl::OnDurationChange(base::TimeDelta duration) { |
| 946 DVLOG(2) << __FUNCTION__; |
| 947 DCHECK(thread_checker_.CalledOnValidThread()); |
| 948 |
| 949 duration_ = duration; |
| 950 |
| 951 if (IsRunning()) { |
| 952 DCHECK(client_); |
| 953 client_->OnDurationChange(); |
| 954 } |
| 955 } |
| 956 |
| 957 void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config, |
| 958 const AddTextTrackDoneCB& done_cb) { |
| 959 DVLOG(2) << __FUNCTION__; |
| 960 DCHECK(thread_checker_.CalledOnValidThread()); |
| 961 |
| 962 if (IsRunning()) { |
| 963 DCHECK(client_); |
| 964 client_->OnAddTextTrack(config, done_cb); |
| 965 } |
| 966 } |
| 967 |
| 968 void PipelineImpl::OnWaitingForDecryptionKey() { |
| 969 DVLOG(2) << __FUNCTION__; |
| 970 DCHECK(thread_checker_.CalledOnValidThread()); |
| 971 |
| 972 if (IsRunning()) { |
| 973 DCHECK(client_); |
| 974 client_->OnWaitingForDecryptionKey(); |
| 975 } |
| 976 } |
| 977 |
| 978 void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) { |
| 979 DVLOG(2) << __FUNCTION__; |
| 980 DCHECK(thread_checker_.CalledOnValidThread()); |
| 981 |
| 982 if (IsRunning()) { |
| 983 DCHECK(client_); |
| 984 client_->OnVideoNaturalSizeChange(size); |
| 985 } |
| 986 } |
| 987 |
| 988 void PipelineImpl::OnVideoOpacityChange(bool opaque) { |
| 989 DVLOG(2) << __FUNCTION__; |
| 990 DCHECK(thread_checker_.CalledOnValidThread()); |
| 991 |
| 992 if (IsRunning()) { |
| 993 DCHECK(client_); |
| 994 client_->OnVideoOpacityChange(opaque); |
| 995 } |
| 996 } |
| 997 |
| 998 void PipelineImpl::OnBufferedTimeRangesChange( |
| 999 const Ranges<base::TimeDelta>& ranges) { |
| 1000 DVLOG(3) << __FUNCTION__; |
| 1001 DCHECK(thread_checker_.CalledOnValidThread()); |
| 1002 |
| 1003 buffered_time_ranges_ = ranges; |
| 1004 did_loading_progress_ = true; |
372 } | 1005 } |
373 | 1006 |
374 void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) { | 1007 void PipelineImpl::OnStatisticsUpdate(const PipelineStatistics& stats) { |
375 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 1008 DVLOG(3) << __FUNCTION__; |
376 | 1009 DCHECK(thread_checker_.CalledOnValidThread()); |
377 base::AutoLock auto_lock(lock_); | 1010 |
378 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; | 1011 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; |
379 statistics_.video_bytes_decoded += stats.video_bytes_decoded; | 1012 statistics_.video_bytes_decoded += stats.video_bytes_decoded; |
380 statistics_.video_frames_decoded += stats.video_frames_decoded; | 1013 statistics_.video_frames_decoded += stats.video_frames_decoded; |
381 statistics_.video_frames_dropped += stats.video_frames_dropped; | 1014 statistics_.video_frames_dropped += stats.video_frames_dropped; |
382 statistics_.audio_memory_usage += stats.audio_memory_usage; | 1015 statistics_.audio_memory_usage += stats.audio_memory_usage; |
383 statistics_.video_memory_usage += stats.video_memory_usage; | 1016 statistics_.video_memory_usage += stats.video_memory_usage; |
384 } | 1017 } |
385 | 1018 |
386 void PipelineImpl::OnBufferingStateChange(BufferingState state) { | 1019 void PipelineImpl::OnSeekDone(base::TimeDelta start_time) { |
387 DVLOG(1) << __FUNCTION__ << "(" << state << ") "; | 1020 DVLOG(3) << __FUNCTION__ << "(" << start_time.InMicroseconds() << ")"; |
388 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 1021 DCHECK(thread_checker_.CalledOnValidThread()); |
389 | 1022 |
390 main_task_runner_->PostTask( | 1023 // Reset the suspend_time now that the pipeline is playing. |
391 FROM_HERE, base::Bind(&Pipeline::Client::OnBufferingStateChange, | 1024 // Media time will now be reported by renderer. |
392 weak_client_, state)); | 1025 suspend_time_ = kNoTimestamp(); |
393 } | 1026 |
394 | 1027 if (IsRunning()) { |
395 void PipelineImpl::OnWaitingForDecryptionKey() { | 1028 DCHECK(!seek_cb_.is_null()); |
396 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 1029 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); |
397 | 1030 } |
398 main_task_runner_->PostTask( | 1031 } |
399 FROM_HERE, | 1032 |
400 base::Bind(&Pipeline::Client::OnWaitingForDecryptionKey, weak_client_)); | 1033 void PipelineImpl::OnSuspendDone(base::TimeDelta suspend_time) { |
401 } | 1034 DVLOG(3) << __FUNCTION__ << "(" << suspend_time.InMicroseconds() << ")"; |
402 | 1035 DCHECK(thread_checker_.CalledOnValidThread()); |
403 void PipelineImpl::OnVideoNaturalSizeChange(const gfx::Size& size) { | 1036 |
404 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 1037 // Cache the time at which pipeline was suspended. |
405 | 1038 // It will be used to report media time while the pipeline is suspended. |
406 main_task_runner_->PostTask( | 1039 suspend_time_ = suspend_time; |
407 FROM_HERE, base::Bind(&Pipeline::Client::OnVideoNaturalSizeChange, | 1040 |
408 weak_client_, size)); | 1041 // Reset audio-video memory usage since renderer has been destroyed. |
409 } | 1042 statistics_.audio_memory_usage = 0; |
410 | 1043 statistics_.video_memory_usage = 0; |
411 void PipelineImpl::OnVideoOpacityChange(bool opaque) { | 1044 |
412 DCHECK(media_task_runner_->BelongsToCurrentThread()); | 1045 if (IsRunning()) { |
413 | 1046 DCHECK(!suspend_cb_.is_null()); |
414 main_task_runner_->PostTask( | 1047 base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK); |
415 FROM_HERE, base::Bind(&Pipeline::Client::OnVideoOpacityChange, | 1048 } |
416 weak_client_, opaque)); | |
417 } | |
418 | |
419 void PipelineImpl::SetDuration(TimeDelta duration) { | |
420 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer | |
421 // implementations call DemuxerHost on the media thread. | |
422 DCHECK(IsRunning()); | |
423 media_log_->AddEvent(media_log_->CreateTimeEvent(MediaLogEvent::DURATION_SET, | |
424 "duration", duration)); | |
425 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); | |
426 | |
427 base::AutoLock auto_lock(lock_); | |
428 duration_ = duration; | |
429 main_task_runner_->PostTask( | |
430 FROM_HERE, base::Bind(&Pipeline::Client::OnDurationChange, weak_client_)); | |
431 } | |
432 | |
433 void PipelineImpl::StateTransitionTask(PipelineStatus status) { | |
434 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
435 | |
436 // No-op any state transitions if we're stopping. | |
437 if (state_ == kStopping || state_ == kStopped) | |
438 return; | |
439 | |
440 // Report error from the previous operation. | |
441 if (status != PIPELINE_OK) { | |
442 ErrorChangedTask(status); | |
443 return; | |
444 } | |
445 | |
446 // Guard against accidentally clearing |pending_callbacks_| for states that | |
447 // use it as well as states that should not be using it. | |
448 DCHECK_EQ(pending_callbacks_.get() != NULL, | |
449 state_ == kSeeking || state_ == kSuspending || state_ == kResuming); | |
450 | |
451 pending_callbacks_.reset(); | |
452 | |
453 PipelineStatusCB done_cb = | |
454 base::Bind(&PipelineImpl::StateTransitionTask, weak_this_); | |
455 | |
456 // Switch states, performing any entrance actions for the new state as well. | |
457 SetState(GetNextState()); | |
458 switch (state_) { | |
459 case kInitDemuxer: | |
460 return InitializeDemuxer(done_cb); | |
461 | |
462 case kInitRenderer: | |
463 // When the state_ transfers to kInitRenderer, it means the demuxer has | |
464 // finished parsing the init info. It should call ReportMetadata in case | |
465 // meeting 'decode' error when passing media segment but WebMediaPlayer's | |
466 // ready_state_ is still ReadyStateHaveNothing. In that case, it will | |
467 // treat it as NetworkStateFormatError not NetworkStateDecodeError. | |
468 ReportMetadata(); | |
469 start_timestamp_ = demuxer_->GetStartTime(); | |
470 | |
471 return InitializeRenderer(done_cb); | |
472 | |
473 case kPlaying: | |
474 DCHECK(start_timestamp_ >= base::TimeDelta()); | |
475 renderer_->StartPlayingFrom(start_timestamp_); | |
476 { | |
477 base::AutoLock auto_lock(lock_); | |
478 suspend_timestamp_ = kNoTimestamp(); | |
479 } | |
480 | |
481 if (text_renderer_) | |
482 text_renderer_->StartPlaying(); | |
483 | |
484 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); | |
485 | |
486 PlaybackRateChangedTask(GetPlaybackRate()); | |
487 VolumeChangedTask(GetVolume()); | |
488 return; | |
489 | |
490 case kSuspended: | |
491 renderer_.reset(); | |
492 { | |
493 base::AutoLock auto_lock(lock_); | |
494 statistics_.audio_memory_usage = 0; | |
495 statistics_.video_memory_usage = 0; | |
496 } | |
497 base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK); | |
498 return; | |
499 | |
500 case kStopping: | |
501 case kStopped: | |
502 case kCreated: | |
503 case kSeeking: | |
504 case kSuspending: | |
505 case kResuming: | |
506 NOTREACHED() << "State has no transition: " << state_; | |
507 return; | |
508 } | |
509 } | |
510 | |
511 // Note that the usage of base::Unretained() with the renderers is considered | |
512 // safe as they are owned by |pending_callbacks_| and share the same lifetime. | |
513 // | |
514 // That being said, deleting the renderers while keeping |pending_callbacks_| | |
515 // running on the media thread would result in crashes. | |
516 void PipelineImpl::DoSeek(TimeDelta seek_timestamp, | |
517 const PipelineStatusCB& done_cb) { | |
518 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
519 DCHECK(!pending_callbacks_.get()); | |
520 DCHECK_EQ(state_, kSeeking); | |
521 SerialRunner::Queue bound_fns; | |
522 | |
523 // Pause. | |
524 if (text_renderer_) { | |
525 bound_fns.Push(base::Bind(&TextRenderer::Pause, | |
526 base::Unretained(text_renderer_.get()))); | |
527 } | |
528 | |
529 // Flush. | |
530 DCHECK(renderer_); | |
531 bound_fns.Push( | |
532 base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); | |
533 | |
534 if (text_renderer_) { | |
535 bound_fns.Push(base::Bind(&TextRenderer::Flush, | |
536 base::Unretained(text_renderer_.get()))); | |
537 } | |
538 | |
539 // Seek demuxer. | |
540 bound_fns.Push( | |
541 base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); | |
542 | |
543 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | |
544 } | |
545 | |
546 void PipelineImpl::DoStop() { | |
547 DVLOG(2) << __FUNCTION__; | |
548 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
549 DCHECK_EQ(state_, kStopping); | |
550 DCHECK(!pending_callbacks_.get()); | |
551 | |
552 // TODO(scherkus): Enforce that Renderer is only called on a single thread, | |
553 // even for accessing media time http://crbug.com/370634 | |
554 std::unique_ptr<Renderer> renderer; | |
555 { | |
556 base::AutoLock auto_lock(lock_); | |
557 renderer.swap(renderer_); | |
558 } | |
559 renderer.reset(); | |
560 text_renderer_.reset(); | |
561 | |
562 if (demuxer_) { | |
563 demuxer_->Stop(); | |
564 demuxer_ = NULL; | |
565 } | |
566 | |
567 { | |
568 base::AutoLock auto_lock(lock_); | |
569 running_ = false; | |
570 } | |
571 SetState(kStopped); | |
572 | |
573 // If we stop during initialization/seeking/suspending we don't want to leave | |
574 // outstanding callbacks around. The callbacks also do not get run if the | |
575 // pipeline is stopped before it had a chance to complete outstanding tasks. | |
576 seek_cb_.Reset(); | |
577 suspend_cb_.Reset(); | |
578 | |
579 if (!stop_cb_.is_null()) { | |
580 // Invalid all weak pointers so it's safe to destroy |this| on the render | |
581 // main thread. | |
582 weak_factory_.InvalidateWeakPtrs(); | |
583 | |
584 // Post the stop callback to enqueue it after the tasks that may have been | |
585 // Demuxer and Renderer during stopping. | |
586 media_task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&stop_cb_)); | |
587 } | |
588 } | |
589 | |
590 void PipelineImpl::OnBufferedTimeRangesChanged( | |
591 const Ranges<base::TimeDelta>& ranges) { | |
592 // TODO(alokp): Add thread DCHECK after ensuring that all Demuxer | |
593 // implementations call DemuxerHost on the media thread. | |
594 base::AutoLock auto_lock(lock_); | |
595 buffered_time_ranges_ = ranges; | |
596 did_loading_progress_ = true; | |
597 } | |
598 | |
599 void PipelineImpl::StartTask() { | |
600 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
601 | |
602 CHECK_EQ(kCreated, state_) | |
603 << "Media pipeline cannot be started more than once"; | |
604 | |
605 text_renderer_ = CreateTextRenderer(); | |
606 if (text_renderer_) { | |
607 text_renderer_->Initialize( | |
608 base::Bind(&PipelineImpl::OnTextRendererEnded, weak_this_)); | |
609 } | |
610 | |
611 StateTransitionTask(PIPELINE_OK); | |
612 } | |
613 | |
614 void PipelineImpl::StopTask(const base::Closure& stop_cb) { | |
615 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
616 DCHECK(stop_cb_.is_null()); | |
617 | |
618 if (state_ == kStopped) { | |
619 // Invalid all weak pointers so it's safe to destroy |this| on the render | |
620 // main thread. | |
621 weak_factory_.InvalidateWeakPtrs(); | |
622 | |
623 // NOTE: pipeline may be deleted at this point in time as a result of | |
624 // executing |stop_cb|. | |
625 stop_cb.Run(); | |
626 | |
627 return; | |
628 } | |
629 | |
630 stop_cb_ = stop_cb; | |
631 | |
632 // We may already be stopping due to a runtime error. | |
633 if (state_ == kStopping) | |
634 return; | |
635 | |
636 // Do not report statistics if the pipeline is not fully initialized. | |
637 if (state_ == kSeeking || state_ == kPlaying || state_ == kSuspending || | |
638 state_ == kSuspended || state_ == kResuming) { | |
639 PipelineStatistics stats = GetStatistics(); | |
640 if (stats.video_frames_decoded > 0) { | |
641 UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount", | |
642 stats.video_frames_dropped); | |
643 } | |
644 } | |
645 | |
646 SetState(kStopping); | |
647 pending_callbacks_.reset(); | |
648 DoStop(); | |
649 } | |
650 | |
651 void PipelineImpl::ErrorChangedTask(PipelineStatus error) { | |
652 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
653 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; | |
654 | |
655 // Preserve existing abnormal status. | |
656 if (status_ != PIPELINE_OK) | |
657 return; | |
658 | |
659 // Don't report pipeline error events to the media log here. The embedder will | |
660 // log this when Client::OnError is called. If the pipeline is already stopped | |
661 // or stopping we also don't want to log any event. In case we are suspending | |
662 // or suspended, the error may be recoverable, so don't propagate it now, | |
663 // instead let the subsequent seek during resume propagate it if it's | |
664 // unrecoverable. | |
665 if (state_ == kStopping || state_ == kStopped || state_ == kSuspending || | |
666 state_ == kSuspended) { | |
667 return; | |
668 } | |
669 | |
670 // Once we enter |kStopping| state, nothing is reported back to the client. | |
671 // If we encounter an error during initialization/seeking/suspending, | |
672 // report the error using the completion callbacks for those tasks. | |
673 status_ = error; | |
674 bool error_reported = false; | |
675 if (!seek_cb_.is_null()) { | |
676 base::ResetAndReturn(&seek_cb_).Run(status_); | |
677 error_reported = true; | |
678 } | |
679 if (!suspend_cb_.is_null()) { | |
680 base::ResetAndReturn(&suspend_cb_).Run(status_); | |
681 error_reported = true; | |
682 } | |
683 if (!error_reported) { | |
684 DCHECK_NE(status_, PIPELINE_OK); | |
685 main_task_runner_->PostTask( | |
686 FROM_HERE, | |
687 base::Bind(&Pipeline::Client::OnError, weak_client_, status_)); | |
688 } | |
689 | |
690 SetState(kStopping); | |
691 pending_callbacks_.reset(); | |
692 DoStop(); | |
693 } | |
694 | |
695 void PipelineImpl::PlaybackRateChangedTask(double playback_rate) { | |
696 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
697 | |
698 // Playback rate changes are only carried out while playing. | |
699 if (state_ != kPlaying) | |
700 return; | |
701 | |
702 renderer_->SetPlaybackRate(playback_rate); | |
703 } | |
704 | |
705 void PipelineImpl::VolumeChangedTask(float volume) { | |
706 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
707 | |
708 // Volume changes are only carried out while playing. | |
709 if (state_ != kPlaying) | |
710 return; | |
711 | |
712 renderer_->SetVolume(volume); | |
713 } | |
714 | |
715 void PipelineImpl::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { | |
716 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
717 DCHECK(stop_cb_.is_null()); | |
718 | |
719 // Suppress seeking if we're not fully started. | |
720 if (state_ != kPlaying) { | |
721 DCHECK(state_ == kStopping || state_ == kStopped) | |
722 << "Receive seek in unexpected state: " << state_; | |
723 seek_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
724 return; | |
725 } | |
726 | |
727 DCHECK(seek_cb_.is_null()); | |
728 | |
729 const base::TimeDelta seek_timestamp = | |
730 std::max(time, demuxer_->GetStartTime()); | |
731 | |
732 SetState(kSeeking); | |
733 seek_cb_ = seek_cb; | |
734 renderer_ended_ = false; | |
735 text_renderer_ended_ = false; | |
736 start_timestamp_ = seek_timestamp; | |
737 | |
738 DoSeek(seek_timestamp, | |
739 base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); | |
740 } | |
741 | |
742 void PipelineImpl::SuspendTask(const PipelineStatusCB& suspend_cb) { | |
743 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
744 | |
745 // Suppress suspending if we're not playing. | |
746 if (state_ != kPlaying) { | |
747 DCHECK(state_ == kStopping || state_ == kStopped) | |
748 << "Receive suspend in unexpected state: " << state_; | |
749 suspend_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
750 return; | |
751 } | |
752 DCHECK(renderer_); | |
753 DCHECK(!pending_callbacks_.get()); | |
754 | |
755 SetState(kSuspending); | |
756 suspend_cb_ = suspend_cb; | |
757 | |
758 // Freeze playback and record the media time before flushing. (Flushing clears | |
759 // the value.) | |
760 renderer_->SetPlaybackRate(0.0); | |
761 { | |
762 base::AutoLock auto_lock(lock_); | |
763 suspend_timestamp_ = renderer_->GetMediaTime(); | |
764 DCHECK(suspend_timestamp_ != kNoTimestamp()); | |
765 } | |
766 | |
767 // Queue the asynchronous actions required to stop playback. (Matches setup in | |
768 // DoSeek().) | |
769 // TODO(sandersd): Share implementation with DoSeek(). | |
770 SerialRunner::Queue fns; | |
771 | |
772 if (text_renderer_) { | |
773 fns.Push(base::Bind(&TextRenderer::Pause, | |
774 base::Unretained(text_renderer_.get()))); | |
775 } | |
776 | |
777 fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); | |
778 | |
779 if (text_renderer_) { | |
780 fns.Push(base::Bind(&TextRenderer::Flush, | |
781 base::Unretained(text_renderer_.get()))); | |
782 } | |
783 | |
784 pending_callbacks_ = SerialRunner::Run( | |
785 fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); | |
786 } | |
787 | |
788 void PipelineImpl::ResumeTask(std::unique_ptr<Renderer> renderer, | |
789 base::TimeDelta timestamp, | |
790 const PipelineStatusCB& seek_cb) { | |
791 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
792 | |
793 // Suppress resuming if we're not suspended. | |
794 if (state_ != kSuspended) { | |
795 DCHECK(state_ == kStopping || state_ == kStopped) | |
796 << "Receive resume in unexpected state: " << state_; | |
797 seek_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
798 return; | |
799 } | |
800 DCHECK(!renderer_); | |
801 DCHECK(!pending_callbacks_.get()); | |
802 | |
803 SetState(kResuming); | |
804 renderer_ = std::move(renderer); | |
805 | |
806 // Set up for a seek. (Matches setup in SeekTask().) | |
807 // TODO(sandersd): Share implementation with SeekTask(). | |
808 seek_cb_ = seek_cb; | |
809 renderer_ended_ = false; | |
810 text_renderer_ended_ = false; | |
811 start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime()); | |
812 | |
813 // Queue the asynchronous actions required to start playback. Unlike DoSeek(), | |
814 // we need to initialize the renderer ourselves (we don't want to enter state | |
815 // kInitDemuxer, and even if we did the current code would seek to the start | |
816 // instead of |timestamp|). | |
817 SerialRunner::Queue fns; | |
818 | |
819 fns.Push( | |
820 base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_)); | |
821 | |
822 fns.Push(base::Bind(&PipelineImpl::InitializeRenderer, weak_this_)); | |
823 | |
824 pending_callbacks_ = SerialRunner::Run( | |
825 fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); | |
826 } | |
827 | |
828 void PipelineImpl::SetCdmTask(CdmContext* cdm_context, | |
829 const CdmAttachedCB& cdm_attached_cb) { | |
830 base::AutoLock auto_lock(lock_); | |
831 if (!renderer_) { | |
832 cdm_context_ = cdm_context; | |
833 cdm_attached_cb.Run(true); | |
834 return; | |
835 } | |
836 | |
837 renderer_->SetCdm(cdm_context, | |
838 base::Bind(&PipelineImpl::OnCdmAttached, weak_this_, | |
839 cdm_attached_cb, cdm_context)); | |
840 } | |
841 | |
842 void PipelineImpl::OnCdmAttached(const CdmAttachedCB& cdm_attached_cb, | |
843 CdmContext* cdm_context, | |
844 bool success) { | |
845 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
846 if (success) | |
847 cdm_context_ = cdm_context; | |
848 cdm_attached_cb.Run(success); | |
849 } | |
850 | |
851 void PipelineImpl::OnTextRendererEnded() { | |
852 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
853 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); | |
854 | |
855 if (state_ != kPlaying) | |
856 return; | |
857 | |
858 DCHECK(!text_renderer_ended_); | |
859 text_renderer_ended_ = true; | |
860 | |
861 RunEndedCallbackIfNeeded(); | |
862 } | |
863 | |
864 void PipelineImpl::RunEndedCallbackIfNeeded() { | |
865 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
866 | |
867 if (renderer_ && !renderer_ended_) | |
868 return; | |
869 | |
870 if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_) | |
871 return; | |
872 | |
873 DCHECK_EQ(status_, PIPELINE_OK); | |
874 main_task_runner_->PostTask( | |
875 FROM_HERE, base::Bind(&Pipeline::Client::OnEnded, weak_client_)); | |
876 } | |
877 | |
878 std::unique_ptr<TextRenderer> PipelineImpl::CreateTextRenderer() { | |
879 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
880 | |
881 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
882 if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks)) | |
883 return nullptr; | |
884 | |
885 return base::WrapUnique(new media::TextRenderer( | |
886 media_task_runner_, | |
887 base::Bind(&PipelineImpl::OnAddTextTrack, weak_this_))); | |
888 } | |
889 | |
890 void PipelineImpl::AddTextStreamTask(DemuxerStream* text_stream, | |
891 const TextTrackConfig& config) { | |
892 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
893 // TODO(matthewjheaney): fix up text_ended_ when text stream | |
894 // is added (http://crbug.com/321446). | |
895 if (text_renderer_) | |
896 text_renderer_->AddTextStream(text_stream, config); | |
897 } | |
898 | |
899 void PipelineImpl::RemoveTextStreamTask(DemuxerStream* text_stream) { | |
900 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
901 | |
902 if (text_renderer_) | |
903 text_renderer_->RemoveTextStream(text_stream); | |
904 } | |
905 | |
906 void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config, | |
907 const AddTextTrackDoneCB& done_cb) { | |
908 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
909 | |
910 main_task_runner_->PostTask( | |
911 FROM_HERE, base::Bind(&Pipeline::Client::OnAddTextTrack, weak_client_, | |
912 config, done_cb)); | |
913 } | |
914 | |
915 void PipelineImpl::InitializeDemuxer(const PipelineStatusCB& done_cb) { | |
916 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
917 | |
918 demuxer_->Initialize(this, done_cb, !!text_renderer_); | |
919 } | |
920 | |
921 void PipelineImpl::InitializeRenderer(const PipelineStatusCB& done_cb) { | |
922 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
923 | |
924 if (!demuxer_->GetStream(DemuxerStream::AUDIO) && | |
925 !demuxer_->GetStream(DemuxerStream::VIDEO)) { | |
926 { | |
927 base::AutoLock auto_lock(lock_); | |
928 renderer_.reset(); | |
929 } | |
930 OnError(PIPELINE_ERROR_COULD_NOT_RENDER); | |
931 return; | |
932 } | |
933 | |
934 if (cdm_context_) | |
935 renderer_->SetCdm(cdm_context_, base::Bind(&IgnoreCdmAttached)); | |
936 | |
937 renderer_->Initialize(demuxer_, this, done_cb); | |
938 } | |
939 | |
940 void PipelineImpl::ReportMetadata() { | |
941 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
942 | |
943 PipelineMetadata metadata; | |
944 metadata.timeline_offset = demuxer_->GetTimelineOffset(); | |
945 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); | |
946 if (stream) { | |
947 metadata.has_video = true; | |
948 metadata.natural_size = stream->video_decoder_config().natural_size(); | |
949 metadata.video_rotation = stream->video_rotation(); | |
950 } | |
951 if (demuxer_->GetStream(DemuxerStream::AUDIO)) { | |
952 metadata.has_audio = true; | |
953 } | |
954 | |
955 main_task_runner_->PostTask( | |
956 FROM_HERE, | |
957 base::Bind(&Pipeline::Client::OnMetadata, weak_client_, metadata)); | |
958 } | 1049 } |
959 | 1050 |
960 } // namespace media | 1051 } // namespace media |
OLD | NEW |