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"
7 #include "base/functional/bind.h"
8 #include "base/logging.h"
9 #include "media/base/demuxer.h"
13 PipelineController::PipelineController(std::unique_ptr<Pipeline> pipeline,
15 SuspendedCB suspended_cb,
16 BeforeResumeCB before_resume_cb,
18 PipelineStatusCB error_cb)
19 : pipeline_(std::move(pipeline)),
20 seeked_cb_(std::move(seeked_cb)),
21 suspended_cb_(std::move(suspended_cb)),
22 before_resume_cb_(std::move(before_resume_cb)),
23 resumed_cb_(std::move(resumed_cb)),
24 error_cb_(std::move(error_cb)) {
27 DCHECK(suspended_cb_);
28 DCHECK(before_resume_cb_);
33 PipelineController::~PipelineController() {
34 DCHECK(thread_checker_.CalledOnValidThread());
37 void PipelineController::Start(Pipeline::StartType start_type,
39 Pipeline::Client* client,
42 DCHECK(thread_checker_.CalledOnValidThread());
43 DCHECK_EQ(state_, State::STOPPED);
46 // Once the pipeline is started, we want to call the seeked callback but
47 // without a time update.
48 pending_startup_ = true;
49 pending_seeked_cb_ = true;
50 state_ = State::STARTING;
53 is_streaming_ = is_streaming;
54 is_static_ = is_static;
55 pipeline_->Start(start_type, demuxer, client,
56 base::BindOnce(&PipelineController::OnPipelineStatus,
57 weak_factory_.GetWeakPtr(),
58 start_type == Pipeline::StartType::kNormal
60 : State::PLAYING_OR_SUSPENDED));
63 void PipelineController::Seek(base::TimeDelta time, bool time_updated) {
64 DCHECK(thread_checker_.CalledOnValidThread());
66 // It would be slightly more clear to set this in Dispatch(), but we want to
67 // be sure it gets updated even if the seek is elided.
69 pending_time_updated_ = true;
70 pending_seeked_cb_ = true;
71 pending_seek_except_start_ = true;
73 // If we are already seeking to |time|, and the media is static, elide the
75 if ((state_ == State::SEEKING || state_ == State::RESUMING) &&
76 seek_time_ == time && is_static_) {
77 pending_seek_ = false;
81 pending_seek_time_ = time;
86 // TODO(sandersd): It may be easier to use this interface if |suspended_cb_| is
87 // executed when Suspend() is called while already suspended.
88 void PipelineController::Suspend() {
89 DCHECK(thread_checker_.CalledOnValidThread());
90 pending_resume_ = false;
91 if (state_ != State::SUSPENDING && state_ != State::SUSPENDED) {
92 pending_suspend_ = true;
97 void PipelineController::Resume() {
98 DCHECK(thread_checker_.CalledOnValidThread());
99 pending_suspend_ = false;
100 // TODO(sandersd) fix resume during suspended start.
101 if (state_ == State::SUSPENDING || state_ == State::SUSPENDED ||
102 (state_ == State::SWITCHING_TRACKS &&
103 previous_track_change_state_ == State::SUSPENDED)) {
104 pending_resume_ = true;
110 void PipelineController::OnDecoderStateLost() {
111 DCHECK(thread_checker_.CalledOnValidThread());
113 // Note: |time_updated| and |pending_seeked_cb_| are both false.
114 pending_seek_except_start_ = true;
116 // If we are already seeking or resuming, or if there's already a seek
117 // pending,elide the seek. This is okay for decoder state lost since it just
118 // needs one seek to recover (the decoder is reset and the next decode starts
119 // from a key frame).
121 // Note on potential race condition: When the seek is elided, it's possible
122 // that the decoder state loss happens before or after the previous seek
123 // (decoder Reset()):
124 // 1. Decoder state loss happens before Decoder::Reset() during the previous
125 // seek. In this case we are fine since we just need a Reset().
126 // 2. Decoder state loss happens after Decoder::Reset() during a previous
128 // 2.1 If state loss happens before any Decode() we are still fine, since the
129 // decoder is in a clean state.
130 // 2.2 If state loss happens after a Decode(), then here we should not be in
131 // the SEEKING state.
132 if (state_ == State::SEEKING || state_ == State::RESUMING || pending_seek_)
135 // Force a seek to the current time.
136 pending_seek_time_ = pipeline_->GetMediaTime();
137 pending_seek_ = true;
142 bool PipelineController::IsStable() {
143 DCHECK(thread_checker_.CalledOnValidThread());
144 return state_ == State::PLAYING;
147 bool PipelineController::IsPendingSeek() {
148 DCHECK(thread_checker_.CalledOnValidThread());
149 return pending_seek_except_start_;
152 bool PipelineController::IsSuspended() {
153 DCHECK(thread_checker_.CalledOnValidThread());
154 return (pending_suspend_ || state_ == State::SUSPENDING ||
155 state_ == State::SUSPENDED) &&
159 bool PipelineController::IsPipelineSuspended() {
160 DCHECK(thread_checker_.CalledOnValidThread());
161 return state_ == State::SUSPENDED;
164 void PipelineController::OnPipelineStatus(State expected_state,
165 PipelineStatus pipeline_status) {
166 DCHECK(thread_checker_.CalledOnValidThread());
168 if (pipeline_status != PIPELINE_OK) {
169 error_cb_.Run(pipeline_status);
173 State old_state = state_;
174 state_ = expected_state;
176 // Resolve ambiguity of the current state if we may have suspended in startup.
177 if (state_ == State::PLAYING_OR_SUSPENDED) {
178 waiting_for_seek_ = false;
179 state_ = pipeline_->IsSuspended() ? State::SUSPENDED : State::PLAYING;
181 // It's possible for a Suspend() call to come in during startup. If we've
182 // completed a suspended startup, we should clear that now.
183 if (state_ == State::SUSPENDED)
184 pending_suspend_ = false;
187 if (state_ == State::PLAYING) {
188 // Start(), Seek(), or Resume() completed; we can be sure that
189 // |demuxer_| got the seek it was waiting for.
190 waiting_for_seek_ = false;
192 // TODO(avayvod): Remove resumed callback after https://crbug.com/678374 is
194 if (old_state == State::RESUMING) {
195 DCHECK(!pipeline_->IsSuspended());
196 DCHECK(!pending_resume_);
202 if (state_ == State::SUSPENDED) {
203 DCHECK(pipeline_->IsSuspended());
204 DCHECK(!pending_suspend_);
206 // Warning: possibly reentrant. The state may change inside this callback.
207 // It must be safe to call Dispatch() twice in a row here.
214 // Note: Dispatch() may be called re-entrantly (by callbacks internally) or
215 // twice in a row (by OnPipelineStatus()).
216 void PipelineController::Dispatch() {
217 DCHECK(thread_checker_.CalledOnValidThread());
219 // Suspend/resume transitions take priority because seeks before a suspend
220 // are wasted, and seeks after can be merged into the resume operation.
221 if (pending_suspend_ && state_ == State::PLAYING) {
222 pending_suspend_ = false;
223 state_ = State::SUSPENDING;
224 pipeline_->Suspend(base::BindOnce(&PipelineController::OnPipelineStatus,
225 weak_factory_.GetWeakPtr(),
230 // In additional to the standard |pending_resume_| case, if we completed a
231 // suspended startup, but a Seek() came in, we need to resume the pipeline to
232 // complete the seek before calling |seeked_cb_|.
233 if ((pending_resume_ || (pending_startup_ && pending_seek_)) &&
234 state_ == State::SUSPENDED) {
235 // If there is a pending seek, resume to that time instead...
237 seek_time_ = pending_seek_time_;
238 pending_seek_ = false;
240 seek_time_ = pipeline_->GetMediaTime();
243 // ...unless the media is streaming, in which case we resume at the start
244 // because seeking doesn't work well.
245 if (is_streaming_ && !seek_time_.is_zero()) {
246 seek_time_ = base::TimeDelta();
248 // In this case we want to make sure that the controls get updated
249 // immediately, so we don't try to hide the seek.
250 pending_time_updated_ = true;
253 // Tell |demuxer_| to expect our resume.
254 DCHECK(!waiting_for_seek_);
255 waiting_for_seek_ = true;
256 demuxer_->StartWaitingForSeek(seek_time_);
258 pending_resume_ = false;
259 state_ = State::RESUMING;
260 before_resume_cb_.Run();
262 seek_time_, base::BindOnce(&PipelineController::OnPipelineStatus,
263 weak_factory_.GetWeakPtr(), State::PLAYING));
267 // If we have pending operations, and a seek is ongoing, abort it.
268 if ((pending_seek_ || pending_suspend_ || pending_audio_track_change_ ||
269 pending_video_track_change_) &&
271 // If there is no pending seek, return the current seek to pending status.
272 if (!pending_seek_) {
273 pending_seek_time_ = seek_time_;
274 pending_seek_ = true;
277 // CancelPendingSeek() may be reentrant, so update state first and return
279 waiting_for_seek_ = false;
280 demuxer_->CancelPendingSeek(pending_seek_time_);
284 // We can only switch tracks if we are not in a transitioning state already.
285 if ((pending_audio_track_change_ || pending_video_track_change_) &&
286 (state_ == State::PLAYING || state_ == State::SUSPENDED)) {
287 previous_track_change_state_ = state_;
288 state_ = State::SWITCHING_TRACKS;
290 // Attempt to do a track change _before_ attempting a seek operation,
291 // otherwise the seek will apply to the old tracks instead of the new
292 // one(s). Also attempt audio before video.
293 if (pending_audio_track_change_) {
294 pending_audio_track_change_ = false;
295 pipeline_->OnEnabledAudioTracksChanged(
296 pending_audio_track_change_ids_,
297 base::BindOnce(&PipelineController::OnTrackChangeComplete,
298 weak_factory_.GetWeakPtr()));
302 if (pending_video_track_change_) {
303 pending_video_track_change_ = false;
304 pipeline_->OnSelectedVideoTrackChanged(
305 pending_video_track_change_id_,
306 base::BindOnce(&PipelineController::OnTrackChangeComplete,
307 weak_factory_.GetWeakPtr()));
313 if (pending_seek_ && state_ == State::PLAYING) {
314 seek_time_ = pending_seek_time_;
316 // Tell |demuxer_| to expect our seek.
317 DCHECK(!waiting_for_seek_);
318 waiting_for_seek_ = true;
319 demuxer_->StartWaitingForSeek(seek_time_);
321 pending_seek_ = false;
322 state_ = State::SEEKING;
323 pipeline_->Seek(seek_time_,
324 base::BindOnce(&PipelineController::OnPipelineStatus,
325 weak_factory_.GetWeakPtr(), State::PLAYING));
329 // If |state_| is PLAYING and we didn't trigger an operation above then we
330 // are in a stable state. If there is a seeked callback pending, emit it.
332 // We also need to emit it if we completed suspended startup.
333 if (pending_seeked_cb_ &&
334 (state_ == State::PLAYING ||
335 (state_ == State::SUSPENDED && pending_startup_))) {
336 // |seeked_cb_| may be reentrant, so update state first and return
338 pending_startup_ = false;
339 pending_seeked_cb_ = false;
340 pending_seek_except_start_ = false;
341 bool was_pending_time_updated = pending_time_updated_;
342 pending_time_updated_ = false;
343 seeked_cb_.Run(was_pending_time_updated);
348 void PipelineController::Stop() {
349 if (state_ == State::STOPPED)
353 waiting_for_seek_ = false;
354 pending_seeked_cb_ = false;
355 pending_seek_except_start_ = false;
356 pending_time_updated_ = false;
357 pending_seek_ = false;
358 pending_suspend_ = false;
359 pending_resume_ = false;
360 pending_audio_track_change_ = false;
361 pending_video_track_change_ = false;
362 state_ = State::STOPPED;
367 bool PipelineController::IsPipelineRunning() const {
368 return pipeline_->IsRunning();
371 double PipelineController::GetPlaybackRate() const {
372 return pipeline_->GetPlaybackRate();
375 void PipelineController::SetPlaybackRate(double playback_rate) {
376 pipeline_->SetPlaybackRate(playback_rate);
379 float PipelineController::GetVolume() const {
380 return pipeline_->GetVolume();
383 void PipelineController::SetVolume(float volume) {
384 pipeline_->SetVolume(volume);
387 void PipelineController::SetLatencyHint(
388 absl::optional<base::TimeDelta> latency_hint) {
389 DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
390 pipeline_->SetLatencyHint(latency_hint);
393 void PipelineController::SetPreservesPitch(bool preserves_pitch) {
394 pipeline_->SetPreservesPitch(preserves_pitch);
397 void PipelineController::SetWasPlayedWithUserActivation(
398 bool was_played_with_user_activation) {
399 pipeline_->SetWasPlayedWithUserActivation(was_played_with_user_activation);
402 base::TimeDelta PipelineController::GetMediaTime() const {
403 return pipeline_->GetMediaTime();
406 Ranges<base::TimeDelta> PipelineController::GetBufferedTimeRanges() const {
407 return pipeline_->GetBufferedTimeRanges();
410 base::TimeDelta PipelineController::GetMediaDuration() const {
411 return pipeline_->GetMediaDuration();
414 bool PipelineController::DidLoadingProgress() {
415 return pipeline_->DidLoadingProgress();
418 PipelineStatistics PipelineController::GetStatistics() const {
419 return pipeline_->GetStatistics();
422 void PipelineController::SetCdm(CdmContext* cdm_context,
423 CdmAttachedCB cdm_attached_cb) {
424 pipeline_->SetCdm(cdm_context, std::move(cdm_attached_cb));
427 void PipelineController::OnEnabledAudioTracksChanged(
428 const std::vector<MediaTrack::Id>& enabled_track_ids) {
429 DCHECK(thread_checker_.CalledOnValidThread());
431 pending_audio_track_change_ = true;
432 pending_audio_track_change_ids_ = enabled_track_ids;
437 void PipelineController::OnSelectedVideoTrackChanged(
438 absl::optional<MediaTrack::Id> selected_track_id) {
439 DCHECK(thread_checker_.CalledOnValidThread());
441 pending_video_track_change_ = true;
442 pending_video_track_change_id_ = selected_track_id;
447 void PipelineController::OnExternalVideoFrameRequest() {
448 DCHECK(thread_checker_.CalledOnValidThread());
449 pipeline_->OnExternalVideoFrameRequest();
452 void PipelineController::FireOnTrackChangeCompleteForTesting(State set_to) {
453 previous_track_change_state_ = set_to;
454 OnTrackChangeComplete();
457 #if defined(TIZEN_MULTIMEDIA)
458 void PipelineController::ToggleFullscreenMode(bool is_fullscreen,
459 ToggledFullscreenCB cb) {
460 pipeline_->ToggleFullscreenMode(is_fullscreen, std::move(cb));
464 #if defined(TIZEN_VIDEO_HOLE)
465 void PipelineController::SetMediaGeometry(gfx::RectF rect_f) {
466 pipeline_->SetMediaGeometry(rect_f);
470 void PipelineController::OnTrackChangeComplete() {
471 DCHECK(thread_checker_.CalledOnValidThread());
473 if (state_ == State::SWITCHING_TRACKS)
474 state_ = previous_track_change_state_;
476 // Other track changed or seek/suspend/resume, etc may be waiting.
480 #if BUILDFLAG(IS_TIZEN_TV)
481 void PipelineController::SetContentMimeType(const std::string& mime_type) {
483 pipeline_->SetContentMimeType(mime_type);
486 void PipelineController::AudioTracksCountChanged(unsigned count) {
488 pipeline_->AudioTracksCountChanged(count);
490 LOG(ERROR) << "pipeline_ is null";
493 void PipelineController::SetParentalRatingResult(bool is_pass) {
495 pipeline_->SetParentalRatingResult(is_pass);
497 LOG(ERROR) << "pipeline_ is null";
500 void PipelineController::SetActiveTextTrack(int id, bool is_in_band) {
502 LOG(ERROR) << "pipeline_ is null";
505 pipeline_->SetActiveTextTrack(id, is_in_band);
508 void PipelineController::SetActiveAudioTrack(int index) {
510 LOG(ERROR) << "pipeline_ is null";
513 pipeline_->SetActiveAudioTrack(index);
516 void PipelineController::SetActiveVideoTrack(int index) {
518 LOG(ERROR) << "pipeline_ is null";
521 pipeline_->SetActiveVideoTrack(index);
524 void PipelineController::SetPreferTextLanguage(const std::string& lang) {
526 LOG(ERROR) << "pipeline_ is null";
529 pipeline_->SetPreferTextLanguage(lang);
532 double PipelineController::GetStartDate() const {
534 return pipeline_->GetStartDate();
536 LOG(ERROR) << "pipeline_ is null";
537 return std::numeric_limits<double>::quiet_NaN();
540 void PipelineController::DestroyPlayerSync(base::OnceClosure cb) {
542 pipeline_->DestroyPlayerSync(std::move(cb));
544 LOG(ERROR) << "pipeline_ is null";