Implement PeerConnection::AddTrack/RemoveTrack for Unified Plan

Bug: webrtc:7600
Change-Id: I2a48426a29ac67b6bdbd7817fe07273cdd5fd980
Reviewed-on: https://webrtc-review.googlesource.com/31647
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21305}
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index da190ad..49ab979 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -1005,83 +1005,195 @@
     MediaStreamTrackInterface* track,
     std::vector<MediaStreamInterface*> streams) {
   TRACE_EVENT0("webrtc", "PeerConnection::AddTrack");
-  if (IsClosed()) {
+  std::vector<std::string> stream_labels;
+  for (auto* stream : streams) {
+    if (!stream) {
+      RTC_LOG(LS_ERROR) << "Stream list has null element.";
+      return nullptr;
+    }
+    stream_labels.push_back(stream->label());
+  }
+  auto sender_or_error = AddTrackWithStreamLabels(track, stream_labels);
+  if (!sender_or_error.ok()) {
     return nullptr;
   }
-  if (streams.size() >= 2) {
-    RTC_LOG(LS_ERROR)
-        << "Adding a track with two streams is not currently supported.";
-    return nullptr;
+  return sender_or_error.MoveValue();
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+PeerConnection::AddTrackWithStreamLabels(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_labels) {
+  TRACE_EVENT0("webrtc", "PeerConnection::AddTrackWithStreamLabels");
+  if (!track) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Track is null.");
+  }
+  if (!(track->kind() == MediaStreamTrackInterface::kAudioKind ||
+        track->kind() == MediaStreamTrackInterface::kVideoKind)) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                         "Track has invalid kind: " + track->kind());
+  }
+  // TODO(bugs.webrtc.org/7932): Support adding a track to multiple streams.
+  if (stream_labels.size() > 1u) {
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::UNSUPPORTED_OPERATION,
+        "AddTrack with more than one stream is not currently supported.");
+  }
+  if (IsClosed()) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
+                         "PeerConnection is closed.");
   }
   if (FindSenderForTrack(track)) {
-    RTC_LOG(LS_ERROR) << "Sender for track " << track->id()
-                      << " already exists.";
-    return nullptr;
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::INVALID_PARAMETER,
+        "Sender already exists for track " + track->id() + ".");
   }
+  // TODO(bugs.webrtc.org/7933): MediaSession expects the sender to have exactly
+  // one stream. AddTrackInternal will return an error if there is more than one
+  // stream, but if the caller specifies none then we need to generate a random
+  // stream label.
+  std::vector<std::string> adjusted_stream_labels = stream_labels;
+  if (stream_labels.empty()) {
+    adjusted_stream_labels.push_back(rtc::CreateRandomUuid());
+  }
+  RTC_DCHECK_EQ(1, adjusted_stream_labels.size());
+  auto sender_or_error =
+      (IsUnifiedPlan() ? AddTrackUnifiedPlan(track, adjusted_stream_labels)
+                       : AddTrackPlanB(track, adjusted_stream_labels));
+  if (sender_or_error.ok()) {
+    observer_->OnRenegotiationNeeded();
+  }
+  return sender_or_error;
+}
 
-  // TODO(deadbeef): Support adding a track to multiple streams.
-  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender;
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+PeerConnection::AddTrackPlanB(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_labels) {
   if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
-    new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+    auto new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(),
-        new AudioRtpSender(static_cast<AudioTrackInterface*>(track),
+        new AudioRtpSender(static_cast<AudioTrackInterface*>(track.get()),
                            voice_channel(), stats_.get()));
     GetAudioTransceiver()->internal()->AddSender(new_sender);
-    if (!streams.empty()) {
-      new_sender->internal()->set_stream_id(streams[0]->label());
-    }
+    new_sender->internal()->set_stream_ids(stream_labels);
     const RtpSenderInfo* sender_info =
         FindSenderInfo(local_audio_sender_infos_,
                        new_sender->internal()->stream_id(), track->id());
     if (sender_info) {
       new_sender->internal()->SetSsrc(sender_info->first_ssrc);
     }
-  } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
-    new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+    return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
+  } else {
+    RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
+    auto new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(),
-        new VideoRtpSender(static_cast<VideoTrackInterface*>(track),
+        new VideoRtpSender(static_cast<VideoTrackInterface*>(track.get()),
                            video_channel()));
     GetVideoTransceiver()->internal()->AddSender(new_sender);
-    if (!streams.empty()) {
-      new_sender->internal()->set_stream_id(streams[0]->label());
-    }
+    new_sender->internal()->set_stream_ids(stream_labels);
     const RtpSenderInfo* sender_info =
         FindSenderInfo(local_video_sender_infos_,
                        new_sender->internal()->stream_id(), track->id());
     if (sender_info) {
       new_sender->internal()->SetSsrc(sender_info->first_ssrc);
     }
-  } else {
-    RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: "
-                      << track->kind();
-    return rtc::scoped_refptr<RtpSenderInterface>();
+    return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
   }
