1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253

media / filters / pipeline_controller.h [blame]

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_FILTERS_PIPELINE_CONTROLLER_H_
#define MEDIA_FILTERS_PIPELINE_CONTROLLER_H_

#include <optional>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "media/base/media_export.h"
#include "media/base/pipeline.h"

namespace media {

class CdmContext;
class Demuxer;

// PipelineController wraps a Pipeline to expose the one-at-a-time operations
// (Seek(), Suspend(), and Resume()) with a simpler API. Internally it tracks
// pending operations and dispatches them when possible. Duplicate requests
// (such as seeking twice to the same time) may be elided.
//
// TODO(sandersd/tguilbert):
//   - Expose an operation that replaces the Renderer (via Suspend/Resume).
//   - Expose an operation that replaces the Demuxer (via Start/Stop). This will
//     also implicitly replace the Renderer.
//   - Block invalid calls after an error occurs.
class MEDIA_EXPORT PipelineController {
 public:
  enum class State {
    STOPPED,
    STARTING,
    PLAYING,
    PLAYING_OR_SUSPENDED,
    SEEKING,
    SWITCHING_TRACKS,
    SUSPENDING,
    SUSPENDED,
    RESUMING,
  };

  using SeekedCB = base::RepeatingCallback<void(bool time_updated)>;
  using SuspendedCB = base::RepeatingClosure;
  using BeforeResumeCB = base::RepeatingClosure;
  using ResumedCB = base::RepeatingClosure;
  using CdmAttachedCB = base::OnceCallback<void(bool)>;

  // Construct a PipelineController wrapping |pipeline_|.
  // The callbacks are:
  //   - |seeked_cb| is called upon reaching a stable state if a seek occurred.
  //   - |suspended_cb| is called immediately after suspending.
  //   - |before_resume_cb| is called immediately before resuming.
  //   - |resumed_cb| is called immediately after resuming.
  //   - |error_cb| is called if any operation on |pipeline_| does not result
  //     in PIPELINE_OK or its error callback is called.
  PipelineController(std::unique_ptr<Pipeline> pipeline,
                     PipelineStatusCB started_cb,
                     SeekedCB seeked_cb,
                     SuspendedCB suspended_cb,
                     BeforeResumeCB before_resume_cb,
                     ResumedCB resumed_cb,
                     PipelineStatusCB error_cb);

  PipelineController(const PipelineController&) = delete;
  PipelineController& operator=(const PipelineController&) = delete;

  ~PipelineController();

  // Start |pipeline_|. |demuxer| will be retained and StartWaitingForSeek()/
  // CancelPendingSeek() will be issued to it as necessary.
  //
  // When |is_streaming| is true, Resume() will always start at the
  // beginning of the stream, rather than attempting to seek to the current
  // time.
  //
  // When |is_static| is true, seeks to the current time may be elided.
  // Otherwise it is assumed that the media data may have changed.
  //
  // The remaining parameters are just passed directly to pipeline_.Start().
  void Start(Pipeline::StartType start_type,
             Demuxer* demuxer,
             Pipeline::Client* client,
             bool is_streaming,
             bool is_static);

  // Request a seek to |time|. If |time_updated| is true, then the eventual
  // |seeked_cb| callback will also have |time_updated| set to true; it
  // indicates that the seek was requested by Blink and a time update is
  // expected so that Blink can fire the seeked event.
  //
  // Note: This will not resume the pipeline if it is in the suspended state; a
  // call to Resume() is required. |seeked_cb_| will not be called until the
  // later Resume() completes. The intention is to avoid unnecessary wake-ups
  // for suspended players.
  void Seek(base::TimeDelta time, bool time_updated);

  // Request that |pipeline_| be suspended. This is a no-op if |pipeline_| has
  // been suspended.
  void Suspend();

  // Request that |pipeline_| be resumed. This is a no-op if |pipeline_| has not
  // been suspended.
  void Resume();

  // Called when a decoder in the pipeline lost its state. This requires a seek
  // so that the decoder can start from a new key frame.
  void OnDecoderStateLost();

  // Returns true if the current state is stable. This means that |state_| is
  // PLAYING and there are no pending operations. Requests are processed
  // immediately when the state is stable, otherwise they are queued.
  //
  // Exceptions to the above:
  //   - Start() is processed immediately while in the CREATED state.
  //   - Resume() is processed immediately while in the SUSPENDED state.
  bool IsStable();

  // Returns true if the current target state is suspended.
  bool IsSuspended();

  // Returns true if Seek() was called and there is a seek operation which has
  // not yet completed.
  bool IsPendingSeek();

  // Returns true if |pipeline_| is suspended.
  bool IsPipelineSuspended();

