Propagate base minimum delay from video jitter buffer to webrtc/api.

On api level two methods were added to api/media_stream_interface.cc on VideoSourceInterface,
GetLatency and SetLatency. Latency is measured in seconds, delay in milliseconds but both describes
the same concept.


Bug: webrtc:10287
Change-Id: Ib8dc62a4d73f63fab7e10b82c716096ee6199957
Reviewed-on: https://webrtc-review.googlesource.com/c/123482
Commit-Queue: Ruslan Burakov <kuddai@google.com>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26877}
diff --git a/api/media_stream_interface.cc b/api/media_stream_interface.cc
index b55a840..cf994cb 100644
--- a/api/media_stream_interface.cc
+++ b/api/media_stream_interface.cc
@@ -32,7 +32,7 @@
   return {};
 }
 
-double AudioSourceInterface::GetLatency() const {
+double MediaSourceInterface::GetLatency() const {
   return 0.0;
 }
 
diff --git a/api/media_stream_interface.h b/api/media_stream_interface.h
index e520361..1249b85 100644
--- a/api/media_stream_interface.h
+++ b/api/media_stream_interface.h
@@ -61,6 +61,13 @@
 
   virtual bool remote() const = 0;
 
+  // Sets the minimum latency of the remote source until audio playout. Actual
+  // observered latency may differ depending on the source. |latency| is in the
+  // range of [0.0, 10.0] seconds.
+  // TODO(kuddai) make pure virtual once not only remote tracks support latency.
+  virtual void SetLatency(double latency) {}
+  virtual double GetLatency() const;
+
  protected:
   ~MediaSourceInterface() override = default;
 };
@@ -201,12 +208,6 @@
   // be applied in the track in a way that does not affect clones of the track.
   virtual void SetVolume(double volume) {}
 
-  // Sets the minimum latency of the remote source until audio playout. Actual
-  // observered latency may differ depending on the source. |latency| is in the
-  // range of [0.0, 10.0] seconds.
-  virtual void SetLatency(double latency) {}
-  virtual double GetLatency() const;
-
   // Registers/unregisters observers to the audio source.
   virtual void RegisterAudioObserver(AudioObserver* observer) {}
   virtual void UnregisterAudioObserver(AudioObserver* observer) {}
diff --git a/api/video_track_source_proxy.h b/api/video_track_source_proxy.h
index 820cdcb..eb11bef 100644
--- a/api/video_track_source_proxy.h
+++ b/api/video_track_source_proxy.h
@@ -32,6 +32,8 @@
                      rtc::VideoSinkInterface<VideoFrame>*,
                      const rtc::VideoSinkWants&)
 PROXY_WORKER_METHOD1(void, RemoveSink, rtc::VideoSinkInterface<VideoFrame>*)
+PROXY_WORKER_METHOD1(void, SetLatency, double)
+PROXY_WORKER_CONSTMETHOD0(double, GetLatency)
 PROXY_METHOD1(void, RegisterObserver, ObserverInterface*)
 PROXY_METHOD1(void, UnregisterObserver, ObserverInterface*)
 END_PROXY_MAP()
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index ecff63d..0bcb6d3 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -252,6 +252,15 @@
 
   virtual std::vector<RtpSource> GetSources() const = 0;
 
+  // Sets a base minimum for the playout delay. Base minimum delay sets lower
+  // bound on minimum delay value determining lower bound on playout delay.
+  //
+  // Returns true if value was successfully set, false overwise.
+  virtual bool SetBaseMinimumPlayoutDelayMs(int delay_ms) = 0;
+
+  // Returns current value of base minimum delay in milliseconds.
+  virtual int GetBaseMinimumPlayoutDelayMs() const = 0;
+
  protected:
   virtual ~VideoReceiveStream() {}
 };
diff --git a/media/BUILD.gn b/media/BUILD.gn
index b3195a1..ccbfd38 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -88,6 +88,7 @@
     "base/audio_source.h",
     "base/codec.cc",
     "base/codec.h",
+    "base/delayable.h",
     "base/media_channel.cc",
     "base/media_channel.h",
     "base/media_constants.cc",
diff --git a/media/base/delayable.h b/media/base/delayable.h
new file mode 100644
index 0000000..90ce5d7
--- /dev/null
+++ b/media/base/delayable.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MEDIA_BASE_DELAYABLE_H_
+#define MEDIA_BASE_DELAYABLE_H_
+
+#include <stdint.h>
+
+#include "absl/types/optional.h"
+
+namespace cricket {
+
+// Delayable is used by user code through ApplyConstraints algorithm. Its
+// methods must take precendence over similar functional in |syncable.h|.
+class Delayable {
+ public:
+  virtual ~Delayable() {}
+  // Set base minimum delay of the receive stream with specified ssrc.
+  // Base minimum delay sets lower bound on minimum delay value which
+  // determines minimum delay until audio playout.
+  // Returns false if there is no stream with given ssrc.
+  virtual bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) = 0;
+
+  // Returns current value of base minimum delay in milliseconds.
+  virtual absl::optional<int> GetBaseMinimumPlayoutDelayMs(
+      uint32_t ssrc) const = 0;
+};
+
+}  // namespace cricket
+
+#endif  // MEDIA_BASE_DELAYABLE_H_
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc
index 8297902..e4e94ca 100644
--- a/media/base/fake_media_engine.cc
+++ b/media/base/fake_media_engine.cc
@@ -341,12 +341,14 @@
   if (!RtpHelper<VideoMediaChannel>::AddRecvStream(sp))
     return false;
   sinks_[sp.first_ssrc()] = NULL;
+  output_delays_[sp.first_ssrc()] = 0;
   return true;
 }
 bool FakeVideoMediaChannel::RemoveRecvStream(uint32_t ssrc) {
   if (!RtpHelper<VideoMediaChannel>::RemoveRecvStream(ssrc))
     return false;
   sinks_.erase(ssrc);
+  output_delays_.erase(ssrc);
   return true;
 }
 void FakeVideoMediaChannel::FillBitrateInfo(BandwidthEstimationInfo* bwe_info) {
@@ -358,6 +360,23 @@
     uint32_t ssrc) const {
   return {};
 }