+}
 
-  observer_->OnRenegotiationNeeded();
-  return new_sender;
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+PeerConnection::AddTrackUnifiedPlan(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_labels) {
+  auto transceiver = FindFirstTransceiverForAddedTrack(track);
+  if (transceiver) {
+    if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) {
+      transceiver->SetDirection(RtpTransceiverDirection::kSendRecv);
+    } else if (transceiver->direction() == RtpTransceiverDirection::kInactive) {
+      transceiver->SetDirection(RtpTransceiverDirection::kSendOnly);
+    }
+  } else {
+    cricket::MediaType media_type =
+        (track->kind() == MediaStreamTrackInterface::kAudioKind
+             ? cricket::MEDIA_TYPE_AUDIO
+             : cricket::MEDIA_TYPE_VIDEO);
+    transceiver = CreateTransceiver(media_type);
+    transceiver->internal()->set_created_by_addtrack(true);
+    transceiver->SetDirection(RtpTransceiverDirection::kSendRecv);
+  }
+  transceiver->sender()->SetTrack(track);
+  transceiver->internal()->sender_internal()->set_stream_ids(stream_labels);
+  return transceiver->sender();
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+PeerConnection::FindFirstTransceiverForAddedTrack(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track) {
+  RTC_DCHECK(track);
+  for (auto transceiver : transceivers_) {
+    if (!transceiver->sender()->track() &&
+        cricket::MediaTypeToString(transceiver->internal()->media_type()) ==
+            track->kind() &&
+        !transceiver->internal()->has_ever_been_used_to_send()) {
+      return transceiver;
+    }
+  }
+  return nullptr;
 }
 
 bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) {
   TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack");
+  return RemoveTrackInternal(sender).ok();
+}
+
+RTCError PeerConnection::RemoveTrackInternal(
+    rtc::scoped_refptr<RtpSenderInterface> sender) {
+  if (!sender) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Sender is null.");
+  }
   if (IsClosed()) {
-    return false;
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
+                         "PeerConnection is closed.");
   }
-
-  bool removed;
-  if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-    removed = GetAudioTransceiver()->internal()->RemoveSender(sender);
+  if (IsUnifiedPlan()) {
+    auto transceiver = FindTransceiverBySender(sender);
+    if (!transceiver || !sender->track()) {
+      return RTCError::OK();
+    }
+    sender->SetTrack(nullptr);
+    if (transceiver->direction() == RtpTransceiverDirection::kSendRecv) {
+      transceiver->internal()->SetDirection(RtpTransceiverDirection::kRecvOnly);
+    } else if (transceiver->direction() == RtpTransceiverDirection::kSendOnly) {
+      transceiver->internal()->SetDirection(RtpTransceiverDirection::kInactive);
+    }
   } else {
-    RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
-    removed = GetVideoTransceiver()->internal()->RemoveSender(sender);
+    bool removed;
+    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      removed = GetAudioTransceiver()->internal()->RemoveSender(sender);
+    } else {
+      RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
+      removed = GetVideoTransceiver()->internal()->RemoveSender(sender);
+    }
+    if (!removed) {
+      LOG_AND_RETURN_ERROR(
+          RTCErrorType::INVALID_PARAMETER,
+          "Couldn't find sender " + sender->id() + " to remove.");
+    }
   }
