Motown: Implement MediaTimelineController and use it.
Implemented MediaTimelineController, which aggregates multiple
MediaTimelineControlSites, and changed MediaPlayerImpl to use the
controller.

R=kulakowski@chromium.org

Review URL: https://codereview.chromium.org/2009643002 .
diff --git a/examples/media_test/media_test.h b/examples/media_test/media_test.h
index fc17161..ca16de2 100644
--- a/examples/media_test/media_test.h
+++ b/examples/media_test/media_test.h
@@ -8,7 +8,6 @@
 #include "base/macros.h"
 #include "mojo/public/cpp/application/application_impl.h"
 #include "mojo/services/media/common/cpp/timeline_function.h"
-#include "mojo/services/media/common/interfaces/rate_control.mojom.h"
 #include "mojo/services/media/control/interfaces/media_factory.mojom.h"
 #include "mojo/services/media/control/interfaces/media_player.mojom.h"
 
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart
index f729ebb..50b5f37 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/media/media_factory.mojom.dart
@@ -14,6 +14,7 @@
 import 'package:mojo_services/mojo/media/media_type_converter.mojom.dart' as media_type_converter_mojom;
 import 'package:mojo_services/mojo/media/media_types.mojom.dart' as media_types_mojom;
 import 'package:mojo_services/mojo/media/seeking_reader.mojom.dart' as seeking_reader_mojom;
+import 'package:mojo_services/mojo/media/timeline_controller.mojom.dart' as timeline_controller_mojom;
 
 
 
@@ -566,12 +567,84 @@
   }
 }
 
+
+class _MediaFactoryCreateTimelineControllerParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController = null;
+
+  _MediaFactoryCreateTimelineControllerParams() : super(kVersions.last.size);
+
+  static _MediaFactoryCreateTimelineControllerParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _MediaFactoryCreateTimelineControllerParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _MediaFactoryCreateTimelineControllerParams result = new _MediaFactoryCreateTimelineControllerParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.timelineController = decoder0.decodeInterfaceRequest(8, false, timeline_controller_mojom.MediaTimelineControllerStub.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    try {
+      encoder0.encodeInterfaceRequest(timelineController, 8, false);
+    } on bindings.MojoCodecError catch(e) {
+      e.message = "Error encountered while encoding field "
+          "timelineController of struct _MediaFactoryCreateTimelineControllerParams: $e";
+      rethrow;
+    }
+  }
+
+  String toString() {
+    return "_MediaFactoryCreateTimelineControllerParams("
+           "timelineController: $timelineController" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
 const int _mediaFactoryMethodCreatePlayerName = 0;
 const int _mediaFactoryMethodCreateSourceName = 1;
 const int _mediaFactoryMethodCreateSinkName = 2;
 const int _mediaFactoryMethodCreateDemuxName = 3;
 const int _mediaFactoryMethodCreateDecoderName = 4;
 const int _mediaFactoryMethodCreateNetworkReaderName = 5;
+const int _mediaFactoryMethodCreateTimelineControllerName = 6;
 
 class _MediaFactoryServiceDescription implements service_describer.ServiceDescription {
   dynamic getTopLevelInterface([Function responseFactory]) =>
@@ -612,6 +685,7 @@
   void createDemux(seeking_reader_mojom.SeekingReaderInterface reader, media_demux_mojom.MediaDemuxInterfaceRequest demux);
   void createDecoder(media_types_mojom.MediaType inputMediaType, media_type_converter_mojom.MediaTypeConverterInterfaceRequest decoder);
   void createNetworkReader(String url, seeking_reader_mojom.SeekingReaderInterfaceRequest reader);
+  void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController);
 }
 
 abstract class MediaFactoryInterface
@@ -756,6 +830,16 @@
     ctrl.sendMessage(params,
         _mediaFactoryMethodCreateNetworkReaderName);
   }
+  void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController) {
+    if (!ctrl.isBound) {
+      ctrl.proxyError("The Proxy is closed.");
+      return;
+    }
+    var params = new _MediaFactoryCreateTimelineControllerParams();
+    params.timelineController = timelineController;
+    ctrl.sendMessage(params,
+        _mediaFactoryMethodCreateTimelineControllerName);
+  }
 }
 
 class _MediaFactoryStubControl
@@ -821,6 +905,11 @@
             message.payload);
         _impl.createNetworkReader(params.url, params.reader);
         break;
+      case _mediaFactoryMethodCreateTimelineControllerName:
+        var params = _MediaFactoryCreateTimelineControllerParams.deserialize(
+            message.payload);
+        _impl.createTimelineController(params.timelineController);
+        break;
       default:
         throw new bindings.MojoCodecError("Unexpected message name");
         break;
@@ -897,6 +986,9 @@
   void createNetworkReader(String url, seeking_reader_mojom.SeekingReaderInterfaceRequest reader) {
     return impl.createNetworkReader(url, reader);
   }
+  void createTimelineController(timeline_controller_mojom.MediaTimelineControllerInterfaceRequest timelineController) {
+    return impl.createTimelineController(timelineController);
+  }
 }
 
 
diff --git a/mojo/services/media/control/interfaces/media_factory.mojom b/mojo/services/media/control/interfaces/media_factory.mojom
index c171886..6ad372a 100644
--- a/mojo/services/media/control/interfaces/media_factory.mojom
+++ b/mojo/services/media/control/interfaces/media_factory.mojom
@@ -12,6 +12,7 @@
 import "mojo/services/media/core/interfaces/media_demux.mojom";
 import "mojo/services/media/core/interfaces/media_type_converter.mojom";
 import "mojo/services/media/core/interfaces/seeking_reader.mojom";