+bool FakeVideoMediaChannel::SetBaseMinimumPlayoutDelayMs(uint32_t ssrc,
+                                                         int delay_ms) {
+  if (output_delays_.find(ssrc) == output_delays_.end()) {
+    return false;
+  } else {
+    output_delays_[ssrc] = delay_ms;
+    return true;
+  }
+}
+absl::optional<int> FakeVideoMediaChannel::GetBaseMinimumPlayoutDelayMs(
+    uint32_t ssrc) const {
+  const auto it = output_delays_.find(ssrc);
+  if (it != output_delays_.end()) {
+    return it->second;
+  }
+  return absl::nullopt;
+}
 bool FakeVideoMediaChannel::SetRecvCodecs(
     const std::vector<VideoCodec>& codecs) {
   if (fail_set_recv_codecs()) {
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index f586580..3f1c28b 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -441,6 +441,10 @@
 
   std::vector<webrtc::RtpSource> GetSources(uint32_t ssrc) const override;
 
+  bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override;
+  absl::optional<int> GetBaseMinimumPlayoutDelayMs(
+      uint32_t ssrc) const override;
+
  private:
   bool SetRecvCodecs(const std::vector<VideoCodec>& codecs);
   bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
@@ -452,6 +456,7 @@
   std::vector<VideoCodec> send_codecs_;
   std::map<uint32_t, rtc::VideoSinkInterface<webrtc::VideoFrame>*> sinks_;
   std::map<uint32_t, rtc::VideoSourceInterface<webrtc::VideoFrame>*> sources_;
+  std::map<uint32_t, int> output_delays_;
   VideoOptions options_;
   int max_bps_;
 };
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 2a08c68..4106949 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -32,6 +32,7 @@
 #include "api/video/video_timing.h"
 #include "api/video_codecs/video_encoder_config.h"
 #include "media/base/codec.h"
+#include "media/base/delayable.h"
 #include "media/base/media_config.h"
 #include "media/base/media_constants.h"
 #include "media/base/stream_params.h"
@@ -707,7 +708,7 @@
 
 struct AudioRecvParameters : RtpParameters<AudioCodec> {};
 
-class VoiceMediaChannel : public MediaChannel {
+class VoiceMediaChannel : public MediaChannel, public Delayable {
  public:
   VoiceMediaChannel() {}
   explicit VoiceMediaChannel(const MediaConfig& config)
@@ -738,13 +739,6 @@
                             AudioSource* source) = 0;
   // Set speaker output volume of the specified ssrc.
   virtual bool SetOutputVolume(uint32_t ssrc, double volume) = 0;
-  // Set base minimum delay of the receive stream with specified ssrc.
-  // Base minimum delay sets lower bound on minimum delay value which
-  // determines minimum delay until audio playout.
-  // Returns false if there is no stream with given ssrc.
-  virtual bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) = 0;
-  virtual absl::optional<int> GetBaseMinimumPlayoutDelayMs(
-      uint32_t ssrc) const = 0;
   // Returns if the telephone-event has been negotiated.
   virtual bool CanInsertDtmf() = 0;
   // Send a DTMF |event|. The DTMF out-of-band signal will be used.
@@ -783,7 +777,7 @@
 // encapsulate all the parameters needed for a video RtpReceiver.
 struct VideoRecvParameters : RtpParameters<VideoCodec> {};
 
-class VideoMediaChannel : public MediaChannel {
+class VideoMediaChannel : public MediaChannel, public Delayable {
  public:
   VideoMediaChannel() {}
   explicit VideoMediaChannel(const MediaConfig& config)
diff --git a/media/engine/fake_webrtc_call.cc b/media/engine/fake_webrtc_call.cc
index 662c6d2..9ad99de 100644
--- a/media/engine/fake_webrtc_call.cc
+++ b/media/engine/fake_webrtc_call.cc
@@ -410,6 +410,15 @@
   return video_receive_streams_;
 }
 
+const FakeVideoReceiveStream* FakeCall::GetVideoReceiveStream(uint32_t ssrc) {
+  for (const auto* p : GetVideoReceiveStreams()) {
+    if (p->GetConfig().rtp.remote_ssrc == ssrc) {
+      return p;
+    }
+  }
+  return nullptr;
+}
+
 const std::vector<FakeAudioSendStream*>& FakeCall::GetAudioSendStreams() {
   return audio_send_streams_;
 }
diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h
index 7df6b52..71a54d6 100644
--- a/media/engine/fake_webrtc_call.h
+++ b/media/engine/fake_webrtc_call.h
@@ -222,6 +222,10 @@
     return std::vector<webrtc::RtpSource>();
   }
 
+  int base_mininum_playout_delay_ms() const {
+    return base_mininum_playout_delay_ms_;
+  }
+
  private:
   // webrtc::VideoReceiveStream implementation.
   void Start() override;
@@ -229,10 +233,21 @@
 
   webrtc::VideoReceiveStream::Stats GetStats() const override;
 
+  bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override {
+    base_mininum_playout_delay_ms_ = delay_ms;
+    return true;
+  }
+
+  int GetBaseMinimumPlayoutDelayMs() const override {
+    return base_mininum_playout_delay_ms_;
+  }
+
   webrtc::VideoReceiveStream::Config config_;
   bool receiving_;
   webrtc::VideoReceiveStream::Stats stats_;
 
+  int base_mininum_playout_delay_ms_ = 0;
+
   int num_added_secondary_sinks_;
   int num_removed_secondary_sinks_;
 };
@@ -268,6 +283,7 @@
   const FakeAudioSendStream* GetAudioSendStream(uint32_t ssrc);
   const std::vector<FakeAudioReceiveStream*>& GetAudioReceiveStreams();
   const FakeAudioReceiveStream* GetAudioReceiveStream(uint32_t ssrc);
+  const FakeVideoReceiveStream* GetVideoReceiveStream(uint32_t ssrc);
 
   const std::vector<FakeFlexfecReceiveStream*>& GetFlexfecReceiveStreams();
 
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 34489c0..144ebd7 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -418,10 +418,17 @@
 
   RTC_LOG(LS_INFO) << "Creating default receive stream for SSRC=" << ssrc
                    << ".";
-  if (!channel->AddRecvStream(sp, true)) {
+  if (!channel->AddRecvStream(sp, /*default_stream=*/true)) {
     RTC_LOG(LS_WARNING) << "Could not create default receive stream.";
   }
 
+  // SSRC 0 returns default_recv_base_minimum_delay_ms.
+  const int unsignaled_ssrc = 0;
+  int default_recv_base_minimum_delay_ms =
+      channel->GetBaseMinimumPlayoutDelayMs(unsignaled_ssrc).value_or(0);
+  // Set base minimum delay if it was set before for the default receive stream.
+  channel->SetBaseMinimumPlayoutDelayMs(ssrc,
+                                        default_recv_base_minimum_delay_ms);
   channel->SetSink(ssrc, default_sink_);
   return kDeliverPacket;
 }
@@ -1497,6 +1504,51 @@
   }
 }
 
+bool WebRtcVideoChannel::SetBaseMinimumPlayoutDelayMs(uint32_t ssrc,
+                                                      int delay_ms) {
+  absl::optional<uint32_t> default_ssrc = GetDefaultReceiveStreamSsrc();
+  rtc::CritScope stream_lock(&stream_crit_);
+
+  // SSRC of 0 represents the default receive stream.
+  if (ssrc == 0) {
+    default_recv_base_minimum_delay_ms_ = delay_ms;
+  }
+
+  if (ssrc == 0 && !default_ssrc) {
+    return true;
+  }
+
+  if (ssrc == 0 && default_ssrc) {
+    ssrc = default_ssrc.value();
+  }
+
+  auto stream = receive_streams_.find(ssrc);
+  if (stream != receive_streams_.end()) {
+    stream->second->SetBaseMinimumPlayoutDelayMs(delay_ms);
+    return true;
+  } else {
+    RTC_LOG(LS_ERROR) << "No stream found to set base minimum playout delay";
+    return false;
+  }
+}
+
+absl::optional<int> WebRtcVideoChannel::GetBaseMinimumPlayoutDelayMs(
+    uint32_t ssrc) const {
+  rtc::CritScope stream_lock(&stream_crit_);
+  // SSRC of 0 represents the default receive stream.
+  if (ssrc == 0) {
+    return default_recv_base_minimum_delay_ms_;
+  }
+
+  auto stream = receive_streams_.find(ssrc);
+  if (stream != receive_streams_.end()) {
+    return stream->second->GetBaseMinimumPlayoutDelayMs();
+  } else {
+    RTC_LOG(LS_ERROR) << "No stream found to get base minimum playout delay";
+    return absl::nullopt;
+  }
+}
+
 absl::optional<uint32_t> WebRtcVideoChannel::GetDefaultReceiveStreamSsrc() {
   rtc::CritScope stream_lock(&stream_crit_);
   absl::optional<uint32_t> ssrc;
@@ -2386,7 +2438,9 @@
 }
 
 void WebRtcVideoChannel::WebRtcVideoReceiveStream::RecreateWebRtcVideoStream() {
+  absl::optional<int> base_minimum_playout_delay_ms;
   if (stream_) {
+    base_minimum_playout_delay_ms = stream_->GetBaseMinimumPlayoutDelayMs();
     MaybeDissociateFlexfecFromVideo();
     call_->DestroyVideoReceiveStream(stream_);
     stream_ = nullptr;
@@ -2395,6 +2449,10 @@
   config.rtp.protected_by_flexfec = (flexfec_stream_ != nullptr);
   config.stream_id = stream_params_.id;
   stream_ = call_->CreateVideoReceiveStream(std::move(config));
+  if (base_minimum_playout_delay_ms) {
+    stream_->SetBaseMinimumPlayoutDelayMs(
+        base_minimum_playout_delay_ms.value());
+  }
   MaybeAssociateFlexfecWithVideo();
   stream_->Start();
 }
@@ -2457,6 +2515,16 @@
   }
 }
 
+bool WebRtcVideoChannel::WebRtcVideoReceiveStream::SetBaseMinimumPlayoutDelayMs(
+    int delay_ms) {
+  return stream_ ? stream_->SetBaseMinimumPlayoutDelayMs(delay_ms) : false;
+}
+
+int WebRtcVideoChannel::WebRtcVideoReceiveStream::GetBaseMinimumPlayoutDelayMs()
+    const {
+  return stream_ ? stream_->GetBaseMinimumPlayoutDelayMs() : 0;
+}
+
 void WebRtcVideoChannel::WebRtcVideoReceiveStream::SetSink(
     rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
   rtc::CritScope crit(&sink_lock_);
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index a3d5a2f..6604ab6 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -170,6 +170,11 @@
                          rtc::scoped_refptr<webrtc::FrameEncryptorInterface>
                              frame_encryptor) override;
 
+  bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override;
+
+  absl::optional<int> GetBaseMinimumPlayoutDelayMs(
+      uint32_t ssrc) const override;
+
   // Implemented for VideoMediaChannelTest.
   bool sending() const { return sending_; }
 
@@ -393,6 +398,10 @@
     void SetFrameDecryptor(
         rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor);
 
+    bool SetBaseMinimumPlayoutDelayMs(int delay_ms);
+
+    int GetBaseMinimumPlayoutDelayMs() const;
+
     void SetSink(rtc::VideoSinkInterface<webrtc::VideoFrame>* sink);
 
     VideoReceiverInfo GetVideoReceiverInfo(bool log_stats);