-  if (!removed) {
-    RTC_LOG(LS_ERROR) << "Couldn't find sender " << sender->id()
-                      << " to remove.";
-    return false;
-  }
-
   observer_->OnRenegotiationNeeded();
-  return true;
+  return RTCError::OK();
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+PeerConnection::FindTransceiverBySender(
+    rtc::scoped_refptr<RtpSenderInterface> sender) {
+  for (auto transceiver : transceivers_) {
+    if (transceiver->sender() == sender) {
+      return transceiver;
+    }
+  }
+  return nullptr;
 }
 
 RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
@@ -1151,10 +1263,26 @@
 
   // TODO(bugs.webrtc.org/7600): Verify init.
 
+  auto transceiver = CreateTransceiver(media_type);
+  transceiver->SetDirection(init.direction);
+  if (track) {
+    transceiver->sender()->SetTrack(track);
+  }
+
+  observer_->OnRenegotiationNeeded();
+
+  return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+PeerConnection::CreateTransceiver(cricket::MediaType media_type) {
   rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender;
   rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
       receiver;
   std::string receiver_id = rtc::CreateRandomUuid();
+  // TODO(bugs.webrtc.org/7600): Initializing the sender/receiver with a null
+  // channel prevents users from calling SetParameters on them, which is needed
+  // to be in compliance with the spec.
   if (media_type == cricket::MEDIA_TYPE_AUDIO) {
     sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(), new AudioRtpSender(nullptr, stats_.get()));
@@ -1168,24 +1296,11 @@
         signaling_thread(),
         new VideoRtpReceiver(receiver_id, {}, worker_thread(), 0, nullptr));
   }
-  // TODO(bugs.webrtc.org/7600): Initializing the sender/receiver with a null
-  // channel prevents users from calling SetParameters on them, which is needed
-  // to be in compliance with the spec.
-
-  if (track) {
-    sender->SetTrack(track);
-  }
-
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
       transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
           signaling_thread(), new RtpTransceiver(sender, receiver));
-  transceiver->SetDirection(init.direction);
-
   transceivers_.push_back(transceiver);
-
-  observer_->OnRenegotiationNeeded();
-
-  return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
+  return transceiver;
 }
 
 rtc::scoped_refptr<DtmfSenderInterface> PeerConnection::CreateDtmfSender(
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index 16185de..50a9da1 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -89,6 +89,9 @@
   bool AddStream(MediaStreamInterface* local_stream) override;
   void RemoveStream(MediaStreamInterface* local_stream) override;
 
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackWithStreamLabels(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_labels) override;
   rtc::scoped_refptr<RtpSenderInterface> AddTrack(
       MediaStreamTrackInterface* track,
       std::vector<MediaStreamInterface*> streams) override;
@@ -342,11 +345,37 @@
   void RemoveVideoTrack(VideoTrackInterface* track,
                         MediaStreamInterface* stream);
 
+  // AddTrack implementation when Unified Plan is specified.
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackUnifiedPlan(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_labels);
+  // AddTrack implementation when Plan B is specified.
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackPlanB(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_labels);
+
+  // Returns the first RtpTransceiver suitable for a newly added track, if such
+  // transceiver is available.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  FindFirstTransceiverForAddedTrack(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track);
+
+  // RemoveTrack that returns an RTCError.
+  RTCError RemoveTrackInternal(rtc::scoped_refptr<RtpSenderInterface> sender);
+
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  FindTransceiverBySender(rtc::scoped_refptr<RtpSenderInterface> sender);
+
   RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
       cricket::MediaType media_type,
       rtc::scoped_refptr<MediaStreamTrackInterface> track,
       const RtpTransceiverInit& init);
 
+  // Create a new RtpTransceiver of the given type and add it to the list of
+  // transceivers.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  CreateTransceiver(cricket::MediaType media_type);
+
   void SetIceConnectionState(IceConnectionState new_state);
   // Called any time the IceGatheringState changes
   void OnIceGatheringChange(IceGatheringState new_state);