+import "mojo/services/media/core/interfaces/timeline_controller.mojom";
 
 // Exposed by the factory service to create media-related agents.
 [ServiceName="mojo::media::MediaFactory"]
@@ -42,4 +43,7 @@
 
   // Creates a network reader.
   CreateNetworkReader(string url, SeekingReader& reader);
+
+  // Creates a timeline controller reader.
+  CreateTimelineController(MediaTimelineController& timeline_controller);
 };
diff --git a/services/media/factory_service/BUILD.gn b/services/media/factory_service/BUILD.gn
index 12f60e1..a38ba0d 100644
--- a/services/media/factory_service/BUILD.gn
+++ b/services/media/factory_service/BUILD.gn
@@ -28,6 +28,8 @@
     "media_sink_impl.h",
     "media_source_impl.cc",
     "media_source_impl.h",
+    "media_timeline_controller_impl.cc",
+    "media_timeline_controller_impl.h",
     "network_reader_impl.cc",
     "network_reader_impl.h",
   ]
diff --git a/services/media/factory_service/factory_service.cc b/services/media/factory_service/factory_service.cc
index 94b4c9b..5d87ad4 100644
--- a/services/media/factory_service/factory_service.cc
+++ b/services/media/factory_service/factory_service.cc
@@ -8,6 +8,7 @@
 #include "services/media/factory_service/media_player_impl.h"
 #include "services/media/factory_service/media_sink_impl.h"
 #include "services/media/factory_service/media_source_impl.h"
+#include "services/media/factory_service/media_timeline_controller_impl.h"
 #include "services/media/factory_service/network_reader_impl.h"
 
 namespace mojo {
@@ -64,5 +65,11 @@
   AddProduct(NetworkReaderImpl::Create(url, reader.Pass(), this));
 }
 
+void MediaFactoryService::CreateTimelineController(
+    InterfaceRequest<MediaTimelineController> timeline_controller) {
+  AddProduct(
+      MediaTimelineControllerImpl::Create(timeline_controller.Pass(), this));
+}
+
 }  // namespace media
 }  // namespace mojo
diff --git a/services/media/factory_service/factory_service.h b/services/media/factory_service/factory_service.h
index 8b4f520..c87e271 100644
--- a/services/media/factory_service/factory_service.h
+++ b/services/media/factory_service/factory_service.h
@@ -46,6 +46,9 @@
   void CreateNetworkReader(const String& url,
                            InterfaceRequest<SeekingReader> reader) override;
 
+  void CreateTimelineController(
+      InterfaceRequest<MediaTimelineController> timeline_controller) override;
+
  private:
   BindingSet<MediaFactory> bindings_;
 };
diff --git a/services/media/factory_service/media_player_impl.cc b/services/media/factory_service/media_player_impl.cc
index a963299..dcaa5f6 100644
--- a/services/media/factory_service/media_player_impl.cc
+++ b/services/media/factory_service/media_player_impl.cc
@@ -31,7 +31,7 @@
                                              uint64_t version) {
     MediaPlayerStatusPtr status = MediaPlayerStatus::New();
     status->timeline_transform = TimelineTransform::From(timeline_function_);
-    status->end_of_stream = AllSinksAtEndOfStream();
+    status->end_of_stream = end_of_stream_;
     status->metadata = metadata_.Clone();
     callback.Run(version, status.Pass());
   });
@@ -41,27 +41,29 @@
   ConnectToService(app()->shell(), "mojo:media_factory", GetProxy(&factory_));
 
   factory_->CreateDemux(reader.Pass(), GetProxy(&demux_));
-
   HandleDemuxMetadataUpdates();
 