@@ -470,6 +479,9 @@
   DefaultUnsignalledSsrcHandler default_unsignalled_ssrc_handler_;
   UnsignalledSsrcHandler* const unsignalled_ssrc_handler_;
 
+  // Delay for unsignaled streams, which may be set before the stream exists.
+  int default_recv_base_minimum_delay_ms_ = 0;
+
   const MediaConfig::Video video_config_;
 
   rtc::CriticalSection stream_crit_;
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 493c18c..3b14f27 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -5012,6 +5012,56 @@
       fake_call_->GetVideoReceiveStreams()[0]->GetConfig().sync_group.empty());
 }
 
+// Test BaseMinimumPlayoutDelayMs on receive streams.
+TEST_F(WebRtcVideoChannelTest, BaseMinimumPlayoutDelayMs) {
+  // Test that set won't work for non-existing receive streams.
+  EXPECT_FALSE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrc + 2, 200));
+  // Test that get won't work for non-existing receive streams.
+  EXPECT_FALSE(channel_->GetBaseMinimumPlayoutDelayMs(kSsrc + 2));
+
+  EXPECT_TRUE(AddRecvStream());
+  // Test that set works for the existing receive stream.
+  EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(last_ssrc_, 200));
+  auto* recv_stream = fake_call_->GetVideoReceiveStream(last_ssrc_);
+  EXPECT_TRUE(recv_stream);
+  EXPECT_EQ(recv_stream->base_mininum_playout_delay_ms(), 200);
+  EXPECT_EQ(channel_->GetBaseMinimumPlayoutDelayMs(last_ssrc_).value_or(0),
+            200);
+}
+
+// Test BaseMinimumPlayoutDelayMs on unsignaled receive streams.
+TEST_F(WebRtcVideoChannelTest, BaseMinimumPlayoutDelayMsUnsignaledRecvStream) {
+  absl::optional<int> delay_ms;
+  const FakeVideoReceiveStream* recv_stream;
+
+  // Set default stream with SSRC 0
+  EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(0, 200));
+  EXPECT_EQ(200, channel_->GetBaseMinimumPlayoutDelayMs(0).value_or(0));
+
+  // Spawn an unsignaled stream by sending a packet, it should inherit
+  // default delay 200.
+  const size_t kDataLength = 12;
+  uint8_t data[kDataLength];
+  memset(data, 0, sizeof(data));
+  rtc::SetBE32(&data[8], kIncomingUnsignalledSsrc);
+  rtc::CopyOnWriteBuffer packet(data, kDataLength);
+  channel_->OnPacketReceived(&packet, /* packet_time_us */ -1);
+
+  recv_stream = fake_call_->GetVideoReceiveStream(kIncomingUnsignalledSsrc);
+  EXPECT_EQ(recv_stream->base_mininum_playout_delay_ms(), 200);
+  delay_ms = channel_->GetBaseMinimumPlayoutDelayMs(kIncomingUnsignalledSsrc);
+  EXPECT_EQ(200, delay_ms.value_or(0));
+
+  // Check that now if we change delay for SSRC 0 it will change delay for the
+  // default receiving stream as well.
+  EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(0, 300));
+  EXPECT_EQ(300, channel_->GetBaseMinimumPlayoutDelayMs(0).value_or(0));
+  delay_ms = channel_->GetBaseMinimumPlayoutDelayMs(kIncomingUnsignalledSsrc);
+  EXPECT_EQ(300, delay_ms.value_or(0));
+  recv_stream = fake_call_->GetVideoReceiveStream(kIncomingUnsignalledSsrc);
+  EXPECT_EQ(recv_stream->base_mininum_playout_delay_ms(), 300);
+}
+
 void WebRtcVideoChannelTest::TestReceiveUnsignaledSsrcPacket(
     uint8_t payload_type,
     bool expect_created_receive_stream) {
diff --git a/modules/video_coding/frame_buffer2.cc b/modules/video_coding/frame_buffer2.cc
index 6401117..e4def86 100644
--- a/modules/video_coding/frame_buffer2.cc
+++ b/modules/video_coding/frame_buffer2.cc
@@ -347,19 +347,6 @@
   return true;
 }
 
-void FrameBuffer::UpdatePlayoutDelays(const EncodedFrame& frame) {
-  TRACE_EVENT0("webrtc", "FrameBuffer::UpdatePlayoutDelays");
-  PlayoutDelay playout_delay = frame.EncodedImage().playout_delay_;
-  if (playout_delay.min_ms >= 0)
-    timing_->set_min_playout_delay(playout_delay.min_ms);
-
-  if (playout_delay.max_ms >= 0)
-    timing_->set_max_playout_delay(playout_delay.max_ms);
-
-  if (!frame.delayed_by_retransmission())
-    timing_->IncomingTimestamp(frame.Timestamp(), frame.ReceivedTime());
-}
-
 int64_t FrameBuffer::InsertFrame(std::unique_ptr<EncodedFrame> frame) {
   TRACE_EVENT0("webrtc", "FrameBuffer::InsertFrame");
   RTC_DCHECK(frame);
@@ -449,7 +436,9 @@
 
   if (!UpdateFrameInfoWithIncomingFrame(*frame, info))
     return last_continuous_picture_id;
-  UpdatePlayoutDelays(*frame);
+
+  if (!frame->delayed_by_retransmission())
+    timing_->IncomingTimestamp(frame->Timestamp(), frame->ReceivedTime());
 
   info->second.frame = std::move(frame);
 
diff --git a/modules/video_coding/frame_buffer2.h b/modules/video_coding/frame_buffer2.h
index c71c19f..2421c36 100644
--- a/modules/video_coding/frame_buffer2.h
+++ b/modules/video_coding/frame_buffer2.h
@@ -118,11 +118,6 @@
   // Check that the references of |frame| are valid.
   bool ValidReferences(const EncodedFrame& frame) const;
 
-  // Updates the minimal and maximal playout delays
-  // depending on the frame.
-  void UpdatePlayoutDelays(const EncodedFrame& frame)
-      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
-
   // Update all directly dependent and indirectly dependent frames and mark
   // them as continuous if all their references has been fulfilled.
   void PropagateContinuity(FrameMap::iterator start)
diff --git a/modules/video_coding/frame_buffer2_unittest.cc b/modules/video_coding/frame_buffer2_unittest.cc
index 94a5551..7dc8cb2 100644
--- a/modules/video_coding/frame_buffer2_unittest.cc
+++ b/modules/video_coding/frame_buffer2_unittest.cc
@@ -271,16 +271,6 @@
   CheckFrame(0, pid, 1);
 }
 