diff --git a/pc/peerconnection_rtp_unittest.cc b/pc/peerconnection_rtp_unittest.cc
index 4989726..1e16728 100644
--- a/pc/peerconnection_rtp_unittest.cc
+++ b/pc/peerconnection_rtp_unittest.cc
@@ -452,7 +452,7 @@
   EXPECT_FALSE(observer->called());
 }
 
-// RtpTransceiver Tests
+// RtpTransceiver Tests.
 
 // Test that by default there are no transceivers with Unified Plan.
 TEST_F(PeerConnectionRtpTest, PeerConnectionHasNoTransceivers) {
@@ -602,4 +602,277 @@
   caller->pc()->Close();
 }
 
+// Unified Plan AddTrack tests.
+
+class PeerConnectionRtpUnifiedPlanTest : public PeerConnectionRtpTest {};
+
+// Test that adding an audio track creates a new audio RtpSender with the given
+// track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddAudioTrackCreatesAudioSender) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_track = caller->CreateAudioTrack("a");
+  auto sender = caller->pc()->AddTrack(audio_track, {});
+  ASSERT_TRUE(sender);
+
+  EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, sender->media_type());
+  EXPECT_EQ(audio_track, sender->track());
+}
+
+// Test that adding a video track creates a new video RtpSender with the given
+// track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddVideoTrackCreatesVideoSender) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto video_track = caller->CreateVideoTrack("a");
+  auto sender = caller->pc()->AddTrack(video_track, {});
+  ASSERT_TRUE(sender);
+
+  EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
+  EXPECT_EQ(video_track, sender->track());
+}
+
+// Test that adding a track to a new PeerConnection creates an RtpTransceiver
+// with the sender that AddTrack returns and in the sendrecv direction.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddFirstTrackCreatesTransceiver) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender = caller->AddAudioTrack("a");
+  ASSERT_TRUE(sender);
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(1u, transceivers.size());
+  EXPECT_EQ(sender, transceivers[0]->sender());
+  EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceivers[0]->direction());
+}
+
+// Test that if a transceiver of the same type but no track had been added to
+// the PeerConnection and later a call to AddTrack is made, the resulting sender
+// is the transceiver's sender and the sender's track is the newly-added track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackReusesTransceiver) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  auto audio_track = caller->CreateAudioTrack("a");
+  auto sender = caller->pc()->AddTrack(audio_track, {});
+  ASSERT_TRUE(sender);
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(1u, transceivers.size());
+  EXPECT_EQ(transceiver, transceivers[0]);
+  EXPECT_EQ(sender, transceiver->sender());
+  EXPECT_EQ(audio_track, sender->track());
+}
+
+// Test that adding two tracks to a new PeerConnection creates two
+// RtpTransceivers in the same order.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, TwoAddTrackCreatesTwoTransceivers) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender1 = caller->AddAudioTrack("a");
+  auto sender2 = caller->AddVideoTrack("v");
+  ASSERT_TRUE(sender2);
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(2u, transceivers.size());
+  EXPECT_EQ(sender1, transceivers[0]->sender());
+  EXPECT_EQ(sender2, transceivers[1]->sender());
+}
+
+// Test that if there are multiple transceivers with no sending track then a
+// later call to AddTrack will use the one of the same type as the newly-added
+// track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackReusesTransceiverOfType) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  auto video_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  auto sender = caller->AddVideoTrack("v");
+
+  ASSERT_EQ(2u, caller->pc()->GetTransceivers().size());
+  EXPECT_NE(sender, audio_transceiver->sender());
+  EXPECT_EQ(sender, video_transceiver->sender());
+}
+
+// Test that if the only transceivers that do not have a sending track have a
+// different type from the added track, then AddTrack will create a new
+// transceiver for the track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       AddTrackDoesNotReuseTransceiverOfWrongType) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  auto sender = caller->AddVideoTrack("v");
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(2u, transceivers.size());
+  EXPECT_NE(sender, transceivers[0]->sender());
+  EXPECT_EQ(sender, transceivers[1]->sender());
+}
+
+// Test that the first available transceiver is reused by AddTrack when multiple
+// are available.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       AddTrackReusesFirstMatchingTransceiver) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  auto sender = caller->AddAudioTrack("a");
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(2u, transceivers.size());
+  EXPECT_EQ(sender, transceivers[0]->sender());
+  EXPECT_NE(sender, transceivers[1]->sender());
+}
+
+// Test that a call to AddTrack that reuses a transceiver will change the
+// direction from inactive to sendonly.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       AddTrackChangesDirectionFromInactiveToSendOnly) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  RtpTransceiverInit init;
+  init.direction = RtpTransceiverDirection::kInactive;
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
+
+  caller->observer()->clear_negotiation_needed();
+  ASSERT_TRUE(caller->AddAudioTrack("a"));
+  EXPECT_TRUE(caller->observer()->negotiation_needed());
+
+  EXPECT_EQ(RtpTransceiverDirection::kSendOnly, transceiver->direction());
+}
+
+// Test that a call to AddTrack that reuses a transceiver will change the
+// direction from recvonly to sendrecv.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       AddTrackChangesDirectionFromRecvOnlyToSendRecv) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  RtpTransceiverInit init;
+  init.direction = RtpTransceiverDirection::kRecvOnly;
+  auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
+
+  caller->observer()->clear_negotiation_needed();
+  ASSERT_TRUE(caller->AddAudioTrack("a"));
+  EXPECT_TRUE(caller->observer()->negotiation_needed());
+
+  EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceiver->direction());
+}
+
+// Unified Plan AddTrack error handling.
+
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackErrorIfClosed) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_track = caller->CreateAudioTrack("a");
+  caller->pc()->Close();
+
+  caller->observer()->clear_negotiation_needed();
+  EXPECT_FALSE(caller->pc()->AddTrack(audio_track, {}));
+  EXPECT_FALSE(caller->observer()->negotiation_needed());
+}
+
+TEST_F(PeerConnectionRtpUnifiedPlanTest, AddTrackErrorIfTrackAlreadyHasSender) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto audio_track = caller->CreateAudioTrack("a");
+  ASSERT_TRUE(caller->pc()->AddTrack(audio_track, {}));
+
+  caller->observer()->clear_negotiation_needed();
+  EXPECT_FALSE(caller->pc()->AddTrack(audio_track, {}));
+  EXPECT_FALSE(caller->observer()->negotiation_needed());
+}
+
+// Unified Plan RemoveTrack tests.
+
+// Test that calling RemoveTrack on a sender with a previously-added track
+// clears the sender's track.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackClearsSenderTrack) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender = caller->AddAudioTrack("a");
+  ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
+
+  EXPECT_FALSE(sender->track());
+}
+
+// Test that calling RemoveTrack on a sender where the transceiver is configured
+// in the sendrecv direction changes the transceiver's direction to recvonly.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       RemoveTrackChangesDirectionFromSendRecvToRecvOnly) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  RtpTransceiverInit init;
+  init.direction = RtpTransceiverDirection::kSendRecv;
+  auto transceiver =
+      caller->AddTransceiver(caller->CreateAudioTrack("a"), init);
+
+  caller->observer()->clear_negotiation_needed();
+  ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender()));
+  EXPECT_TRUE(caller->observer()->negotiation_needed());
+
+  EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceiver->direction());
+  EXPECT_TRUE(caller->observer()->renegotiation_needed_);
+}
+
+// Test that calling RemoveTrack on a sender where the transceiver is configured
+// in the sendonly direction changes the transceiver's direction to inactive.
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       RemoveTrackChangesDirectionFromSendOnlyToInactive) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  RtpTransceiverInit init;
+  init.direction = RtpTransceiverDirection::kSendOnly;
+  auto transceiver =
+      caller->AddTransceiver(caller->CreateAudioTrack("a"), init);
+
+  caller->observer()->clear_negotiation_needed();
+  ASSERT_TRUE(caller->pc()->RemoveTrack(transceiver->sender()));
+  EXPECT_TRUE(caller->observer()->negotiation_needed());
+
+  EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction());
+}
+
+// Test that calling RemoveTrack with a sender that has a null track results in
+// no change in state.
+TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackWithNullSenderTrackIsNoOp) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender = caller->AddAudioTrack("a");
+  auto transceiver = caller->pc()->GetTransceivers()[0];
+  ASSERT_TRUE(sender->SetTrack(nullptr));
+
+  caller->observer()->clear_negotiation_needed();
+  ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
+  EXPECT_FALSE(caller->observer()->negotiation_needed());
+
+  EXPECT_EQ(RtpTransceiverDirection::kSendRecv, transceiver->direction());
+}
+
+// Unified Plan RemoveTrack error handling.
+
+TEST_F(PeerConnectionRtpUnifiedPlanTest, RemoveTrackErrorIfClosed) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender = caller->AddAudioTrack("a");
+  caller->pc()->Close();
+
+  caller->observer()->clear_negotiation_needed();
+  EXPECT_FALSE(caller->pc()->RemoveTrack(sender));
+  EXPECT_FALSE(caller->observer()->negotiation_needed());
+}
+
+TEST_F(PeerConnectionRtpUnifiedPlanTest,
+       RemoveTrackNoErrorIfTrackAlreadyRemoved) {
+  auto caller = CreatePeerConnectionWithUnifiedPlan();
+
+  auto sender = caller->AddAudioTrack("a");
+  ASSERT_TRUE(caller->pc()->RemoveTrack(sender));
+
+  caller->observer()->clear_negotiation_needed();
+  EXPECT_TRUE(caller->pc()->RemoveTrack(sender));
+  EXPECT_FALSE(caller->observer()->negotiation_needed());
+}
+
 }  // namespace webrtc
