Motown: Change player state machine to support preroll
This CL changes the internal logic of MediaPlayerImpl so that an initial
call to the Pause method will cause the first frame of video to be
displayed. Logic around priming/flushing the pipeline was previously
handled using a separated state variable (flushed_). This CL removes
that state variable and replaces the internal kPaused state with
kFlushed and kPrimed. The pause method is treated as a request to
enter kPrimed state, in which packets will be queued for rendering (so
the first video frame can be shown). The player starts in kFlushed state
and only transitions back to that state in order to seek.

R=kulakowski@chromium.org

Review URL: https://codereview.chromium.org/2085593002 .
diff --git a/services/media/factory_service/media_player_impl.cc b/services/media/factory_service/media_player_impl.cc
index e174c2f..ee33b06 100644
--- a/services/media/factory_service/media_player_impl.cc
+++ b/services/media/factory_service/media_player_impl.cc
@@ -83,7 +83,7 @@
     callback_joiner->WhenJoined([this]() {
       // The enabled streams are prepared.
       factory_.reset();
-      state_ = State::kPaused;
+      state_ = State::kFlushed;
       Update();
     });
   });
@@ -92,93 +92,170 @@
 MediaPlayerImpl::~MediaPlayerImpl() {}
 
 void MediaPlayerImpl::Update() {
+  // This method is called whenever we might want to take action based on the
+  // current state and recent events. The current state is in |state_|. Recent
+  // events are recorded in |target_state_|, which indicates what state we'd
+  // like to transition to, |target_position_|, which can indicate a position
+  // we'd like to stream to, and |end_of_stream_| which tells us we've reached
+  // end of stream.
+  //
+  // The states are as follows:
+  //
+  // |kWaiting| - Indicates that we've done something asynchronous, and no
+  //              further action should be taken by the state machine until that
+  //              something completes (at which point the callback will change
+  //              the state and call |Update|).
+  // |kFlushed| - Indicates that presentation time is not progressing and that
+  //              the pipeline is not primed with packets. This is the initial
+  //              state and the state we transition to in preparation for
+  //              seeking. A seek is currently only done when when the pipeline
+  //              is clear of packets.
+  // |kPrimed| -  Indicates that presentation time is not progressing and that
+  //              the pipeline is primed with packets. We transition to this
+  //              state when the client calls |Pause|, either from |kFlushed| or
+  //              |kPlaying| state.
+  // |kPlaying| - Indicates that presentation time is progressing and there are
+  //              packets in the pipeline. We transition to this state when the
+  //              client calls |Play|. If we're in |kFlushed| when |Play| is
+  //              called, we transition through |kPrimed| state.
+  //
+  // The while loop that surrounds all the logic below is there because, after
+  // taking some action and transitioning to a new state, we may want to check
+  // to see if there's more to do in the new state. You'll also notice that
+  // the callback lambdas generally call |Update|.
   while (true) {
     switch (state_) {
-      case State::kPaused:
+      case State::kFlushed:
+        // Presentation time is not progressing, and the pipeline is clear of
+        // packets.
         if (target_position_ != kUnspecifiedTime) {
-          WhenPausedAndSeeking();
-          break;
+          // We want to seek. Enter |kWaiting| state until the operation is
+          // complete.
+          state_ = State::kWaiting;
+          demux_->Seek(target_position_, [this]() {
+            transform_subject_time_ = target_position_;
+            target_position_ = kUnspecifiedTime;
+            state_ = State::kFlushed;
+            // Back in |kFlushed|. Call |Update| to see if there's further
+            // action to be taken.
+            Update();
+          });
+
+          // Done for now. We're in kWaiting, and the callback will call Update
+          // when the Seek call is complete.
+          return;
+        }
+
+        if (target_state_ == State::kPlaying ||
+            target_state_ == State::kPrimed) {
+          // We want to transition to |kPrimed| or to |kPlaying|, for which
+          // |kPrimed| is a prerequisite. We enter |kWaiting| state, issue the
+          // |Prime| request and transition to |kPrimed| when the operation is
+          // complete.
+          state_ = State::kWaiting;
+          demux_->Prime([this]() {
+            state_ = State::kPrimed;
+            // Now we're in |kPrimed|. Call |Update| to see if there's further
+            // action to be taken.
+            Update();
+          });
+
+          // Done for now. We're in |kWaiting|, and the callback will call
+          // |Update| when the prime is complete.
+          return;
+        }
+
+        // No interesting events to respond to. Done for now.
+        return;
+
+      case State::kPrimed:
+        // Presentation time is not progressing, and the pipeline is primed with
+        // packets.
+        if (target_position_ != kUnspecifiedTime ||
+            target_state_ == State::kFlushed) {
+          // Either we want to seek or just want to transition to |kFlushed|.
+          // We transition to |kWaiting|, issue the |Flush| request and
+          // transition to |kFlushed| when the operation is complete.
+          state_ = State::kWaiting;
+          demux_->Flush([this]() {
+            state_ = State::kFlushed;
+            // Now we're in |kFlushed|. Call |Update| to see if there's further
+            // action to be taken.
+            Update();
+          });
+
+          // Done for now. We're in |kWaiting|, and the callback will call
+          // |Update| when the flush is complete.
+          return;
         }
 
         if (target_state_ == State::kPlaying) {
-          if (!flushed_) {
-            SetTimelineTransform(1, 1);
-            state_ = State::kWaiting;
-            break;
-          }
-
-          flushed_ = false;
+          // We want to transition to |kPlaying|. Enter |kWaiting|, start the
+          // presentation timeline and transition to |kPlaying| when the
+          // operation completes.
           state_ = State::kWaiting;
-          demux_->Prime([this]() {
-            SetTimelineTransform(1, 1);
-            state_ = State::kWaiting;
-            Update();
-          });
+          timeline_consumer_->SetTimelineTransform(
+              transform_subject_time_, 1, 1,
+              Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
+              [this](bool completed) {
+                state_ = State::kPlaying;
+                // Now we're in |kPlaying|. Call |Update| to see if there's
+                // further
+                // action to be taken.
+                Update();
+              });
+
+          transform_subject_time_ = kUnspecifiedTime;
+          return;
         }
+
+        // No interesting events to respond to. Done for now.
         return;
 
       case State::kPlaying:
+        // Presentation time is progressing, and packets are moving through
+        // the pipeline.
         if (target_position_ != kUnspecifiedTime ||
-            target_state_ == State::kPaused) {
-          SetTimelineTransform(1, 0);
+            target_state_ == State::kFlushed ||
+            target_state_ == State::kPrimed) {
+          // Either we want to seek or we want to stop playback. In either case,
+          // we need to enter |kWaiting|, stop the presentation timeline and
+          // transition to |kPrimed| when the operation completes.
           state_ = State::kWaiting;
-          break;
+          timeline_consumer_->SetTimelineTransform(
+              transform_subject_time_, 1, 0,
+              Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
+              [this](bool completed) {
+                state_ = State::kPrimed;
+                // Now we're in |kPrimed|. Call |Update| to see if there's
+                // further
+                // action to be taken.
+                Update();
+              });
+
+          transform_subject_time_ = kUnspecifiedTime;
+          return;
         }
 
         if (end_of_stream_) {
-          target_state_ = State::kPaused;
-          state_ = State::kPaused;
+          // We've reached end of stream. The presentation timeline stops by
+          // itself, so we just need to transition to |kPrimed|.
+          target_state_ = State::kPrimed;
+          state_ = State::kPrimed;
+          // Loop around to check if there's more work to do.
           break;
         }
+
+        // No interesting events to respond to. Done for now.
         return;
 
       case State::kWaiting:
+        // Waiting for some async operation. Nothing to do until it completes.
         return;
     }
   }
 }
 
