[M120 Migration][hbbtv] Audio tracks count notification
[platform/framework/web/chromium-efl.git] / media / filters / pipeline_controller.cc
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.
4
5 #include "media/filters/pipeline_controller.h"
6
7 #include "base/functional/bind.h"
8 #include "base/logging.h"
9 #include "media/base/demuxer.h"
10
11 namespace media {
12
13 PipelineController::PipelineController(std::unique_ptr<Pipeline> pipeline,
14                                        SeekedCB seeked_cb,
15                                        SuspendedCB suspended_cb,
16                                        BeforeResumeCB before_resume_cb,
17                                        ResumedCB resumed_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)) {
25   DCHECK(pipeline_);
26   DCHECK(seeked_cb_);
27   DCHECK(suspended_cb_);
28   DCHECK(before_resume_cb_);
29   DCHECK(resumed_cb_);
30   DCHECK(error_cb_);
31 }
32
33 PipelineController::~PipelineController() {
34   DCHECK(thread_checker_.CalledOnValidThread());
35 }
36
37 void PipelineController::Start(Pipeline::StartType start_type,
38                                Demuxer* demuxer,
39                                Pipeline::Client* client,
40                                bool is_streaming,
41                                bool is_static) {
42   DCHECK(thread_checker_.CalledOnValidThread());
43   DCHECK_EQ(state_, State::STOPPED);
44   DCHECK(demuxer);
45
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;
51
52   demuxer_ = demuxer;
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
59                                       ? State::PLAYING
60                                       : State::PLAYING_OR_SUSPENDED));
61 }
62
63 void PipelineController::Seek(base::TimeDelta time, bool time_updated) {
64   DCHECK(thread_checker_.CalledOnValidThread());
65
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.
68   if (time_updated)
69     pending_time_updated_ = true;
70   pending_seeked_cb_ = true;
71   pending_seek_except_start_ = true;
72
73   // If we are already seeking to |time|, and the media is static, elide the
74   // seek.
75   if ((state_ == State::SEEKING || state_ == State::RESUMING) &&
76       seek_time_ == time && is_static_) {
77     pending_seek_ = false;
78     return;
79   }
80
81   pending_seek_time_ = time;
82   pending_seek_ = true;
83   Dispatch();
84 }
85
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;
93     Dispatch();
94   }
95 }
96
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;
105     Dispatch();
106     return;
107   }
108 }
109
110 void PipelineController::OnDecoderStateLost() {
111   DCHECK(thread_checker_.CalledOnValidThread());
112
113   // Note: |time_updated| and |pending_seeked_cb_| are both false.
114   pending_seek_except_start_ = true;
115
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).
120   //
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
127   // seek:
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_)
133     return;
134
135   // Force a seek to the current time.
136   pending_seek_time_ = pipeline_->GetMediaTime();
137   pending_seek_ = true;
138
139   Dispatch();
140 }
141
142 bool PipelineController::IsStable() {
143   DCHECK(thread_checker_.CalledOnValidThread());
144   return state_ == State::PLAYING;
145 }
146
147 bool PipelineController::IsPendingSeek() {
148   DCHECK(thread_checker_.CalledOnValidThread());
149   return pending_seek_except_start_;
150 }
151
152 bool PipelineController::IsSuspended() {
153   DCHECK(thread_checker_.CalledOnValidThread());
154   return (pending_suspend_ || state_ == State::SUSPENDING ||
155           state_ == State::SUSPENDED) &&
156          !pending_resume_;
157 }
158
159 bool PipelineController::IsPipelineSuspended() {
160   DCHECK(thread_checker_.CalledOnValidThread());
161   return state_ == State::SUSPENDED;
162 }
163
164 void PipelineController::OnPipelineStatus(State expected_state,
165                                           PipelineStatus pipeline_status) {
166   DCHECK(thread_checker_.CalledOnValidThread());
167
168   if (pipeline_status != PIPELINE_OK) {
169     error_cb_.Run(pipeline_status);
170     return;
171   }
172
173   State old_state = state_;
174   state_ = expected_state;
175
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;
180
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;
185   }
186
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;
191
192     // TODO(avayvod): Remove resumed callback after https://crbug.com/678374 is
193     // properly fixed.
194     if (old_state == State::RESUMING) {
195       DCHECK(!pipeline_->IsSuspended());
196       DCHECK(!pending_resume_);
197
198       resumed_cb_.Run();
199     }
200   }
201
202   if (state_ == State::SUSPENDED) {
203     DCHECK(pipeline_->IsSuspended());
204     DCHECK(!pending_suspend_);
205
206     // Warning: possibly reentrant. The state may change inside this callback.
207     // It must be safe to call Dispatch() twice in a row here.
208     suspended_cb_.Run();
209   }
210
211   Dispatch();
212 }
213
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());
218
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(),
226                                       State::SUSPENDED));
227     return;
228   }
229
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...
236     if (pending_seek_) {
237       seek_time_ = pending_seek_time_;
238       pending_seek_ = false;
239     } else {
240       seek_time_ = pipeline_->GetMediaTime();
241     }
242
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();
247
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;
251     }
252
253     // Tell |demuxer_| to expect our resume.
254     DCHECK(!waiting_for_seek_);
255     waiting_for_seek_ = true;
256     demuxer_->StartWaitingForSeek(seek_time_);
257
258     pending_resume_ = false;
259     state_ = State::RESUMING;
260     before_resume_cb_.Run();
261     pipeline_->Resume(
262         seek_time_, base::BindOnce(&PipelineController::OnPipelineStatus,
263                                    weak_factory_.GetWeakPtr(), State::PLAYING));
264     return;
265   }
266
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_) &&
270       waiting_for_seek_) {
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;
275     }
276
277     // CancelPendingSeek() may be reentrant, so update state first and return
278     // immediately.
279     waiting_for_seek_ = false;
280     demuxer_->CancelPendingSeek(pending_seek_time_);
281     return;
282   }
283
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;
289
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()));
299       return;
300     }
301
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()));
308       return;
309     }
310   }
311
312   // Ordinary seeking.
313   if (pending_seek_ && state_ == State::PLAYING) {
314     seek_time_ = pending_seek_time_;
315
316     // Tell |demuxer_| to expect our seek.
317     DCHECK(!waiting_for_seek_);
318     waiting_for_seek_ = true;
319     demuxer_->StartWaitingForSeek(seek_time_);
320
321     pending_seek_ = false;
322     state_ = State::SEEKING;
323     pipeline_->Seek(seek_time_,
324                     base::BindOnce(&PipelineController::OnPipelineStatus,
325                                    weak_factory_.GetWeakPtr(), State::PLAYING));
326     return;
327   }
328
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.
331   //
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
337     // immediately.
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);
344     return;
345   }
346 }
347
348 void PipelineController::Stop() {
349   if (state_ == State::STOPPED)
350     return;
351
352   demuxer_ = nullptr;
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;
363
364   pipeline_->Stop();
365 }
366
367 bool PipelineController::IsPipelineRunning() const {
368   return pipeline_->IsRunning();
369 }
370
371 double PipelineController::GetPlaybackRate() const {
372   return pipeline_->GetPlaybackRate();
373 }
374
375 void PipelineController::SetPlaybackRate(double playback_rate) {
376   pipeline_->SetPlaybackRate(playback_rate);
377 }
378
379 float PipelineController::GetVolume() const {
380   return pipeline_->GetVolume();
381 }
382
383 void PipelineController::SetVolume(float volume) {
384   pipeline_->SetVolume(volume);
385 }
386
387 void PipelineController::SetLatencyHint(
388     absl::optional<base::TimeDelta> latency_hint) {
389   DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
390   pipeline_->SetLatencyHint(latency_hint);
391 }
392
393 void PipelineController::SetPreservesPitch(bool preserves_pitch) {
394   pipeline_->SetPreservesPitch(preserves_pitch);
395 }
396
397 void PipelineController::SetWasPlayedWithUserActivation(
398     bool was_played_with_user_activation) {
399   pipeline_->SetWasPlayedWithUserActivation(was_played_with_user_activation);
400 }
401
402 base::TimeDelta PipelineController::GetMediaTime() const {
403   return pipeline_->GetMediaTime();
404 }
405
406 Ranges<base::TimeDelta> PipelineController::GetBufferedTimeRanges() const {
407   return pipeline_->GetBufferedTimeRanges();
408 }
409
410 base::TimeDelta PipelineController::GetMediaDuration() const {
411   return pipeline_->GetMediaDuration();
412 }
413
414 bool PipelineController::DidLoadingProgress() {
415   return pipeline_->DidLoadingProgress();
416 }
417
418 PipelineStatistics PipelineController::GetStatistics() const {
419   return pipeline_->GetStatistics();
420 }
421
422 void PipelineController::SetCdm(CdmContext* cdm_context,
423                                 CdmAttachedCB cdm_attached_cb) {
424   pipeline_->SetCdm(cdm_context, std::move(cdm_attached_cb));
425 }
426
427 void PipelineController::OnEnabledAudioTracksChanged(
428     const std::vector<MediaTrack::Id>& enabled_track_ids) {
429   DCHECK(thread_checker_.CalledOnValidThread());
430
431   pending_audio_track_change_ = true;
432   pending_audio_track_change_ids_ = enabled_track_ids;
433
434   Dispatch();
435 }
436
437 void PipelineController::OnSelectedVideoTrackChanged(
438     absl::optional<MediaTrack::Id> selected_track_id) {
439   DCHECK(thread_checker_.CalledOnValidThread());
440
441   pending_video_track_change_ = true;
442   pending_video_track_change_id_ = selected_track_id;
443
444   Dispatch();
445 }
446
447 void PipelineController::OnExternalVideoFrameRequest() {
448   DCHECK(thread_checker_.CalledOnValidThread());
449   pipeline_->OnExternalVideoFrameRequest();
450 }
451
452 void PipelineController::FireOnTrackChangeCompleteForTesting(State set_to) {
453   previous_track_change_state_ = set_to;
454   OnTrackChangeComplete();
455 }
456
457 #if defined(TIZEN_MULTIMEDIA)
458 void PipelineController::ToggleFullscreenMode(bool is_fullscreen,
459                                               ToggledFullscreenCB cb) {
460   pipeline_->ToggleFullscreenMode(is_fullscreen, std::move(cb));
461 }
462 #endif
463
464 #if defined(TIZEN_VIDEO_HOLE)
465 void PipelineController::SetMediaGeometry(gfx::RectF rect_f) {
466   pipeline_->SetMediaGeometry(rect_f);
467 }
468 #endif
469
470 void PipelineController::OnTrackChangeComplete() {
471   DCHECK(thread_checker_.CalledOnValidThread());
472
473   if (state_ == State::SWITCHING_TRACKS)
474     state_ = previous_track_change_state_;
475
476   // Other track changed or seek/suspend/resume, etc may be waiting.
477   Dispatch();
478 }
479
480 #if BUILDFLAG(IS_TIZEN_TV)
481 void PipelineController::SetContentMimeType(const std::string& mime_type) {
482   if (pipeline_)
483     pipeline_->SetContentMimeType(mime_type);
484 }
485
486 void PipelineController::AudioTracksCountChanged(unsigned count) {
487   if (pipeline_)
488     pipeline_->AudioTracksCountChanged(count);
489   else
490     LOG(ERROR) << "pipeline_ is null";
491 }
492
493 void PipelineController::SetParentalRatingResult(bool is_pass) {
494   if (pipeline_)
495     pipeline_->SetParentalRatingResult(is_pass);
496   else
497     LOG(ERROR) << "pipeline_ is null";
498 }
499
500 void PipelineController::SetActiveTextTrack(int id, bool is_in_band) {
501   if (!pipeline_) {
502     LOG(ERROR) << "pipeline_ is null";
503     return;
504   }
505   pipeline_->SetActiveTextTrack(id, is_in_band);
506 }
507
508 void PipelineController::SetActiveAudioTrack(int index) {
509   if (!pipeline_) {
510     LOG(ERROR) << "pipeline_ is null";
511     return;
512   }
513   pipeline_->SetActiveAudioTrack(index);
514 }
515
516 void PipelineController::SetActiveVideoTrack(int index) {
517   if (!pipeline_) {
518     LOG(ERROR) << "pipeline_ is null";
519     return;
520   }
521   pipeline_->SetActiveVideoTrack(index);
522 }
523
524 void PipelineController::SetPreferTextLanguage(const std::string& lang) {
525   if (!pipeline_) {
526     LOG(ERROR) << "pipeline_ is null";
527     return;
528   }
529   pipeline_->SetPreferTextLanguage(lang);
530 }
531
532 double PipelineController::GetStartDate() const {
533   if (pipeline_)
534     return pipeline_->GetStartDate();
535
536   LOG(ERROR) << "pipeline_ is null";
537   return std::numeric_limits<double>::quiet_NaN();
538 }
539
540 void PipelineController::DestroyPlayerSync(base::OnceClosure cb) {
541   if (pipeline_) {
542     pipeline_->DestroyPlayerSync(std::move(cb));
543   } else {
544     LOG(ERROR) << "pipeline_ is null";
545     std::move(cb).Run();
546   }
547 }
548 #endif
549 }  // namespace media