Motown: Use new TimelineTransform and related definitions
1) Deleted rate_control.mojom and replaced with timelines.mojom and
   timeline_controller.mojom.
2) Removed redundant functionality from MediaTimelineController etc.
3) Deleted rate_control_base.* rate control implementation and replaced
   with timeline_control_site.* timeline control site implementation.
4) Moved mojo_publisher from factory_service to common so it can be used
   by timeline_control_site.
5) Updated many dependencies thereof.

R=kulakowski@chromium.org

Review URL: https://codereview.chromium.org/1986303002 .
diff --git a/examples/audio_play_test/BUILD.gn b/examples/audio_play_test/BUILD.gn
index a710e28..bda93ca 100644
--- a/examples/audio_play_test/BUILD.gn
+++ b/examples/audio_play_test/BUILD.gn
@@ -19,6 +19,7 @@
     "//mojo/services/media/audio/interfaces",
     "//mojo/services/media/common/cpp",
     "//mojo/services/media/common/interfaces",
+    "//mojo/services/media/core/interfaces",
     "//mojo/services/network/interfaces",
   ]
 
@@ -34,6 +35,7 @@
     "//mojo/services/media/audio/interfaces",
     "//mojo/services/media/common/cpp",
     "//mojo/services/media/common/interfaces",
+    "//mojo/services/media/core/interfaces",
   ]
 
   sources = [
diff --git a/examples/audio_play_test/play_tone.cc b/examples/audio_play_test/play_tone.cc
index 3a0ebc1..025bdbb 100644
--- a/examples/audio_play_test/play_tone.cc
+++ b/examples/audio_play_test/play_tone.cc
@@ -16,7 +16,7 @@
 #include "mojo/services/media/common/cpp/circular_buffer_media_pipe_adapter.h"
 #include "mojo/services/media/common/cpp/linear_transform.h"
 #include "mojo/services/media/common/cpp/local_time.h"
-#include "mojo/services/media/common/interfaces/rate_control.mojom.h"
+#include "mojo/services/media/common/interfaces/timelines.mojom.h"
 
 namespace mojo {
 namespace media {
@@ -52,8 +52,8 @@
   void OnConnectionError(const std::string& connection_name);
 
   AudioServerPtr audio_server_;
-  AudioTrackPtr  audio_track_;
-  RateControlPtr rate_control_;
+  AudioTrackPtr audio_track_;
+  TimelineConsumerPtr timeline_consumer_;
   std::unique_ptr<CircularBufferMediaPipeAdapter> audio_pipe_;
 
   bool     clock_started_ = false;
@@ -64,7 +64,7 @@
 };
 
 void PlayToneApp::Quit() {
-  rate_control_.reset();
+  timeline_consumer_.reset();
   audio_pipe_.reset();
   audio_track_.reset();
   audio_server_.reset();
@@ -115,11 +115,12 @@
   // TODO(johngro): do something useful with our capabilities description.
   sink_desc.reset();
 
-  // Grab the rate control interface for our audio renderer.
-  audio_track_->GetRateControl(GetProxy(&rate_control_));
-  rate_control_.set_connection_error_handler([this]() {
-    OnConnectionError("rate_control");
-  });
+  // Grab the timeline consumer interface for our audio renderer.
+  MediaTimelineControlSitePtr timeline_control_site;
+  audio_track_->GetTimelineControlSite(GetProxy(&timeline_control_site));
+  timeline_control_site->GetTimelineConsumer(GetProxy(&timeline_consumer_));
+  timeline_consumer_.set_connection_error_handler(
+      [this]() { OnConnectionError("timeline_consumer"); });
 
   // Configure our sink for 16-bit 48KHz mono.
   AudioTrackConfigurationPtr cfg = AudioTrackConfiguration::New();
@@ -204,20 +205,11 @@
   }
 
   if (!clock_started_) {
-    // In theory, this could be done at compile time using std::ratio, but
-    // std::ratio is prohibited.
-    LinearTransform::Ratio audio_rate(SAMP_FREQ, 1);
-    LinearTransform::Ratio local_time_rate(LocalDuration::period::num,
-                                           LocalDuration::period::den);
-    LinearTransform::Ratio rate;
-    bool success = LinearTransform::Ratio::Compose(local_time_rate,
-                                                   audio_rate,
-                                                   &rate);
-    MOJO_DCHECK(success);  // assert that there was no loss of precision.
+    MOJO_LOG(INFO) << "Setting rate 1/1";
 
-    MOJO_LOG(INFO) << "Setting rate " << rate;
-
-    rate_control_->SetRate(rate.numerator, rate.denominator);
+    timeline_consumer_->SetTimelineTransform(
+        kUnspecifiedTime, 1, 1, kUnspecifiedTime, kUnspecifiedTime,
+        [](bool completed) {});
     clock_started_ = true;
   }
 }
diff --git a/examples/audio_play_test/play_wav.cc b/examples/audio_play_test/play_wav.cc
index d5dc2e1..f5f11ea 100644
--- a/examples/audio_play_test/play_wav.cc
+++ b/examples/audio_play_test/play_wav.cc
@@ -128,7 +128,7 @@
   AudioServerPtr               audio_server_;
   AudioTrackPtr                audio_track_;
   AudioPipePtr                 audio_pipe_;
-  RateControlPtr               rate_control_;
+  TimelineConsumerPtr          timeline_consumer_;
   AudioPacket                  audio_packet_;
   PacketCbk                    playout_complete_cbk_;
   NetworkServicePtr            network_service_;
@@ -196,7 +196,7 @@
   url_loader_.reset();
   network_service_.reset();
   audio_pipe_.reset();
-  rate_control_.reset();
+  timeline_consumer_.reset();
   audio_track_.reset();
   audio_server_.reset();
 }
@@ -290,11 +290,12 @@
   MediaConsumerPtr media_pipe;
   audio_track_->Configure(cfg.Pass(), GetProxy(&media_pipe));
 
-  // Grab the rate control interface for our audio renderer.
-  audio_track_->GetRateControl(GetProxy(&rate_control_));
-  rate_control_.set_connection_error_handler([this]() {
-    OnConnectionError("rate_control");
-  });
+  // Grab the timeline consumer interface for our audio renderer.
+  MediaTimelineControlSitePtr timeline_control_site;
+  audio_track_->GetTimelineControlSite(GetProxy(&timeline_control_site));
+  timeline_control_site->GetTimelineConsumer(GetProxy(&timeline_consumer_));
+  timeline_consumer_.set_connection_error_handler(
+      [this]() { OnConnectionError("timeline_consumer"); });
 
   // Set up our media pipe helper, configure its callback and water marks to
   // kick off the playback process.
@@ -501,8 +502,9 @@
   }
 
   if (!clock_started_ && (audio_pipe_->AboveHiWater() || !payload_len_)) {
-    LocalTime sched = LocalClock::now() + local_time::from_msec(50);
-    rate_control_->SetRateAtTargetTime(1, 1, sched.time_since_epoch().count());
+    timeline_consumer_->SetTimelineTransform(
+        kUnspecifiedTime, 1, 1, kUnspecifiedTime, kUnspecifiedTime,
+        [](bool completed) {});
     clock_started_ = true;
   }
 }
diff --git a/examples/media_test/media_test.cc b/examples/media_test/media_test.cc
index 3e78fe4..c1f0d19 100644
--- a/examples/media_test/media_test.cc
+++ b/examples/media_test/media_test.cc
@@ -4,8 +4,8 @@
 
 #include "examples/media_test/media_test.h"
 #include "mojo/public/cpp/application/connect.h"