-void MediaPlayerImpl::WhenPausedAndSeeking() {
-  if (!flushed_) {
-    state_ = State::kWaiting;
-    demux_->Flush([this]() {
-      flushed_ = true;
-      WhenFlushedAndSeeking();
-    });
-  } else {
-    WhenFlushedAndSeeking();
-  }
-}
-
-void MediaPlayerImpl::WhenFlushedAndSeeking() {
-  state_ = State::kWaiting;
-  DCHECK(target_position_ != kUnspecifiedTime);
-  demux_->Seek(target_position_, [this]() {
-    transform_subject_time_ = target_position_;
-    target_position_ = kUnspecifiedTime;
-    state_ = State::kPaused;
-    Update();
-  });
-}
-
-void MediaPlayerImpl::SetTimelineTransform(uint32_t reference_delta,
-                                           uint32_t subject_delta) {
-  timeline_consumer_->SetTimelineTransform(
-      transform_subject_time_, reference_delta, subject_delta,
-      Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
-      [this, subject_delta](bool completed) {
-        RCHECK(state_ == State::kWaiting);
-
-        if (subject_delta == 0) {
-          state_ = State::kPaused;
-        } else {
-          state_ = State::kPlaying;
-        }
-
-        Update();
-      });
-}
-
 void MediaPlayerImpl::GetStatus(uint64_t version_last_seen,
                                 const GetStatusCallback& callback) {
   status_publisher_.Get(version_last_seen, callback);
@@ -190,7 +267,7 @@
 }
 
 void MediaPlayerImpl::Pause() {
-  target_state_ = State::kPaused;
+  target_state_ = State::kPrimed;
   Update();
 }
 