-TEST_F(TestFrameBuffer2, SetPlayoutDelay) {
-  const PlayoutDelay kPlayoutDelayMs = {123, 321};
-  std::unique_ptr<FrameObjectFake> test_frame(new FrameObjectFake());
-  test_frame->id.picture_id = 0;
-  test_frame->SetPlayoutDelay(kPlayoutDelayMs);
-  buffer_->InsertFrame(std::move(test_frame));
-  EXPECT_EQ(kPlayoutDelayMs.min_ms, timing_.min_playout_delay());
-  EXPECT_EQ(kPlayoutDelayMs.max_ms, timing_.max_playout_delay());
-}
-
 TEST_F(TestFrameBuffer2, ZeroPlayoutDelay) {
   VCMTiming timing(&clock_);
   buffer_.reset(
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 59fe940..5c3fb0e 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -151,6 +151,10 @@
     "peer_connection_factory.cc",
     "peer_connection_factory.h",
     "peer_connection_internal.h",
+    "playout_latency.cc",
+    "playout_latency.h",
+    "playout_latency_interface.h",
+    "playout_latency_proxy.h",
     "remote_audio_source.cc",
     "remote_audio_source.h",
     "rtc_stats_collector.cc",
@@ -396,6 +400,7 @@
       "test/frame_generator_capturer_video_track_source.h",
       "test/mock_channel_interface.h",
       "test/mock_data_channel.h",
+      "test/mock_delayable.h",
       "test/mock_peer_connection_observers.h",
       "test/mock_rtp_receiver_internal.h",
       "test/mock_rtp_sender_internal.h",
@@ -476,6 +481,7 @@
       "peer_connection_simulcast_unittest.cc",
       "peer_connection_wrapper.cc",
       "peer_connection_wrapper.h",
+      "playout_latency_unittest.cc",
       "proxy_unittest.cc",
       "rtc_stats_collector_unittest.cc",
       "rtc_stats_integrationtest.cc",
diff --git a/pc/playout_latency.cc b/pc/playout_latency.cc
new file mode 100644
index 0000000..be78439
--- /dev/null
+++ b/pc/playout_latency.cc
@@ -0,0 +1,87 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/playout_latency.h"
+
+#include "iostream"
+
+#include "rtc_base/checks.h"
+#include "rtc_base/location.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_conversions.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/thread_checker.h"
+
+namespace {
+constexpr int kDefaultLatency = 0;
+constexpr int kRoundToZeroThresholdMs = 10;
+}  // namespace
+
+namespace webrtc {
+
+PlayoutLatency::PlayoutLatency(rtc::Thread* worker_thread)
+    : signaling_thread_(rtc::Thread::Current()), worker_thread_(worker_thread) {
+  RTC_DCHECK(worker_thread_);
+}
+
+void PlayoutLatency::OnStart(cricket::Delayable* media_channel, uint32_t ssrc) {
+  RTC_DCHECK_RUN_ON(signaling_thread_);
+
+  media_channel_ = media_channel;
+  ssrc_ = ssrc;
+
+  // Trying to apply cached latency for the audio stream.
+  if (cached_latency_) {
+    SetLatency(cached_latency_.value());
+  }
+}
+
+void PlayoutLatency::OnStop() {
+  RTC_DCHECK_RUN_ON(signaling_thread_);
+  // Assume that audio stream is no longer present for latency calls.
+  media_channel_ = nullptr;
+  ssrc_ = absl::nullopt;
+}
+
+void PlayoutLatency::SetLatency(double latency) {
+  RTC_DCHECK_RUN_ON(worker_thread_);
+  RTC_DCHECK_GE(latency, 0);
+  RTC_DCHECK_LE(latency, 10);
+
+  int delay_ms = rtc::dchecked_cast<int>(latency * 1000);
+  // In JitterBuffer 0 delay has special meaning of being unconstrained value
+  // that is why we round delay to 0 if it is small enough during conversion
+  // from latency.
+  if (delay_ms <= kRoundToZeroThresholdMs) {
+    delay_ms = 0;
+  }
+
+  cached_latency_ = latency;
+  if (media_channel_ && ssrc_) {
+    media_channel_->SetBaseMinimumPlayoutDelayMs(ssrc_.value(), delay_ms);
+  }
+}
+
+double PlayoutLatency::GetLatency() const {
+  RTC_DCHECK_RUN_ON(worker_thread_);
+
+  absl::optional<int> delay_ms;
+  if (media_channel_ && ssrc_) {
+    delay_ms = media_channel_->GetBaseMinimumPlayoutDelayMs(ssrc_.value());
+  }
+
+  if (delay_ms) {
+    return delay_ms.value() / 1000.0;
+  } else {
+    return cached_latency_.value_or(kDefaultLatency);
+  }
+}
+
+}  // namespace webrtc
diff --git a/pc/playout_latency.h b/pc/playout_latency.h
new file mode 100644
index 0000000..f51a927
--- /dev/null
+++ b/pc/playout_latency.h
@@ -0,0 +1,53 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_PLAYOUT_LATENCY_H_
+#define PC_PLAYOUT_LATENCY_H_
+
+#include <stdint.h>
+
+#include "absl/types/optional.h"
+#include "media/base/delayable.h"
+#include "pc/playout_latency_interface.h"
+#include "rtc_base/thread.h"
+
+namespace webrtc {
+
+// PlayoutLatency converts latency measured in seconds to delay measured in
+// milliseconds for the underlying media channel. It also handles cases when
+// user sets Latency before the start of media_channel by caching its request.
+// Note, this class is not thread safe. Its thread safe version is defined in
+// pc/playout_latency_proxy.h
+class PlayoutLatency : public PlayoutLatencyInterface {
+ public:
+  // Must be called on signaling thread.
+  explicit PlayoutLatency(rtc::Thread* worker_thread);
+
+  void OnStart(cricket::Delayable* media_channel, uint32_t ssrc) override;
+
+  void OnStop() override;
+
+  void SetLatency(double latency) override;
+
+  double GetLatency() const override;
+
+ private:
+  // Throughout webrtc source, sometimes it is also called as |main_thread_|.
+  rtc::Thread* const signaling_thread_;
+  rtc::Thread* const worker_thread_;
+  // Media channel and ssrc together uniqely identify audio stream.
+  cricket::Delayable* media_channel_ = nullptr;
+  absl::optional<uint32_t> ssrc_;
+  absl::optional<double> cached_latency_;
+};
+
+}  // namespace webrtc
+
+#endif  // PC_PLAYOUT_LATENCY_H_
diff --git a/pc/playout_latency_interface.h b/pc/playout_latency_interface.h
new file mode 100644
index 0000000..e995733
--- /dev/null
+++ b/pc/playout_latency_interface.h
@@ -0,0 +1,43 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_PLAYOUT_LATENCY_INTERFACE_H_
+#define PC_PLAYOUT_LATENCY_INTERFACE_H_
+
+#include <stdint.h>
+
+#include "media/base/delayable.h"
+#include "rtc_base/ref_count.h"
+
+namespace webrtc {
+
+// PlayoutLatency delivers user's latency queries to the underlying media
+// channel. It can describe either video or audio latency for receiving stream.
+// "Interface" suffix in the interface name is required to be compatible with
+// api/proxy.cc
+class PlayoutLatencyInterface : public rtc::RefCountInterface {
+ public:
+  // OnStart allows to uniqely identify to which receiving stream playout
+  // latency must correpond through |media_channel| and |ssrc| pair.
+  virtual void OnStart(cricket::Delayable* media_channel, uint32_t ssrc) = 0;
+
+  // Indicates that underlying receiving stream is stopped.
+  virtual void OnStop() = 0;
+
+  // Sets latency in seconds.
+  virtual void SetLatency(double latency) = 0;
+
+  // Returns latency in seconds.
+  virtual double GetLatency() const = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // PC_PLAYOUT_LATENCY_INTERFACE_H_
diff --git a/pc/playout_latency_proxy.h b/pc/playout_latency_proxy.h
new file mode 100644
index 0000000..22f02c5
--- /dev/null
+++ b/pc/playout_latency_proxy.h
@@ -0,0 +1,32 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_PLAYOUT_LATENCY_PROXY_H_
+#define PC_PLAYOUT_LATENCY_PROXY_H_
+
+#include <stdint.h>
+
+#include "api/proxy.h"
+#include "media/base/delayable.h"
+#include "pc/playout_latency_interface.h"
+
+namespace webrtc {
+
+BEGIN_PROXY_MAP(PlayoutLatency)
+PROXY_SIGNALING_THREAD_DESTRUCTOR()
+PROXY_METHOD2(void, OnStart, cricket::Delayable*, uint32_t)
+PROXY_METHOD0(void, OnStop)
+PROXY_WORKER_METHOD1(void, SetLatency, double)
+PROXY_WORKER_CONSTMETHOD0(double, GetLatency)
+END_PROXY_MAP()
+
+}  // namespace webrtc
+
+#endif  // PC_PLAYOUT_LATENCY_PROXY_H_
diff --git a/pc/playout_latency_unittest.cc b/pc/playout_latency_unittest.cc
new file mode 100644
index 0000000..0a67161
--- /dev/null
+++ b/pc/playout_latency_unittest.cc
@@ -0,0 +1,103 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <stdint.h>
+
+#include "absl/types/optional.h"
+#include "api/scoped_refptr.h"
+#include "pc/playout_latency.h"
+#include "pc/test/mock_delayable.h"
+#include "rtc_base/ref_counted_object.h"
+#include "rtc_base/thread.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+using ::testing::Return;
+
+namespace {
+constexpr int kSsrc = 1234;
+}  // namespace
+
+namespace webrtc {
+
+class PlayoutLatencyTest : public testing::Test {
+ public:
+  PlayoutLatencyTest()
+      : latency_(
+            new rtc::RefCountedObject<PlayoutLatency>(rtc::Thread::Current())) {
+  }
+
+ protected:
+  rtc::scoped_refptr<PlayoutLatencyInterface> latency_;
+  MockDelayable delayable_;
+};
+
+TEST_F(PlayoutLatencyTest, DefaultValue) {
+  EXPECT_DOUBLE_EQ(0.0, latency_->GetLatency());
+}
+
+TEST_F(PlayoutLatencyTest, GetLatency) {
+  latency_->OnStart(&delayable_, kSsrc);
+
+  EXPECT_CALL(delayable_, GetBaseMinimumPlayoutDelayMs(kSsrc))
+      .WillOnce(Return(2000));
+  // Latency in seconds.
+  EXPECT_DOUBLE_EQ(2.0, latency_->GetLatency());
+
+  EXPECT_CALL(delayable_, GetBaseMinimumPlayoutDelayMs(kSsrc))
+      .WillOnce(Return(absl::nullopt));
+  // When no value is returned by GetBaseMinimumPlayoutDelayMs, and there are
+  // no caching, then return default value.
+  EXPECT_DOUBLE_EQ(0.0, latency_->GetLatency());
+}
+
+TEST_F(PlayoutLatencyTest, SetLatency) {
+  latency_->OnStart(&delayable_, kSsrc);
+
+  EXPECT_CALL(delayable_, SetBaseMinimumPlayoutDelayMs(kSsrc, 3000))
+      .WillOnce(Return(true));
+
+  // Latency in seconds.
+  latency_->SetLatency(3.0);
+}
+
+TEST_F(PlayoutLatencyTest, Caching) {
+  // Check that value is cached before start.
+  latency_->SetLatency(4.0);
+  // Latency in seconds.
+  EXPECT_DOUBLE_EQ(4.0, latency_->GetLatency());
+
+  // Check that cached value applied on the start.
+  EXPECT_CALL(delayable_, SetBaseMinimumPlayoutDelayMs(kSsrc, 4000))
+      .WillOnce(Return(true));
+  latency_->OnStart(&delayable_, kSsrc);
+
+  EXPECT_CALL(delayable_, GetBaseMinimumPlayoutDelayMs(kSsrc))
+      .WillOnce(Return(absl::nullopt));
+  // On false the latest cached value is returned.
+  EXPECT_DOUBLE_EQ(4.0, latency_->GetLatency());
+
+  latency_->OnStop();
+
+  // Check that after stop it returns last cached value.
+  EXPECT_DOUBLE_EQ(4.0, latency_->GetLatency());
+}
+
+TEST_F(PlayoutLatencyTest, Rounding) {
+  latency_->OnStart(&delayable_, kSsrc);
+  // In Jitter Buffer (Audio or Video) delay 0 has a special meaning of
+  // unconstrained variable, that is why here if latency is small enough we
+  // round it to 0 delay.
+  EXPECT_CALL(delayable_, SetBaseMinimumPlayoutDelayMs(kSsrc, 0))
+      .WillOnce(Return(true));
+  latency_->SetLatency(0.005);
+}
+
+}  // namespace webrtc
diff --git a/pc/remote_audio_source.cc b/pc/remote_audio_source.cc
index 63944c6..aba4400 100644
--- a/pc/remote_audio_source.cc
+++ b/pc/remote_audio_source.cc
@@ -16,6 +16,8 @@
 #include "absl/algorithm/container.h"
 #include "absl/memory/memory.h"
 #include "api/scoped_refptr.h"
+#include "pc/playout_latency.h"
+#include "pc/playout_latency_proxy.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/constructor_magic.h"
 #include "rtc_base/location.h"
@@ -26,11 +28,6 @@
 
 namespace webrtc {
 
-namespace {
-constexpr int kDefaultLatency = 0;
-constexpr int kRoundToZeroThresholdMs = 10;
-}  // namespace
-
 // This proxy is passed to the underlying media engine to receive audio data as
 // they come in. The data will then be passed back up to the RemoteAudioSource
 // which will fan it out to all the sinks that have been added to it.
@@ -55,7 +52,11 @@
 RemoteAudioSource::RemoteAudioSource(rtc::Thread* worker_thread)
     : main_thread_(rtc::Thread::Current()),
       worker_thread_(worker_thread),
-      state_(MediaSourceInterface::kLive) {
+      state_(MediaSourceInterface::kLive),
+      latency_(PlayoutLatencyProxy::Create(
+          main_thread_,
+          worker_thread_,
+          new rtc::RefCountedObject<PlayoutLatency>(worker_thread))) {
   RTC_DCHECK(main_thread_);
   RTC_DCHECK(worker_thread_);
 }
@@ -70,12 +71,6 @@
                               uint32_t ssrc) {
   RTC_DCHECK_RUN_ON(main_thread_);
   RTC_DCHECK(media_channel);
-  // Check that there are no consecutive start calls.
-  RTC_DCHECK(!media_channel_ && !ssrc_);
-
-  // Remember media channel ssrc pair for latency calls.
-  media_channel_ = media_channel;
-  ssrc_ = ssrc;
 
   // Register for callbacks immediately before AddSink so that we always get
   // notified when a channel goes out of scope (signaled when "AudioDataProxy"
@@ -85,10 +80,8 @@
                                    absl::make_unique<AudioDataProxy>(this));
   });
 
-  // Trying to apply cached latency for the audio stream.
-  if (cached_latency_) {
-    SetLatency(cached_latency_.value());
-  }
+  // Apply latency to the audio stream if |SetLatency| was called before.
+  latency_->OnStart(media_channel, ssrc);
 }
 
 void RemoteAudioSource::Stop(cricket::VoiceMediaChannel* media_channel,
@@ -96,9 +89,7 @@
   RTC_DCHECK_RUN_ON(main_thread_);
   RTC_DCHECK(media_channel);
 
-  // Assume that audio stream is no longer present for latency calls.
-  media_channel_ = nullptr;
-  ssrc_ = absl::nullopt;
+  latency_->OnStop();
 
   worker_thread_->Invoke<void>(
       RTC_FROM_HERE, [&] { media_channel->SetRawAudioSink(ssrc, nullptr); });
@@ -123,50 +114,11 @@
 }
 
 void RemoteAudioSource::SetLatency(double latency) {
-  RTC_DCHECK_GE(latency, 0);
-  RTC_DCHECK_LE(latency, 10);
-
-  int delay_ms = rtc::dchecked_cast<int>(latency * 1000);
-  // In NetEq 0 delay has special meaning of being unconstrained value that is
-  // why we round delay to 0 if it is small enough during conversion from
-  // latency.
-  if (delay_ms <= kRoundToZeroThresholdMs) {
-    delay_ms = 0;
-  }
-
-  cached_latency_ = latency;
-  SetDelayMs(delay_ms);
+  latency_->SetLatency(latency);
 }
 
 double RemoteAudioSource::GetLatency() const {
-  absl::optional<int> delay_ms = GetDelayMs();
-
-  if (delay_ms) {
-    return delay_ms.value() / 1000.0;
-  } else {
-    return cached_latency_.value_or(kDefaultLatency);
-  }
-}
-
-bool RemoteAudioSource::SetDelayMs(int delay_ms) {
-  if (!media_channel_ || !ssrc_) {
-    return false;
-  }
-
-  worker_thread_->Invoke<void>(RTC_FROM_HERE, [&] {
-    media_channel_->SetBaseMinimumPlayoutDelayMs(ssrc_.value(), delay_ms);
-  });
-  return true;
-}
-
-absl::optional<int> RemoteAudioSource::GetDelayMs() const {
-  if (!media_channel_ || !ssrc_) {
-    return absl::nullopt;
-  }
-
-  return worker_thread_->Invoke<absl::optional<int>>(RTC_FROM_HERE, [&] {
-    return media_channel_->GetBaseMinimumPlayoutDelayMs(ssrc_.value());
-  });
+  return latency_->GetLatency();
 }
 
 void RemoteAudioSource::RegisterAudioObserver(AudioObserver* observer) {
diff --git a/pc/remote_audio_source.h b/pc/remote_audio_source.h
index 0773c38..f4a5ec8 100644
--- a/pc/remote_audio_source.h
+++ b/pc/remote_audio_source.h
@@ -17,6 +17,7 @@
 #include "api/call/audio_sink.h"
 #include "api/notifier.h"
 #include "pc/channel.h"
+#include "pc/playout_latency_interface.h"
 #include "rtc_base/critical_section.h"
 #include "rtc_base/message_handler.h"
 
@@ -65,19 +66,15 @@
 
   void OnMessage(rtc::Message* msg) override;
 
-  bool SetDelayMs(int delay_ms);
-  absl::optional<int> GetDelayMs() const;
-
   rtc::Thread* const main_thread_;
   rtc::Thread* const worker_thread_;
   std::list<AudioObserver*> audio_observers_;
   rtc::CriticalSection sink_lock_;
   std::list<AudioTrackSinkInterface*> sinks_;
   SourceState state_;
-  // Media channel and ssrc together uniqely identify audio stream.
-  cricket::VoiceMediaChannel* media_channel_ = nullptr;
-  absl::optional<uint32_t> ssrc_;
-  absl::optional<double> cached_latency_;
+  // Allows to thread safely change playout latency. Handles caching cases if
+  // |SetLatency| is called before start.
+  rtc::scoped_refptr<PlayoutLatencyInterface> latency_;
 };
 
 }  // namespace webrtc
diff --git a/pc/rtp_sender_receiver_unittest.cc b/pc/rtp_sender_receiver_unittest.cc
index d55e4f4..c8d84f6 100644
--- a/pc/rtp_sender_receiver_unittest.cc
+++ b/pc/rtp_sender_receiver_unittest.cc
@@ -456,6 +456,41 @@
     RunSetLastLayerAsInactiveTest(video_rtp_sender_.get());
   }
 
