OLD | NEW |
| (Empty) |
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 #include <utility> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/bind_helpers.h" | |
12 #include "base/callback.h" | |
13 #include "base/callback_helpers.h" | |
14 #include "base/command_line.h" | |
15 #include "base/compiler_specific.h" | |
16 #include "base/location.h" | |
17 #include "base/metrics/histogram.h" | |
18 #include "base/single_thread_task_runner.h" | |
19 #include "base/stl_util.h" | |
20 #include "base/strings/string_number_conversions.h" | |
21 #include "base/strings/string_util.h" | |
22 #include "base/synchronization/condition_variable.h" | |
23 #include "media/base/media_log.h" | |
24 #include "media/base/media_switches.h" | |
25 #include "media/base/renderer.h" | |
26 #include "media/base/text_renderer.h" | |
27 #include "media/base/text_track_config.h" | |
28 #include "media/base/timestamp_constants.h" | |
29 #include "media/base/video_decoder_config.h" | |
30 | |
31 using base::TimeDelta; | |
32 | |
33 namespace media { | |
34 | |
35 Pipeline::Pipeline( | |
36 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, | |
37 MediaLog* media_log) | |
38 : task_runner_(task_runner), | |
39 media_log_(media_log), | |
40 running_(false), | |
41 did_loading_progress_(false), | |
42 volume_(1.0f), | |
43 playback_rate_(0.0), | |
44 status_(PIPELINE_OK), | |
45 state_(kCreated), | |
46 suspend_timestamp_(kNoTimestamp()), | |
47 renderer_ended_(false), | |
48 text_renderer_ended_(false), | |
49 demuxer_(NULL), | |
50 pending_cdm_context_(nullptr), | |
51 weak_factory_(this) { | |
52 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); | |
53 } | |
54 | |
55 Pipeline::~Pipeline() { | |
56 DCHECK(thread_checker_.CalledOnValidThread()) | |
57 << "Pipeline must be destroyed on same thread that created it"; | |
58 DCHECK(!running_) << "Stop() must complete before destroying object"; | |
59 DCHECK(stop_cb_.is_null()); | |
60 DCHECK(seek_cb_.is_null()); | |
61 } | |
62 | |
63 void Pipeline::Start(Demuxer* demuxer, | |
64 scoped_ptr<Renderer> renderer, | |
65 const base::Closure& ended_cb, | |
66 const PipelineStatusCB& error_cb, | |
67 const PipelineStatusCB& seek_cb, | |
68 const PipelineMetadataCB& metadata_cb, | |
69 const BufferingStateCB& buffering_state_cb, | |
70 const base::Closure& duration_change_cb, | |
71 const AddTextTrackCB& add_text_track_cb, | |
72 const base::Closure& waiting_for_decryption_key_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_ = std::move(renderer); | |
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 waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb; | |
93 | |
94 task_runner_->PostTask( | |
95 FROM_HERE, base::Bind(&Pipeline::StartTask, weak_factory_.GetWeakPtr())); | |
96 } | |
97 | |
98 void Pipeline::Stop(const base::Closure& stop_cb) { | |
99 DVLOG(2) << __FUNCTION__; | |
100 task_runner_->PostTask( | |
101 FROM_HERE, | |
102 base::Bind(&Pipeline::StopTask, weak_factory_.GetWeakPtr(), stop_cb)); | |
103 } | |
104 | |
105 void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { | |
106 base::AutoLock auto_lock(lock_); | |
107 if (!running_) { | |
108 DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek()."; | |
109 return; | |
110 } | |
111 | |
112 task_runner_->PostTask( | |
113 FROM_HERE, | |
114 base::Bind( | |
115 &Pipeline::SeekTask, weak_factory_.GetWeakPtr(), time, seek_cb)); | |
116 } | |
117 | |
118 bool Pipeline::IsRunning() const { | |
119 base::AutoLock auto_lock(lock_); | |
120 return running_; | |
121 } | |
122 | |
123 double Pipeline::GetPlaybackRate() const { | |
124 base::AutoLock auto_lock(lock_); | |
125 return playback_rate_; | |
126 } | |
127 | |
128 void Pipeline::SetPlaybackRate(double playback_rate) { | |
129 if (playback_rate < 0.0) | |
130 return; | |
131 | |
132 base::AutoLock auto_lock(lock_); | |
133 playback_rate_ = playback_rate; | |
134 if (running_) { | |
135 task_runner_->PostTask(FROM_HERE, | |
136 base::Bind(&Pipeline::PlaybackRateChangedTask, | |
137 weak_factory_.GetWeakPtr(), | |
138 playback_rate)); | |
139 } | |
140 } | |
141 | |
142 void Pipeline::Suspend(const PipelineStatusCB& suspend_cb) { | |
143 task_runner_->PostTask( | |
144 FROM_HERE, base::Bind(&Pipeline::SuspendTask, weak_factory_.GetWeakPtr(), | |
145 suspend_cb)); | |
146 } | |
147 | |
148 void Pipeline::Resume(scoped_ptr<Renderer> renderer, | |
149 base::TimeDelta timestamp, | |
150 const PipelineStatusCB& seek_cb) { | |
151 task_runner_->PostTask( | |
152 FROM_HERE, | |
153 base::Bind(&Pipeline::ResumeTask, weak_factory_.GetWeakPtr(), | |
154 base::Passed(std::move(renderer)), timestamp, seek_cb)); | |
155 } | |
156 | |
157 float Pipeline::GetVolume() const { | |
158 base::AutoLock auto_lock(lock_); | |
159 return volume_; | |
160 } | |
161 | |
162 void Pipeline::SetVolume(float volume) { | |
163 if (volume < 0.0f || volume > 1.0f) | |
164 return; | |
165 | |
166 base::AutoLock auto_lock(lock_); | |
167 volume_ = volume; | |
168 if (running_) { | |
169 task_runner_->PostTask( | |
170 FROM_HERE, | |
171 base::Bind( | |
172 &Pipeline::VolumeChangedTask, weak_factory_.GetWeakPtr(), volume)); | |
173 } | |
174 } | |
175 | |
176 TimeDelta Pipeline::GetMediaTime() const { | |
177 base::AutoLock auto_lock(lock_); | |
178 if (suspend_timestamp_ != kNoTimestamp()) | |
179 return suspend_timestamp_; | |
180 return renderer_ ? std::min(renderer_->GetMediaTime(), duration_) | |
181 : TimeDelta(); | |
182 } | |
183 | |
184 Ranges<TimeDelta> Pipeline::GetBufferedTimeRanges() const { | |
185 base::AutoLock auto_lock(lock_); | |
186 return buffered_time_ranges_; | |
187 } | |
188 | |
189 TimeDelta Pipeline::GetMediaDuration() const { | |
190 base::AutoLock auto_lock(lock_); | |
191 return duration_; | |
192 } | |
193 | |
194 bool Pipeline::DidLoadingProgress() { | |
195 base::AutoLock auto_lock(lock_); | |
196 bool ret = did_loading_progress_; | |
197 did_loading_progress_ = false; | |
198 return ret; | |
199 } | |
200 | |
201 PipelineStatistics Pipeline::GetStatistics() const { | |
202 base::AutoLock auto_lock(lock_); | |
203 return statistics_; | |
204 } | |
205 | |
206 void Pipeline::SetCdm(CdmContext* cdm_context, | |
207 const CdmAttachedCB& cdm_attached_cb) { | |
208 task_runner_->PostTask( | |
209 FROM_HERE, base::Bind(&Pipeline::SetCdmTask, weak_factory_.GetWeakPtr(), | |
210 cdm_context, cdm_attached_cb)); | |
211 } | |
212 | |
213 void Pipeline::SetErrorForTesting(PipelineStatus status) { | |
214 OnError(status); | |
215 } | |
216 | |
217 bool Pipeline::HasWeakPtrsForTesting() const { | |
218 DCHECK(task_runner_->BelongsToCurrentThread()); | |
219 return weak_factory_.HasWeakPtrs(); | |
220 } | |
221 | |
222 void Pipeline::SetState(State next_state) { | |
223 DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state); | |
224 | |
225 state_ = next_state; | |
226 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); | |
227 } | |
228 | |
229 #define RETURN_STRING(state) case state: return #state; | |
230 | |
231 const char* Pipeline::GetStateString(State state) { | |
232 switch (state) { | |
233 RETURN_STRING(kCreated); | |
234 RETURN_STRING(kInitDemuxer); | |
235 RETURN_STRING(kInitRenderer); | |
236 RETURN_STRING(kSeeking); | |
237 RETURN_STRING(kPlaying); | |
238 RETURN_STRING(kStopping); | |
239 RETURN_STRING(kStopped); | |
240 RETURN_STRING(kSuspending); | |
241 RETURN_STRING(kSuspended); | |
242 RETURN_STRING(kResuming); | |
243 } | |
244 NOTREACHED(); | |
245 return "INVALID"; | |
246 } | |
247 | |
248 #undef RETURN_STRING | |
249 | |
250 Pipeline::State Pipeline::GetNextState() const { | |
251 DCHECK(task_runner_->BelongsToCurrentThread()); | |
252 DCHECK(stop_cb_.is_null()) | |
253 << "State transitions don't happen when stopping"; | |
254 DCHECK_EQ(status_, PIPELINE_OK) | |
255 << "State transitions don't happen when there's an error: " << status_; | |
256 | |
257 switch (state_) { | |
258 case kCreated: | |
259 return kInitDemuxer; | |
260 | |
261 case kInitDemuxer: | |
262 return kInitRenderer; | |
263 | |
264 case kInitRenderer: | |
265 case kSeeking: | |
266 return kPlaying; | |
267 | |
268 case kSuspending: | |
269 return kSuspended; | |
270 | |
271 case kSuspended: | |
272 return kResuming; | |
273 | |
274 case kResuming: | |
275 return kPlaying; | |
276 | |
277 case kPlaying: | |
278 case kStopping: | |
279 case kStopped: | |
280 break; | |
281 } | |
282 NOTREACHED() << "State has no transition: " << state_; | |
283 return state_; | |
284 } | |
285 | |
286 void Pipeline::OnDemuxerError(PipelineStatus error) { | |
287 task_runner_->PostTask(FROM_HERE, | |
288 base::Bind(&Pipeline::ErrorChangedTask, | |
289 weak_factory_.GetWeakPtr(), | |
290 error)); | |
291 } | |
292 | |
293 void Pipeline::AddTextStream(DemuxerStream* text_stream, | |
294 const TextTrackConfig& config) { | |
295 task_runner_->PostTask(FROM_HERE, | |
296 base::Bind(&Pipeline::AddTextStreamTask, | |
297 weak_factory_.GetWeakPtr(), | |
298 text_stream, | |
299 config)); | |
300 } | |
301 | |
302 void Pipeline::RemoveTextStream(DemuxerStream* text_stream) { | |
303 task_runner_->PostTask(FROM_HERE, | |
304 base::Bind(&Pipeline::RemoveTextStreamTask, | |
305 weak_factory_.GetWeakPtr(), | |
306 text_stream)); | |
307 } | |
308 | |
309 void Pipeline::OnError(PipelineStatus error) { | |
310 DCHECK(task_runner_->BelongsToCurrentThread()); | |
311 DCHECK(IsRunning()); | |
312 DCHECK_NE(PIPELINE_OK, error); | |
313 VLOG(1) << "Media pipeline error: " << error; | |
314 | |
315 task_runner_->PostTask(FROM_HERE, base::Bind( | |
316 &Pipeline::ErrorChangedTask, weak_factory_.GetWeakPtr(), error)); | |
317 } | |
318 | |
319 void Pipeline::SetDuration(TimeDelta duration) { | |
320 DCHECK(IsRunning()); | |
321 media_log_->AddEvent( | |
322 media_log_->CreateTimeEvent( | |
323 MediaLogEvent::DURATION_SET, "duration", duration)); | |
324 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); | |
325 | |
326 base::AutoLock auto_lock(lock_); | |
327 duration_ = duration; | |
328 if (!duration_change_cb_.is_null()) | |
329 duration_change_cb_.Run(); | |
330 } | |
331 | |
332 void Pipeline::StateTransitionTask(PipelineStatus status) { | |
333 DCHECK(task_runner_->BelongsToCurrentThread()); | |
334 | |
335 // No-op any state transitions if we're stopping. | |
336 if (state_ == kStopping || state_ == kStopped) | |
337 return; | |
338 | |
339 // Preserve existing abnormal status, otherwise update based on the result of | |
340 // the previous operation. | |
341 status_ = (status_ != PIPELINE_OK ? status_ : status); | |
342 | |
343 if (status_ != PIPELINE_OK) { | |
344 ErrorChangedTask(status_); | |
345 return; | |
346 } | |
347 | |
348 // Guard against accidentally clearing |pending_callbacks_| for states that | |
349 // use it as well as states that should not be using it. | |
350 DCHECK_EQ(pending_callbacks_.get() != NULL, | |
351 state_ == kSeeking || state_ == kSuspending || state_ == kResuming); | |
352 | |
353 pending_callbacks_.reset(); | |
354 | |
355 PipelineStatusCB done_cb = | |
356 base::Bind(&Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr()); | |
357 | |
358 // Switch states, performing any entrance actions for the new state as well. | |
359 SetState(GetNextState()); | |
360 switch (state_) { | |
361 case kInitDemuxer: | |
362 return InitializeDemuxer(done_cb); | |
363 | |
364 case kInitRenderer: | |
365 // When the state_ transfers to kInitRenderer, it means the demuxer has | |
366 // finished parsing the init info. It should call ReportMetadata in case | |
367 // meeting 'decode' error when passing media segment but WebMediaPlayer's | |
368 // ready_state_ is still ReadyStateHaveNothing. In that case, it will | |
369 // treat it as NetworkStateFormatError not NetworkStateDecodeError. | |
370 ReportMetadata(); | |
371 start_timestamp_ = demuxer_->GetStartTime(); | |
372 | |
373 return InitializeRenderer(done_cb); | |
374 | |
375 case kPlaying: | |
376 DCHECK(start_timestamp_ >= base::TimeDelta()); | |
377 renderer_->StartPlayingFrom(start_timestamp_); | |
378 { | |
379 base::AutoLock auto_lock(lock_); | |
380 suspend_timestamp_ = kNoTimestamp(); | |
381 } | |
382 | |
383 if (text_renderer_) | |
384 text_renderer_->StartPlaying(); | |
385 | |
386 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); | |
387 | |
388 PlaybackRateChangedTask(GetPlaybackRate()); | |
389 VolumeChangedTask(GetVolume()); | |
390 return; | |
391 | |
392 case kSuspended: | |
393 renderer_.reset(); | |
394 base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK); | |
395 return; | |
396 | |
397 case kStopping: | |
398 case kStopped: | |
399 case kCreated: | |
400 case kSeeking: | |
401 case kSuspending: | |
402 case kResuming: | |
403 NOTREACHED() << "State has no transition: " << state_; | |
404 return; | |
405 } | |
406 } | |
407 | |
408 // Note that the usage of base::Unretained() with the renderers is considered | |
409 // safe as they are owned by |pending_callbacks_| and share the same lifetime. | |
410 // | |
411 // That being said, deleting the renderers while keeping |pending_callbacks_| | |
412 // running on the media thread would result in crashes. | |
413 void Pipeline::DoSeek(TimeDelta seek_timestamp, | |
414 const PipelineStatusCB& done_cb) { | |
415 DCHECK(task_runner_->BelongsToCurrentThread()); | |
416 DCHECK(!pending_callbacks_.get()); | |
417 DCHECK_EQ(state_, kSeeking); | |
418 SerialRunner::Queue bound_fns; | |
419 | |
420 // Pause. | |
421 if (text_renderer_) { | |
422 bound_fns.Push(base::Bind( | |
423 &TextRenderer::Pause, base::Unretained(text_renderer_.get()))); | |
424 } | |
425 | |
426 // Flush. | |
427 DCHECK(renderer_); | |
428 bound_fns.Push( | |
429 base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); | |
430 | |
431 if (text_renderer_) { | |
432 bound_fns.Push(base::Bind( | |
433 &TextRenderer::Flush, base::Unretained(text_renderer_.get()))); | |
434 } | |
435 | |
436 // Seek demuxer. | |
437 bound_fns.Push(base::Bind( | |
438 &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); | |
439 | |
440 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | |
441 } | |
442 | |
443 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { | |
444 DVLOG(2) << __FUNCTION__; | |
445 DCHECK(task_runner_->BelongsToCurrentThread()); | |
446 DCHECK(!pending_callbacks_.get()); | |
447 | |
448 // TODO(scherkus): Enforce that Renderer is only called on a single thread, | |
449 // even for accessing media time http://crbug.com/370634 | |
450 scoped_ptr<Renderer> renderer; | |
451 { | |
452 base::AutoLock auto_lock(lock_); | |
453 renderer.swap(renderer_); | |
454 } | |
455 renderer.reset(); | |
456 text_renderer_.reset(); | |
457 | |
458 if (demuxer_) { | |
459 demuxer_->Stop(); | |
460 demuxer_ = NULL; | |
461 } | |
462 | |
463 task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK)); | |
464 } | |
465 | |
466 void Pipeline::OnStopCompleted(PipelineStatus status) { | |
467 DVLOG(2) << __FUNCTION__; | |
468 DCHECK(task_runner_->BelongsToCurrentThread()); | |
469 DCHECK_EQ(state_, kStopping); | |
470 DCHECK(!renderer_); | |
471 DCHECK(!text_renderer_); | |
472 | |
473 { | |
474 base::AutoLock auto_lock(lock_); | |
475 running_ = false; | |
476 } | |
477 | |
478 SetState(kStopped); | |
479 demuxer_ = NULL; | |
480 | |
481 // If we stop during initialization/seeking/suspending we don't want to leave | |
482 // outstanding callbacks around. | |
483 if (!seek_cb_.is_null()) { | |
484 base::ResetAndReturn(&seek_cb_).Run(status_); | |
485 error_cb_.Reset(); | |
486 } | |
487 if (!suspend_cb_.is_null()) { | |
488 base::ResetAndReturn(&suspend_cb_).Run(status_); | |
489 error_cb_.Reset(); | |
490 } | |
491 if (!stop_cb_.is_null()) { | |
492 error_cb_.Reset(); | |
493 | |
494 // Invalid all weak pointers so it's safe to destroy |this| on the render | |
495 // main thread. | |
496 weak_factory_.InvalidateWeakPtrs(); | |
497 | |
498 base::ResetAndReturn(&stop_cb_).Run(); | |
499 | |
500 // NOTE: pipeline may be deleted at this point in time as a result of | |
501 // executing |stop_cb_|. | |
502 return; | |
503 } | |
504 if (!error_cb_.is_null()) { | |
505 DCHECK_NE(status_, PIPELINE_OK); | |
506 base::ResetAndReturn(&error_cb_).Run(status_); | |
507 } | |
508 } | |
509 | |
510 void Pipeline::OnBufferedTimeRangesChanged( | |
511 const Ranges<base::TimeDelta>& ranges) { | |
512 DCHECK(IsRunning()); | |
513 base::AutoLock auto_lock(lock_); | |
514 buffered_time_ranges_ = ranges; | |
515 did_loading_progress_ = true; | |
516 } | |
517 | |
518 // Called from any thread. | |
519 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats_delta) { | |
520 base::AutoLock auto_lock(lock_); | |
521 statistics_.audio_bytes_decoded += stats_delta.audio_bytes_decoded; | |
522 statistics_.video_bytes_decoded += stats_delta.video_bytes_decoded; | |
523 statistics_.video_frames_decoded += stats_delta.video_frames_decoded; | |
524 statistics_.video_frames_dropped += stats_delta.video_frames_dropped; | |
525 statistics_.audio_memory_usage += stats_delta.audio_memory_usage; | |
526 statistics_.video_memory_usage += stats_delta.video_memory_usage; | |
527 } | |
528 | |
529 void Pipeline::StartTask() { | |
530 DCHECK(task_runner_->BelongsToCurrentThread()); | |
531 | |
532 CHECK_EQ(kCreated, state_) | |
533 << "Media pipeline cannot be started more than once"; | |
534 | |
535 text_renderer_ = CreateTextRenderer(); | |
536 if (text_renderer_) { | |
537 text_renderer_->Initialize( | |
538 base::Bind(&Pipeline::OnTextRendererEnded, weak_factory_.GetWeakPtr())); | |
539 } | |
540 | |
541 // Set CDM early to avoid unnecessary delay in Renderer::Initialize(). | |
542 if (pending_cdm_context_) { | |
543 renderer_->SetCdm(pending_cdm_context_, base::Bind(&IgnoreCdmAttached)); | |
544 pending_cdm_context_ = nullptr; | |
545 } | |
546 | |
547 StateTransitionTask(PIPELINE_OK); | |
548 } | |
549 | |
550 void Pipeline::StopTask(const base::Closure& stop_cb) { | |
551 DCHECK(task_runner_->BelongsToCurrentThread()); | |
552 DCHECK(stop_cb_.is_null()); | |
553 | |
554 if (state_ == kStopped) { | |
555 // Invalid all weak pointers so it's safe to destroy |this| on the render | |
556 // main thread. | |
557 weak_factory_.InvalidateWeakPtrs(); | |
558 | |
559 // NOTE: pipeline may be deleted at this point in time as a result of | |
560 // executing |stop_cb|. | |
561 stop_cb.Run(); | |
562 | |
563 return; | |
564 } | |
565 | |
566 stop_cb_ = stop_cb; | |
567 | |
568 // We may already be stopping due to a runtime error. | |
569 if (state_ == kStopping) | |
570 return; | |
571 | |
572 // Do not report statistics if the pipeline is not fully initialized. | |
573 if (state_ == kSeeking || state_ == kPlaying || state_ == kSuspending || | |
574 state_ == kSuspended || state_ == kResuming) { | |
575 PipelineStatistics stats = GetStatistics(); | |
576 if (stats.video_frames_decoded > 0) { | |
577 UMA_HISTOGRAM_COUNTS("Media.DroppedFrameCount", | |
578 stats.video_frames_dropped); | |
579 } | |
580 } | |
581 | |
582 SetState(kStopping); | |
583 pending_callbacks_.reset(); | |
584 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); | |
585 } | |
586 | |
587 void Pipeline::ErrorChangedTask(PipelineStatus error) { | |
588 DCHECK(task_runner_->BelongsToCurrentThread()); | |
589 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; | |
590 | |
591 media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error)); | |
592 | |
593 if (state_ == kStopping || state_ == kStopped) | |
594 return; | |
595 | |
596 SetState(kStopping); | |
597 pending_callbacks_.reset(); | |
598 status_ = error; | |
599 | |
600 DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); | |
601 } | |
602 | |
603 void Pipeline::PlaybackRateChangedTask(double playback_rate) { | |
604 DCHECK(task_runner_->BelongsToCurrentThread()); | |
605 | |
606 // Playback rate changes are only carried out while playing. | |
607 if (state_ != kPlaying) | |
608 return; | |
609 | |
610 renderer_->SetPlaybackRate(playback_rate); | |
611 } | |
612 | |
613 void Pipeline::VolumeChangedTask(float volume) { | |
614 DCHECK(task_runner_->BelongsToCurrentThread()); | |
615 | |
616 // Volume changes are only carried out while playing. | |
617 if (state_ != kPlaying) | |
618 return; | |
619 | |
620 renderer_->SetVolume(volume); | |
621 } | |
622 | |
623 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { | |
624 DCHECK(task_runner_->BelongsToCurrentThread()); | |
625 DCHECK(stop_cb_.is_null()); | |
626 | |
627 // Suppress seeking if we're not fully started. | |
628 if (state_ != kPlaying) { | |
629 DCHECK(state_ == kStopping || state_ == kStopped) | |
630 << "Receive seek in unexpected state: " << state_; | |
631 seek_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
632 return; | |
633 } | |
634 | |
635 DCHECK(seek_cb_.is_null()); | |
636 | |
637 const base::TimeDelta seek_timestamp = | |
638 std::max(time, demuxer_->GetStartTime()); | |
639 | |
640 SetState(kSeeking); | |
641 seek_cb_ = seek_cb; | |
642 renderer_ended_ = false; | |
643 text_renderer_ended_ = false; | |
644 start_timestamp_ = seek_timestamp; | |
645 | |
646 DoSeek(seek_timestamp, base::Bind(&Pipeline::StateTransitionTask, | |
647 weak_factory_.GetWeakPtr())); | |
648 } | |
649 | |
650 void Pipeline::SuspendTask(const PipelineStatusCB& suspend_cb) { | |
651 DCHECK(task_runner_->BelongsToCurrentThread()); | |
652 | |
653 // Suppress suspending if we're not playing. | |
654 if (state_ != kPlaying) { | |
655 DCHECK(state_ == kStopping || state_ == kStopped) | |
656 << "Receive suspend in unexpected state: " << state_; | |
657 suspend_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
658 return; | |
659 } | |
660 DCHECK(renderer_); | |
661 DCHECK(!pending_callbacks_.get()); | |
662 | |
663 SetState(kSuspending); | |
664 suspend_cb_ = suspend_cb; | |
665 | |
666 // Freeze playback and record the media time before flushing. (Flushing clears | |
667 // the value.) | |
668 renderer_->SetPlaybackRate(0.0); | |
669 { | |
670 base::AutoLock auto_lock(lock_); | |
671 suspend_timestamp_ = renderer_->GetMediaTime(); | |
672 DCHECK(suspend_timestamp_ != kNoTimestamp()); | |
673 } | |
674 | |
675 // Queue the asynchronous actions required to stop playback. (Matches setup in | |
676 // DoSeek().) | |
677 // TODO(sandersd): Share implementation with DoSeek(). | |
678 SerialRunner::Queue fns; | |
679 | |
680 if (text_renderer_) { | |
681 fns.Push(base::Bind(&TextRenderer::Pause, | |
682 base::Unretained(text_renderer_.get()))); | |
683 } | |
684 | |
685 fns.Push(base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); | |
686 | |
687 if (text_renderer_) { | |
688 fns.Push(base::Bind(&TextRenderer::Flush, | |
689 base::Unretained(text_renderer_.get()))); | |
690 } | |
691 | |
692 pending_callbacks_ = SerialRunner::Run( | |
693 fns, | |
694 base::Bind(&Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr())); | |
695 } | |
696 | |
697 void Pipeline::ResumeTask(scoped_ptr<Renderer> renderer, | |
698 base::TimeDelta timestamp, | |
699 const PipelineStatusCB& seek_cb) { | |
700 DCHECK(task_runner_->BelongsToCurrentThread()); | |
701 | |
702 // Suppress resuming if we're not suspended. | |
703 if (state_ != kSuspended) { | |
704 DCHECK(state_ == kStopping || state_ == kStopped) | |
705 << "Receive resume in unexpected state: " << state_; | |
706 seek_cb.Run(PIPELINE_ERROR_INVALID_STATE); | |
707 return; | |
708 } | |
709 DCHECK(!renderer_); | |
710 DCHECK(!pending_callbacks_.get()); | |
711 | |
712 SetState(kResuming); | |
713 renderer_ = std::move(renderer); | |
714 | |
715 // Set up for a seek. (Matches setup in SeekTask().) | |
716 // TODO(sandersd): Share implementation with SeekTask(). | |
717 seek_cb_ = seek_cb; | |
718 renderer_ended_ = false; | |
719 text_renderer_ended_ = false; | |
720 start_timestamp_ = std::max(timestamp, demuxer_->GetStartTime()); | |
721 | |
722 // Queue the asynchronous actions required to start playback. Unlike DoSeek(), | |
723 // we need to initialize the renderer ourselves (we don't want to enter state | |
724 // kInitDemuxer, and even if we did the current code would seek to the start | |
725 // instead of |timestamp|). | |
726 SerialRunner::Queue fns; | |
727 base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr(); | |
728 | |
729 fns.Push( | |
730 base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_)); | |
731 | |
732 fns.Push(base::Bind(&Pipeline::InitializeRenderer, weak_this)); | |
733 | |
734 pending_callbacks_ = SerialRunner::Run( | |
735 fns, base::Bind(&Pipeline::StateTransitionTask, weak_this)); | |
736 } | |
737 | |
738 void Pipeline::SetCdmTask(CdmContext* cdm_context, | |
739 const CdmAttachedCB& cdm_attached_cb) { | |
740 base::AutoLock auto_lock(lock_); | |
741 if (!renderer_) { | |
742 pending_cdm_context_ = cdm_context; | |
743 cdm_attached_cb.Run(true); | |
744 return; | |
745 } | |
746 | |
747 renderer_->SetCdm(cdm_context, cdm_attached_cb); | |
748 } | |
749 | |
750 void Pipeline::OnRendererEnded() { | |
751 DCHECK(task_runner_->BelongsToCurrentThread()); | |
752 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); | |
753 | |
754 if (state_ != kPlaying) | |
755 return; | |
756 | |
757 DCHECK(!renderer_ended_); | |
758 renderer_ended_ = true; | |
759 | |
760 RunEndedCallbackIfNeeded(); | |
761 } | |
762 | |
763 void Pipeline::OnTextRendererEnded() { | |
764 DCHECK(task_runner_->BelongsToCurrentThread()); | |
765 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); | |
766 | |
767 if (state_ != kPlaying) | |
768 return; | |
769 | |
770 DCHECK(!text_renderer_ended_); | |
771 text_renderer_ended_ = true; | |
772 | |
773 RunEndedCallbackIfNeeded(); | |
774 } | |
775 | |
776 void Pipeline::RunEndedCallbackIfNeeded() { | |
777 DCHECK(task_runner_->BelongsToCurrentThread()); | |
778 | |
779 if (renderer_ && !renderer_ended_) | |
780 return; | |
781 | |
782 if (text_renderer_ && text_renderer_->HasTracks() && !text_renderer_ended_) | |
783 return; | |
784 | |
785 DCHECK_EQ(status_, PIPELINE_OK); | |
786 ended_cb_.Run(); | |
787 } | |
788 | |
789 scoped_ptr<TextRenderer> Pipeline::CreateTextRenderer() { | |
790 DCHECK(task_runner_->BelongsToCurrentThread()); | |
791 | |
792 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
793 if (!cmd_line->HasSwitch(switches::kEnableInbandTextTracks)) | |
794 return scoped_ptr<media::TextRenderer>(); | |
795 | |
796 return scoped_ptr<media::TextRenderer>(new media::TextRenderer( | |
797 task_runner_, | |
798 base::Bind(&Pipeline::OnAddTextTrack, weak_factory_.GetWeakPtr()))); | |
799 } | |
800 | |
801 void Pipeline::AddTextStreamTask(DemuxerStream* text_stream, | |
802 const TextTrackConfig& config) { | |
803 DCHECK(task_runner_->BelongsToCurrentThread()); | |
804 // TODO(matthewjheaney): fix up text_ended_ when text stream | |
805 // is added (http://crbug.com/321446). | |
806 if (text_renderer_) | |
807 text_renderer_->AddTextStream(text_stream, config); | |
808 } | |
809 | |
810 void Pipeline::RemoveTextStreamTask(DemuxerStream* text_stream) { | |
811 DCHECK(task_runner_->BelongsToCurrentThread()); | |
812 if (text_renderer_) | |
813 text_renderer_->RemoveTextStream(text_stream); | |
814 } | |
815 | |
816 void Pipeline::OnAddTextTrack(const TextTrackConfig& config, | |
817 const AddTextTrackDoneCB& done_cb) { | |
818 DCHECK(task_runner_->BelongsToCurrentThread()); | |
819 add_text_track_cb_.Run(config, done_cb); | |
820 } | |
821 | |
822 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) { | |
823 DCHECK(task_runner_->BelongsToCurrentThread()); | |
824 demuxer_->Initialize(this, done_cb, !!text_renderer_); | |
825 } | |
826 | |
827 void Pipeline::InitializeRenderer(const PipelineStatusCB& done_cb) { | |
828 DCHECK(task_runner_->BelongsToCurrentThread()); | |
829 | |
830 if (!demuxer_->GetStream(DemuxerStream::AUDIO) && | |
831 !demuxer_->GetStream(DemuxerStream::VIDEO)) { | |
832 { | |
833 base::AutoLock auto_lock(lock_); | |
834 renderer_.reset(); | |
835 } | |
836 OnError(PIPELINE_ERROR_COULD_NOT_RENDER); | |
837 return; | |
838 } | |
839 | |
840 base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr(); | |
841 renderer_->Initialize( | |
842 demuxer_, | |
843 done_cb, | |
844 base::Bind(&Pipeline::OnUpdateStatistics, weak_this), | |
845 base::Bind(&Pipeline::BufferingStateChanged, weak_this), | |
846 base::Bind(&Pipeline::OnRendererEnded, weak_this), | |
847 base::Bind(&Pipeline::OnError, weak_this), | |
848 waiting_for_decryption_key_cb_); | |
849 } | |
850 | |
851 void Pipeline::ReportMetadata() { | |
852 DCHECK(task_runner_->BelongsToCurrentThread()); | |
853 PipelineMetadata metadata; | |
854 metadata.timeline_offset = demuxer_->GetTimelineOffset(); | |
855 DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); | |
856 if (stream) { | |
857 metadata.has_video = true; | |
858 metadata.natural_size = stream->video_decoder_config().natural_size(); | |
859 metadata.video_rotation = stream->video_rotation(); | |
860 } | |
861 if (demuxer_->GetStream(DemuxerStream::AUDIO)) { | |
862 metadata.has_audio = true; | |
863 } | |
864 metadata_cb_.Run(metadata); | |
865 } | |
866 | |
867 void Pipeline::BufferingStateChanged(BufferingState new_buffering_state) { | |
868 DVLOG(1) << __FUNCTION__ << "(" << new_buffering_state << ") "; | |
869 DCHECK(task_runner_->BelongsToCurrentThread()); | |
870 buffering_state_cb_.Run(new_buffering_state); | |
871 } | |
872 | |
873 } // namespace media | |
OLD | NEW |