-#include "mojo/services/media/common/cpp/linear_transform.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/control/interfaces/media_factory.mojom.h"
 
 namespace mojo {
@@ -35,10 +35,8 @@
 MediaTest::~MediaTest() {}
 
 int64_t MediaTest::position_ns() const {
-  // Apply the transform to the current time.
-  int64_t position;
-  transform_.DoForwardTransform(LocalClock::now().time_since_epoch().count(),
-                                &position);
+  // Apply the timeline function to the current time.
+  int64_t position = timeline_function_(Timeline::local_now());
 
   if (position < 0) {
     position = 0;
@@ -62,15 +60,8 @@
     previous_state_ = state_;
     state_ = status->state;
 
-    // Create a linear transform that translates local time to presentation
-    // time. Note that 'reference' here refers to the presentation time, and
-    // 'target' refers to the local time.
     if (status->timeline_transform) {
-      transform_ =
-          LinearTransform(status->timeline_transform->quad->target_offset,
-                          status->timeline_transform->quad->reference_delta,
-                          status->timeline_transform->quad->target_delta,
-                          status->timeline_transform->quad->reference_offset);
+      timeline_function_ = status->timeline_transform.To<TimelineFunction>();
     }
 
     metadata_ = status->metadata.Pass();
diff --git a/examples/media_test/media_test.h b/examples/media_test/media_test.h
index 00ce340..da5473e 100644
--- a/examples/media_test/media_test.h
+++ b/examples/media_test/media_test.h
@@ -7,7 +7,7 @@
 
 #include "base/macros.h"
 #include "mojo/public/cpp/application/application_impl.h"
-#include "mojo/services/media/common/cpp/linear_transform.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"
@@ -63,7 +63,7 @@
   MediaPlayerPtr media_player_;
   MediaState previous_state_ = MediaState::UNPREPARED;
   MediaState state_ = MediaState::UNPREPARED;
-  LinearTransform transform_ = LinearTransform(0, 0, 1, 0);
+  TimelineFunction timeline_function_;
   MediaMetadataPtr metadata_;
   UpdateCallback update_callback_;
 
diff --git a/mojo/dart/packages/mojo_services/BUILD.gn b/mojo/dart/packages/mojo_services/BUILD.gn
index 80dc70c..28cd6fd 100644
--- a/mojo/dart/packages/mojo_services/BUILD.gn
+++ b/mojo/dart/packages/mojo_services/BUILD.gn
@@ -63,7 +63,6 @@
   "lib/mojo/media/media_transport.mojom.dart",
   "lib/mojo/media/media_type_converter.mojom.dart",
   "lib/mojo/media/media_types.mojom.dart",
-  "lib/mojo/media/rate_control.mojom.dart",
   "lib/mojo/media/timeline_controller.mojom.dart",
   "lib/mojo/native_viewport_event_dispatcher.mojom.dart",
   "lib/mojo/navigation.mojom.dart",
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/audio_track.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/audio_track.mojom.dart
index f559530..df9cd3e 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/media/audio_track.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/media/audio_track.mojom.dart
@@ -9,7 +9,7 @@
 import 'package:mojo/mojo/bindings/types/service_describer.mojom.dart' as service_describer;
 import 'package:mojo_services/mojo/media/media_transport.mojom.dart' as media_transport_mojom;
 import 'package:mojo_services/mojo/media/media_types.mojom.dart' as media_types_mojom;
-import 'package:mojo_services/mojo/media/rate_control.mojom.dart' as rate_control_mojom;
+import 'package:mojo_services/mojo/media/timeline_controller.mojom.dart' as timeline_controller_mojom;
 
 
 
@@ -418,15 +418,15 @@
 }
 
 
-class _AudioTrackGetRateControlParams extends bindings.Struct {
+class _AudioTrackGetTimelineControlSiteParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
   ];
-  rate_control_mojom.RateControlInterfaceRequest rateControl = null;
+  Object timelineControlSite = null;
 
-  _AudioTrackGetRateControlParams() : super(kVersions.last.size);
+  _AudioTrackGetTimelineControlSiteParams() : super(kVersions.last.size);
 
-  static _AudioTrackGetRateControlParams deserialize(bindings.Message message) {
+  static _AudioTrackGetTimelineControlSiteParams deserialize(bindings.Message message) {
     var decoder = new bindings.Decoder(message);
     var result = decode(decoder);
     if (decoder.excessHandles != null) {
@@ -435,11 +435,11 @@
     return result;
   }
 
-  static _AudioTrackGetRateControlParams decode(bindings.Decoder decoder0) {
+  static _AudioTrackGetTimelineControlSiteParams decode(bindings.Decoder decoder0) {
     if (decoder0 == null) {
       return null;
     }
-    _AudioTrackGetRateControlParams result = new _AudioTrackGetRateControlParams();
+    _AudioTrackGetTimelineControlSiteParams result = new _AudioTrackGetTimelineControlSiteParams();
 
     var mainDataHeader = decoder0.decodeStructDataHeader();
     if (mainDataHeader.version <= kVersions.last.version) {
@@ -461,7 +461,7 @@
     }
     if (mainDataHeader.version >= 0) {
       
-      result.rateControl = decoder0.decodeInterfaceRequest(8, false, rate_control_mojom.RateControlStub.newFromEndpoint);
+      result.timelineControlSite = decoder0.decodeInterfaceRequest(8, false, timeline_controller_mojom.MediaTimelineControlSiteStub.newFromEndpoint);
     }
     return result;
   }
@@ -469,17 +469,17 @@
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
     try {
-      encoder0.encodeInterfaceRequest(rateControl, 8, false);
+      encoder0.encodeInterfaceRequest(timelineControlSite, 8, false);
     } on bindings.MojoCodecError catch(e) {
       e.message = "Error encountered while encoding field "
-          "rateControl of struct _AudioTrackGetRateControlParams: $e";
+          "timelineControlSite of struct _AudioTrackGetTimelineControlSiteParams: $e";
       rethrow;
     }
   }
 
   String toString() {
-    return "_AudioTrackGetRateControlParams("
-           "rateControl: $rateControl" ")";
+    return "_AudioTrackGetTimelineControlSiteParams("
+           "timelineControlSite: $timelineControlSite" ")";
   }
 
   Map toJson() {
@@ -562,7 +562,7 @@
 
 const int _audioTrackMethodDescribeName = 0;
 const int _audioTrackMethodConfigureName = 1;
-const int _audioTrackMethodGetRateControlName = 2;
+const int _audioTrackMethodGetTimelineControlSiteName = 2;
 const int _audioTrackMethodSetGainName = 3;
 
 class _AudioTrackServiceDescription implements service_describer.ServiceDescription {
@@ -599,8 +599,8 @@
     return p;
   }
   dynamic describe([Function responseFactory = null]);
-  void configure(AudioTrackConfiguration configuration, media_transport_mojom.MediaConsumerInterfaceRequest pipe);
-  void getRateControl(rate_control_mojom.RateControlInterfaceRequest rateControl);
+  void configure(AudioTrackConfiguration configuration, Object pipe);
+  void getTimelineControlSite(Object timelineControlSite);
   void setGain(double dbGain);
   static const double kMutedGain = -160.0;
   static const double kMaxGain = 20.0;
@@ -719,15 +719,15 @@
     ctrl.sendMessage(params,
         _audioTrackMethodConfigureName);
   }
-  void getRateControl(rate_control_mojom.RateControlInterfaceRequest rateControl) {
+  void getTimelineControlSite(Object timelineControlSite) {
     if (!ctrl.isBound) {
       ctrl.proxyError("The Proxy is closed.");
       return;
     }
-    var params = new _AudioTrackGetRateControlParams();
-    params.rateControl = rateControl;
+    var params = new _AudioTrackGetTimelineControlSiteParams();
+    params.timelineControlSite = timelineControlSite;
     ctrl.sendMessage(params,
-        _audioTrackMethodGetRateControlName);
+        _audioTrackMethodGetTimelineControlSiteName);
   }
   void setGain(double dbGain) {
     if (!ctrl.isBound) {
@@ -804,10 +804,10 @@
             message.payload);
         _impl.configure(params.configuration, params.pipe);
         break;
-      case _audioTrackMethodGetRateControlName:
-        var params = _AudioTrackGetRateControlParams.deserialize(
+      case _audioTrackMethodGetTimelineControlSiteName:
+        var params = _AudioTrackGetTimelineControlSiteParams.deserialize(
             message.payload);
-        _impl.getRateControl(params.rateControl);
+        _impl.getTimelineControlSite(params.timelineControlSite);
         break;
       case _audioTrackMethodSetGainName:
         var params = _AudioTrackSetGainParams.deserialize(
@@ -878,8 +878,8 @@
   void configure(AudioTrackConfiguration configuration, media_transport_mojom.MediaConsumerInterfaceRequest pipe) {
     return impl.configure(configuration, pipe);
   }
-  void getRateControl(rate_control_mojom.RateControlInterfaceRequest rateControl) {
-    return impl.getRateControl(rateControl);
+  void getTimelineControlSite(Object timelineControlSite) {
+    return impl.getTimelineControlSite(timelineControlSite);
   }
   void setGain(double dbGain) {
     return impl.setGain(dbGain);
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/media_player.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/media_player.mojom.dart
index 5ab0c9e..d570d96 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/media/media_player.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/media/media_player.mojom.dart
@@ -9,7 +9,7 @@
 import 'package:mojo/mojo/bindings/types/service_describer.mojom.dart' as service_describer;
 import 'package:mojo_services/mojo/media/media_metadata.mojom.dart' as media_metadata_mojom;
 import 'package:mojo_services/mojo/media/media_state.mojom.dart' as media_state_mojom;
-import 'package:mojo_services/mojo/media/rate_control.mojom.dart' as rate_control_mojom;
+import 'package:mojo_services/mojo/timelines.mojom.dart' as timelines_mojom;
 
 
 
@@ -18,7 +18,7 @@
     const bindings.StructDataHeader(32, 0)
   ];
   media_state_mojom.MediaState state = null;
-  rate_control_mojom.TimelineTransform timelineTransform = null;
+  timelines_mojom.TimelineTransform timelineTransform = null;
   media_metadata_mojom.MediaMetadata metadata = null;
 
   MediaPlayerStatus() : super(kVersions.last.size);
@@ -67,7 +67,7 @@
     if (mainDataHeader.version >= 0) {
       
       var decoder1 = decoder0.decodePointer(16, true);
-      result.timelineTransform = rate_control_mojom.TimelineTransform.decode(decoder1);
+      result.timelineTransform = timelines_mojom.TimelineTransform.decode(decoder1);
     }
     if (mainDataHeader.version >= 0) {
       
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/media_sink.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/media_sink.mojom.dart
index b21bfb3..6b73e31 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/media/media_sink.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/media/media_sink.mojom.dart
@@ -9,7 +9,7 @@
 import 'package:mojo/mojo/bindings/types/service_describer.mojom.dart' as service_describer;
 import 'package:mojo_services/mojo/media/media_state.mojom.dart' as media_state_mojom;
 import 'package:mojo_services/mojo/media/media_transport.mojom.dart' as media_transport_mojom;
-import 'package:mojo_services/mojo/media/rate_control.mojom.dart' as rate_control_mojom;
+import 'package:mojo_services/mojo/timelines.mojom.dart' as timelines_mojom;
 
 
 
@@ -18,7 +18,7 @@
     const bindings.StructDataHeader(24, 0)
   ];
   media_state_mojom.MediaState state = null;
-  rate_control_mojom.TimelineTransform timelineTransform = null;
+  timelines_mojom.TimelineTransform timelineTransform = null;
 
   MediaSinkStatus() : super(kVersions.last.size);
 
@@ -66,7 +66,7 @@
     if (mainDataHeader.version >= 0) {
       
       var decoder1 = decoder0.decodePointer(16, true);
-      result.timelineTransform = rate_control_mojom.TimelineTransform.decode(decoder1);
+      result.timelineTransform = timelines_mojom.TimelineTransform.decode(decoder1);
     }
     return result;
   }
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/media/timeline_controller.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/media/timeline_controller.mojom.dart
index 4ab5371..8ea67ec 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/media/timeline_controller.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/media/timeline_controller.mojom.dart
@@ -11,100 +11,13 @@
 
 
 
-class MediaTimelineControllerStatus extends bindings.Struct {
+class MediaTimelineControlSiteStatus extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(24, 0)
   ];
   timelines_mojom.TimelineTransform timelineTransform = null;
   bool endOfStream = false;
 
-  MediaTimelineControllerStatus() : super(kVersions.last.size);
-
-  static MediaTimelineControllerStatus 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 MediaTimelineControllerStatus decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    MediaTimelineControllerStatus result = new MediaTimelineControllerStatus();
-
-    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) {
-      
-      var decoder1 = decoder0.decodePointer(8, false);
-      result.timelineTransform = timelines_mojom.TimelineTransform.decode(decoder1);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.endOfStream = decoder0.decodeBool(16, 0);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    try {
-      encoder0.encodeStruct(timelineTransform, 8, false);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "timelineTransform of struct MediaTimelineControllerStatus: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeBool(endOfStream, 16, 0);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "endOfStream of struct MediaTimelineControllerStatus: $e";
-      rethrow;
-    }
-  }
-
-  String toString() {
-    return "MediaTimelineControllerStatus("
-           "timelineTransform: $timelineTransform" ", "
-           "endOfStream: $endOfStream" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["timelineTransform"] = timelineTransform;
-    map["endOfStream"] = endOfStream;
-    return map;
-  }
-}
-
-
-class MediaTimelineControlSiteStatus extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(16, 0)
-  ];
-  bool endOfStream = false;
-  bool starving = false;
-
   MediaTimelineControlSiteStatus() : super(kVersions.last.size);
 
   static MediaTimelineControlSiteStatus deserialize(bindings.Message message) {
@@ -142,11 +55,12 @@
     }
     if (mainDataHeader.version >= 0) {
       
-      result.endOfStream = decoder0.decodeBool(8, 0);
+      var decoder1 = decoder0.decodePointer(8, false);
+      result.timelineTransform = timelines_mojom.TimelineTransform.decode(decoder1);
     }
     if (mainDataHeader.version >= 0) {
       
-      result.starving = decoder0.decodeBool(8, 1);
+      result.endOfStream = decoder0.decodeBool(16, 0);
     }
     return result;
   }
@@ -154,31 +68,31 @@
   void encode(bindings.Encoder encoder) {
     var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
     try {
-      encoder0.encodeBool(endOfStream, 8, 0);
+      encoder0.encodeStruct(timelineTransform, 8, false);
     } on bindings.MojoCodecError catch(e) {
       e.message = "Error encountered while encoding field "
-          "endOfStream of struct MediaTimelineControlSiteStatus: $e";
+          "timelineTransform of struct MediaTimelineControlSiteStatus: $e";
       rethrow;
     }
     try {
-      encoder0.encodeBool(starving, 8, 1);
+      encoder0.encodeBool(endOfStream, 16, 0);
     } on bindings.MojoCodecError catch(e) {
       e.message = "Error encountered while encoding field "
-          "starving of struct MediaTimelineControlSiteStatus: $e";
+          "endOfStream of struct MediaTimelineControlSiteStatus: $e";
       rethrow;
     }
   }
 
   String toString() {
     return "MediaTimelineControlSiteStatus("
-           "endOfStream: $endOfStream" ", "
-           "starving: $starving" ")";
+           "timelineTransform: $timelineTransform" ", "
+           "endOfStream: $endOfStream" ")";
   }
 
   Map toJson() {
     Map map = new Map();
+    map["timelineTransform"] = timelineTransform;
     map["endOfStream"] = endOfStream;
-    map["starving"] = starving;
     return map;
   }
 }
@@ -255,365 +169,6 @@
 }
 
 
-class _MediaTimelineControllerGetStatusParams extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(16, 0)
-  ];
-  int versionLastSeen = 0;
-
-  _MediaTimelineControllerGetStatusParams() : super(kVersions.last.size);
-
-  static _MediaTimelineControllerGetStatusParams 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 _MediaTimelineControllerGetStatusParams decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    _MediaTimelineControllerGetStatusParams result = new _MediaTimelineControllerGetStatusParams();
-
-    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.versionLastSeen = decoder0.decodeUint64(8);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    try {
-      encoder0.encodeUint64(versionLastSeen, 8);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "versionLastSeen of struct _MediaTimelineControllerGetStatusParams: $e";
-      rethrow;
-    }
-  }
-
-  String toString() {
-    return "_MediaTimelineControllerGetStatusParams("
-           "versionLastSeen: $versionLastSeen" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["versionLastSeen"] = versionLastSeen;
-    return map;
-  }
-}
-
-
-class MediaTimelineControllerGetStatusResponseParams extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(24, 0)
-  ];
-  int version = 0;
-  MediaTimelineControllerStatus status = null;
-
-  MediaTimelineControllerGetStatusResponseParams() : super(kVersions.last.size);
-
-  static MediaTimelineControllerGetStatusResponseParams 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 MediaTimelineControllerGetStatusResponseParams decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    MediaTimelineControllerGetStatusResponseParams result = new MediaTimelineControllerGetStatusResponseParams();
-
-    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.version = decoder0.decodeUint64(8);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      var decoder1 = decoder0.decodePointer(16, false);
-      result.status = MediaTimelineControllerStatus.decode(decoder1);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    try {
-      encoder0.encodeUint64(version, 8);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "version of struct MediaTimelineControllerGetStatusResponseParams: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeStruct(status, 16, false);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "status of struct MediaTimelineControllerGetStatusResponseParams: $e";
-      rethrow;
-    }
-  }
-
-  String toString() {
-    return "MediaTimelineControllerGetStatusResponseParams("
-           "version: $version" ", "
-           "status: $status" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["version"] = version;
-    map["status"] = status;
-    return map;
-  }
-}
-
-
-class _MediaTimelineControllerSetTimelineTransformParams extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(40, 0)
-  ];
-  int subjectTime = 0;
-  int subjectDelta = 0;
-  int referenceDelta = 0;
-  int effectiveSubjectTime = 0;
-  int effectiveReferenceTime = 0;
-
-  _MediaTimelineControllerSetTimelineTransformParams() : super(kVersions.last.size);
-
-  static _MediaTimelineControllerSetTimelineTransformParams 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 _MediaTimelineControllerSetTimelineTransformParams decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    _MediaTimelineControllerSetTimelineTransformParams result = new _MediaTimelineControllerSetTimelineTransformParams();
-
-    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.subjectTime = decoder0.decodeInt64(8);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.subjectDelta = decoder0.decodeUint32(16);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.referenceDelta = decoder0.decodeUint32(20);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.effectiveSubjectTime = decoder0.decodeInt64(24);
-    }
-    if (mainDataHeader.version >= 0) {
-      
-      result.effectiveReferenceTime = decoder0.decodeInt64(32);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    try {
-      encoder0.encodeInt64(subjectTime, 8);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "subjectTime of struct _MediaTimelineControllerSetTimelineTransformParams: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeUint32(subjectDelta, 16);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "subjectDelta of struct _MediaTimelineControllerSetTimelineTransformParams: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeUint32(referenceDelta, 20);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "referenceDelta of struct _MediaTimelineControllerSetTimelineTransformParams: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeInt64(effectiveSubjectTime, 24);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "effectiveSubjectTime of struct _MediaTimelineControllerSetTimelineTransformParams: $e";
-      rethrow;
-    }
-    try {
-      encoder0.encodeInt64(effectiveReferenceTime, 32);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "effectiveReferenceTime of struct _MediaTimelineControllerSetTimelineTransformParams: $e";
-      rethrow;
-    }
-  }
-
-  String toString() {
-    return "_MediaTimelineControllerSetTimelineTransformParams("
-           "subjectTime: $subjectTime" ", "
-           "subjectDelta: $subjectDelta" ", "
-           "referenceDelta: $referenceDelta" ", "
-           "effectiveSubjectTime: $effectiveSubjectTime" ", "
-           "effectiveReferenceTime: $effectiveReferenceTime" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["subjectTime"] = subjectTime;
-    map["subjectDelta"] = subjectDelta;
-    map["referenceDelta"] = referenceDelta;
-    map["effectiveSubjectTime"] = effectiveSubjectTime;
-    map["effectiveReferenceTime"] = effectiveReferenceTime;
-    return map;
-  }
-}
-
-
-class MediaTimelineControllerSetTimelineTransformResponseParams extends bindings.Struct {
-  static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(16, 0)
-  ];
-  bool completed = false;
-
-  MediaTimelineControllerSetTimelineTransformResponseParams() : super(kVersions.last.size);
-
-  static MediaTimelineControllerSetTimelineTransformResponseParams 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 MediaTimelineControllerSetTimelineTransformResponseParams decode(bindings.Decoder decoder0) {
-    if (decoder0 == null) {
-      return null;
-    }
-    MediaTimelineControllerSetTimelineTransformResponseParams result = new MediaTimelineControllerSetTimelineTransformResponseParams();
-
-    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.completed = decoder0.decodeBool(8, 0);
-    }
-    return result;
-  }
-
-  void encode(bindings.Encoder encoder) {
-    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
-    try {
-      encoder0.encodeBool(completed, 8, 0);
-    } on bindings.MojoCodecError catch(e) {
-      e.message = "Error encountered while encoding field "
-          "completed of struct MediaTimelineControllerSetTimelineTransformResponseParams: $e";
-      rethrow;
-    }
-  }
-
-  String toString() {
-    return "MediaTimelineControllerSetTimelineTransformResponseParams("
-           "completed: $completed" ")";
-  }
-
-  Map toJson() {
-    Map map = new Map();
-    map["completed"] = completed;
-    return map;
-  }
-}
-
-
 class _MediaTimelineControllerGetControlSiteParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -915,9 +470,7 @@
 }
 
 const int _mediaTimelineControllerMethodAddControlSiteName = 0;
