Upload upstream chromium 108.0.5359.1
[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/bind.h"
8 #include "media/base/demuxer.h"
9
10 namespace media {
11
12 PipelineController::PipelineController(std::unique_ptr<Pipeline> pipeline,
13                                        SeekedCB seeked_cb,
14                                        SuspendedCB suspended_cb,
15                                        BeforeResumeCB before_resume_cb,
16                                        ResumedCB resumed_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)) {
24   DCHECK(pipeline_);
25   DCHECK(seeked_cb_);
26   DCHECK(suspended_cb_);
27   DCHECK(before_resume_cb_);
28   DCHECK(resumed_cb_);
29   DCHECK(error_cb_);
30 }
31
32 PipelineController::~PipelineController() {
33   DCHECK(thread_checker_.CalledOnValidThread());
34 }
35
36 void PipelineController::Start(Pipeline::StartType start_type,
37                                Demuxer* demuxer,
38                                Pipeline::Client* client,
39                                bool is_streaming,
40                                bool is_static) {
41   DCHECK(thread_checker_.CalledOnValidThread());
42   DCHECK_EQ(state_, State::STOPPED);
43   DCHECK(demuxer);
44
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;
50
51   demuxer_ = demuxer;
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
58                                       ? State::PLAYING
59                                       : State::PLAYING_OR_SUSPENDED));
60 }
61
62 void PipelineController::Seek(base::TimeDelta time, bool time_updated) {
63   DCHECK(thread_checker_.CalledOnValidThread());
64
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.
67   if (time_updated)
68     pending_time_updated_ = true;
69   pending_seeked_cb_ = true;
70   pending_seek_except_start_ = true;
71
72   // If we are already seeking to |time|, and the media is static, elide the
73   // seek.
74   if ((state_ == State::SEEKING || state_ == State::RESUMING) &&
75       seek_time_ == time && is_static_) {
76     pending_seek_ = false;
77     return;
78   }
79
80   pending_seek_time_ = time;
81   pending_seek_ = true;
82   Dispatch();
83 }
84
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;
92     Dispatch();
93   }
94 }
95
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;
104     Dispatch();
105     return;
106   }
107 }
108
109 void PipelineController::OnDecoderStateLost() {
110   DCHECK(thread_checker_.CalledOnValidThread());
111
112   // Note: |time_updated| and |pending_seeked_cb_| are both false.
113   pending_seek_except_start_ = true;
114
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).
119   //
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
126   // seek:
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_)
132     return;
133
134   // Force a seek to the current time.
135   pending_seek_time_ = pipeline_->GetMediaTime();
136   pending_seek_ = true;
137
138   Dispatch();
139 }
140
141 bool PipelineController::IsStable() {
142   DCHECK(thread_checker_.CalledOnValidThread());
143   return state_ == State::PLAYING;
144 }
145
146 bool PipelineController::IsPendingSeek() {
147   DCHECK(thread_checker_.CalledOnValidThread());
148   return pending_seek_except_start_;
149 }
150
151 bool PipelineController::IsSuspended() {
152   DCHECK(thread_checker_.CalledOnValidThread());
153   return (pending_suspend_ || state_ == State::SUSPENDING ||
154           state_ == State::SUSPENDED) &&
155          !pending_resume_;
156 }
157
158 bool PipelineController::IsPipelineSuspended() {
159   DCHECK(thread_checker_.CalledOnValidThread());
160   return state_ == State::SUSPENDED;
161 }
162
163 void PipelineController::OnPipelineStatus(State expected_state,
164                                           PipelineStatus pipeline_status) {
165   DCHECK(thread_checker_.CalledOnValidThread());
166
167   if (pipeline_status != PIPELINE_OK) {
168     error_cb_.Run(pipeline_status);
169     return;
170   }
171
172   State old_state = state_;
173   state_ = expected_state;
174
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;
179
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;
184   }
185
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;
190
191     // TODO(avayvod): Remove resumed callback after https://crbug.com/678374 is
192     // properly fixed.
193     if (old_state == State::RESUMING) {
194       DCHECK(!pipeline_->IsSuspended());
195       DCHECK(!pending_resume_);
196
197       resumed_cb_.Run();
198     }
199   }
200
201   if (state_ == State::SUSPENDED) {
202     DCHECK(pipeline_->IsSuspended());
203     DCHECK(!pending_suspend_);
204
205     // Warning: possibly reentrant. The state may change inside this callback.
206     // It must be safe to call Dispatch() twice in a row here.
207     suspended_cb_.Run();
208   }
209
210   Dispatch();
211 }
212
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());
217
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(),
225                                       State::SUSPENDED));
226     return;
227   }
228
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...
235     if (pending_seek_) {
236       seek_time_ = pending_seek_time_;
237       pending_seek_ = false;
238     } else {
239       seek_time_ = pipeline_->GetMediaTime();
240     }
241
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();
246
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;
250     }
251
252     // Tell |demuxer_| to expect our resume.
253     DCHECK(!waiting_for_seek_);
254     waiting_for_seek_ = true;
255     demuxer_->StartWaitingForSeek(seek_time_);
256
257     pending_resume_ = false;
258     state_ = State::RESUMING;
259     before_resume_cb_.Run();
260     pipeline_->Resume(
261         seek_time_, base::BindOnce(&PipelineController::OnPipelineStatus,
262                                    weak_factory_.GetWeakPtr(), State::PLAYING));
263     return;
264   }
265
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_) &&
269       waiting_for_seek_) {
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;
274     }
275
276     // CancelPendingSeek() may be reentrant, so update state first and return
277     // immediately.
278     waiting_for_seek_ = false;
279     demuxer_->CancelPendingSeek(pending_seek_time_);
280     return;
281   }
282
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;
288
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()));
298       return;
299     }
300
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()));
307       return;
308     }
309   }
310
311   // Ordinary seeking.
312   if (pending_seek_ && state_ == State::PLAYING) {
313     seek_time_ = pending_seek_time_;
314
315     // Tell |demuxer_| to expect our seek.
316     DCHECK(!waiting_for_seek_);
317     waiting_for_seek_ = true;
318     demuxer_->StartWaitingForSeek(seek_time_);
319
320     pending_seek_ = false;
321     state_ = State::SEEKING;
322     pipeline_->Seek(seek_time_,
323                     base::BindOnce(&PipelineController::OnPipelineStatus,
324                                    weak_factory_.GetWeakPtr(), State::PLAYING));
325     return;
326   }
327
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.
330   //
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
336     // immediately.
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);
343     return;
344   }
345 }
346
347 void PipelineController::Stop() {
348   if (state_ == State::STOPPED)
349     return;
350
351   demuxer_ = nullptr;
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;
362
363   pipeline_->Stop();
364 }
365
366 bool PipelineController::IsPipelineRunning() const {
367   return pipeline_->IsRunning();
368 }
369
370 double PipelineController::GetPlaybackRate() const {
371   return pipeline_->GetPlaybackRate();
372 }
373
374 void PipelineController::SetPlaybackRate(double playback_rate) {
375   pipeline_->SetPlaybackRate(playback_rate);
376 }
377
378 float PipelineController::GetVolume() const {
379   return pipeline_->GetVolume();
380 }
381
382 void PipelineController::SetVolume(float volume) {
383   pipeline_->SetVolume(volume);
384 }
385
386 void PipelineController::SetLatencyHint(
387     absl::optional<base::TimeDelta> latency_hint) {
388   DCHECK(!latency_hint || (*latency_hint >= base::TimeDelta()));
389   pipeline_->SetLatencyHint(latency_hint);
390 }
391
392 void PipelineController::SetPreservesPitch(bool preserves_pitch) {
393   pipeline_->SetPreservesPitch(preserves_pitch);
394 }
395
396 void PipelineController::SetWasPlayedWithUserActivation(
397     bool was_played_with_user_activation) {
398   pipeline_->SetWasPlayedWithUserActivation(was_played_with_user_activation);
399 }
400
401 base::TimeDelta PipelineController::GetMediaTime() const {
402   return pipeline_->GetMediaTime();
403 }
404
405 Ranges<base::TimeDelta> PipelineController::GetBufferedTimeRanges() const {
406   return pipeline_->GetBufferedTimeRanges();
407 }
408
409 base::TimeDelta PipelineController::GetMediaDuration() const {
410   return pipeline_->GetMediaDuration();
411 }
412
413 bool PipelineController::DidLoadingProgress() {
414   return pipeline_->DidLoadingProgress();
415 }
416
417 PipelineStatistics PipelineController::GetStatistics() const {
418   return pipeline_->GetStatistics();
419 }
420
421 void PipelineController::SetCdm(CdmContext* cdm_context,
422                                 CdmAttachedCB cdm_attached_cb) {
423   pipeline_->SetCdm(cdm_context, std::move(cdm_attached_cb));
424 }
425
426 void PipelineController::OnEnabledAudioTracksChanged(
427     const std::vector<MediaTrack::Id>& enabled_track_ids) {
428   DCHECK(thread_checker_.CalledOnValidThread());
429
430   pending_audio_track_change_ = true;
431   pending_audio_track_change_ids_ = enabled_track_ids;
432
433   Dispatch();
434 }
435
436 void PipelineController::OnSelectedVideoTrackChanged(
437     absl::optional<MediaTrack::Id> selected_track_id) {
438   DCHECK(thread_checker_.CalledOnValidThread());
439
440   pending_video_track_change_ = true;
441   pending_video_track_change_id_ = selected_track_id;
442
443   Dispatch();
444 }
445
446 void PipelineController::OnExternalVideoFrameRequest() {
447   DCHECK(thread_checker_.CalledOnValidThread());
448   pipeline_->OnExternalVideoFrameRequest();
449 }
450
451 void PipelineController::FireOnTrackChangeCompleteForTesting(State set_to) {
452   previous_track_change_state_ = set_to;
453   OnTrackChangeComplete();
454 }
455
456 void PipelineController::OnTrackChangeComplete() {
457   DCHECK(thread_checker_.CalledOnValidThread());
458
459   if (state_ == State::SWITCHING_TRACKS)
460     state_ = previous_track_change_state_;
461
462   // Other track changed or seek/suspend/resume, etc may be waiting.
463   Dispatch();
464 }
465
466 }  // namespace media