1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/pipeline_controller.h"
8 #include "media/base/demuxer.h"
12 PipelineController::PipelineController(std::unique_ptr<Pipeline> pipeline,
14 SuspendedCB suspended_cb,
15 BeforeResumeCB before_resume_cb,
17 PipelineStatusCB error_cb)
18 : pipeline_(std::move(pipeline)),
19 seeked_cb_(std::move(seeked_cb)),
20 suspended_cb_(std::move(suspended_cb)),
21 before_resume_cb_(std::move(before_resume_cb)),
22 resumed_cb_(std::move(resumed_cb)),
23 error_cb_(std::move(error_cb)) {
26 DCHECK(suspended_cb_);
27 DCHECK(before_resume_cb_);
32 PipelineController::~PipelineController() {
33 DCHECK(thread_checker_.CalledOnValidThread());
36 void PipelineController::Start(Pipeline::StartType start_type,
38 Pipeline::Client* client,
41 DCHECK(thread_checker_.CalledOnValidThread());
42 DCHECK_EQ(state_, State::STOPPED);
45 // Once the pipeline is started, we want to call the seeked callback but
46 // without a time update.
47 pending_startup_ = true;
48 pending_seeked_cb_ = true;
49 state_ = State::STARTING;
52 is_streaming_ = is_streaming;
53 is_static_ = is_static;
54 pipeline_->Start(start_type, demuxer, client,
55 base::BindOnce(&PipelineController::OnPipelineStatus,
56 weak_factory_.GetWeakPtr(),
57 start_type == Pipeline::StartType::kNormal
59 : State::PLAYING_OR_SUSPENDED));
62 void PipelineController::Seek(base::TimeDelta time, bool time_updated) {
63 DCHECK(thread_checker_.CalledOnValidThread());
65 // It would be slightly more clear to set this in Dispatch(), but we want to
66 // be sure it gets updated even if the seek is elided.
68 pending_time_updated_ = true;
69 pending_seeked_cb_ = true;
70 pending_seek_except_start_ = true;
72 // If we are already seeking to |time|, and the media is static, elide the
74 if ((state_ == State::SEEKING || state_ == State::RESUMING) &&
75 seek_time_ == time && is_static_) {
76 pending_seek_ = false;
80 pending_seek_time_ = time;
85 // TODO(sandersd): It may be easier to use this interface if |suspended_cb_| is
86 // executed when Suspend() is called while already suspended.
87 void PipelineController::Suspend() {
88 DCHECK(thread_checker_.CalledOnValidThread());
89 pending_resume_ = false;
90 if (state_ != State::SUSPENDING && state_ != State::SUSPENDED) {
91 pending_suspend_ = true;
96 void PipelineController::Resume() {
97 DCHECK(thread_checker_.CalledOnValidThread());
98 pending_suspend_ = false;
99 // TODO(sandersd) fix resume during suspended start.
100 if (state_ == State::SUSPENDING || state_ == State::SUSPENDED ||
101 (state_ == State::SWITCHING_TRACKS &&
102 previous_track_change_state_ == State::SUSPENDED)) {
103 pending_resume_ = true;
109 void PipelineController::OnDecoderStateLost() {
110 DCHECK(thread_checker_.CalledOnValidThread());
112 // Note: |time_updated| and |pending_seeked_cb_| are both false.
113 pending_seek_except_start_ = true;
115 // If we are already seeking or resuming, or if there's already a seek
116 // pending,elide the seek. This is okay for decoder state lost since it just
117 // needs one seek to recover (the decoder is reset and the next decode starts
118 // from a key frame).
120 // Note on potential race condition: When the seek is elided, it's possible
121 // that the decoder state loss happens before or after the previous seek
122 // (decoder Reset()):
123 // 1. Decoder state loss happens before Decoder::Reset() during the previous
124 // seek. In this case we are fine since we just need a Reset().
125 // 2. Decoder state loss happens after Decoder::Reset() during a previous
127 // 2.1 If state loss happens before any Decode() we are still fine, since the
128 // decoder is in a clean state.
129 // 2.2 If state loss happens after a Decode(), then here we should not be in
130 // the SEEKING state.
131 if (state_ == State::SEEKING || state_ == State::RESUMING || pending_seek_)
134 // Force a seek to the current time.
135 pending_seek_time_ = pipeline_->GetMediaTime();
136 pending_seek_ = true;
141 bool PipelineController::IsStable() {
142 DCHECK(thread_checker_.CalledOnValidThread());
143 return state_ == State::PLAYING;
146 bool PipelineController::IsPendingSeek() {
147 DCHECK(thread_checker_.CalledOnValidThread());
148 return pending_seek_except_start_;
151 bool PipelineController::IsSuspended() {
152 DCHECK(thread_checker_.CalledOnValidThread());
153 return (pending_suspend_ || state_ == State::SUSPENDING ||
154 state_ == State::SUSPENDED) &&
158 bool PipelineController::IsPipelineSuspended() {
159 DCHECK(thread_checker_.CalledOnValidThread());
160 return state_ == State::SUSPENDED;
163 void PipelineController::OnPipelineStatus(State expected_state,
164 PipelineStatus pipeline_status) {
165 DCHECK(thread_checker_.CalledOnValidThread());
167 if (pipeline_status != PIPELINE_OK) {
168 error_cb_.Run(pipeline_status);
172 State old_state = state_;
173 state_ = expected_state;
175 // Resolve ambiguity of the current state if we may have suspended in startup.
176 if (state_ == State::PLAYING_OR_SUSPENDED) {
177 waiting_for_seek_ = false;
178 state_ = pipeline_->IsSuspended() ? State::SUSPENDED : State::PLAYING;
180 // It's possible for a Suspend() call to come in during startup. If we've
181 // completed a suspended startup, we should clear that now.
182 if (state_ == State::SUSPENDED)
183 pending_suspend_ = false;
186 if (state_ == State::PLAYING) {
187 // Start(), Seek(), or Resume() completed; we can be sure that
188 // |demuxer_| got the seek it was waiting for.
189 waiting_for_seek_ = false;
191 // TODO(avayvod): Remove resumed callback after https://crbug.com/678374 is
193 if (old_state == State::RESUMING) {
194 DCHECK(!pipeline_->IsSuspended());
195 DCHECK(!pending_resume_);
201 if (state_ == State::SUSPENDED) {
202 DCHECK(pipeline_->IsSuspended());
203 DCHECK(!pending_suspend_);
205 // Warning: possibly reentrant. The state may change inside this callback.
206 // It must be safe to call Dispatch() twice in a row here.
213 // Note: Dispatch() may be called re-entrantly (by callbacks internally) or
214 // twice in a row (by OnPipelineStatus()).
215 void PipelineController::Dispatch() {
216 DCHECK(thread_checker_.CalledOnValidThread());
218 // Suspend/resume transitions take priority because seeks before a suspend
219 // are wasted, and seeks after can be merged into the resume operation.
220 if (pending_suspend_ && state_ == State::PLAYING) {
221 pending_suspend_ = false;
222 state_ = State::SUSPENDING;
223 pipeline_->Suspend(base::BindOnce(&PipelineController::OnPipelineStatus,
224 weak_factory_.GetWeakPtr(),
229 // In additional to the standard |pending_resume_| case, if we completed a
230 // suspended startup, but a Seek() came in, we need to resume the pipeline to
231 // complete the seek before calling |seeked_cb_|.
232 if ((pending_resume_ || (pending_startup_ && pending_seek_)) &&
233 state_ == State::SUSPENDED) {
234 // If there is a pending seek, resume to that time instead...
236 seek_time_ = pending_seek_time_;
237 pending_seek_ = false;
239 seek_time_ = pipeline_->GetMediaTime();
242 // ...unless the media is streaming, in which case we resume at the start
243 // because seeking doesn't work well.
244 if (is_streaming_ && !seek_time_.is_zero()) {
245 seek_time_ = base::TimeDelta();
247 // In this case we want to make sure that the controls get updated
248 // immediately, so we don't try to hide the seek.
249 pending_time_updated_ = true;
252 // Tell |demuxer_| to expect our resume.
253 DCHECK(!waiting_for_seek_);
254 waiting_for_seek_ = true;
255 demuxer_->StartWaitingForSeek(seek_time_);
257 pending_resume_ = false;
258 state_ = State::RESUMING;
259 before_resume_cb_.Run();
261 seek_time_, base::BindOnce(&PipelineController::OnPipelineStatus,
262 weak_factory_.GetWeakPtr(), State::PLAYING));
266 // If we have pending operations, and a seek is ongoing, abort it.
267 if ((pending_seek_ || pending_suspend_ || pending_audio_track_change_ ||
268 pending_video_track_change_) &&
270 // If there is no pending seek, return the current seek to pending status.
271 if (!pending_seek_) {
272 pending_seek_time_ = seek_time_;
273 pending_seek_ = true;
276 // CancelPendingSeek() may be reentrant, so update state first and return
278 waiting_for_seek_ = false;
279 demuxer_->CancelPendingSeek(pending_seek_time_);
283 // We can only switch tracks if we are not in a transitioning state already.
284 if ((pending_audio_track_change_ || pending_video_track_change_) &&
285 (state_ == State::PLAYING || state_ == State::SUSPENDED)) {
286 previous_track_change_state_ = state_;
287 state_ = State::SWITCHING_TRACKS;
289 // Attempt to do a track change _before_ attempting a seek operation,
290 // otherwise the seek will apply to the old tracks instead of the new
291 // one(s). Also attempt audio before video.
292 if (pending_audio_track_change_) {
293 pending_audio_track_change_ = false;
294 pipeline_->OnEnabledAudioTracksChanged(
295 pending_audio_track_change_ids_,
296 base::BindOnce(&PipelineController::OnTrackChangeComplete,
297 weak_factory_.GetWeakPtr()));
301 if (pending_video_track_change_) {
302 pending_video_track_change_ = false;
303 pipeline_->OnSelectedVideoTrackChanged(
304 pending_video_track_change_id_,
305 base::BindOnce(&PipelineController::OnTrackChangeComplete,
306 weak_factory_.GetWeakPtr()));
312 if (pending_seek_ && state_ == State::PLAYING) {
313 seek_time_ = pending_seek_time_;
315 // Tell |demuxer_| to expect our seek.
316 DCHECK(!waiting_for_seek_);
317 waiting_for_seek_ = true;
318 demuxer_->StartWaitingForSeek(seek_time_);
320 pending_seek_ = false;
321 state_ = State::SEEKING;
322 pipeline_->Seek(seek_time_,
323 base::BindOnce(&PipelineController::OnPipelineStatus,
324 weak_factory_.GetWeakPtr(), State::PLAYING));
328 // If |state_| is PLAYING and we didn't trigger an operation above then we
329 // are in a stable state. If there is a seeked callback pending, emit it.
331 // We also need to emit it if we completed suspended startup.
332 if (pending_seeked_cb_ &&
333 (state_ == State::PLAYING ||
334 (state_ == State::SUSPENDED && pending_startup_))) {
335 // |seeked_cb_| may be reentrant, so update state first and return
337 pending_startup_ = false;
338 pending_seeked_cb_ = false;
339 pending_seek_except_start_ = false;
340 bool was_pending_time_updated = pending_time_updated_;
341 pending_time_updated_ = false;
342 seeked_cb_.Run(was_pending_time_updated);
347 void PipelineController::Stop() {
348 if (state_ == State::STOPPED)
352 waiting_for_seek_ = false;
353 pending_seeked_cb_ = false;
354 pending_seek_except_start_ = false;
355 pending_time_updated_ = false;
356 pending_seek_ = false;
357 pending_suspend_ = false;
358 pending_resume_ = false;
359 pending_audio_track_change_ = false;
360 pending_video_track_change_ = false;
361 state_ = State::STOPPED;
366 bool PipelineController::IsPipelineRunning() const {
367 return pipeline_->IsRunning();
370 double PipelineController::GetPlaybackRate() const {
371 return pipeline_->GetPlaybackRate();
374 void PipelineController::SetPlaybackRate(double playback_rate) {
375 pipeline_->SetPlaybackRate(playback_rate);
378 float PipelineController::GetVolume() const {
379 return pipeline_->GetVolume();
382 void PipelineController::SetVolume(float volume) {
383 pipeline_->SetVolume(volume);
386 void PipelineController::SetLatencyHint(
387 absl::optional<base::TimeDelta> latency_hint) {
388 DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
389 pipeline_->SetLatencyHint(latency_hint);
392 void PipelineController::SetPreservesPitch(bool preserves_pitch) {
393 pipeline_->SetPreservesPitch(preserves_pitch);
396 void PipelineController::SetWasPlayedWithUserActivation(
397 bool was_played_with_user_activation) {
398 pipeline_->SetWasPlayedWithUserActivation(was_played_with_user_activation);
401 base::TimeDelta PipelineController::GetMediaTime() const {
402 return pipeline_->GetMediaTime();
405 Ranges<base::TimeDelta> PipelineController::GetBufferedTimeRanges() const {
406 return pipeline_->GetBufferedTimeRanges();
409 base::TimeDelta PipelineController::GetMediaDuration() const {
410 return pipeline_->GetMediaDuration();
413 bool PipelineController::DidLoadingProgress() {
414 return pipeline_->DidLoadingProgress();
417 PipelineStatistics PipelineController::GetStatistics() const {
418 return pipeline_->GetStatistics();
421 void PipelineController::SetCdm(CdmContext* cdm_context,
422 CdmAttachedCB cdm_attached_cb) {
423 pipeline_->SetCdm(cdm_context, std::move(cdm_attached_cb));
426 void PipelineController::OnEnabledAudioTracksChanged(
427 const std::vector<MediaTrack::Id>& enabled_track_ids) {
428 DCHECK(thread_checker_.CalledOnValidThread());
430 pending_audio_track_change_ = true;
431 pending_audio_track_change_ids_ = enabled_track_ids;
436 void PipelineController::OnSelectedVideoTrackChanged(
437 absl::optional<MediaTrack::Id> selected_track_id) {
438 DCHECK(thread_checker_.CalledOnValidThread());
440 pending_video_track_change_ = true;
441 pending_video_track_change_id_ = selected_track_id;
446 void PipelineController::OnExternalVideoFrameRequest() {
447 DCHECK(thread_checker_.CalledOnValidThread());
448 pipeline_->OnExternalVideoFrameRequest();
451 void PipelineController::FireOnTrackChangeCompleteForTesting(State set_to) {
452 previous_track_change_state_ = set_to;
453 OnTrackChangeComplete();
456 void PipelineController::OnTrackChangeComplete() {
457 DCHECK(thread_checker_.CalledOnValidThread());
459 if (state_ == State::SWITCHING_TRACKS)
460 state_ = previous_track_change_state_;
462 // Other track changed or seek/suspend/resume, etc may be waiting.