+  void VerifyTrackLatencyBehaviour(cricket::Delayable* media_channel,
+                                   MediaStreamTrackInterface* track,
+                                   MediaSourceInterface* source,
+                                   uint32_t ssrc) {
+    absl::optional<int> delay_ms;  // In milliseconds.
+    double latency_s = 0.5;        // In seconds.
+
+    source->SetLatency(latency_s);
+    delay_ms = media_channel->GetBaseMinimumPlayoutDelayMs(ssrc);
+    EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+
+    // Disabling the track should take no effect on previously set value.
+    track->set_enabled(false);
+    delay_ms = media_channel->GetBaseMinimumPlayoutDelayMs(ssrc);
+    EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+
+    // When the track is disabled, we still should be able to set latency.
+    latency_s = 0.3;
+    source->SetLatency(latency_s);
+    delay_ms = media_channel->GetBaseMinimumPlayoutDelayMs(ssrc);
+    EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+
+    // Enabling the track should take no effect on previously set value.
+    track->set_enabled(true);
+    delay_ms = media_channel->GetBaseMinimumPlayoutDelayMs(ssrc);
+    EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+
+    // We still should be able to change latency.
+    latency_s = 0.0;
+    source->SetLatency(latency_s);
+    delay_ms = media_channel->GetBaseMinimumPlayoutDelayMs(ssrc);
+    EXPECT_EQ(0, delay_ms.value_or(-1));
+    EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+  }
+
  protected:
   rtc::Thread* const network_thread_;
   rtc::Thread* const worker_thread_;