diff --git a/services/media/factory_service/media_player_impl.h b/services/media/factory_service/media_player_impl.h
index 1f915e8..eabd015 100644
--- a/services/media/factory_service/media_player_impl.h
+++ b/services/media/factory_service/media_player_impl.h
@@ -51,8 +51,9 @@
   // Internal state.
   enum class State {
     kWaiting,  // Waiting for some work to complete.
-    kPaused,
-    kPlaying,
+    kFlushed,  // Paused with no data in the pipeline.
+    kPrimed,   // Paused with data in the pipeline.
+    kPlaying,  // Time is progressing.
   };
 
   struct Stream {
@@ -77,17 +78,6 @@
   // Takes action based on current state.
   void Update();
 
-  // Handles seeking in paused state.
-  void WhenPausedAndSeeking();
-
-  // Handles seeking in paused state with flushed pipeline.
-  void WhenFlushedAndSeeking();
-
-  // Sets the timeline transform. transform_subject_time_ is used for the
-  // subject_time, and the effective_reference_time is now plus
-  // kMinimumLeadTime.
-  void SetTimelineTransform(uint32_t reference_delta, uint32_t subject_delta);
-
   // Prepares a stream.
   void PrepareStream(Stream* stream,
                      size_t index,
@@ -117,16 +107,33 @@
   MediaTimelineControlSitePtr timeline_control_site_;
   TimelineConsumerPtr timeline_consumer_;
   std::vector<std::unique_ptr<Stream>> streams_;
+
+  // The state we're currently in.
   State state_ = State::kWaiting;
-  State target_state_ = State::kPaused;
-  bool flushed_ = true;
+
+  // The state we trying to transition to, either because the client has called
+  // |Play| or |Pause| or because we've hit end-of-stream.
+  State target_state_ = State::kFlushed;
+
+  // Whether we're currently at end-of-stream.
   bool end_of_stream_ = false;
+
+  // The position we want to seek to (because the client called Seek) or
+  // kUnspecifiedTime, which indicates there's no desire to seek.
   int64_t target_position_ = kUnspecifiedTime;
+
+  // The subject time to be used for SetTimelineTransform. The value is
+  // kUnspecifiedTime if there's no need to seek or the position we want to
+  // seek to if there is.
   int64_t transform_subject_time_ = kUnspecifiedTime;
+
+  // A function that translates local time into presentation time in ns.
   TimelineFunction timeline_function_;
+
   CallbackJoiner set_transform_joiner_;
   MediaMetadataPtr metadata_;
   MojoPublisher<GetStatusCallback> status_publisher_;
+
   // The following fields are just temporaries used to solve lambda capture
   // problems.
   InterfaceHandle<MediaRenderer> audio_renderer_;