diff --git a/pc/rtpsender.cc b/pc/rtpsender.cc
index f5f8a71..9c1d3b6 100644
--- a/pc/rtpsender.cc
+++ b/pc/rtpsender.cc
@@ -46,7 +46,7 @@
   sink_ = sink;
 }
 
-AudioRtpSender::AudioRtpSender(AudioTrackInterface* track,
+AudioRtpSender::AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
                                const std::vector<std::string>& stream_ids,
                                cricket::VoiceChannel* channel,
                                StatsCollector* stats)
@@ -65,7 +65,7 @@
   CreateDtmfSender();
 }
 
-AudioRtpSender::AudioRtpSender(AudioTrackInterface* track,
+AudioRtpSender::AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
                                cricket::VoiceChannel* channel,
                                StatsCollector* stats)
     : id_(track->id()),
@@ -303,7 +303,7 @@
       DtmfSenderProxy::Create(rtc::Thread::Current(), sender.get());
 }
 
-VideoRtpSender::VideoRtpSender(VideoTrackInterface* track,
+VideoRtpSender::VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
                                const std::vector<std::string>& stream_ids,
                                cricket::VideoChannel* channel)
     : id_(track->id()),
@@ -318,7 +318,7 @@
   track_->RegisterObserver(this);
 }
 