@@ -636,101 +671,36 @@
   DestroyAudioRtpReceiver();
 }
 
-TEST_F(RtpSenderReceiverTest, RemoteAudioSourceLatencyCaching) {
+TEST_F(RtpSenderReceiverTest, RemoteAudioSourceLatency) {
   absl::optional<int> delay_ms;  // In milliseconds.
-  double latency_s = 0.5;        // In seconds.
-  rtc::scoped_refptr<RemoteAudioSource> source =
-      new rtc::RefCountedObject<RemoteAudioSource>(rtc::Thread::Current());
-
-  // Check default value.
-  EXPECT_DOUBLE_EQ(source->GetLatency(), 0.0);
-
-  // Check caching behaviour.
-  source->SetLatency(latency_s);
-  EXPECT_DOUBLE_EQ(source->GetLatency(), latency_s);
-
-  // Check that cached value applied on the start.
-  source->Start(voice_media_channel_, kAudioSsrc);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // Check that setting latency changes delay.
-  latency_s = 0.8;
-  source->SetLatency(latency_s);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-  EXPECT_DOUBLE_EQ(latency_s, source->GetLatency());
-
-  // Check that if underlying delay is changed then remote source will reflect
-  // it.
-  delay_ms = 300;
-  voice_media_channel_->SetBaseMinimumPlayoutDelayMs(kAudioSsrc,
-                                                     delay_ms.value());
-  EXPECT_DOUBLE_EQ(source->GetLatency(), delay_ms.value() / 1000.0);
-
-  // Check that after stop we get last cached value.
-  source->Stop(voice_media_channel_, kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, source->GetLatency());
-
-  // Check that if we start source again with new ssrc then cached value is
-  // applied.
-  source->Start(voice_media_channel_, kAudioSsrc2);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc2);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // Check rounding behavior.
-  source->SetLatency(2 / 1000.0);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc2);
-  EXPECT_EQ(0, delay_ms.value_or(-1));
-  EXPECT_DOUBLE_EQ(0, source->GetLatency());
-}
-
-TEST_F(RtpSenderReceiverTest, RemoteAudioSourceLatencyNoCaching) {
-  int delay_ms = 300;  // In milliseconds.
   rtc::scoped_refptr<RemoteAudioSource> source =
       new rtc::RefCountedObject<RemoteAudioSource>(rtc::Thread::Current());
 
   // Set it to value different from default zero.
-  voice_media_channel_->SetBaseMinimumPlayoutDelayMs(kAudioSsrc, delay_ms);
+  voice_media_channel_->SetBaseMinimumPlayoutDelayMs(kAudioSsrc, 300);
 
   // Check that calling GetLatency on the source that hasn't been started yet
-  // won't trigger caching.
+  // won't trigger caching and return default value.
   EXPECT_DOUBLE_EQ(source->GetLatency(), 0);
+
+  // Check that cached latency will be applied on start.
+  source->SetLatency(0.4);
+  EXPECT_DOUBLE_EQ(source->GetLatency(), 0.4);
   source->Start(voice_media_channel_, kAudioSsrc);
-  EXPECT_DOUBLE_EQ(source->GetLatency(), delay_ms / 1000.0);
+  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
+  EXPECT_EQ(400, delay_ms);
 }
 
-TEST_F(RtpSenderReceiverTest, RemoteAudioTrackSetLatency) {
+TEST_F(RtpSenderReceiverTest, RemoteAudioTrackLatency) {
   CreateAudioRtpReceiver();
+  VerifyTrackLatencyBehaviour(voice_media_channel_, audio_track_.get(),
+                              audio_track_->GetSource(), kAudioSsrc);
+}
 
-  absl::optional<int> delay_ms;  // In milliseconds.
-  double latency_s = 0.5;        // In seconds.
-  audio_track_->GetSource()->SetLatency(latency_s);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // Disabling the track should take no effect on previously set value.
-  audio_track_->set_enabled(false);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // When the track is disabled, we still should be able to set latency.
-  latency_s = 0.3;
-  audio_track_->GetSource()->SetLatency(latency_s);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // Enabling the track should take no effect on previously set value.
-  audio_track_->set_enabled(true);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
-
-  // We still should be able to change latency.
-  latency_s = 0.0;
-  audio_track_->GetSource()->SetLatency(latency_s);
-  delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc);
-  EXPECT_EQ(0, delay_ms.value_or(-1));
-  EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0);
+TEST_F(RtpSenderReceiverTest, RemoteVideoTrackLatency) {
+  CreateVideoRtpReceiver();
+  VerifyTrackLatencyBehaviour(video_media_channel_, video_track_.get(),
+                              video_track_->GetSource(), kVideoSsrc);
 }
 
 // Test that the media channel isn't enabled for sending if the audio sender
diff --git a/pc/test/mock_delayable.h b/pc/test/mock_delayable.h
new file mode 100644
index 0000000..548f9f8
--- /dev/null
+++ b/pc/test/mock_delayable.h
@@ -0,0 +1,31 @@
+/*
+ *  Copyright 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_TEST_MOCK_DELAYABLE_H_
+#define PC_TEST_MOCK_DELAYABLE_H_
+
+#include <stdint.h>
+
+#include "absl/types/optional.h"
+#include "media/base/delayable.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+
+class MockDelayable : public cricket::Delayable {
+ public:
+  MOCK_METHOD2(SetBaseMinimumPlayoutDelayMs, bool(uint32_t ssrc, int delay_ms));
+  MOCK_CONST_METHOD1(GetBaseMinimumPlayoutDelayMs,
+                     absl::optional<int>(uint32_t ssrc));
+};
+
+}  // namespace webrtc
+
+#endif  // PC_TEST_MOCK_DELAYABLE_H_
diff --git a/pc/video_rtp_receiver.cc b/pc/video_rtp_receiver.cc
index 2d9a23f..b112f04 100644
--- a/pc/video_rtp_receiver.cc
+++ b/pc/video_rtp_receiver.cc
@@ -39,7 +39,7 @@
     const std::vector<rtc::scoped_refptr<MediaStreamInterface>>& streams)
     : worker_thread_(worker_thread),
       id_(receiver_id),
-      source_(new RefCountedObject<VideoRtpTrackSource>()),
+      source_(new RefCountedObject<VideoRtpTrackSource>(worker_thread_)),
       track_(VideoTrackProxy::Create(
           rtc::Thread::Current(),
           worker_thread,
@@ -123,6 +123,7 @@
     // media channel has already been deleted.
     SetSink(nullptr);
   }
+  source_->Stop();
   stopped_ = true;
 }
 
@@ -142,6 +143,8 @@
   // Attach any existing frame decryptor to the media channel.
   MaybeAttachFrameDecryptorToMediaChannel(
       ssrc_, worker_thread_, frame_decryptor_, media_channel_, stopped_);
+
+  source_->Start(media_channel_, ssrc);
 }
 
 void VideoRtpReceiver::set_stream_ids(std::vector<std::string> stream_ids) {
diff --git a/pc/video_rtp_receiver.h b/pc/video_rtp_receiver.h
index ab507ae..2795e7a 100644
--- a/pc/video_rtp_receiver.h
+++ b/pc/video_rtp_receiver.h
@@ -27,6 +27,8 @@
 #include "api/video/video_source_interface.h"
 #include "media/base/media_channel.h"
 #include "media/base/video_broadcaster.h"
+#include "pc/playout_latency.h"
+#include "pc/playout_latency_proxy.h"
 #include "pc/rtp_receiver.h"
 #include "pc/video_track_source.h"
 #include "rtc_base/ref_counted_object.h"
@@ -37,7 +39,7 @@
 class VideoRtpReceiver : public rtc::RefCountedObject<RtpReceiverInternal> {
  public:
   // An SSRC of 0 will create a receiver that will match the first SSRC it
-  // sees.
+  // sees. Must be called on signaling thread.
   VideoRtpReceiver(rtc::Thread* worker_thread,
                    std::string receiver_id,
                    std::vector<std::string> streams_ids);
@@ -103,23 +105,42 @@
 
   std::vector<RtpSource> GetSources() const override;
 
- private:
   class VideoRtpTrackSource : public VideoTrackSource {
    public:
-    VideoRtpTrackSource() : VideoTrackSource(true /* remote */) {}
+    explicit VideoRtpTrackSource(rtc::Thread* worker_thread)
+        : VideoTrackSource(true /* remote */),
+          latency_(PlayoutLatencyProxy::Create(
+              rtc::Thread::Current(),
+              worker_thread,
+              new rtc::RefCountedObject<PlayoutLatency>(worker_thread))) {}
 
     rtc::VideoSourceInterface<VideoFrame>* source() override {
       return &broadcaster_;
     }
     rtc::VideoSinkInterface<VideoFrame>* sink() { return &broadcaster_; }
 