  // Subset of the Pipeline interface directly exposing |pipeline_|.
  void Stop();
  bool IsPipelineRunning() const;
  double GetPlaybackRate() const;
  void SetPlaybackRate(double playback_rate);
  float GetVolume() const;
  void SetVolume(float volume);
  void SetLatencyHint(std::optional<base::TimeDelta> latency_hint);
  void SetPreservesPitch(bool preserves_pitch);
  void SetWasPlayedWithUserActivationAndHighMediaEngagement(
      bool was_played_with_user_activation_and_high_media_engagement);
  base::TimeDelta GetMediaTime() const;
  Ranges<base::TimeDelta> GetBufferedTimeRanges() const;
  base::TimeDelta GetMediaDuration() const;
  bool DidLoadingProgress();
  PipelineStatistics GetStatistics() const;
  void SetCdm(CdmContext* cdm_context, CdmAttachedCB cdm_attached_cb);
  void OnEnabledAudioTracksChanged(
      const std::vector<MediaTrack::Id>& enabled_track_ids);
  void OnSelectedVideoTrackChanged(
      std::optional<MediaTrack::Id> selected_track_id);
  void OnExternalVideoFrameRequest();

  // Used to fire the OnTrackChangeComplete function which is captured in a
  // OnceCallback, and doesn't play nicely with gmock.
  void FireOnTrackChangeCompleteForTesting(State set_to);

 private:
  // Attempts to make progress from the current state to the target state.
  void Dispatch();

  // PipelineStaus callback that also carries the target state.
  void OnPipelineStatus(State state, PipelineStatus pipeline_status);

  void OnTrackChangeComplete();

  // The Pipeline we are managing state for.
  std::unique_ptr<Pipeline> pipeline_;

  // Called immediately when |pipeline_| completes starting, i.e., when
  // metadata are ready.
  const PipelineStatusCB started_cb_;

  // Called after seeks (which includes Start()) upon reaching a stable state.
  // Multiple seeks result in only one callback if no stable state occurs
  // between them.
  const SeekedCB seeked_cb_;

  // Called immediately when |pipeline_| completes a suspend operation.
  const SuspendedCB suspended_cb_;

  // Called immediately before |pipeline_| starts a resume operation.
  const BeforeResumeCB before_resume_cb_;

  // Called immediately when |pipeline_| completes a resume operation.
  const ResumedCB resumed_cb_;

  // Called immediately when any operation on |pipeline_| results in an error.
  const PipelineStatusCB error_cb_;

  // State for handling StartWaitingForSeek()/CancelPendingSeek().
  raw_ptr<Demuxer> demuxer_ = nullptr;
  bool waiting_for_seek_ = false;

  // When true, Resume() will start at time zero instead of seeking to the
  // current time.
  bool is_streaming_ = false;

  // When true, seeking to the current time may be elided.
  bool is_static_ = true;

  // Tracks the current state of |pipeline_|.
  State state_ = State::STOPPED;

  // The previous state of |pipeline_| if it's currently undergoing a track
  // change.
  State previous_track_change_state_ = State::STOPPED;

  // Indicates that a seek has occurred. When set, a seeked callback will be
  // issued at the next stable state.
  bool pending_seeked_cb_ = false;

  // Indicates that a seek has occurred from an explicit call to Seek() or
  // OnDecoderStateLost().
  bool pending_seek_except_start_ = false;

  // Indicates that time has been changed by a seek, which will be reported at
  // the next seeked callback.
  bool pending_time_updated_ = false;

  // The target time of the active seek; valid while SEEKING or RESUMING.
  base::TimeDelta seek_time_;

  // Target state which we will work to achieve.
  bool pending_seek_ = false;
  bool pending_suspend_ = false;
  bool pending_resume_ = false;
  bool pending_audio_track_change_ = false;
  bool pending_video_track_change_ = false;

  // |pending_seek_time_| is only valid when |pending_seek_| is true.
  // |pending_track_change_type_| is only valid when |pending_track_change_|.
  // |pending_audio_track_change_ids_| is only valid when
  //   |pending_audio_track_change_|.
  // |pending_video_track_change_id_| is only valid when
  //   |pending_video_track_change_|.
  base::TimeDelta pending_seek_time_;
  std::vector<MediaTrack::Id> pending_audio_track_change_ids_;
  std::optional<MediaTrack::Id> pending_video_track_change_id_;

  // Set to true during Start(). Indicates that |seeked_cb_| must be fired once
  // we've completed startup.
  bool pending_startup_ = false;

  base::ThreadChecker thread_checker_;
  base::WeakPtrFactory<PipelineController> weak_factory_{this};
};

}  // namespace media

#endif  // MEDIA_FILTERS_PIPELINE_CONTROLLER_H_