-VideoRtpSender::VideoRtpSender(VideoTrackInterface* track,
+VideoRtpSender::VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
                                cricket::VideoChannel* channel)
     : id_(track->id()),
       // TODO(steveanton): With Unified Plan this should be empty.
diff --git a/pc/rtpsender.h b/pc/rtpsender.h
index ce8c657..4eeb4d5 100644
--- a/pc/rtpsender.h
+++ b/pc/rtpsender.h
@@ -83,14 +83,14 @@
   // StatsCollector provided so that Add/RemoveLocalAudioTrack can be called
   // at the appropriate times.
   // |channel| can be null if one does not exist yet.
-  AudioRtpSender(AudioTrackInterface* track,
+  AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
                  const std::vector<std::string>& stream_id,
                  cricket::VoiceChannel* channel,
                  StatsCollector* stats);
 
   // Randomly generates stream_id.
   // |channel| can be null if one does not exist yet.
-  AudioRtpSender(AudioTrackInterface* track,
+  AudioRtpSender(rtc::scoped_refptr<AudioTrackInterface> track,
                  cricket::VoiceChannel* channel,
                  StatsCollector* stats);
 
@@ -181,13 +181,14 @@
                        public rtc::RefCountedObject<RtpSenderInternal> {
  public:
   // |channel| can be null if one does not exist yet.
-  VideoRtpSender(VideoTrackInterface* track,
+  VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
                  const std::vector<std::string>& stream_id,
                  cricket::VideoChannel* channel);
 
   // Randomly generates stream_id.
   // |channel| can be null if one does not exist yet.
-  VideoRtpSender(VideoTrackInterface* track, cricket::VideoChannel* channel);
+  VideoRtpSender(rtc::scoped_refptr<VideoTrackInterface> track,
+                 cricket::VideoChannel* channel);
 
   // Randomly generates id and stream_id.
   // |channel| can be null if one does not exist yet.
diff --git a/pc/rtptransceiver.cc b/pc/rtptransceiver.cc
index 8ca29fc..2919ee7 100644
--- a/pc/rtptransceiver.cc
+++ b/pc/rtptransceiver.cc
@@ -113,6 +113,19 @@
   return true;
 }
 
+rtc::scoped_refptr<RtpSenderInternal> RtpTransceiver::sender_internal() const {
+  RTC_DCHECK(unified_plan_);
+  RTC_CHECK_EQ(1u, senders_.size());
+  return senders_[0]->internal();
+}
+
+rtc::scoped_refptr<RtpReceiverInternal> RtpTransceiver::receiver_internal()
+    const {
+  RTC_DCHECK(unified_plan_);
+  RTC_CHECK_EQ(1u, receivers_.size());
+  return receivers_[0]->internal();
+}
+
 rtc::Optional<std::string> RtpTransceiver::mid() const {
   return mid_;
 }
diff --git a/pc/rtptransceiver.h b/pc/rtptransceiver.h
index 7053fad..19f393f 100644
--- a/pc/rtptransceiver.h
+++ b/pc/rtptransceiver.h
@@ -109,6 +109,25 @@
     return receivers_;
   }
 