+    void SetLatency(double latency) override { latency_->SetLatency(latency); }
+
+    void Start(cricket::VideoMediaChannel* media_channel, uint32_t ssrc) {
+      latency_->OnStart(media_channel, ssrc);
+    }
+
+    void Stop() { latency_->OnStop(); }
+
+    double GetLatency() const override { return latency_->GetLatency(); }
+
    private:
+    // Allows to thread safely change playout latency. Handles caching cases if
+    // |SetLatency| is called before start.
+    rtc::scoped_refptr<PlayoutLatencyInterface> latency_;
+
     // |broadcaster_| is needed since the decoder can only handle one sink.
     // It might be better if the decoder can handle multiple sinks and consider
     // the VideoSinkWants.
     rtc::VideoBroadcaster broadcaster_;
   };
 
+ private:
   bool SetSink(rtc::VideoSinkInterface<VideoFrame>* sink);
 
   rtc::Thread* const worker_thread_;
diff --git a/video/video_receive_stream.cc b/video/video_receive_stream.cc
index c92a349..3cf786f 100644
--- a/video/video_receive_stream.cc
+++ b/video/video_receive_stream.cc
@@ -54,6 +54,9 @@
 namespace webrtc {
 
 namespace {
+constexpr int kMinBaseMinimumDelayMs = 0;
+constexpr int kMaxBaseMinimumDelayMs = 10000;
+
 VideoCodec CreateDecoderVideoCodec(const VideoReceiveStream::Decoder& decoder) {
   VideoCodec codec;
   memset(&codec, 0, sizeof(codec));
@@ -166,12 +169,14 @@
     PacketRouter* packet_router,
     VideoReceiveStream::Config config,
     ProcessThread* process_thread,
-    CallStats* call_stats)
+    CallStats* call_stats,
+    Clock* clock,
+    VCMTiming* timing)
     : transport_adapter_(config.rtcp_send_transport),
       config_(std::move(config)),
       num_cpu_cores_(num_cpu_cores),
       process_thread_(process_thread),
-      clock_(Clock::GetRealTimeClock()),
+      clock_(clock),
       decode_thread_(&DecodeThreadFunction,
                      this,
                      "DecodingThread",
@@ -180,8 +185,11 @@
       stats_proxy_(&config_, clock_),
       rtp_receive_statistics_(
           ReceiveStatistics::Create(clock_, &stats_proxy_, &stats_proxy_)),
-      timing_(new VCMTiming(clock_)),
-      video_receiver_(clock_, timing_.get(), this, this),
+      timing_(timing),
+      video_receiver_(clock_,
+                      timing_.get(),
+                      this,   // NackSender
+                      this),  // KeyFrameRequestSender
       rtp_video_stream_receiver_(&transport_adapter_,
                                  call_stats,
                                  packet_router,
@@ -201,6 +209,7 @@
   RTC_DCHECK(call_stats_);
 
   module_process_sequence_checker_.Detach();
+  network_sequence_checker_.Detach();
 
   RTC_DCHECK(!config_.decoders.empty());
   std::set<int> decoder_payload_types;
@@ -241,6 +250,22 @@
   }
 }
 
+VideoReceiveStream::VideoReceiveStream(
+    RtpStreamReceiverControllerInterface* receiver_controller,
+    int num_cpu_cores,
+    PacketRouter* packet_router,
+    VideoReceiveStream::Config config,
+    ProcessThread* process_thread,
+    CallStats* call_stats)
+    : VideoReceiveStream(receiver_controller,
+                         num_cpu_cores,
+                         packet_router,
+                         std::move(config),
+                         process_thread,
+                         call_stats,
+                         Clock::GetRealTimeClock(),
+                         new VCMTiming(Clock::GetRealTimeClock())) {}
+
 VideoReceiveStream::~VideoReceiveStream() {
   RTC_DCHECK_CALLED_SEQUENTIALLY(&worker_sequence_checker_);
   RTC_LOG(LS_INFO) << "~VideoReceiveStream: " << config_.ToString();
@@ -393,6 +418,25 @@
   rtp_video_stream_receiver_.RemoveSecondarySink(sink);
 }
 
+bool VideoReceiveStream::SetBaseMinimumPlayoutDelayMs(int delay_ms) {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&worker_sequence_checker_);
+  if (delay_ms < kMinBaseMinimumDelayMs || delay_ms > kMaxBaseMinimumDelayMs) {
+    return false;
+  }
+
+  rtc::CritScope cs(&playout_delay_lock_);
+  base_minimum_playout_delay_ms_ = delay_ms;
+  UpdatePlayoutDelays();
+  return true;
+}
+
+int VideoReceiveStream::GetBaseMinimumPlayoutDelayMs() const {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&worker_sequence_checker_);
+
+  rtc::CritScope cs(&playout_delay_lock_);
+  return base_minimum_playout_delay_ms_;
+}
+
 // TODO(tommi): This method grabs a lock 6 times.
 void VideoReceiveStream::OnFrame(const VideoFrame& video_frame) {
   int64_t sync_offset_ms;
@@ -428,6 +472,7 @@
 
 void VideoReceiveStream::OnCompleteFrame(
     std::unique_ptr<video_coding::EncodedFrame> frame) {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&network_sequence_checker_);
   // TODO(https://bugs.webrtc.org/9974): Consider removing this workaround.
   int64_t time_now_ms = rtc::TimeMillis();
   if (last_complete_frame_time_ms_ > 0 &&
@@ -436,6 +481,19 @@
   }
   last_complete_frame_time_ms_ = time_now_ms;
 
+  const PlayoutDelay& playout_delay = frame->EncodedImage().playout_delay_;
+  if (playout_delay.min_ms >= 0) {
+    rtc::CritScope cs(&playout_delay_lock_);
+    frame_minimum_playout_delay_ms_ = playout_delay.min_ms;
+    UpdatePlayoutDelays();
+  }
+
+  if (playout_delay.max_ms >= 0) {
+    rtc::CritScope cs(&playout_delay_lock_);
+    frame_maximum_playout_delay_ms_ = playout_delay.max_ms;
+    UpdatePlayoutDelays();
+  }
+
   int64_t last_continuous_pid = frame_buffer_->InsertFrame(std::move(frame));
   if (last_continuous_pid != -1)
     rtp_video_stream_receiver_.FrameContinuous(last_continuous_pid);
@@ -482,7 +540,9 @@
 
 void VideoReceiveStream::SetMinimumPlayoutDelay(int delay_ms) {
   RTC_DCHECK_CALLED_SEQUENTIALLY(&module_process_sequence_checker_);
-  video_receiver_.SetMinimumPlayoutDelay(delay_ms);
+  rtc::CritScope cs(&playout_delay_lock_);
+  syncable_minimum_playout_delay_ms_ = delay_ms;
+  UpdatePlayoutDelays();
 }
 
 void VideoReceiveStream::DecodeThreadFunction(void* ptr) {
@@ -565,6 +625,20 @@
   return true;
 }
 
+void VideoReceiveStream::UpdatePlayoutDelays() const {
+  const int minimum_delay_ms =
+      std::max({frame_minimum_playout_delay_ms_, base_minimum_playout_delay_ms_,
+                syncable_minimum_playout_delay_ms_});
+  if (minimum_delay_ms >= 0) {
+    timing_->set_min_playout_delay(minimum_delay_ms);
+  }
+
+  const int maximum_delay_ms = frame_maximum_playout_delay_ms_;
+  if (maximum_delay_ms >= 0) {
+    timing_->set_max_playout_delay(maximum_delay_ms);
+  }
+}
+
 std::vector<webrtc::RtpSource> VideoReceiveStream::GetSources() const {
   return rtp_video_stream_receiver_.GetSources();
 }
diff --git a/video/video_receive_stream.h b/video/video_receive_stream.h
index 30b227d..978294d 100644
--- a/video/video_receive_stream.h
+++ b/video/video_receive_stream.h
@@ -57,6 +57,14 @@
                      PacketRouter* packet_router,
                      VideoReceiveStream::Config config,
                      ProcessThread* process_thread,
+                     CallStats* call_stats,
+                     Clock* clock,
+                     VCMTiming* timing);
+  VideoReceiveStream(RtpStreamReceiverControllerInterface* receiver_controller,
+                     int num_cpu_cores,
+                     PacketRouter* packet_router,
+                     VideoReceiveStream::Config config,
+                     ProcessThread* process_thread,
                      CallStats* call_stats);
   ~VideoReceiveStream() override;
 