-const int _mediaTimelineControllerMethodGetStatusName = 1;
-const int _mediaTimelineControllerMethodSetTimelineTransformName = 2;
-const int _mediaTimelineControllerMethodGetControlSiteName = 3;
+const int _mediaTimelineControllerMethodGetControlSiteName = 1;
 
 class _MediaTimelineControllerServiceDescription implements service_describer.ServiceDescription {
   dynamic getTopLevelInterface([Function responseFactory]) =>
@@ -932,50 +485,8 @@
 
 abstract class MediaTimelineController {
   static const String serviceName = null;
-
-  static service_describer.ServiceDescription _cachedServiceDescription;
-  static service_describer.ServiceDescription get serviceDescription {
-    if (_cachedServiceDescription == null) {
-      _cachedServiceDescription = new _MediaTimelineControllerServiceDescription();
-    }
-    return _cachedServiceDescription;
-  }
-
-  static MediaTimelineControllerProxy connectToService(
-      bindings.ServiceConnector s, String url, [String serviceName]) {
-    MediaTimelineControllerProxy p = new MediaTimelineControllerProxy.unbound();
-    String name = serviceName ?? MediaTimelineController.serviceName;
-    if ((name == null) || name.isEmpty) {
-      throw new core.MojoApiError(
-          "If an interface has no ServiceName, then one must be provided.");
-    }
-    s.connectToService(url, p, name);
-    return p;
-  }
-  void addControlSite(MediaTimelineControlSiteInterface controlSite);
-  dynamic getStatus(int versionLastSeen,[Function responseFactory = null]);
-  dynamic setTimelineTransform(int subjectTime,int subjectDelta,int referenceDelta,int effectiveSubjectTime,int effectiveReferenceTime,[Function responseFactory = null]);
-  void getControlSite(MediaTimelineControlSiteInterfaceRequest controlSite);
-  static const int kUnspecifiedTime = 9223372036854775807;
-  static const int kInitialStatus = 0;
-}
-
-abstract class MediaTimelineControllerInterface
-    implements bindings.MojoInterface<MediaTimelineController>,
-               MediaTimelineController {
-  factory MediaTimelineControllerInterface([MediaTimelineController impl]) =>
-      new MediaTimelineControllerStub.unbound(impl);
-  factory MediaTimelineControllerInterface.fromEndpoint(
-      core.MojoMessagePipeEndpoint endpoint,
-      [MediaTimelineController impl]) =>
-      new MediaTimelineControllerStub.fromEndpoint(endpoint, impl);
-}
-
-abstract class MediaTimelineControllerInterfaceRequest
-    implements bindings.MojoInterface<MediaTimelineController>,
-               MediaTimelineController {
-  factory MediaTimelineControllerInterfaceRequest() =>
-      new MediaTimelineControllerProxy.unbound();
+  void addControlSite(Object controlSite);
+  void getControlSite(Object controlSite);
 }
 
 class _MediaTimelineControllerProxyControl
@@ -993,46 +504,6 @@
 
   void handleResponse(bindings.ServiceMessage message) {
     switch (message.header.type) {
-      case _mediaTimelineControllerMethodGetStatusName:
-        var r = MediaTimelineControllerGetStatusResponseParams.deserialize(
-            message.payload);
-        if (!message.header.hasRequestId) {
-          proxyError("Expected a message with a valid request Id.");
-          return;
-        }
-        Completer c = completerMap[message.header.requestId];
-        if (c == null) {
-          proxyError(
-              "Message had unknown request Id: ${message.header.requestId}");
-          return;
-        }
-        completerMap.remove(message.header.requestId);
-        if (c.isCompleted) {
-          proxyError("Response completer already completed");
-          return;
-        }
-        c.complete(r);
-        break;
-      case _mediaTimelineControllerMethodSetTimelineTransformName:
-        var r = MediaTimelineControllerSetTimelineTransformResponseParams.deserialize(
-            message.payload);
-        if (!message.header.hasRequestId) {
-          proxyError("Expected a message with a valid request Id.");
-          return;
-        }
-        Completer c = completerMap[message.header.requestId];
-        if (c == null) {
-          proxyError(
-              "Message had unknown request Id: ${message.header.requestId}");
-          return;
-        }
-        completerMap.remove(message.header.requestId);
-        if (c.isCompleted) {
-          proxyError("Response completer already completed");
-          return;
-        }
-        c.complete(r);
-        break;
       default:
         proxyError("Unexpected message type: ${message.header.type}");
         close(immediate: true);
@@ -1084,29 +555,7 @@
     ctrl.sendMessage(params,
         _mediaTimelineControllerMethodAddControlSiteName);
   }
-  dynamic getStatus(int versionLastSeen,[Function responseFactory = null]) {
-    var params = new _MediaTimelineControllerGetStatusParams();
-    params.versionLastSeen = versionLastSeen;
-    return ctrl.sendMessageWithRequestId(
-        params,
-        _mediaTimelineControllerMethodGetStatusName,
-        -1,
-        bindings.MessageHeader.kMessageExpectsResponse);
-  }
-  dynamic setTimelineTransform(int subjectTime,int subjectDelta,int referenceDelta,int effectiveSubjectTime,int effectiveReferenceTime,[Function responseFactory = null]) {
-    var params = new _MediaTimelineControllerSetTimelineTransformParams();
-    params.subjectTime = subjectTime;
-    params.subjectDelta = subjectDelta;
-    params.referenceDelta = referenceDelta;
-    params.effectiveSubjectTime = effectiveSubjectTime;
-    params.effectiveReferenceTime = effectiveReferenceTime;
-    return ctrl.sendMessageWithRequestId(
-        params,
-        _mediaTimelineControllerMethodSetTimelineTransformName,
-        -1,
-        bindings.MessageHeader.kMessageExpectsResponse);
-  }
-  void getControlSite(MediaTimelineControlSiteInterfaceRequest controlSite) {
+  void getControlSite(Object controlSite) {
     if (!ctrl.isBound) {
       ctrl.proxyError("The Proxy is closed.");
       return;
@@ -1140,17 +589,6 @@
   String get serviceName => MediaTimelineController.serviceName;
 
 
-  MediaTimelineControllerGetStatusResponseParams _mediaTimelineControllerGetStatusResponseParamsFactory(int version, MediaTimelineControllerStatus status) {
-    var result = new MediaTimelineControllerGetStatusResponseParams();
-    result.version = version;
-    result.status = status;
-    return result;
-  }
-  MediaTimelineControllerSetTimelineTransformResponseParams _mediaTimelineControllerSetTimelineTransformResponseParamsFactory(bool completed) {
-    var result = new MediaTimelineControllerSetTimelineTransformResponseParams();
-    result.completed = completed;
-    return result;
-  }
 
   dynamic handleMessage(bindings.ServiceMessage message) {
     if (bindings.ControlMessageHandler.isControlMessage(message)) {
@@ -1167,50 +605,6 @@
             message.payload);
         _impl.addControlSite(params.controlSite);
         break;
-      case _mediaTimelineControllerMethodGetStatusName:
-        var params = _MediaTimelineControllerGetStatusParams.deserialize(
-            message.payload);
-        var response = _impl.getStatus(params.versionLastSeen,_mediaTimelineControllerGetStatusResponseParamsFactory);
-        if (response is Future) {
-          return response.then((response) {
-            if (response != null) {
-              return buildResponseWithId(
-                  response,
-                  _mediaTimelineControllerMethodGetStatusName,
-                  message.header.requestId,
-                  bindings.MessageHeader.kMessageIsResponse);
-            }
-          });
-        } else if (response != null) {
-          return buildResponseWithId(
-              response,
-              _mediaTimelineControllerMethodGetStatusName,
-              message.header.requestId,
-              bindings.MessageHeader.kMessageIsResponse);
-        }
-        break;
-      case _mediaTimelineControllerMethodSetTimelineTransformName:
-        var params = _MediaTimelineControllerSetTimelineTransformParams.deserialize(
-            message.payload);
-        var response = _impl.setTimelineTransform(params.subjectTime,params.subjectDelta,params.referenceDelta,params.effectiveSubjectTime,params.effectiveReferenceTime,_mediaTimelineControllerSetTimelineTransformResponseParamsFactory);
-        if (response is Future) {
-          return response.then((response) {
-            if (response != null) {
-              return buildResponseWithId(
-                  response,
-                  _mediaTimelineControllerMethodSetTimelineTransformName,
-                  message.header.requestId,
-                  bindings.MessageHeader.kMessageIsResponse);
-            }
-          });
-        } else if (response != null) {
-          return buildResponseWithId(
-              response,
-              _mediaTimelineControllerMethodSetTimelineTransformName,
-              message.header.requestId,
-              bindings.MessageHeader.kMessageIsResponse);
-        }
-        break;
       case _mediaTimelineControllerMethodGetControlSiteName:
         var params = _MediaTimelineControllerGetControlSiteParams.deserialize(
             message.payload);
@@ -1277,13 +671,7 @@
   void addControlSite(MediaTimelineControlSiteInterface controlSite) {
     return impl.addControlSite(controlSite);
   }
-  dynamic getStatus(int versionLastSeen,[Function responseFactory = null]) {
-    return impl.getStatus(versionLastSeen,responseFactory);
-  }
-  dynamic setTimelineTransform(int subjectTime,int subjectDelta,int referenceDelta,int effectiveSubjectTime,int effectiveReferenceTime,[Function responseFactory = null]) {
-    return impl.setTimelineTransform(subjectTime,subjectDelta,referenceDelta,effectiveSubjectTime,effectiveReferenceTime,responseFactory);
-  }
-  void getControlSite(MediaTimelineControlSiteInterfaceRequest controlSite) {
+  void getControlSite(Object controlSite) {
     return impl.getControlSite(controlSite);
   }
 }
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/timelines.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/timelines.mojom.dart
index 639bb64..6381061 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/timelines.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/timelines.mojom.dart
@@ -7,6 +7,7 @@
 import 'package:mojo/bindings.dart' as bindings;
 import 'package:mojo/core.dart' as core;
 import 'package:mojo/mojo/bindings/types/service_describer.mojom.dart' as service_describer;
+const int kUnspecifiedTime = 9223372036854775807;
 
 
 
@@ -359,7 +360,6 @@
     return p;
   }
   dynamic setTimelineTransform(int subjectTime,int referenceDelta,int subjectDelta,int effectiveReferenceTime,int effectiveSubjectTime,[Function responseFactory = null]);
-  static const int kUnspecifiedTime = 9223372036854775807;
 }
 
 abstract class TimelineConsumerInterface
diff --git a/mojo/services/media/audio/interfaces/audio_track.mojom b/mojo/services/media/audio/interfaces/audio_track.mojom
index b9f251e..b5cf803 100644
--- a/mojo/services/media/audio/interfaces/audio_track.mojom
+++ b/mojo/services/media/audio/interfaces/audio_track.mojom
@@ -8,7 +8,7 @@
 import "mojo/services/media/common/interfaces/media_common.mojom";
 import "mojo/services/media/common/interfaces/media_transport.mojom";
 import "mojo/services/media/common/interfaces/media_types.mojom";
-import "mojo/services/media/common/interfaces/rate_control.mojom";
+import "mojo/services/media/core/interfaces/timeline_controller.mojom";
 
 struct AudioTrackDescriptor {
   // The track supports the union of all these media type sets.
@@ -50,8 +50,8 @@
   // Set the configuration, receive a pipe to send data to in return.
   Configure(AudioTrackConfiguration configuration, MediaConsumer& pipe);
 
-  // Request the rate control interface for this AudioTrack
-  GetRateControl(RateControl& rate_control);
+  // Request the timeline control site for this AudioTrack
+  GetTimelineControlSite(MediaTimelineControlSite& timeline_control_site);
 
   // Sets the current gain/attenuation of the track, expressed in dB.  Legal
   // values are in the range [-inf, 20.0].  Any value less than or equal to the
diff --git a/mojo/services/media/common/cpp/BUILD.gn b/mojo/services/media/common/cpp/BUILD.gn
index c2f6b70..42272a0 100644
--- a/mojo/services/media/common/cpp/BUILD.gn
+++ b/mojo/services/media/common/cpp/BUILD.gn
@@ -19,6 +19,7 @@
     "mapped_shared_buffer.h",
     "shared_media_buffer_allocator.cc",
     "shared_media_buffer_allocator.h",
+    "timeline.h",
     "timeline_function.cc",
     "timeline_function.h",
     "timeline_rate.cc",
diff --git a/mojo/services/media/common/cpp/timeline.h b/mojo/services/media/common/cpp/timeline.h
new file mode 100644
index 0000000..4fc3651
--- /dev/null
+++ b/mojo/services/media/common/cpp/timeline.h
@@ -0,0 +1,53 @@
+// 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_COMMON_CPP_TIMELINE_H_
+#define MOJO_SERVICES_MEDIA_COMMON_CPP_TIMELINE_H_
+
+#include <stdint.h>
+#include <chrono>  // NOLINT(build/c++11)
+
+// TODO(johngro): As we add support for other environments, extend this list.
+#if defined(OS_POSIX)
+#include "mojo/services/media/common/cpp/platform/posix/local_time.h"
+#else
+// TODO(johngro): consider adding a #warning or #info to inform the user that
+// they are using the generic implementation of LocalTime, and really should get
+// around to implementing proper platform support ASAP.
+#include "mojo/services/media/common/cpp/platform/generic/local_time.h"
+#endif
+
+namespace mojo {
+namespace media {
+
+// Some helpful constants and static methods relating to timelines.
+class Timeline {
+ public:
+  // Returns the current local time in nanoseconds since epoch.
+  static int64_t local_now() {
+    return local_time::Clock::now().time_since_epoch().count();
+  }
+
+  template <typename T>
+  static constexpr int64_t ns_from_seconds(T seconds) {
+    return static_cast<int64_t>(seconds * std::nano::den);
+  }
+
+  template <typename T>
+  static constexpr int64_t ns_from_ms(T milliseconds) {
+    return static_cast<int64_t>(milliseconds *
+                                (std::nano::den / std::milli::den));
+  }
+
+  template <typename T>
+  static constexpr int64_t ns_from_us(T microseconds) {
+    return static_cast<int64_t>(microseconds *
+                                (std::nano::den / std::micro::den));
+  }
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_MEDIA_COMMON_CPP_TIMELINE_H_
diff --git a/mojo/services/media/common/cpp/timeline_function.cc b/mojo/services/media/common/cpp/timeline_function.cc
index 350fde6..f2b1ec1 100644
--- a/mojo/services/media/common/cpp/timeline_function.cc
+++ b/mojo/services/media/common/cpp/timeline_function.cc
@@ -29,4 +29,25 @@
 }
 
 }  // namespace media
+
+TimelineTransformPtr
+TypeConverter<TimelineTransformPtr, media::TimelineFunction>::Convert(
+    const media::TimelineFunction& input) {
+  TimelineTransformPtr result = TimelineTransform::New();
+  result->reference_time = input.reference_time();
+  result->subject_time = input.subject_time();
+  result->reference_delta = input.reference_delta();
+  result->subject_delta = input.subject_delta();
+  return result;
+}
+
+media::TimelineFunction
+TypeConverter<media::TimelineFunction, TimelineTransformPtr>::Convert(
+    const TimelineTransformPtr& input) {
+  return input ? media::TimelineFunction(
+                     input->reference_time, input->subject_time,
+                     input->reference_delta, input->subject_delta)
+               : media::TimelineFunction();
+}
+
 }  // namespace mojo
diff --git a/mojo/services/media/common/cpp/timeline_function.h b/mojo/services/media/common/cpp/timeline_function.h
index 67ac80a..075ee01 100644
--- a/mojo/services/media/common/cpp/timeline_function.h
+++ b/mojo/services/media/common/cpp/timeline_function.h
@@ -5,8 +5,10 @@
 #ifndef MOJO_SERVICES_MEDIA_COMMON_CPP_TIMELINE_FUNCTION_H_
 #define MOJO_SERVICES_MEDIA_COMMON_CPP_TIMELINE_FUNCTION_H_
 
+#include "mojo/public/cpp/bindings/type_converter.h"
 #include "mojo/public/cpp/environment/logging.h"
 #include "mojo/services/media/common/cpp/timeline_rate.h"
+#include "mojo/services/media/common/interfaces/timelines.mojom.h"
 
 namespace mojo {
 namespace media {
@@ -128,6 +130,19 @@
 }
 
 }  // namespace media
+
+template <>
+struct TypeConverter<TimelineTransformPtr, media::TimelineFunction> {
+  static TimelineTransformPtr Convert(
+      const media::TimelineFunction& input);
+};
+
+template <>
+struct TypeConverter<media::TimelineFunction, TimelineTransformPtr> {
+  static media::TimelineFunction Convert(
+      const TimelineTransformPtr& input);
+};
+
 }  // namespace mojo
 
 #endif  // MOJO_SERVICES_MEDIA_COMMON_CPP_TIMELINE_FUNCTION_H_
diff --git a/mojo/services/media/common/cpp/timeline_rate.h b/mojo/services/media/common/cpp/timeline_rate.h
index 7aa0216..4718f1f 100644
--- a/mojo/services/media/common/cpp/timeline_rate.h
+++ b/mojo/services/media/common/cpp/timeline_rate.h
@@ -62,6 +62,25 @@
   explicit TimelineRate(uint32_t subject_delta)
       : subject_delta_(subject_delta), reference_delta_(1) {}
 
+  explicit TimelineRate(float rate_as_float)
+      : subject_delta_(
+            rate_as_float > 1.0f
+                ? kFloatFactor
+                : static_cast<uint32_t>(kFloatFactor * rate_as_float)),
+        reference_delta_(
+            rate_as_float > 1.0f
+                ? static_cast<uint32_t>(kFloatFactor / rate_as_float)
+                : kFloatFactor) {
+    // The expressions above are intended to provide good precision for
+    // 'reasonable' playback rate values (say in the range 0.0 to 4.0). The
+    // expressions always produce a ratio of kFloatFactor and a number smaller
+    // than kFloatFactor. kFloatFactor's value was chosen because floats have
+    // a 23-bit mantissa, and operations with a larger factor would sacrifice
+    // precision.
+    MOJO_DCHECK(rate_as_float >= 0.0f);
+    Reduce(&subject_delta_, &reference_delta_);
+  }
+
   TimelineRate(uint32_t subject_delta, uint32_t reference_delta)
       : subject_delta_(subject_delta), reference_delta_(reference_delta) {
     MOJO_DCHECK(reference_delta != 0);
@@ -84,6 +103,10 @@
   uint32_t reference_delta() const { return reference_delta_; }
 
  private:
+  // A multiplier for float-to-TimelineRate conversions chosen because floats
+  // have a 23-bit mantissa.
+  static constexpr uint32_t kFloatFactor = 1ul << 23;
+
   uint32_t subject_delta_;
   uint32_t reference_delta_;
 };
diff --git a/mojo/services/media/common/interfaces/BUILD.gn b/mojo/services/media/common/interfaces/BUILD.gn
index 109f979..ffdb131 100644
--- a/mojo/services/media/common/interfaces/BUILD.gn
+++ b/mojo/services/media/common/interfaces/BUILD.gn
@@ -12,7 +12,6 @@
     "media_state.mojom",
     "media_transport.mojom",
     "media_types.mojom",
-    "rate_control.mojom",
     "timelines.mojom",
   ]
 }
diff --git a/mojo/services/media/common/interfaces/rate_control.mojom b/mojo/services/media/common/interfaces/rate_control.mojom
deleted file mode 100644
index bd7dd92..0000000
--- a/mojo/services/media/common/interfaces/rate_control.mojom
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2015 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.
-
-[DartPackage="mojo_services"]
-module mojo.media;
-
-// TimelineQuad
-// TODO(dalesat): Rename reference -> presentation.
-// TODO(dalesat): Rename target -> reference.
-//
-// A structure which holds the four numbers needed to define a linear
-// relationship between the points in two different timelines.  The relationship
-// is expressed using 4 integers in order to facilitate compositions of multiple
-// transformations (A->B can be composed with B->C to produce the transformation
-// from A->C), and to minimize rounding and scaling errors when mapping points
-// between timelines which do not have an integer scaling relationship between
-// each other.
-//
-// These values are used to define the following functions which map from
-// points from the reference timeline to the target timeline, and back again.
-//
-// Let r be a point in the reference timeline.
-// Let t be a point in the target timeline timeline.
-// Let ref and tgt be abbreviations for reference and target in equations below.
-//
-// Given that r and t represent the same instant in time (in a single frame of
-// reference)
-//
-// t = f(r) = (((r - ref_offset) * tgt_delta) / ref_delta) + tgt_offset
-// r = F(t) = (((t - tgt_offset) * ref_delta) / tgt_delta) + ref_offset
-//
-// See also...
-// mojo/services/media/common/linear_transform.h
-//
-// no-format
-struct TimelineQuad {
-  int64  reference_offset = 0;
-  int64  target_offset    = 0;
-  uint32 reference_delta  = 0;
-  uint32 target_delta     = 1;
-};
-// end-no-format
-
-// TimelineTransform
-// TODO(dalesat): Rename reference -> presentation.
-// TODO(dalesat): Rename target -> reference.
-//
-// A structure which holds both a timeline quad, and a pair of identifiers which
-// define the specific timelines which are the reference and target timelines.
-struct TimelineTransform {
-  // TODO: These constants should probably defined by a central time management
-  // service, not here.
-  const uint32 kLocalTimeID = 0xFFFFFFFF;
-  const uint32 kContextual = 0xFFFFFFFE;
-
-  TimelineQuad quad;
-  uint32 reference_timeline_id = kContextual;
-  uint32 target_timeline_id = kLocalTimeID;
-};
-
-// RateControl
-//
-// An interface typically exposed by media renderers which allow producers of
-// media to specify how the presentation time stamps of the media queued to the
-// renderer relate to real time.  Users may initialize the transformation with a
-// specific Quad, change the rate immediately in a first order contiguous
-// fashion, or schedule ranges in the rate at points in time on either the
-// reference or target timelines.
-interface RateControl {
-  // Get the current quad which describes the transformation between the
-  // reference and target timelines.
-  GetCurrentTransform() => (TimelineTransform trans);
-
-  // Immediately, explicitly set the quad which describes the mapping from
-  // reference to target timeline.  It is understood that this can cause
-  // discontinuities and should only be used in situations which are already
-  // fundamentally discontinuous (startup/seeking, for example)
-  SetCurrentQuad(TimelineQuad quad);
-
-  // Configure the target timeline ID.  Note, the reference timeline ID will
-  // always be contextual.
-  SetTargetTimelineID(uint32 id);
-
-  // Immediately change the rate of the existing transformation in a fashion
-  // which is first order continuous with the current transformation.
-  SetRate(uint32 reference_delta, uint32 target_delta);
-
-  // Schedule a first order continuous rate change at the specified reference
-  // time.
-  SetRateAtReferenceTime(uint32 reference_delta,
-                         uint32 target_delta,
-                         int64 reference_time);
-
-  // Schedule a first order continuous rate change at the specified target time.
-  SetRateAtTargetTime(uint32 reference_delta,
-                      uint32 target_delta,
-                      int64 target_time);
-
-  // Cancel any pending rate changes
-  CancelPendingChanges();
-};
diff --git a/mojo/services/media/common/interfaces/timelines.mojom b/mojo/services/media/common/interfaces/timelines.mojom
index edee74d..39e4aef 100644
--- a/mojo/services/media/common/interfaces/timelines.mojom
+++ b/mojo/services/media/common/interfaces/timelines.mojom
@@ -7,6 +7,9 @@
 
 // TODO(dalesat): Move out of media to somewhere more generic.
 
+// Used as a placefolder for unspecified time values.
+const int64 kUnspecifiedTime = 0x7fffffffffffffff;
+
 // Represents the relationship between and subject timeline and a reference
 // timeline.
 //
@@ -37,8 +40,6 @@
 
 // A push-mode consumer of timeline updates.
 interface TimelineConsumer {
-  const int64 kUnspecifiedTime = 0x7fffffffffffffff;
-
   // Sets the timeline transform at the indicated effective time. Exactly one
   // of the effective_*_time values must be kUnspecifiedTime.
   // effective_subject_time can only be specified if the current subject_delta
diff --git a/mojo/services/media/control/interfaces/media_player.mojom b/mojo/services/media/control/interfaces/media_player.mojom
index b612c17..49f4aa4 100644
--- a/mojo/services/media/control/interfaces/media_player.mojom
+++ b/mojo/services/media/control/interfaces/media_player.mojom
@@ -7,7 +7,7 @@
 
 import "mojo/services/media/common/interfaces/media_metadata.mojom";
 import "mojo/services/media/common/interfaces/media_state.mojom";
-import "mojo/services/media/common/interfaces/rate_control.mojom";
+import "mojo/services/media/common/interfaces/timelines.mojom";
 
 // Plays media.
 interface MediaPlayer {
@@ -40,7 +40,7 @@
 
   // Transform translating local time to presentation time. Reverse translation
   // (presentation time to local time) is only valid when media is playing.
-  TimelineTransform? timeline_transform;
+  mojo.TimelineTransform? timeline_transform;
 
   // Describes the media.
   MediaMetadata? metadata;
diff --git a/mojo/services/media/control/interfaces/media_sink.mojom b/mojo/services/media/control/interfaces/media_sink.mojom
index 45268cc..ac6c311 100644
--- a/mojo/services/media/control/interfaces/media_sink.mojom
+++ b/mojo/services/media/control/interfaces/media_sink.mojom
@@ -9,7 +9,7 @@
 import "mojo/services/media/common/interfaces/media_state.mojom";
 import "mojo/services/media/common/interfaces/media_transport.mojom";
 import "mojo/services/media/common/interfaces/media_types.mojom";
-import "mojo/services/media/common/interfaces/rate_control.mojom";
+import "mojo/services/media/common/interfaces/timelines.mojom";
 
 // TODO(dalesat): Define a media sink that multiplexes streams.
 
@@ -44,5 +44,5 @@
 
   // Transform translating local time to presentation time. Reverse translation
   // (presentation time to local time) is only valid when media is playing.
-  TimelineTransform? timeline_transform;
+  mojo.TimelineTransform? timeline_transform;
 };
diff --git a/mojo/services/media/core/interfaces/timeline_controller.mojom b/mojo/services/media/core/interfaces/timeline_controller.mojom
index 895b5d5..b18bea0 100644
--- a/mojo/services/media/core/interfaces/timeline_controller.mojom
+++ b/mojo/services/media/core/interfaces/timeline_controller.mojom
@@ -9,50 +9,13 @@
 
 // Timing controller for a media graph.
 interface MediaTimelineController {
-  const int64 kUnspecifiedTime = 0x7fffffffffffffff;
-  const uint64 kInitialStatus = 0;
-
   // Associates a control site with the controller.
   AddControlSite(MediaTimelineControlSite control_site);
 
-  // Gets the status. To get the status immediately, call
-  // GetStatus(kInitialStatus). To get updates thereafter, pass
-  // the version sent in the previous callback.
-  GetStatus(uint64 version_last_seen)
-      => (uint64 version, MediaTimelineControllerStatus status);
-
-  // Sets the timeline transform at the indicated effective time. At least one
-  // of the effective_*_time values must be kUnspecifiedTime. If both are
-  // kUnspecifiedTime, the requested change is implemented as soon as possible.
-  // effective_subject_time can only be specified if the current subject_delta
-  // isn’t zero. reference_delta may not be zero. subject_time may be
-  // kUnspecifiedTime to indicate that the new transform subject_time should
-  // be inferred from the effective time. The reference time for the new
-  // transform (the reference time that will correspond to the specified or
-  // inferred subject_time) is always inferred from the effective time. The
-  // callback is called at the effective time or when a pending operation is
-  // cancelled due to a subsequent call, in which case the 'completed' value is
-  // false.
-  SetTimelineTransform(
-      int64 subject_time,
-      uint32 subject_delta,
-      uint32 reference_delta,
-      int64 effective_subject_time,
-      int64 effective_reference_time) => (bool completed);
-
   // Gets a timeline control site interface for the controller.
   GetControlSite(MediaTimelineControlSite& control_site);
 };
 
-// Status returned by MediaTimelineController's GetStatus method.
-struct MediaTimelineControllerStatus {
-  // Current timeline transform.
-  mojo.TimelineTransform timeline_transform;
-
-  // Whether end of stream was encountered.
-  bool end_of_stream;
-};
-
 // Media graph component controlled by a MediaTimelineController.
 interface MediaTimelineControlSite {
   const uint64 kInitialStatus = 0;
@@ -69,9 +32,9 @@
 
 // Status returned by MediaTimelineControlSite's GetStatus method.
 struct MediaTimelineControlSiteStatus {
-  // Whether end of stream was encountered.
-  bool end_of_stream;
+  // Current timeline transform.
+  mojo.TimelineTransform timeline_transform;
 
-  // Whether the site is starving.
-  bool starving;
+  // Indicates whether presentation has reached end-of-stream.
+  bool end_of_stream;
 };
diff --git a/services/media/audio/audio_track_impl.cc b/services/media/audio/audio_track_impl.cc
index 7b9a8e0..87f3418 100644
--- a/services/media/audio/audio_track_impl.cc
+++ b/services/media/audio/audio_track_impl.cc
@@ -7,6 +7,7 @@
 
 #include "base/logging.h"
 #include "mojo/services/media/common/cpp/linear_transform.h"
+#include "mojo/services/media/common/cpp/timeline.h"
 #include "services/media/audio/audio_output_manager.h"
 #include "services/media/audio/audio_server_impl.h"
 #include "services/media/audio/audio_track_impl.h"
@@ -72,7 +73,7 @@
   // for the service to destroy us.  Run some DCHECK sanity checks and get out.
   if (!binding_.is_bound()) {
     DCHECK(!pipe_.IsInitialized());
-    DCHECK(!rate_control_.is_bound());
+    DCHECK(!timeline_control_site_.is_bound());
     DCHECK(!outputs_.size());
     return;
   }
@@ -84,7 +85,7 @@
   // reset all of our internal state and close any other client connections in
   // the process.
   pipe_.Reset();
-  rate_control_.Reset();
+  timeline_control_site_.Reset();
   outputs_.clear();
 
   DCHECK(owner_);
@@ -193,6 +194,8 @@
     return;
   }
 
+  frames_per_ns_ =
+      TimelineRate(cfg->frames_per_second, Timeline::ns_from_seconds(1));
 
   // Figure out the rate we need to scale by in order to produce our fixed
   // point timestamps.
@@ -262,10 +265,9 @@
   owner_->GetOutputManager().SelectOutputsForTrack(strong_this);
 }
 
-void AudioTrackImpl::GetRateControl(InterfaceRequest<RateControl> req) {
-  if (!rate_control_.Bind(req.Pass())) {
-    Shutdown();
-  }
+void AudioTrackImpl::GetTimelineControlSite(
+    InterfaceRequest<MediaTimelineControlSite> req) {
+  timeline_control_site_.Bind(req.Pass());
 }
 
 void AudioTrackImpl::SetGain(float db_gain) {
@@ -306,6 +308,23 @@
   }
 }
 
+void AudioTrackImpl::SnapshotRateTrans(LinearTransform* out,
+                                       uint32_t* generation) {
+  TimelineFunction timeline_function;
+  timeline_control_site_.SnapshotCurrentFunction(
+      Timeline::local_now(), &timeline_function, generation);
+
+  // The control site works in ns units. We want the rate in frames per
+  // nanosecond, so we convert here.
+  TimelineRate rate_in_frames_per_ns =
+      timeline_function.rate() * frames_per_ns_;
+
+  *out = LinearTransform(timeline_function.reference_time(),
+                         rate_in_frames_per_ns.subject_delta(),
+                         rate_in_frames_per_ns.reference_delta(),
+                         timeline_function.subject_time() * frames_per_ns_);
+}
+
 void AudioTrackImpl::OnPacketReceived(AudioPipe::AudioPacketRefPtr packet) {
   DCHECK(packet);
   for (const auto& output : outputs_) {
diff --git a/services/media/audio/audio_track_impl.h b/services/media/audio/audio_track_impl.h
index f282849..71ec3e1 100644
--- a/services/media/audio/audio_track_impl.h
+++ b/services/media/audio/audio_track_impl.h
@@ -15,7 +15,7 @@
 #include "mojo/services/media/common/cpp/linear_transform.h"
 #include "services/media/audio/audio_pipe.h"
 #include "services/media/audio/fwd_decls.h"
-#include "services/media/common/rate_control_base.h"
+#include "services/media/common/timeline_control_site.h"
 
 namespace mojo {
 namespace media {
@@ -41,9 +41,7 @@
 
   // Accessors used by AudioOutputs during mixing to access parameters which are
   // important for the mixing process.
-  void SnapshotRateTrans(LinearTransform* out, uint32_t* generation = nullptr) {
-    rate_control_.SnapshotCurrentTransform(out, generation);
-  }
+  void SnapshotRateTrans(LinearTransform* out, uint32_t* generation = nullptr);
 
   const LinearTransform::Ratio& FractionalFrameToMediaTimeRatio() const {
     return frame_to_media_ratio_;
@@ -63,7 +61,8 @@
   void Describe(const DescribeCallback& cbk) override;
   void Configure(AudioTrackConfigurationPtr configuration,
                  InterfaceRequest<MediaConsumer> req) override;
-  void GetRateControl(InterfaceRequest<RateControl> req) override;
+  void GetTimelineControlSite(InterfaceRequest<MediaTimelineControlSite> req)
+      override;
   void SetGain(float db_gain) override;
 
   // Methods called by our AudioPipe.
@@ -80,7 +79,8 @@
   AudioServerImpl*          owner_;
   Binding<AudioTrack>       binding_;
   AudioPipe                 pipe_;
-  RateControlBase           rate_control_;
+  TimelineControlSite       timeline_control_site_;
+  TimelineRate              frames_per_ns_;
   LinearTransform::Ratio    frame_to_media_ratio_;
   uint32_t                  bytes_per_frame_ = 1;
   AudioMediaTypeDetailsPtr  format_;
diff --git a/services/media/common/BUILD.gn b/services/media/common/BUILD.gn
index bc1e1b2..51880fa 100644
--- a/services/media/common/BUILD.gn
+++ b/services/media/common/BUILD.gn
@@ -10,13 +10,16 @@
   sources = [
     "media_pipe_base.cc",
     "media_pipe_base.h",
-    "rate_control_base.cc",
+    "mojo_publisher.h",
+    "timeline_control_site.cc",
+    "timeline_control_site.h",
   ]
 
   deps = [
     "//base",
     "//mojo/services/media/common/cpp",
     "//mojo/services/media/common/interfaces",
+    "//mojo/services/media/core/interfaces",
   ]
 }
 
diff --git a/services/media/factory_service/mojo_publisher.h b/services/media/common/mojo_publisher.h
similarity index 91%
rename from services/media/factory_service/mojo_publisher.h
rename to services/media/common/mojo_publisher.h
index 0fb4e88..503ce61 100644
--- a/services/media/factory_service/mojo_publisher.h
+++ b/services/media/common/mojo_publisher.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SERVICES_MEDIA_FRAMEWORK_MOJO_PUBLISHER_H_
-#define SERVICES_MEDIA_FRAMEWORK_MOJO_PUBLISHER_H_
+#ifndef SERVICES_MEDIA_COMMON_MOJO_PUBLISHER_H_
+#define SERVICES_MEDIA_COMMON_MOJO_PUBLISHER_H_
 
 #include <functional>
 #include <vector>
@@ -62,4 +62,4 @@
 }  // namespace media
 }  // namespace mojo
 
-#endif  // SERVICES_MEDIA_FRAMEWORK_MOJO_MOJO_ALLOCATOR_H_
+#endif  // SERVICES_MEDIA_COMMON_MOJO_PUBLISHER_H_
diff --git a/services/media/common/rate_control_base.cc b/services/media/common/rate_control_base.cc
deleted file mode 100644
index c72d265..0000000
--- a/services/media/common/rate_control_base.cc
+++ /dev/null
@@ -1,299 +0,0 @@
-// Copyright 2015 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/debug/stack_trace.h"
-#include "base/logging.h"
-#include "mojo/services/media/common/cpp/local_time.h"
-#include "services/media/common/rate_control_base.h"
-
-namespace mojo {
-namespace media {
-
-static inline int64_t LocalTimeNow() {
-  return LocalClock::now().time_since_epoch().count();
-}
-
-RateControlBase::RateControlBase()
-  : binding_(this)
-  , current_transform_(0, 1) {
-}
-
-RateControlBase::~RateControlBase() {
-  Reset();
-}
-
-bool RateControlBase::Bind(InterfaceRequest<RateControl> request) {
-  Reset();
-
-  binding_.Bind(request.Pass());
-  binding_.set_connection_error_handler([this]() -> void {
-    Reset();
-  });
-
-  return true;
-}
-
-void RateControlBase::SnapshotCurrentTransform(LinearTransform* out,
-                                               uint32_t* generation) {
-  DCHECK(out);
-  base::AutoLock lock(transform_lock_);
-  ApplyPendingChangesLocked(LocalTimeNow());
-  *out = current_transform_;
-  if (generation) {
-    *generation = generation_;
-  }
-}
-
-void RateControlBase::GetCurrentTransform(
-    const GetCurrentTransformCallback& cbk) {
-  TimelineTransformPtr ret(TimelineTransform::New());
-  ret->quad = TimelineQuad::New();
-
-  LinearTransform trans;
-  SnapshotCurrentTransform(&trans);
-  ret->quad->target_offset    = trans.a_zero;
-  ret->quad->reference_offset = trans.b_zero;
-  ret->quad->target_delta     = trans.scale.denominator;
-  ret->quad->reference_delta  = trans.scale.numerator;
-  ret->reference_timeline_id  = TimelineTransform::kContextual;
-  ret->target_timeline_id     = TimelineTransform::kLocalTimeID;
-
-  cbk.Run(ret.Pass());
-}
-
-// TODO(johngro): implement or remove.  Until we have the ability to query the
-// clock in the target timeline (or at least, transform local time to the target
-// timeline), we have no way to apply scheduled changes.
-void RateControlBase::SetTargetTimelineID(uint32_t id) {
-  if (id != TimelineTransform::kLocalTimeID) {
-    LOG(ERROR) << "Unsupported target timeline id ("
-               << id << ") during SetTargetTimelineID";
-    Reset();
-  }
-}
-
-void RateControlBase::SetCurrentQuad(TimelineQuadPtr quad) {
-  // A target delta of zero means that the transformation from the target
-  // timeline to the media timeline is singular.  This is not permitted, log an
-  // error and close the connection if someone attempts to do this.
-  if (!quad->target_delta) {
-    OnIllegalRateChange(quad->reference_delta, quad->target_delta);
-    return;
-  } else {
-    base::AutoLock lock(transform_lock_);
-
-    reference_pending_changes_.clear();
-    target_pending_changes_.clear();
-
-    current_transform_.a_zero = quad->target_offset;
-    current_transform_.b_zero = quad->reference_offset;
-
-    if (quad->reference_delta) {
-      current_transform_.scale =
-        LinearTransform::Ratio(quad->reference_delta, quad->target_delta);
-    } else {
-      current_transform_.scale.numerator = 0;
-      current_transform_.scale.denominator = 1;
-    }
-
-    AdvanceGenerationLocked();
-  }
-}
-
-void RateControlBase::SetRate(uint32_t reference_delta, uint32_t target_delta) {
-  // Only rate changes with a non-zero target_delta are permitted.  See comment
-  // in SetCurrentQuad.
-  if (!target_delta) {
-    OnIllegalRateChange(reference_delta, target_delta);
-    return;
-  } else {
-    base::AutoLock lock(transform_lock_);
-
-    // Make sure we are up to date.
-    int64_t target_now = LocalTimeNow();
-    ApplyPendingChangesLocked(target_now);
-
-    DCHECK(current_transform_.scale.denominator);
-    int64_t reference_now;
-    if (!current_transform_.DoForwardTransform(target_now, &reference_now)) {
-      // TODO(johngro): we cannot apply this transformation because of
-      // overflow, so we are forced to skip it.  Should we introduce a callback
-      // to allow the user to know that their transformation was skipped?
-      // Alternatively, should we log something about how the transformation was
-      // skipped?
-      return;
-    }
-
-    current_transform_.a_zero = target_now;
-    current_transform_.b_zero = reference_now;
-    current_transform_.scale.numerator = reference_delta;
-    current_transform_.scale.denominator = target_delta;
-
-    AdvanceGenerationLocked();
-  }
-}
-
-void RateControlBase::SetRateAtReferenceTime(uint32_t reference_delta,
-                                             uint32_t target_delta,
-                                             int64_t  reference_time) {
-  // Only rate changes with a non-zero target_delta are permitted.  See comment
-  // in SetCurrentQuad.
-  if (!target_delta) {
-    OnIllegalRateChange(reference_delta, target_delta);
-    return;
-  } else {
-    base::AutoLock lock(transform_lock_);
-
-    // If the user tries to schedule a change which takes place before any
-    // already scheduled change, ignore it.
-    if (reference_pending_changes_.size() &&
-        reference_pending_changes_.back().b_zero >= reference_time) {
-      return;
-    }
-
-    reference_pending_changes_.emplace_back(0,
-                                            reference_delta,
-                                            target_delta,
-                                            reference_time);
-  }
-}
-
-void RateControlBase::SetRateAtTargetTime(uint32_t reference_delta,
-                                          uint32_t target_delta,
-                                          int64_t  target_time) {
-  // Only rate changes with a non-zero target_delta are permitted.  See comment
-  // in SetCurrentQuad.
-  if (!target_delta) {
-    OnIllegalRateChange(reference_delta, target_delta);
-    return;
-  } else {
-    base::AutoLock lock(transform_lock_);
-
-    // If the user tries to schedule a change which takes place before any
-    // already scheduled change, ignore it.
-    if (target_pending_changes_.size() &&
-        target_pending_changes_.back().a_zero >= target_time) {
-      return;
-    }
-
-    target_pending_changes_.emplace_back(target_time,
-                                         reference_delta,
-                                         target_delta,
-                                         0);
-  }
-}
-
-void RateControlBase::CancelPendingChanges() {
-  base::AutoLock lock(transform_lock_);
-
-  reference_pending_changes_.clear();
-  target_pending_changes_.clear();
-}
-
-void RateControlBase::ApplyPendingChangesLocked(int64_t target_now) {
-  bool advance_generation = false;
-
-  do {
-    // Grab a pointer to the next pending target scheduled transform which is
-    // not in the future, if any.
-    int64_t target_age;
-    const LinearTransform* target_trans = nullptr;
-    if (target_pending_changes_.size() &&
-       (target_now >= target_pending_changes_.front().a_zero)) {
-      target_trans = &target_pending_changes_.front();
-      target_age = target_now - target_trans->a_zero;
-    }
-
-    // Grab a pointer to the next pending reference scheduled transform which is
-    // not in the future, if any.
-    //
-    // TODO(johngro): Optimize this.  When we have pending reference scheduled
-    // transformations, we don't have to compute this each and every time.  We
-    // could just keep the time of the next reference scheduled change
-    // (expressed in target time) pre-computed, and only update it when the
-    // current transformation actually changes.
-    int64_t reference_age;
-    int64_t next_reference_change_target_time;
-    const LinearTransform* reference_trans = nullptr;
-    if (reference_pending_changes_.size()) {
-      if (current_transform_.DoReverseTransform(
-            reference_pending_changes_.front().b_zero,
-            &next_reference_change_target_time)) {
-        if (target_now >= next_reference_change_target_time) {
-          reference_age = target_now - next_reference_change_target_time;
-          reference_trans = &reference_pending_changes_.front();
-        }
-      }
-    }
-
-    if (target_trans && (!reference_trans || (reference_age <= target_age))) {
-      // If we have a target scheduled transform which should be applied, and we
-      // either have no reference scheduled transform which should be applied,
-      // or we have a reference scheduled transform which should be applied
-      // after the pending target scheduled transform, go ahead and apply the
-      // target transform.
-      //
-      // Note: if we cannot apply this transformation due to overflow, we have a
-      // serious problem.  For now, we just purge the scheduled transformation
-      // and move on, but this is something which should never happen.  We
-      // should probably signal an error up to the user somehow.
-      int64_t next_target_change_reference_time;
-
-      if (current_transform_.DoForwardTransform(
-            target_trans->a_zero,
-            &next_target_change_reference_time)) {
-        current_transform_.a_zero = target_trans->a_zero;
-        current_transform_.b_zero = next_target_change_reference_time;
-        current_transform_.scale.numerator = target_trans->scale.numerator;
-        current_transform_.scale.denominator = target_trans->scale.denominator;
-        DCHECK(current_transform_.scale.denominator);
-      }
-
-      advance_generation = true;
-      target_pending_changes_.pop_front();
-    } else if (reference_trans) {
-      // We have a reference scheduled transformation which should be applied
-      // before any pending target scheduled transformation.  Do so now.  No
-      // need to compute the splice point for the function, we have already done
-      // so when determining if we should apply this transformation or not.
-      current_transform_.a_zero = next_reference_change_target_time;
-      current_transform_.b_zero = reference_trans->a_zero;
-      current_transform_.scale.numerator = reference_trans->scale.numerator;
-      current_transform_.scale.denominator = reference_trans->scale.denominator;
-      DCHECK(current_transform_.scale.denominator);
-
-      advance_generation = true;
-      reference_pending_changes_.pop_front();
-    } else {
-      // We have no transformations which need to be applied at the moment.  We
-      // are done for now.
-      break;
-    }
-  } while (true);
-
-  // If we have applied any changes, advance the transformation generation
-  if (advance_generation) {
-    AdvanceGenerationLocked();
-  }
-}
-
-void RateControlBase::OnIllegalRateChange(uint32_t numerator,
-                                          uint32_t denominator) {
-  LOG(ERROR) << "Illegal rate change requested ("
-             << numerator << "/" << denominator << ")";
-  Reset();
-}
-
-void RateControlBase::Reset() {
-  CancelPendingChanges();
-  SetRate(0, 1);
-
-  if (binding_.is_bound()) {
-    binding_.set_connection_error_handler(mojo::Closure());
-    binding_.Close();
-  }
-}
-
-}  // namespace media
-}  // namespace mojo
diff --git a/services/media/common/rate_control_base.h b/services/media/common/rate_control_base.h
deleted file mode 100644
index 95d55fa..0000000
--- a/services/media/common/rate_control_base.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2015 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 SERVICES_MEDIA_COMMON_RATE_CONTROL_BASE_H_
-#define SERVICES_MEDIA_COMMON_RATE_CONTROL_BASE_H_
-
-#include <deque>
-
-#include "base/synchronization/lock.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/services/media/common/cpp/linear_transform.h"
-#include "mojo/services/media/common/interfaces/media_common.mojom.h"
-#include "mojo/services/media/common/interfaces/rate_control.mojom.h"
-
-namespace mojo {
-namespace media {
-
-class RateControlBase : public RateControl {
- public:
-  // Default constructor and destructor
-  RateControlBase();
-  ~RateControlBase() override;
-
-  bool Bind(InterfaceRequest<RateControl> request);
-  bool is_bound() const { return binding_.is_bound(); }
-
-  // Close any existing connections to clients, clear any pending rate changes
-  // and set the clock rate to 0/1.
-  void Reset();
-
-  // TODO(johngro): snapshotting the current transform requires an evaluation of
-  // all the pending timeline transformations.  Currently, we allow users to
-  // schedule an arbitrary number of pending transformations.  This could cause
-  // DoS hazards if a malicious (or just poorly written) application is
-  // schedules a ton of transformations, and then something like the mixer
-  // threads in the audio server is forced to collapse all of these
-  // transformations in order to mix then next set of outbound audio frames.
-  //
-  // A simple way to avoid this would be to allow the user to have only one
-  // pending transformation at any point in time.
-  //
-  // It would be nice to be able to simply return the transformation and use
-  // Rvalue references in calling code to access the temporary snapshot, but
-  // style does not permit us to use Rvalue references in such a way.
-  //
-  // Also; the way pending transformations get applied probably needs to be
-  // re-worked.  Currently, when we snapshot, we collapse any pending
-  // transformations which should have occurred relative to LocalClock::now()
-  // into the current transformation.  For both video and audio, however, we are
-  // always mixing/composing for a point in time in the near future, not for
-  // right now.  We really want to given the mixer compositor a view of what the
-  // transformation is going to be at the mix/composition point, not what it is
-  // now.  Additionally, since audio process many frames at a time, we need to
-  // give the audio mixer some knowledge of when we think the snapshotted
-  // transformation is going to change next.  The audio mixer wants to mix up to
-  // that point, but not past it, and then fetch the new transformation before
-  // proceeding.
-  void SnapshotCurrentTransform(LinearTransform* out,
-                                uint32_t* generation = nullptr);
-
-  // RateControl interface
-  //
-  void GetCurrentTransform(const GetCurrentTransformCallback& cbk) override;
-  void SetTargetTimelineID(uint32_t id) override;
-  void SetCurrentQuad(TimelineQuadPtr quad) override;
-  void SetRate(uint32_t reference_delta, uint32_t target_delta) override;
-  void SetRateAtReferenceTime(uint32_t reference_delta,
-                              uint32_t target_delta,
-                              int64_t  reference_time) override;
-  void SetRateAtTargetTime(uint32_t reference_delta,
-                           uint32_t target_delta,
-                           int64_t  target_time) override;
-  void CancelPendingChanges() override;
-
- protected:
-  void ApplyPendingChangesLocked(int64_t target_now);
-  void AdvanceGenerationLocked() {
-    // bump the generation counter.  Do not use the value 0.
-    while (!(++generation_)) {}
-  }
-  void OnIllegalRateChange(uint32_t numerator, uint32_t denominator);
-
-  Binding<RateControl> binding_;
-  uint32_t target_timeline_id = TimelineTransform::kLocalTimeID;
-
-  //  Transformation state.
-  //
-  //  Note: We use the LinearTransforms such that space A is the target timeline
-  //  and space B is the reference timeline.  Applying this convention,
-  //  transforming from target to reference is the "forward" transformation and
-  //  is always defined.  Transforming from reference to target is the "reverse"
-  //  transformation, and is only defined when we are not paused.
-  //  <pedantic>
-  //  OK; It is defined, but the equation has a singularity and the mapping is
-  //  not 1-to-1.
-  //  </pedantic>.
-  base::Lock transform_lock_;
-  LinearTransform current_transform_;
-  std::deque<LinearTransform> reference_pending_changes_;
-  std::deque<LinearTransform> target_pending_changes_;
-  uint32_t generation_ = 1;
-};
-
-}  // namespace media
-}  // namespace mojo
-
-#endif  // SERVICES_MEDIA_COMMON_RATE_CONTROL_BASE_H_
diff --git a/services/media/common/timeline_control_site.cc b/services/media/common/timeline_control_site.cc
new file mode 100644
index 0000000..26dc07f
--- /dev/null
+++ b/services/media/common/timeline_control_site.cc
@@ -0,0 +1,193 @@
+// 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/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/services/media/common/cpp/timeline.h"
+#include "services/media/common/timeline_control_site.h"
+
+namespace mojo {
+namespace media {
+
+// For checking preconditions when handling mojo requests.
+// Checks the condition, and, if it's false, resets and calls return.
+#define RCHECK(condition)                                         \
+  if (!(condition)) {                                             \
+    LOG(ERROR) << "request precondition failed: " #condition "."; \
+    ResetUnsafe();                                                \
+    return;                                                       \
+  }
+
+TimelineControlSite::TimelineControlSite()
+    : control_site_binding_(this), consumer_binding_(this) {
+  task_runner_ = base::MessageLoop::current()->task_runner();
+  DCHECK(task_runner_);
+
+  base::AutoLock lock(lock_);
+  ClearPendingTimelineFunctionUnsafe(false);
+
+  status_publisher_.SetCallbackRunner(
+      [this](const GetStatusCallback& callback, uint64_t version) {
+        MediaTimelineControlSiteStatusPtr status;
+        {
+          base::AutoLock lock(lock_);
+          status = MediaTimelineControlSiteStatus::New();
+          status->timeline_transform =
+              TimelineTransform::From(current_timeline_function_);
+          status->end_of_stream = false; // TODO(dalesat): Provide this.
+        }
+        callback.Run(version, status.Pass());
+      });
+}
+
+TimelineControlSite::~TimelineControlSite() {}
+
+void TimelineControlSite::Bind(
+    InterfaceRequest<MediaTimelineControlSite> request) {
+  if (control_site_binding_.is_bound()) {
+    control_site_binding_.Close();
+  }
+
+  control_site_binding_.Bind(request.Pass());
+}
+
+void TimelineControlSite::Reset() {
+  if (control_site_binding_.is_bound()) {
+    control_site_binding_.Close();
+  }
+
+  if (consumer_binding_.is_bound()) {
+    consumer_binding_.Close();
+  }
+
+  {
+    base::AutoLock lock(lock_);
+    current_timeline_function_ = TimelineFunction();
+    ClearPendingTimelineFunctionUnsafe(false);
+    generation_ = 1;
+  }
+
+  status_publisher_.SendUpdates();
+}
+
+void TimelineControlSite::SnapshotCurrentFunction(int64_t reference_time,
+                                                  TimelineFunction* out,
+                                                  uint32_t* generation) {
+  DCHECK(out);
+  base::AutoLock lock(lock_);
+  ApplyPendingChangesUnsafe(reference_time);
+  *out = current_timeline_function_;
+  if (generation) {
+    *generation = generation_;
+  }
+}
+
+void TimelineControlSite::GetStatus(uint64_t version_last_seen,
+                                    const GetStatusCallback& callback) {
+  status_publisher_.Get(version_last_seen, callback);
+}
+
+void TimelineControlSite::GetTimelineConsumer(
+    InterfaceRequest<TimelineConsumer> timeline_consumer) {
+  if (consumer_binding_.is_bound()) {
+    consumer_binding_.Close();
+  }
+
+  consumer_binding_.Bind(timeline_consumer.Pass());
+}
+
+void TimelineControlSite::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) {
+  base::AutoLock lock(lock_);
+
+  // At most one of the effective times must be specified.
+  RCHECK(effective_reference_time == kUnspecifiedTime ||
+         effective_subject_time == kUnspecifiedTime);
+  // effective_subject_time can only be used if we're progressing already.
+  RCHECK(effective_subject_time == kUnspecifiedTime ||
+         current_timeline_function_.subject_delta() != 0);
+  RCHECK(reference_delta != 0);
+
+  if (effective_subject_time != kUnspecifiedTime) {
+    // Infer effective_reference_time from effective_subject_time.
+    effective_reference_time =
+        current_timeline_function_.ApplyInverse(effective_subject_time);
+
+    if (subject_time == kUnspecifiedTime) {
+      // Infer subject_time from effective_subject_time.
+      subject_time = effective_subject_time;
+    }
+  } else {
+    if (effective_reference_time == kUnspecifiedTime) {
+      // Neither effective time was specified. Effective time is now.
+      effective_reference_time = Timeline::local_now();
+    }
+
+    if (subject_time == kUnspecifiedTime) {
+      // Infer subject_time from effective_reference_time.
+      subject_time = current_timeline_function_(effective_reference_time);
+    }
+  }
+
+  // Eject any previous pending change.
+  ClearPendingTimelineFunctionUnsafe(false);
+
+  // Queue up the new pending change.
+  pending_timeline_function_ = TimelineFunction(
+      effective_reference_time, subject_time, reference_delta, subject_delta);
+
+  set_timeline_transform_callback_ = callback;
+}
+
+void TimelineControlSite::ApplyPendingChangesUnsafe(int64_t reference_time) {
+  lock_.AssertAcquired();
+
+  if (!TimelineFunctionPendingUnsafe() ||
+      pending_timeline_function_.reference_time() > reference_time) {
+    return;
+  }
+
+  current_timeline_function_ = pending_timeline_function_;
+  ClearPendingTimelineFunctionUnsafe(true);
+
+  ++generation_;
+
+  task_runner_->PostTask(
+      FROM_HERE, base::Bind(&MojoPublisher<GetStatusCallback>::SendUpdates,
+                            base::Unretained(&status_publisher_)));
+}
+
+void TimelineControlSite::ClearPendingTimelineFunctionUnsafe(bool completed) {
+  lock_.AssertAcquired();
+
+  pending_timeline_function_ =
+      TimelineFunction(kUnspecifiedTime, kUnspecifiedTime, 1, 0);
+  if (!set_timeline_transform_callback_.is_null()) {
+    task_runner_->PostTask(
+        FROM_HERE, base::Bind(&TimelineControlSite::RunCallback,
+                              set_timeline_transform_callback_, completed));
+    set_timeline_transform_callback_.reset();
+  }
+}
+
+void TimelineControlSite::ResetUnsafe() {
+  lock_.AssertAcquired();
+  task_runner_->PostTask(FROM_HERE, base::Bind(&TimelineControlSite::Reset,
+                                               base::Unretained(this)));
+}
+
+// static
+void TimelineControlSite::RunCallback(SetTimelineTransformCallback callback,
+                                      bool completed) {
+  callback.Run(completed);
+}
+
+}  // namespace media
+}  // namespace mojo
diff --git a/services/media/common/timeline_control_site.h b/services/media/common/timeline_control_site.h
new file mode 100644
index 0000000..4b2506f
--- /dev/null
+++ b/services/media/common/timeline_control_site.h
@@ -0,0 +1,94 @@
+// 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_COMMON_TIMELINE_CONTROL_SITE_IMPL_H_
+#define MOJO_SERVICES_MEDIA_COMMON_TIMELINE_CONTROL_SITE_IMPL_H_
+
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "mojo/public/cpp/bindings/binding.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"
+
+namespace mojo {
+namespace media {
+
+// MediaTimelineControlSite implementation.
+class TimelineControlSite : public MediaTimelineControlSite,
+                            public TimelineConsumer {
+ public:
+  TimelineControlSite();
+
+  ~TimelineControlSite() override;
+
+  // Binds to the control site. If a binding exists already, it is closed.
+  void Bind(InterfaceRequest<MediaTimelineControlSite> request);
+
+  // Determines whether the control site is currently bound.
+  bool is_bound() { return control_site_binding_.is_bound(); }
+
+  // Unbinds from clients and resets to initial state.
+  void Reset();
+
+  // Get the TimelineFunction for the reference_time (which should be 'now',
+  // approximately).
+  void SnapshotCurrentFunction(int64_t reference_time,
+                               TimelineFunction* out,
+                               uint32_t* generation = nullptr);
+
+  // 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:
+  // Applies pending_timeline_function_ if it's time to do so based on the
+  // given reference time.
+  void ApplyPendingChangesUnsafe(int64_t reference_time);
+
+  // Clears the pending timeline function and calls its associated callback
+  // with the indicated completed status.
+  void ClearPendingTimelineFunctionUnsafe(bool completed);
+
+  // Determines if an unrealized timeline function is currently pending.
+  bool TimelineFunctionPendingUnsafe() {
+    return pending_timeline_function_.reference_time() != kUnspecifiedTime;
+  }
+
+  // Unbinds from clients and resets to initial state.
+  void ResetUnsafe();
+
+  static void RunCallback(SetTimelineTransformCallback callback,
+                          bool completed);
+
+  Binding<MediaTimelineControlSite> control_site_binding_;
+  Binding<TimelineConsumer> consumer_binding_;
+  MojoPublisher<GetStatusCallback> status_publisher_;
+
+  base::Lock lock_;
+  // BEGIN fields synchronized using lock_.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  TimelineFunction current_timeline_function_;
+  TimelineFunction pending_timeline_function_;
+  SetTimelineTransformCallback set_timeline_transform_callback_;
+  uint32_t generation_ = 1;
+  // END fields synchronized using lock_.
+};
+
+}  // namespace media
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_MEDIA_COMMON_TIMELINE_CONTROL_SITE_IMPL_H_
diff --git a/services/media/factory_service/BUILD.gn b/services/media/factory_service/BUILD.gn
index 21a1e4b..12f60e1 100644
--- a/services/media/factory_service/BUILD.gn
+++ b/services/media/factory_service/BUILD.gn
@@ -28,7 +28,6 @@
     "media_sink_impl.h",
     "media_source_impl.cc",
     "media_source_impl.h",
-    "mojo_publisher.h",
     "network_reader_impl.cc",
     "network_reader_impl.h",
   ]
@@ -45,6 +44,7 @@
     "//mojo/services/media/control/interfaces",
     "//mojo/services/media/core/interfaces",
     "//mojo/services/network/interfaces",
+    "//services/media/common",
     "//services/media/framework",
     "//services/media/framework_create",
     "//services/media/framework_ffmpeg",
diff --git a/services/media/factory_service/audio_track_controller.cc b/services/media/factory_service/audio_track_controller.cc
index e36e04b..3b85597 100644
--- a/services/media/factory_service/audio_track_controller.cc
+++ b/services/media/factory_service/audio_track_controller.cc
@@ -45,10 +45,10 @@
   MediaConsumerPtr consumer;
   audio_track_->Configure(config.Pass(), GetProxy(&consumer));
 
-  RateControlPtr rate_control;
-  audio_track_->GetRateControl(GetProxy(&rate_control));
+  MediaTimelineControlSitePtr timeline_control_site;
+  audio_track_->GetTimelineControlSite(GetProxy(&timeline_control_site));
 
-  callback(consumer.Pass(), rate_control.Pass());
+  callback(consumer.Pass(), timeline_control_site.Pass());
 }
 
 }  // namespace media
diff --git a/services/media/factory_service/audio_track_controller.h b/services/media/factory_service/audio_track_controller.h
index 9e15485..6a60dc9 100644
--- a/services/media/factory_service/audio_track_controller.h
+++ b/services/media/factory_service/audio_track_controller.h
@@ -19,7 +19,7 @@
   using GetSupportedMediaTypesCallback = std::function<void(
       std::unique_ptr<std::vector<std::unique_ptr<StreamTypeSet>>>)>;
   using ConfigureCallback =
-      std::function<void(MediaConsumerPtr, RateControlPtr)>;
+      std::function<void(MediaConsumerPtr, MediaTimelineControlSitePtr)>;
 
   AudioTrackController(const String& url, ApplicationImpl* app);
 
diff --git a/services/media/factory_service/media_demux_impl.h b/services/media/factory_service/media_demux_impl.h
index 0be2435..43d96bf 100644
--- a/services/media/factory_service/media_demux_impl.h
+++ b/services/media/factory_service/media_demux_impl.h
@@ -13,8 +13,8 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/services/media/core/interfaces/media_demux.mojom.h"
 #include "mojo/services/media/core/interfaces/seeking_reader.mojom.h"
+#include "services/media/common/mojo_publisher.h"
 #include "services/media/factory_service/factory_service.h"
-#include "services/media/factory_service/mojo_publisher.h"
 #include "services/media/framework/graph.h"
 #include "services/media/framework/parts/demux.h"
 #include "services/media/framework/util/incident.h"
diff --git a/services/media/factory_service/media_player_impl.h b/services/media/factory_service/media_player_impl.h
index 33a7595..0a09d9c 100644
--- a/services/media/factory_service/media_player_impl.h
+++ b/services/media/factory_service/media_player_impl.h
@@ -13,8 +13,8 @@
 #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 "services/media/common/mojo_publisher.h"
 #include "services/media/factory_service/factory_service.h"
-#include "services/media/factory_service/mojo_publisher.h"
 
 namespace mojo {
 namespace media {
diff --git a/services/media/factory_service/media_sink_impl.cc b/services/media/factory_service/media_sink_impl.cc
index da4b7ef..1fec5bd 100644
--- a/services/media/factory_service/media_sink_impl.cc
+++ b/services/media/factory_service/media_sink_impl.cc
@@ -3,8 +3,8 @@
 // found in the LICENSE file.
 
 #include "base/logging.h"
-#include "mojo/services/media/common/cpp/linear_transform.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 "services/media/factory_service/media_sink_impl.h"
 #include "services/media/framework/util/conversion_pipeline_builder.h"
 #include "services/media/framework_mojo/mojo_type_conversions.h"
@@ -32,15 +32,15 @@
   DCHECK(destination_url);
   DCHECK(media_type);
 
-  status_publisher_.SetCallbackRunner(
-      [this](const GetStatusCallback& callback, uint64_t version) {
-        MediaSinkStatusPtr status = MediaSinkStatus::New();
-        status->state = (producer_state_ == MediaState::PAUSED && rate_ != 0.0)
-                            ? MediaState::PLAYING
-                            : producer_state_;
-        status->timeline_transform = status_transform_.Clone();
-        callback.Run(version, status.Pass());
-      });
+  status_publisher_.SetCallbackRunner([this](const GetStatusCallback& callback,
+                                             uint64_t version) {
+    MediaSinkStatusPtr status = MediaSinkStatus::New();
+    status->state = (producer_state_ == MediaState::PAUSED && rate_ != 0.0)
+                        ? MediaState::PLAYING
+                        : producer_state_;
+    status->timeline_transform = TimelineTransform::From(timeline_function_);
+    callback.Run(version, status.Pass());
+  });
 
   PartRef consumer_ref = graph_.Add(consumer_);
   PartRef producer_ref = graph_.Add(producer_);
@@ -112,7 +112,10 @@
     graph_.ConnectOutputToPart(out, producer_ref);
 
     if (producer_stream_type->medium() == StreamType::Medium::kAudio) {
-      frames_per_second_ = producer_stream_type->audio()->frames_per_second();
+      frames_per_ns_ =
+          TimelineRate(producer_stream_type->audio()->frames_per_second(),
+                       Timeline::ns_from_seconds(1));
+
     } else {
       // Unsupported producer stream type.
       LOG(ERROR) << "unsupported producer stream type";
@@ -121,10 +124,12 @@
 
     controller_->Configure(
         std::move(producer_stream_type),
-        [this](MediaConsumerPtr consumer, RateControlPtr rate_control) {
+        [this](MediaConsumerPtr consumer,
+               MediaTimelineControlSitePtr timeline_control_site) {
           DCHECK(consumer);
-          DCHECK(rate_control);
-          rate_control_ = rate_control.Pass();
+          DCHECK(timeline_control_site);
+          timeline_control_site->GetTimelineConsumer(
+              GetProxy(&timeline_consumer_));
           producer_->Connect(consumer.Pass(), [this]() {
             graph_.Prepare();
             ready_.Occur();
@@ -160,99 +165,46 @@
     return;
   }
 
-  if (!rate_control_) {
+  if (!timeline_consumer_) {
     rate_ = target_rate_;
     status_publisher_.SendUpdates();
     return;
   }
 
-  // Desired rate in frames per second.
-  LinearTransform::Ratio rate_frames_per_second(
-      static_cast<uint32_t>(frames_per_second_ * target_rate_), 1);
+  // TODO(dalesat): start_local_time and start_presentation_time should be
+  // supplied via the mojo interface. For now, start_local_time is hard-coded
+  // to be 30ms in the future, and start_presentation_time is grabbed from the
+  // first primed packet or is calculated from start_local_time based on the
+  // previous timeline function.
 
-  // Local time rate in seconds_per_tick.
-  LinearTransform::Ratio local_seconds_per_tick(LocalDuration::period::num,
-                                                LocalDuration::period::den);
-
-  // Desired rate in frames per local tick.
-  LinearTransform::Ratio rate_frames_per_tick;
-  bool success = LinearTransform::Ratio::Compose(
-      local_seconds_per_tick, rate_frames_per_second, &rate_frames_per_tick);
-  DCHECK(success)
-      << "LinearTransform::Ratio::Compose reports loss of precision";
-
-  // TODO(dalesat): start_local_time should be supplied via the mojo interface.
-  // For now, it's hard-coded to be 30ms in the future.
   // The local time when we want the rate to change.
-  int64_t start_local_time =
-      (LocalClock::now().time_since_epoch() + std::chrono::milliseconds(30))
-          .count();
+  int64_t start_local_time = Timeline::local_now() + Timeline::ns_from_ms(30);
 
   // The media time corresponding to start_local_time.
-  int64_t start_media_time;
+  int64_t start_presentation_time;
   if (flushed_ && producer_->GetFirstPtsSinceFlush() != Packet::kUnknownPts) {
     // We're getting started initially or after a flush/prime, so the media
     // time corresponding to start_local_time should be the PTS of
-    // the first packet.
-    start_media_time = producer_->GetFirstPtsSinceFlush();
+    // the first packet converted to ns (rather than frame) units.
+    start_presentation_time =
+        producer_->GetFirstPtsSinceFlush() / frames_per_ns_;
   } else {
     // We're resuming, so the media time corresponding to start_local_time can
     // be calculated using the existing transform.
-    success =
-        transform_.DoForwardTransform(start_local_time, &start_media_time);
-    DCHECK(success)
-        << "LinearTransform::DoForwardTransform reports loss of precision";
+    start_presentation_time = timeline_function_(start_local_time);
   }
 
   flushed_ = false;
 
   // Update the transform.
-  transform_ =
-      LinearTransform(start_local_time, rate_frames_per_tick, start_media_time);
+  timeline_function_ = TimelineFunction(
+      start_local_time, start_presentation_time, TimelineRate(target_rate_));
 
   // Set the rate.
-  TimelineQuadPtr rate_quad = TimelineQuad::New();
-  rate_quad->reference_offset = start_media_time;
-  rate_quad->target_offset = start_local_time;
-  rate_quad->reference_delta = rate_frames_per_tick.numerator;
-  rate_quad->target_delta = rate_frames_per_tick.denominator;
-
-  rate_control_->SetCurrentQuad(rate_quad.Pass());
-
-  // Get the frame rate in frames per local tick.
-  LinearTransform::Ratio frame_rate_frames_per_second(frames_per_second_, 1);
-  LinearTransform::Ratio frame_rate_frames_per_tick;
-  success = LinearTransform::Ratio::Compose(local_seconds_per_tick,
-                                            frame_rate_frames_per_second,
-                                            &frame_rate_frames_per_tick);
-  DCHECK(success)
-      << "LinearTransform::Ratio::Compose reports loss of precision";
-
-  // Create a LinearTransform to translate from presentation units to
-  // local duration units.
-  LinearTransform local_to_presentation(0, frame_rate_frames_per_tick, 0);
-
-  status_transform_ = TimelineTransform::New();
-  status_transform_->quad = TimelineQuad::New();
-
-  // Translate the current transform quad so the presentation time units
-  // are the same as the local time units.
-  success = local_to_presentation.DoReverseTransform(
-      start_media_time, &status_transform_->quad->reference_offset);
-  DCHECK(success)
-      << "LinearTransform::DoReverseTransform reports loss of precision";
-  status_transform_->quad->target_offset = start_local_time;
-  int64_t presentation_delta;
-  success = local_to_presentation.DoReverseTransform(
-      static_cast<int64_t>(rate_frames_per_tick.numerator),
-      &presentation_delta);
-  DCHECK(success)
-      << "LinearTransform::DoReverseTransform reports loss of precision";
-  status_transform_->quad->reference_delta =
-      static_cast<uint32_t>(presentation_delta);
-  status_transform_->quad->target_delta = rate_frames_per_tick.denominator;
-  LinearTransform::Ratio::Reduce(&status_transform_->quad->reference_delta,
-                                 &status_transform_->quad->target_delta);
+  timeline_consumer_->SetTimelineTransform(
+      timeline_function_.subject_time(), timeline_function_.reference_delta(),
+      timeline_function_.subject_delta(), timeline_function_.reference_time(),
+      kUnspecifiedTime, [](bool completed) {});
 
   rate_ = target_rate_;
   status_publisher_.SendUpdates();
diff --git a/services/media/factory_service/media_sink_impl.h b/services/media/factory_service/media_sink_impl.h
index 17db63a..f323f62 100644
--- a/services/media/factory_service/media_sink_impl.h
+++ b/services/media/factory_service/media_sink_impl.h
@@ -9,11 +9,11 @@
 
 #include "mojo/public/cpp/application/application_impl.h"
 #include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/services/media/common/cpp/linear_transform.h"
+#include "mojo/services/media/common/cpp/timeline_function.h"
 #include "mojo/services/media/control/interfaces/media_sink.mojom.h"
+#include "services/media/common/mojo_publisher.h"
 #include "services/media/factory_service/audio_track_controller.h"
 #include "services/media/factory_service/factory_service.h"
-#include "services/media/factory_service/mojo_publisher.h"
 #include "services/media/framework/graph.h"
 #include "services/media/framework/parts/decoder.h"
 #include "services/media/framework/util/incident.h"
@@ -61,13 +61,12 @@
   std::shared_ptr<MojoConsumer> consumer_;
   std::shared_ptr<MojoProducer> producer_;
   std::unique_ptr<AudioTrackController> controller_;
-  RateControlPtr rate_control_;
+  TimelineConsumerPtr timeline_consumer_;
   float rate_ = 0.0f;
   float target_rate_ = 0.0f;
   MediaState producer_state_ = MediaState::UNPREPARED;
-  LinearTransform transform_ = LinearTransform(0, 0, 1, 0);
-  TimelineTransformPtr status_transform_;
-  uint32_t frames_per_second_ = 0u;
+  TimelineFunction timeline_function_;
+  TimelineRate frames_per_ns_;
   bool flushed_ = true;
   MojoPublisher<GetStatusCallback> status_publisher_;
 };
diff --git a/services/media/factory_service/media_source_impl.h b/services/media/factory_service/media_source_impl.h
index 5fdd3ee..d056aa4 100644
--- a/services/media/factory_service/media_source_impl.h
+++ b/services/media/factory_service/media_source_impl.h
@@ -11,8 +11,8 @@
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/services/media/control/interfaces/media_source.mojom.h"
 #include "mojo/services/media/core/interfaces/seeking_reader.mojom.h"
+#include "services/media/common/mojo_publisher.h"
 #include "services/media/factory_service/factory_service.h"
-#include "services/media/factory_service/mojo_publisher.h"
 #include "services/media/framework/graph.h"
 #include "services/media/framework/parts/decoder.h"
 #include "services/media/framework/parts/demux.h"
diff --git a/services/media/framework_mojo/mojo_formatting.cc b/services/media/framework_mojo/mojo_formatting.cc
index 3afefac..7dfbc86 100644
--- a/services/media/framework_mojo/mojo_formatting.cc
+++ b/services/media/framework_mojo/mojo_formatting.cc
@@ -259,23 +259,6 @@
   return os << outdent;
 }
 
-std::ostream& operator<<(std::ostream& os, const TimelineQuadPtr& value) {
-  if (!value) {
-    return os << "<nullptr>" << std::endl;
-  } else {
-    os << std::endl;
-  }
-
-  os << indent;
-  os << begl << "int64 reference_offset: " << value->reference_offset
-     << std::endl;
-  os << begl << "int64 target_offset: " << value->target_offset << std::endl;
-  os << begl << "int32 reference_delta: " << value->reference_delta
-     << std::endl;
-  os << begl << "uint32 target_delta: " << value->target_delta << std::endl;
-  return os << outdent;
-}
-
 std::ostream& operator<<(std::ostream& os, const TimelineTransformPtr& value) {
   if (!value) {
     return os << "<nullptr>" << std::endl;
@@ -284,11 +267,12 @@
   }
 
   os << indent;
-  os << begl << "TimelineQuad quad: " << value->quad;
-  os << begl << "uint32 reference_timeline_id: " << value->reference_timeline_id
+  os << begl << "int64 reference_time: " << value->reference_time
      << std::endl;
-  os << begl << "uint32 target_timeline_id: " << value->target_timeline_id
+  os << begl << "int64 subject_time: " << value->subject_time << std::endl;
+  os << begl << "uint32 reference_delta: " << value->reference_delta
      << std::endl;
+  os << begl << "uint32 subject_delta: " << value->subject_delta << std::endl;
   return os << outdent;
 }
 
diff --git a/services/media/framework_mojo/mojo_formatting.h b/services/media/framework_mojo/mojo_formatting.h
index 507cd5c..020d47e 100644
--- a/services/media/framework_mojo/mojo_formatting.h
+++ b/services/media/framework_mojo/mojo_formatting.h
@@ -8,7 +8,7 @@
 #include "mojo/services/media/common/interfaces/media_common.mojom.h"
 #include "mojo/services/media/common/interfaces/media_transport.mojom.h"
 #include "mojo/services/media/common/interfaces/media_types.mojom.h"
-#include "mojo/services/media/common/interfaces/rate_control.mojom.h"
+#include "mojo/services/media/common/interfaces/timelines.mojom.h"
 #include "mojo/services/media/control/interfaces/media_source.mojom.h"
 #include "mojo/services/network/interfaces/network_service.mojom.h"
 #include "services/media/framework/util/formatting.h"
@@ -50,7 +50,6 @@
                          const SubpictureMediaTypeSetDetailsPtr& value);
 std::ostream& operator<<(std::ostream& os,
                          const MediaSourceStreamDescriptorPtr& value);
-std::ostream& operator<<(std::ostream& os, const TimelineQuadPtr& value);
 std::ostream& operator<<(std::ostream& os, const TimelineTransformPtr& value);
 
 std::ostream& operator<<(std::ostream& os, const HttpHeaderPtr& value);