+  // Returns the backing object for the transceiver's Unified Plan sender.
+  rtc::scoped_refptr<RtpSenderInternal> sender_internal() const;
+
+  // Returns the backing object for the transceiver's Unified Plan receiver.
+  rtc::scoped_refptr<RtpReceiverInternal> receiver_internal() const;
+
+  // According to JSEP rules for SetRemoteDescription, RtpTransceivers can be
+  // reused only if they were added by AddTrack.
+  void set_created_by_addtrack(bool created_by_addtrack) {
+    created_by_addtrack_ = created_by_addtrack;
+  }
+  bool created_by_addtrack() const { return created_by_addtrack_; }
+
+  // Returns true if this transceiver has ever had the current direction set to
+  // sendonly or sendrecv.
+  bool has_ever_been_used_to_send() const {
+    return has_ever_been_used_to_send_;
+  }
+
   // RtpTransceiverInterface implementation.
   rtc::Optional<std::string> mid() const override;
   rtc::scoped_refptr<RtpSenderInterface> sender() const override;
@@ -133,6 +152,10 @@
   RtpTransceiverDirection direction_ = RtpTransceiverDirection::kInactive;
   rtc::Optional<RtpTransceiverDirection> current_direction_;
   rtc::Optional<std::string> mid_;
+  bool created_by_addtrack_ = false;
+  // TODO(steveanton): Implement this once there is a mechanism to set the
+  // current direction.
+  bool has_ever_been_used_to_send_ = false;
 
   cricket::BaseChannel* channel_ = nullptr;
 };
diff --git a/pc/test/mockpeerconnectionobservers.h b/pc/test/mockpeerconnectionobservers.h
index b45c67d..5eb29f4 100644
--- a/pc/test/mockpeerconnectionobservers.h
+++ b/pc/test/mockpeerconnectionobservers.h
@@ -158,6 +158,9 @@
     return candidates;
   }
 
+  bool negotiation_needed() const { return renegotiation_needed_; }
+  void clear_negotiation_needed() { renegotiation_needed_ = false; }
+
   rtc::scoped_refptr<PeerConnectionInterface> pc_;
   PeerConnectionInterface::SignalingState state_;
   std::vector<std::unique_ptr<IceCandidateInterface>> candidates_;