+  factory_->CreateTimelineController(GetProxy(&timeline_controller_));
+  timeline_controller_->GetControlSite(GetProxy(&timeline_control_site_));
+  timeline_control_site_->GetTimelineConsumer(GetProxy(&timeline_consumer_));
+  HandleTimelineControlSiteStatusUpdates();
+
   demux_->Describe([this](mojo::Array<MediaTypePtr> stream_types) {
     // Populate streams_ and enable the streams we want.
     std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create();
 
     for (MediaTypePtr& stream_type : stream_types) {
-      streams_.push_back(std::unique_ptr<Stream>(
-          new Stream(streams_.size(), stream_type.Pass())));
+      streams_.push_back(std::unique_ptr<Stream>(new Stream()));
       Stream& stream = *streams_.back();
-      switch (stream.media_type_->medium) {
+      switch (stream_type->medium) {
         case MediaTypeMedium::AUDIO:
-          stream.enabled_ = true;
-          PrepareStream(&stream, "mojo:audio_server",
-                        callback_joiner->NewCallback());
+          PrepareStream(&stream, streams_.size() - 1, stream_type,
+                        "mojo:audio_server", callback_joiner->NewCallback());
           break;
         case MediaTypeMedium::VIDEO:
-          stream.enabled_ = true;
           // TODO(dalesat): Send video somewhere.
-          PrepareStream(&stream, "nowhere", callback_joiner->NewCallback());
+          PrepareStream(&stream, streams_.size() - 1, stream_type, "nowhere",
+                        callback_joiner->NewCallback());
           break;
         // TODO(dalesat): Enable other stream types.
         default:
@@ -91,7 +93,7 @@
 
         if (target_state_ == State::kPlaying) {
           if (!flushed_) {
-            SetSinkTimelineTransforms(1, 1);
+            SetTimelineTransform(1, 1);
             state_ = State::kWaiting;
             break;
           }
@@ -99,7 +101,7 @@
           flushed_ = false;
           state_ = State::kWaiting;
           demux_->Prime([this]() {
-            SetSinkTimelineTransforms(1, 1);
+            SetTimelineTransform(1, 1);
             state_ = State::kWaiting;
             Update();
           });
@@ -109,12 +111,12 @@
       case State::kPlaying:
         if (target_position_ != kUnspecifiedTime ||
             target_state_ == State::kPaused) {
-          SetSinkTimelineTransforms(1, 0);
+          SetTimelineTransform(1, 0);
           state_ = State::kWaiting;
           break;
         }
 
-        if (AllSinksAtEndOfStream()) {
+        if (end_of_stream_) {
           target_state_ = State::kPaused;
           state_ = State::kPaused;
           break;
@@ -150,62 +152,22 @@
   });
 }
 
-void MediaPlayerImpl::SetSinkTimelineTransforms(uint32_t reference_delta,
-                                                uint32_t subject_delta) {
-  SetSinkTimelineTransforms(
+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);
-}
+      Timeline::local_now() + kMinimumLeadTime, kUnspecifiedTime,
+      [this, subject_delta](bool completed) {
+        RCHECK(state_ == State::kWaiting);
 
-void MediaPlayerImpl::SetSinkTimelineTransforms(
-    int64_t subject_time,
-    uint32_t reference_delta,
-    uint32_t subject_delta,
-    int64_t effective_reference_time,
-    int64_t effective_subject_time) {
-  std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create();
+        if (subject_delta == 0) {
+          state_ = State::kPaused;
+        } else {
+          state_ = State::kPlaying;
+        }
 
-  for (auto& stream : streams_) {
-    if (stream->enabled_) {
-      DCHECK(stream->timeline_consumer_);
-      callback_joiner->Spawn();
-      stream->timeline_consumer_->SetTimelineTransform(
-          subject_time, reference_delta, subject_delta,
-          effective_reference_time, effective_subject_time,
-          [this, callback_joiner](bool completed) {
-            callback_joiner->Complete();
-          });
-    }
-  }
-
-  transform_subject_time_ = kUnspecifiedTime;
-
-  callback_joiner->WhenJoined([this, subject_delta]() {
-    RCHECK(state_ == State::kWaiting);
-
-    if (subject_delta == 0) {
-      state_ = State::kPaused;
-    } else {
-      state_ = State::kPlaying;
-    }
-
-    Update();
-  });
-}
-
-bool MediaPlayerImpl::AllSinksAtEndOfStream() {
-  int result = false;
-
-  for (auto& stream : streams_) {
-    if (stream->enabled_) {
-      result = stream->end_of_stream_;
-      if (!result) {
-        break;
-      }
-    }
-  }
-
-  return result;
+        Update();
+      });
 }
 
 void MediaPlayerImpl::GetStatus(uint64_t version_last_seen,
@@ -229,19 +191,21 @@
 }
 
 void MediaPlayerImpl::PrepareStream(Stream* stream,
+                                    size_t index,
+                                    const MediaTypePtr& input_media_type,
                                     const String& url,
                                     const std::function<void()>& callback) {
   DCHECK(factory_);
 
-  demux_->GetProducer(stream->index_, GetProxy(&stream->encoded_producer_));
+  demux_->GetProducer(index, GetProxy(&stream->encoded_producer_));
 
-  if (stream->media_type_->encoding != MediaType::kAudioEncodingLpcm &&
-      stream->media_type_->encoding != MediaType::kVideoEncodingUncompressed) {
+  if (input_media_type->encoding != MediaType::kAudioEncodingLpcm &&
+      input_media_type->encoding != MediaType::kVideoEncodingUncompressed) {
     std::shared_ptr<CallbackJoiner> callback_joiner = CallbackJoiner::Create();
 
     // Compressed media. Insert a decoder in front of the sink. The sink would
     // add its own internal decoder, but we want to test the decoder.
-    factory_->CreateDecoder(stream->media_type_.Clone(),
+    factory_->CreateDecoder(input_media_type.Clone(),
                             GetProxy(&stream->decoder_));
 
     MediaConsumerPtr decoder_consumer;
@@ -268,7 +232,7 @@
     // would work for compressed media as well (the sink would decode), but we
     // want to test the decoder.
     stream->decoded_producer_ = stream->encoded_producer_.Pass();
-    CreateSink(stream, stream->media_type_, url, callback);
+    CreateSink(stream, input_media_type, url, callback);
   }
 }
 
@@ -281,13 +245,11 @@
   DCHECK(factory_);
 
   factory_->CreateSink(url, input_media_type.Clone(), GetProxy(&stream->sink_));
-  stream->sink_->GetTimelineControlSite(
-      GetProxy(&stream->timeline_control_site_));
 
-  HandleTimelineControlSiteStatusUpdates(stream);
+  MediaTimelineControlSitePtr timeline_control_site;
+  stream->sink_->GetTimelineControlSite(GetProxy(&timeline_control_site));
 
-  stream->timeline_control_site_->GetTimelineConsumer(
-      GetProxy(&stream->timeline_consumer_));
+  timeline_controller_->AddControlSite(timeline_control_site.Pass());
 
   MediaConsumerPtr consumer;
   stream->sink_->GetConsumer(GetProxy(&consumer));
@@ -313,26 +275,23 @@
 }
 
 void MediaPlayerImpl::HandleTimelineControlSiteStatusUpdates(
-    Stream* stream,
     uint64_t version,
     MediaTimelineControlSiteStatusPtr status) {
   if (status) {
-    // TODO(dalesat): Why does one sink determine timeline_function_?
     timeline_function_ = status->timeline_transform.To<TimelineFunction>();
-    stream->end_of_stream_ = status->end_of_stream;
+    end_of_stream_ = status->end_of_stream;
     status_publisher_.SendUpdates();
     Update();
   }
 
-  stream->timeline_control_site_->GetStatus(
-      version, [this, stream](uint64_t version,
-                              MediaTimelineControlSiteStatusPtr status) {
-        HandleTimelineControlSiteStatusUpdates(stream, version, status.Pass());
+  timeline_control_site_->GetStatus(
+      version,
+      [this](uint64_t version, MediaTimelineControlSiteStatusPtr status) {
+        HandleTimelineControlSiteStatusUpdates(version, status.Pass());
       });
 }
 
-MediaPlayerImpl::Stream::Stream(size_t index, MediaTypePtr media_type)
-    : index_(index), media_type_(media_type.Pass()) {}
+MediaPlayerImpl::Stream::Stream() {}
 
 MediaPlayerImpl::Stream::~Stream() {}
 
diff --git a/services/media/factory_service/media_player_impl.h b/services/media/factory_service/media_player_impl.h
index d9b5be8..6ee1061 100644
--- a/services/media/factory_service/media_player_impl.h
+++ b/services/media/factory_service/media_player_impl.h
@@ -15,6 +15,7 @@
 #include "mojo/services/media/common/interfaces/media_transport.mojom.h"
 #include "mojo/services/media/control/interfaces/media_factory.mojom.h"
 #include "mojo/services/media/core/interfaces/seeking_reader.mojom.h"
+#include "mojo/services/media/core/interfaces/timeline_controller.mojom.h"
 #include "services/media/common/mojo_publisher.h"
 #include "services/media/factory_service/factory_service.h"
 #include "services/media/framework/util/callback_joiner.h"
@@ -54,16 +55,13 @@
   };
 
   struct Stream {
-    Stream(size_t index, MediaTypePtr media_type);
+    Stream();
     ~Stream();
-    size_t index_;
-    bool enabled_ = false;
-    bool end_of_stream_ = false;
-    MediaTypePtr media_type_;
+    // TODO(dalesat): Have the sink enlist the decoder.
     MediaTypeConverterPtr decoder_;
     MediaSinkPtr sink_;
-    MediaTimelineControlSitePtr timeline_control_site_;
-    TimelineConsumerPtr timeline_consumer_;
+    // The following fields are just temporaries used to solve lambda capture
+    // problems.
     MediaProducerPtr encoded_producer_;
     MediaProducerPtr decoded_producer_;
   };
@@ -81,25 +79,15 @@
   // Handles seeking in paused state with flushed pipeline.
   void WhenFlushedAndSeeking();
 
-  // Sets the timeline transforms on all the sinks. transform_subject_time_ is
-  // used for the subject_time, and the effective_reference_time is now plus an
-  // epsilon.
-  void SetSinkTimelineTransforms(uint32_t reference_delta,
-                                 uint32_t subject_delta);
-
-  // Sets the timeline transforms on all the sinks.
-  void SetSinkTimelineTransforms(int64_t subject_time,
-                                 uint32_t reference_delta,
-                                 uint32_t subject_delta,
-                                 int64_t effective_reference_time,
-                                 int64_t effective_subject_time);
-
-  // Determines if all the enabled sinks have reached end-of-stream. Returns
-  // false if there are no enabled streams.
-  bool AllSinksAtEndOfStream();
+  // 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,
+                     const MediaTypePtr& input_media_type,
                      const String& url,
                      const std::function<void()>& callback);
 
@@ -115,19 +103,22 @@
       uint64_t version = MediaDemux::kInitialMetadata,
       MediaMetadataPtr metadata = nullptr);
 
-  // Handles a status update from a control site. When called with the default
+  // Handles a status update from the control site. When called with the default
   // argument values, initiates control site. status updates.
   void HandleTimelineControlSiteStatusUpdates(
-      Stream* stream,
       uint64_t version = MediaTimelineControlSite::kInitialStatus,
       MediaTimelineControlSiteStatusPtr status = nullptr);
 
   MediaFactoryPtr factory_;
   MediaDemuxPtr demux_;
+  MediaTimelineControllerPtr timeline_controller_;
+  MediaTimelineControlSitePtr timeline_control_site_;
+  TimelineConsumerPtr timeline_consumer_;
   std::vector<std::unique_ptr<Stream>> streams_;
   State state_ = State::kWaiting;
   State target_state_ = State::kPaused;
   bool flushed_ = true;
+  bool end_of_stream_ = false;
   int64_t target_position_ = kUnspecifiedTime;
   int64_t transform_subject_time_ = kUnspecifiedTime;
   TimelineFunction timeline_function_;
diff --git a/services/media/factory_service/media_timeline_controller_impl.cc b/services/media/factory_service/media_timeline_controller_impl.cc
new file mode 100644
index 0000000..54bb564
--- /dev/null
+++ b/services/media/factory_service/media_timeline_controller_impl.cc
@@ -0,0 +1,264 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "mojo/services/media/common/cpp/timeline.h"
+#include "services/media/factory_service/media_timeline_controller_impl.h"
+#include "services/media/framework_mojo/mojo_type_conversions.h"
+
+namespace mojo {
+namespace media {
+
+// static
+std::shared_ptr<MediaTimelineControllerImpl>
+MediaTimelineControllerImpl::Create(
+    InterfaceRequest<MediaTimelineController> request,
+    MediaFactoryService* owner) {
+  return std::shared_ptr<MediaTimelineControllerImpl>(
+      new MediaTimelineControllerImpl(request.Pass(), owner));
+}
+
+MediaTimelineControllerImpl::MediaTimelineControllerImpl(
+    InterfaceRequest<MediaTimelineController> request,
+    MediaFactoryService* owner)
+    : MediaFactoryService::Product<MediaTimelineController>(this,
+                                                            request.Pass(),
+                                                            owner),
+      control_site_binding_(this),
+      consumer_binding_(this) {
+  status_publisher_.SetCallbackRunner(
+      [this](const GetStatusCallback& callback, uint64_t version) {
+        MediaTimelineControlSiteStatusPtr status =
+            MediaTimelineControlSiteStatus::New();
+        status->timeline_transform =
+            mojo::TimelineTransform::From(current_timeline_function_);
+        status->end_of_stream = end_of_stream_;
+        callback.Run(version, status.Pass());
+      });
+}
+
+MediaTimelineControllerImpl::~MediaTimelineControllerImpl() {
+  status_publisher_.SendUpdates();
+}
+
+void MediaTimelineControllerImpl::AddControlSite(
+    InterfaceHandle<MediaTimelineControlSite> control_site) {
+  site_states_.emplace_back(
+      this, MediaTimelineControlSitePtr::Create(std::move(control_site)));
+  site_states_.back().HandleStatusUpdates();
+}
+
+void MediaTimelineControllerImpl::GetControlSite(
+    InterfaceRequest<MediaTimelineControlSite> control_site) {
+  if (control_site_binding_.is_bound()) {
+    control_site_binding_.Close();
+  }
+
+  control_site_binding_.Bind(control_site.Pass());
+}
+
+void MediaTimelineControllerImpl::GetStatus(uint64_t version_last_seen,
+                                            const GetStatusCallback& callback) {
+  status_publisher_.Get(version_last_seen, callback);
+}
+
+void MediaTimelineControllerImpl::GetTimelineConsumer(
+    InterfaceRequest<TimelineConsumer> timeline_consumer) {
+  if (consumer_binding_.is_bound()) {
+    consumer_binding_.Close();
+  }
+
+  consumer_binding_.Bind(timeline_consumer.Pass());
+}
+
+void MediaTimelineControllerImpl::SetTimelineTransform(
+    int64_t subject_time,
+    uint32_t reference_delta,
+    uint32_t subject_delta,
+    int64_t effective_reference_time,
+    int64_t effective_subject_time,
+    const SetTimelineTransformCallback& callback) {
+  // At most one effective time may be specified.
+  RCHECK(effective_subject_time == kUnspecifiedTime ||
+         effective_reference_time == kUnspecifiedTime);
+  // effective_subject_time can only be specified if we're progressing.
+  RCHECK(effective_subject_time == kUnspecifiedTime ||
+         current_timeline_function_.subject_delta() != 0u);
+  RCHECK(reference_delta != 0);
+
+  // There can only be once SetTimelineTransform transition pending at any
+  // moment, so a new SetTimelineTransform call that arrives before a previous
+  // one completes cancels the previous one. This causes some problems for us,
+  // because some sites may complete the previous transition while other may
+  // not.
+  //
+  // We start by noticing that there's an incomplete previous transition, and
+  // we 'cancel' it, meaning we call its callback with a false complete
+  // parameter.
+  //
+  // If we're cancelling a previous transition, we need to take steps to make
+  // sure the sites will end up in the right state regardless of whether they
+  // completed the previous transition. We do two things:
+  //
+  // 1) If subject_time isn't specified, we infer it here and supply the
+  //    inferred value to the sites, so there's no disagreement about its
+  //    value.
+  // 2) If the effective_subject_time is specified rather than the
+  //    effective_reference_time, we infer effective_reference_time and send
+  //    it to the sites instead of effective_subject_time, so there's no
+  //    disagreement about effective time and so that no sites reject the
+  //    transition due to having a zero subject_delta.
+
+  std::shared_ptr<TimelineTransition> pending_transition =
+      pending_transition_.lock();
+  if (pending_transition) {
+    // A transition is pending - cancel it.
+    pending_transition->Cancel();
+  }
+
+  // These will be recorded as part of the new TimelineFunction.
+  int64_t new_reference_time;
+  int64_t new_subject_time;
+
+  if (effective_subject_time != kUnspecifiedTime) {
+    // Infer new_reference_time from effective_subject_time.
+    new_reference_time =
+        current_timeline_function_.ApplyInverse(effective_subject_time);
+
+    // Figure out what the subject_time will be after this transition.
+    if (subject_time == kUnspecifiedTime) {
+      new_subject_time = effective_subject_time;
+    } else {
+      new_subject_time = subject_time;
+    }
+  } else {
+    if (effective_reference_time == kUnspecifiedTime) {
+      // Neither effective time was specified. Effective time is now.
+      effective_reference_time = Timeline::local_now() + kDefaultLeadTime;
+    }
+
+    // new_reference_time is just effective_reference_time.
+    new_reference_time = effective_reference_time;
+
+    // Figure out what the subject_time will be after this transition.
+    if (subject_time == kUnspecifiedTime) {
+      new_subject_time = current_timeline_function_(effective_reference_time);
+    } else {
+      new_subject_time = subject_time;
+    }
+  }
+
+  if (pending_transition) {
+    // This transition cancels a previous one. Use effective_reference_time
+    // rather than effective_subject_time, because we can't be sure what
+    // effective_subject_time will mean to the sites.
+    effective_reference_time = new_reference_time;
+    effective_subject_time = kUnspecifiedTime;
+
+    // We don't want the sites to have to infer the subject_time, because we
+    // can't be sure what subject_time a site will infer.
+    subject_time = new_subject_time;
+  }
+
+  // Recording the new pending transition.
+  std::shared_ptr<TimelineTransition> transition =
+      std::shared_ptr<TimelineTransition>(
+          new TimelineTransition(new_reference_time, new_subject_time,
+                                 reference_delta, subject_delta, callback));
+
+  pending_transition_ = transition;
+
+  // Initiate the transition for each site.
+  for (const SiteState& site_state : site_states_) {
+    site_state.consumer_->SetTimelineTransform(
+        subject_time, reference_delta, subject_delta, effective_reference_time,
+        effective_subject_time, transition->NewCallback());
+  }
+
+  // If and when this transition is complete, adopt the new TimelineFunction
+  // and tell any status subscribers.
+  transition->WhenCompleted([this, transition]() {
+    current_timeline_function_ = transition->new_timeline_function();
+    status_publisher_.SendUpdates();
+  });
+}
+
+void MediaTimelineControllerImpl::HandleSiteEndOfStreamChange() {
+  bool end_of_stream = true;
+  for (const SiteState& site_state : site_states_) {
+    if (!site_state.end_of_stream_) {
+      end_of_stream = false;
+      break;
+    }
+  }
+
+  if (end_of_stream_ != end_of_stream) {
+    end_of_stream_ = end_of_stream;
+    status_publisher_.SendUpdates();
+  }
+}
+
+MediaTimelineControllerImpl::SiteState::SiteState(
+    MediaTimelineControllerImpl* parent,
+    MediaTimelineControlSitePtr site)
+    : parent_(parent), site_(site.Pass()) {
+  site_->GetTimelineConsumer(GetProxy(&consumer_));
+}
+
+MediaTimelineControllerImpl::SiteState::SiteState(SiteState&& other)
+    : parent_(other.parent_),
+      site_(other.site_.Pass()),
+      consumer_(other.consumer_.Pass()) {}
+
+MediaTimelineControllerImpl::SiteState::~SiteState() {}
+
+void MediaTimelineControllerImpl::SiteState::HandleStatusUpdates(
+    uint64_t version,
+    MediaTimelineControlSiteStatusPtr status) {
+  if (status) {
+    // Respond to any end-of-stream changes.
+    if (end_of_stream_ != status->end_of_stream) {
+      end_of_stream_ = status->end_of_stream;
+      parent_->HandleSiteEndOfStreamChange();
+    }
+  }
+
+  site_->GetStatus(version, [this](uint64_t version,
+                                   MediaTimelineControlSiteStatusPtr status) {
+    HandleStatusUpdates(version, status.Pass());
+  });
+}
+
+MediaTimelineControllerImpl::TimelineTransition::TimelineTransition(
+    int64_t reference_time,
+    int64_t subject_time,
+    uint32_t reference_delta,
+    uint32_t subject_delta,
+    const SetTimelineTransformCallback& callback)
+    : new_timeline_function_(reference_time,
+                             subject_time,
+                             reference_delta,
+                             subject_delta),
+      callback_(callback) {
+  DCHECK(!callback_.is_null());
+  callback_joiner_.WhenJoined([this]() {
+    if (cancelled_) {
+      DCHECK(callback_.is_null());
+      return;
+    }
+
+    DCHECK(!callback_.is_null());
+    callback_.Run(true);
+    callback_.reset();
+    if (!completed_callback_.is_null()) {
+      completed_callback_.Run();
+      completed_callback_.reset();
+    }
+  });
+}
+
+MediaTimelineControllerImpl::TimelineTransition::~TimelineTransition() {}
+
+}  // namespace media
+}  // namespace mojo
diff --git a/services/media/factory_service/media_timeline_controller_impl.h b/services/media/factory_service/media_timeline_controller_impl.h
new file mode 100644
index 0000000..7dca89d
--- /dev/null
+++ b/services/media/factory_service/media_timeline_controller_impl.h
@@ -0,0 +1,161 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_
+#define MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/services/media/common/cpp/local_time.h"
+#include "mojo/services/media/common/cpp/timeline.h"
+#include "mojo/services/media/common/cpp/timeline_function.h"
+#include "mojo/services/media/core/interfaces/timeline_controller.mojom.h"
+#include "services/media/common/mojo_publisher.h"
+#include "services/media/factory_service/factory_service.h"
+#include "services/media/framework/util/callback_joiner.h"
+
+namespace mojo {
+namespace media {
+
+// Mojo agent that controls timing in a graph.
+class MediaTimelineControllerImpl
+    : public MediaFactoryService::Product<MediaTimelineController>,
+      public MediaTimelineController,
+      public MediaTimelineControlSite,
+      public TimelineConsumer {
+ public:
+  static std::shared_ptr<MediaTimelineControllerImpl> Create(
+      InterfaceRequest<MediaTimelineController> request,
+      MediaFactoryService* owner);
+
+  ~MediaTimelineControllerImpl() override;
+
+  // MediaTimelineController implementation.
+  void AddControlSite(
+      InterfaceHandle<MediaTimelineControlSite> control_site) override;
+
+  void GetControlSite(
+      InterfaceRequest<MediaTimelineControlSite> control_site) override;
+
+  // MediaTimelineControlSite implementation.
+  void GetStatus(uint64_t version_last_seen,
+                 const GetStatusCallback& callback) override;
+
+  void GetTimelineConsumer(
+      InterfaceRequest<TimelineConsumer> timeline_consumer) override;
+
+  // TimelineConsumer implementation.
+  void SetTimelineTransform(
+      int64_t subject_time,
+      uint32_t reference_delta,
+      uint32_t subject_delta,
+      int64_t effective_reference_time,
+      int64_t effective_subject_time,
+      const SetTimelineTransformCallback& callback) override;
+
+ private:
+  static constexpr int64_t kDefaultLeadTime = Timeline::ns_from_ms(30);
+
+  // Relationship to subordinate control site.
+  struct SiteState {
+    SiteState(MediaTimelineControllerImpl* parent,
+              MediaTimelineControlSitePtr site);
+
+    SiteState(SiteState&& other);
+
+    ~SiteState();
+
+    void HandleStatusUpdates(
+        uint64_t version = MediaTimelineControlSite::kInitialStatus,
+        MediaTimelineControlSiteStatusPtr status = nullptr);
+
+    MediaTimelineControllerImpl* parent_;
+    MediaTimelineControlSitePtr site_;
+    TimelineConsumerPtr consumer_;
+    bool end_of_stream_ = false;
+  };
+
+  class TimelineTransition
+      : public std::enable_shared_from_this<TimelineTransition> {
+   public:
+    TimelineTransition(int64_t reference_time,
+                       int64_t subject_time,
+                       uint32_t reference_delta,
+                       uint32_t subject_delta,
+                       const SetTimelineTransformCallback& callback);
+
+    ~TimelineTransition();
+
+    // Calls returns a new callback for a child (site) transition. THIS METHOD
+    // WILL ONLY WORK IF THERE IS ALREADY A SHARED POINTER TO THIS OBJECT.
+    std::function<void(bool)> NewCallback() {
+      callback_joiner_.Spawn();
+
+      std::shared_ptr<TimelineTransition> this_ptr = shared_from_this();
+      DCHECK(!this_ptr.unique());
+
+      return [this_ptr](bool completed) {
+        DCHECK(this_ptr);
+        if (!completed && !this_ptr->cancelled_) {
+          LOG(WARNING) << "A site transition was cancelled unexpectedly.";
+        }
+        this_ptr->callback_joiner_.Complete();
+      };
+    }
+
+    // Cancels this transition.
+    void Cancel() {
+      DCHECK(!cancelled_);
+      cancelled_ = true;
+      DCHECK(callback_.is_null());
+      callback_.Run(false);
+      callback_.reset();
+      completed_callback_.reset();
+    }
+
+    // Specifies a callback to be called if and when the transition is complete.
+    // The callback will never be called if the transition is cancelled.
+    void WhenCompleted(const mojo::Callback<void()>& completed_callback) {
+      DCHECK(completed_callback_.is_null());
+      if (callback_.is_null() && !cancelled_) {
+        completed_callback.Run();
+      } else {
+        completed_callback_ = completed_callback;
+      }
+    }
+
+    // Returns the TimelineFunction that will result from this transition.
+    const TimelineFunction& new_timeline_function() const {
+      return new_timeline_function_;
+    }
+
+   private:
+    TimelineFunction new_timeline_function_;
+    SetTimelineTransformCallback callback_;
+    CallbackJoiner callback_joiner_;
+    bool cancelled_ = false;
+    Callback<void()> completed_callback_;
+  };
+
+  MediaTimelineControllerImpl(InterfaceRequest<MediaTimelineController> request,
+                              MediaFactoryService* owner);
+
+  // Takes action when a site changes its end-of-stream value.
+  void HandleSiteEndOfStreamChange();
+
+  Binding<MediaTimelineControlSite> control_site_binding_;
+  Binding<TimelineConsumer> consumer_binding_;
+  MojoPublisher<GetStatusCallback> status_publisher_;
+  std::vector<SiteState> site_states_;
+  TimelineFunction current_timeline_function_;
+  bool end_of_stream_;
+  std::weak_ptr<TimelineTransition> pending_transition_;
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_MEDIA_FACTORY_TIMELINE_CONTROLLER_IMPL_H_
diff --git a/services/media/framework/util/callback_joiner.h b/services/media/framework/util/callback_joiner.h
index 0c035ba..35fb353 100644
--- a/services/media/framework/util/callback_joiner.h
+++ b/services/media/framework/util/callback_joiner.h
@@ -73,7 +73,7 @@
     DCHECK(counter_ != 0);
     --counter_;
     if (counter_ == 0) {
-      mojo::Callback<void()> join_callback;
+      Callback<void()> join_callback;
       join_callback = join_callback_;
       join_callback_.reset();
       join_callback.Run();
@@ -97,7 +97,7 @@
   // immediately. If child operations are pending, the callback is copied. The
   // copy called later (and reset) when all child oeprations have completed.
   // Only one callback at a time can be registered with WhenJoined.
-  void WhenJoined(const mojo::Callback<void()>& join_callback) {
+  void WhenJoined(const Callback<void()>& join_callback) {
     DCHECK(join_callback_.is_null());
     if (counter_ == 0) {
       join_callback.Run();
@@ -116,7 +116,7 @@
 
  private:
   size_t counter_ = 0;
-  mojo::Callback<void()> join_callback_;
+  Callback<void()> join_callback_;
 };
 
 }  // namespace media
diff --git a/services/media/framework_mojo/mojo_producer.h b/services/media/framework_mojo/mojo_producer.h
index fb8b12a..ed78add 100644
--- a/services/media/framework_mojo/mojo_producer.h
+++ b/services/media/framework_mojo/mojo_producer.h
@@ -8,7 +8,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "mojo/common/binding_set.h"
-#include "mojo/services/media/common/interfaces/media_state.mojom.h"
 #include "mojo/services/media/common/interfaces/media_transport.mojom.h"
 #include "services/media/framework/models/active_sink.h"
 #include "services/media/framework_mojo/mojo_allocator.h"
diff --git a/services/media/framework_mojo/mojo_pull_mode_producer.cc b/services/media/framework_mojo/mojo_pull_mode_producer.cc
index f953aa5..f0101b9 100644
--- a/services/media/framework_mojo/mojo_pull_mode_producer.cc
+++ b/services/media/framework_mojo/mojo_pull_mode_producer.cc
@@ -9,8 +9,7 @@
 namespace media {
 
 MojoPullModeProducer::MojoPullModeProducer()
-    : state_(MediaState::UNPREPARED),
-      demand_(Demand::kNegative),
+    : demand_(Demand::kNegative),
       pts_(0),
       cached_packet_(nullptr) {}
 
@@ -28,13 +27,6 @@
     mojo_allocator_.InitNew(256 * 1024);  // TODO(dalesat): Made up!
   }
 
-  {
-    base::AutoLock lock(lock_);
-    if (state_ == MediaState::UNPREPARED) {
-      state_ = MediaState::PAUSED;
-    }
-  }
-
   callback.Run(mojo_allocator_.GetDuplicateHandle());
 
   DCHECK(!cached_packet_);
@@ -52,11 +44,11 @@
   {
     base::AutoLock lock(lock_);
 
-    if (state_ == MediaState::UNPREPARED) {
+    //if (state_ == MediaState::UNPREPARED) {
       // The consumer has yet to call GetBuffer. This request will have to wait.
-      pending_pulls_.push_back(callback);
-      return;
-    }
+    //  pending_pulls_.push_back(callback);
+    //  return;
+    //}
 
     DCHECK(mojo_allocator_.initialized());
 
@@ -111,7 +103,6 @@
 Demand MojoPullModeProducer::SupplyPacket(PacketPtr packet) {
   base::AutoLock lock(lock_);
   DCHECK(demand_ != Demand::kNegative) << "packet pushed with negative demand";
-  DCHECK(state_ != MediaState::ENDED) << "packet pushed after end-of-stream";
 
   DCHECK(!cached_packet_);
 
@@ -119,7 +110,6 @@
   // happen if a pull client disconnects unexpectedly.
   if (bindings_.size() == 0) {
     demand_ = Demand::kNegative;
-    state_ = MediaState::UNPREPARED;
     // TODO(dalesat): More shutdown?
     return demand_;
   }
@@ -152,11 +142,11 @@
   DCHECK(!callback.is_null());
   lock_.AssertAcquired();
 
-  if (state_ == MediaState::ENDED) {
+  //if (state_ == MediaState::ENDED) {
     // At end-of-stream. Respond with empty end-of-stream packet.
-    HandlePullWithPacketUnsafe(callback, Packet::CreateEndOfStream(pts_));
-    return true;
-  }
+  //  HandlePullWithPacketUnsafe(callback, Packet::CreateEndOfStream(pts_));
+  //  return true;
+  //}
 
   if (!cached_packet_) {
     // Waiting for packet or end-of-stream indication.
diff --git a/services/media/framework_mojo/mojo_pull_mode_producer.h b/services/media/framework_mojo/mojo_pull_mode_producer.h
index 0d27a74..741458c 100644
--- a/services/media/framework_mojo/mojo_pull_mode_producer.h
+++ b/services/media/framework_mojo/mojo_pull_mode_producer.h
@@ -9,7 +9,6 @@
 
 #include "base/synchronization/lock.h"
 #include "mojo/common/binding_set.h"
-#include "mojo/services/media/common/interfaces/media_state.mojom.h"
 #include "mojo/services/media/common/interfaces/media_transport.mojom.h"
 #include "services/media/framework/models/active_sink.h"
 #include "services/media/framework_mojo/mojo_allocator.h"
@@ -72,7 +71,6 @@
 
   mutable base::Lock lock_;
   // THE FIELDS BELOW SHOULD ONLY BE ACCESSED WITH lock_ TAKEN.
-  MediaState state_;
   Demand demand_;
   int64_t pts_;