@@ -76,6 +84,12 @@
   void AddSecondarySink(RtpPacketSinkInterface* sink) override;
   void RemoveSecondarySink(const RtpPacketSinkInterface* sink) override;
 
+  // SetBaseMinimumPlayoutDelayMs and GetBaseMinimumPlayoutDelayMs are called
+  // from webrtc/api level and requested by user code. For e.g. blink/js layer
+  // in Chromium.
+  bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override;
+  int GetBaseMinimumPlayoutDelayMs() const override;
+
   // Implements rtc::VideoSinkInterface<VideoFrame>.
   void OnFrame(const VideoFrame& video_frame) override;
 
@@ -104,6 +118,8 @@
   int id() const override;
   absl::optional<Syncable::Info> GetInfo() const override;
   uint32_t GetPlayoutTimestamp() const override;
+
+  // SetMinimumPlayoutDelay is only called by A/V sync.
   void SetMinimumPlayoutDelay(int delay_ms) override;
 
   std::vector<webrtc::RtpSource> GetSources() const override;
@@ -111,9 +127,12 @@
  private:
   static void DecodeThreadFunction(void* ptr);
   bool Decode();
+  void UpdatePlayoutDelays() const
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(playout_delay_lock_);
 
   rtc::SequencedTaskChecker worker_sequence_checker_;
   rtc::SequencedTaskChecker module_process_sequence_checker_;
+  rtc::SequencedTaskChecker network_sequence_checker_;
 
   TransportAdapter transport_adapter_;
   const VideoReceiveStream::Config config_;
@@ -158,6 +177,23 @@
 
   int64_t last_keyframe_request_ms_ = 0;
   int64_t last_complete_frame_time_ms_ = 0;
+
+  rtc::CriticalSection playout_delay_lock_;
+
+  // All of them tries to change current min_playout_delay on |timing_| but
+  // source of the change request is different in each case. Among them the
+  // biggest delay is used. -1 means use default value from the |timing_|.
+  //
+  // Minimum delay as decided by the RTP playout delay extension.
+  int frame_minimum_playout_delay_ms_ RTC_GUARDED_BY(playout_delay_lock_) = -1;
+  // Minimum delay as decided by the setLatency function in "webrtc/api".
+  int base_minimum_playout_delay_ms_ RTC_GUARDED_BY(playout_delay_lock_) = -1;
+  // Minimum delay as decided by the A/V synchronization feature.
+  int syncable_minimum_playout_delay_ms_ RTC_GUARDED_BY(playout_delay_lock_) =
+      -1;
+
+  // Maximum delay as decided by the RTP playout delay extension.
+  int frame_maximum_playout_delay_ms_ RTC_GUARDED_BY(playout_delay_lock_) = -1;
 };
 }  // namespace internal
 }  // namespace webrtc
diff --git a/video/video_receive_stream_unittest.cc b/video/video_receive_stream_unittest.cc
index 259026a..0c76c86 100644
--- a/video/video_receive_stream_unittest.cc
+++ b/video/video_receive_stream_unittest.cc
@@ -8,6 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
+#include <utility>
 #include <vector>
 
 #include "test/gmock.h"
@@ -59,6 +60,13 @@
   const char* ImplementationName() const { return "MockVideoDecoder"; }
 };
 
+class FrameObjectFake : public video_coding::EncodedFrame {
+ public:
+  int64_t ReceivedTime() const override { return 0; }
+
+  int64_t RenderTime() const override { return _renderTimeMs; }
+};
+
 }  // namespace
 
 class VideoReceiveStreamTest : public testing::Test {
@@ -88,9 +96,12 @@
     null_decoder.decoder_factory = &null_decoder_factory_;
     config_.decoders.push_back(null_decoder);
 
+    Clock* clock = Clock::GetRealTimeClock();
+    timing_ = new VCMTiming(clock);
+
     video_receive_stream_.reset(new webrtc::internal::VideoReceiveStream(
         &rtp_stream_receiver_controller_, kDefaultNumCpuCores, &packet_router_,
-        config_.Copy(), process_thread_.get(), &call_stats_));
+        config_.Copy(), process_thread_.get(), &call_stats_, clock, timing_));
   }
 
  protected:
@@ -106,6 +117,7 @@
   PacketRouter packet_router_;
   RtpStreamReceiverController rtp_stream_receiver_controller_;
   std::unique_ptr<webrtc::internal::VideoReceiveStream> video_receive_stream_;
+  VCMTiming* timing_;
 };
 
 TEST_F(VideoReceiveStreamTest, CreateFrameFromH264FmtpSpropAndIdr) {
@@ -136,4 +148,65 @@
   init_decode_event_.Wait(kDefaultTimeOutMs);
 }
 
+TEST_F(VideoReceiveStreamTest, PlayoutDelay) {
+  const PlayoutDelay kPlayoutDelayMs = {123, 321};
+  std::unique_ptr<FrameObjectFake> test_frame(new FrameObjectFake());
+  test_frame->id.picture_id = 0;
+  test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+  video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+  EXPECT_EQ(kPlayoutDelayMs.min_ms, timing_->min_playout_delay());
+  EXPECT_EQ(kPlayoutDelayMs.max_ms, timing_->max_playout_delay());
+
+  // Check that the biggest minimum delay is chosen.
+  video_receive_stream_->SetMinimumPlayoutDelay(400);
+  EXPECT_EQ(400, timing_->min_playout_delay());
+
+  // Check base minimum delay validation.
+  EXPECT_FALSE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(12345));
+  EXPECT_FALSE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(-1));
+  EXPECT_TRUE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(500));
+  EXPECT_EQ(500, timing_->min_playout_delay());
+
+  // Check that intermidiate values are remembered and the biggest remembered
+  // is chosen.
+  video_receive_stream_->SetBaseMinimumPlayoutDelayMs(0);
+  EXPECT_EQ(400, timing_->min_playout_delay());
+
+  video_receive_stream_->SetMinimumPlayoutDelay(0);
+  EXPECT_EQ(123, timing_->min_playout_delay());
+}
+
+TEST_F(VideoReceiveStreamTest, PlayoutDelayPreservesDefaultMaxValue) {
+  const int default_max_playout_latency = timing_->max_playout_delay();
+  const PlayoutDelay kPlayoutDelayMs = {123, -1};
+
+  std::unique_ptr<FrameObjectFake> test_frame(new FrameObjectFake());
+  test_frame->id.picture_id = 0;
+  test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+  video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+
+  // Ensure that -1 preserves default maximum value from |timing_|.
+  EXPECT_EQ(kPlayoutDelayMs.min_ms, timing_->min_playout_delay());
+  EXPECT_NE(kPlayoutDelayMs.max_ms, timing_->max_playout_delay());
+  EXPECT_EQ(default_max_playout_latency, timing_->max_playout_delay());
+}
+
+TEST_F(VideoReceiveStreamTest, PlayoutDelayPreservesDefaultMinValue) {
+  const int default_min_playout_latency = timing_->min_playout_delay();
+  const PlayoutDelay kPlayoutDelayMs = {-1, 321};
+
+  std::unique_ptr<FrameObjectFake> test_frame(new FrameObjectFake());
+  test_frame->id.picture_id = 0;
+  test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+  video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+
+  // Ensure that -1 preserves default minimum value from |timing_|.
+  EXPECT_NE(kPlayoutDelayMs.min_ms, timing_->min_playout_delay());
+  EXPECT_EQ(kPlayoutDelayMs.max_ms, timing_->max_playout_delay());
+  EXPECT_EQ(default_min_playout_latency, timing_->min_playout_delay());
+}
+
 }  // namespace webrtc