Improve spec compliance of SetStreamIDs in RtpSenderInterface

This CL makes RtpSender::SetStreamIDs fire fire negotiationneeded
event if needed and exposes the method on RtpSenderInterface.

This is a spec-compliance change.

Bug: webrtc:10129
Change-Id: I2b98b92665c847102838b094516a79b24de0e47e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/135121
Commit-Queue: Guido Urdaneta <guidou@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27974}
diff --git a/api/rtp_sender_interface.h b/api/rtp_sender_interface.h
index e0a29a3..8507731 100644
--- a/api/rtp_sender_interface.h
+++ b/api/rtp_sender_interface.h
@@ -61,6 +61,11 @@
   // tracks.
   virtual std::vector<std::string> stream_ids() const = 0;
 
+  // Sets the IDs of the media streams associated with  this sender's track.
+  // These are signalled in the SDP so that the remote side can associate
+  // tracks.
+  virtual void SetStreamIDs(const std::vector<std::string>& stream_ids) {}
+
   // Returns the list of encoding parameters that will be applied when the SDP
   // local description is set. These initial encoding parameters can be set by
   // PeerConnection::AddTransceiver, and later updated with Get/SetParameters.
@@ -112,6 +117,7 @@
               rtc::scoped_refptr<FrameEncryptorInterface>)
 PROXY_CONSTMETHOD0(rtc::scoped_refptr<FrameEncryptorInterface>,
                    GetFrameEncryptor)
+PROXY_METHOD1(void, SetStreamIDs, const std::vector<std::string>&)
 END_PROXY_MAP()
 
 }  // namespace webrtc
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 065247d..551b0ed 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -1373,7 +1373,7 @@
           RtpTransceiverDirection::kSendOnly);
     }
     transceiver->sender()->SetTrack(track);
-    transceiver->internal()->sender_internal()->set_stream_ids(stream_ids);
+    transceiver->internal()->sender_internal()->SetStreamIDs(stream_ids);
   } else {
     cricket::MediaType media_type =
         (track->kind() == MediaStreamTrackInterface::kAudioKind
@@ -1632,19 +1632,19 @@
                (track->kind() == MediaStreamTrackInterface::kAudioKind));
     sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(),
-        AudioRtpSender::Create(worker_thread(), id, stats_.get()));
+        AudioRtpSender::Create(worker_thread(), id, stats_.get(), this));
     NoteUsageEvent(UsageEvent::AUDIO_ADDED);
   } else {
     RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
     RTC_DCHECK(!track ||
                (track->kind() == MediaStreamTrackInterface::kVideoKind));
     sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
-        signaling_thread(), VideoRtpSender::Create(worker_thread(), id));
+        signaling_thread(), VideoRtpSender::Create(worker_thread(), id, this));
     NoteUsageEvent(UsageEvent::VIDEO_ADDED);
   }
   bool set_track_succeeded = sender->SetTrack(track);
   RTC_DCHECK(set_track_succeeded);
-  sender->internal()->set_stream_ids(stream_ids);
+  sender->internal()->SetStreamIDs(stream_ids);
   sender->internal()->set_init_send_encodings(send_encodings);
   return sender;
 }
@@ -1722,14 +1722,14 @@
   rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender;
   if (kind == MediaStreamTrackInterface::kAudioKind) {
     auto audio_sender = AudioRtpSender::Create(
-        worker_thread(), rtc::CreateRandomUuid(), stats_.get());
+        worker_thread(), rtc::CreateRandomUuid(), stats_.get(), this);
     audio_sender->SetMediaChannel(voice_media_channel());
     new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(), audio_sender);
     GetAudioTransceiver()->internal()->AddSender(new_sender);
   } else if (kind == MediaStreamTrackInterface::kVideoKind) {
     auto video_sender =
-        VideoRtpSender::Create(worker_thread(), rtc::CreateRandomUuid());
+        VideoRtpSender::Create(worker_thread(), rtc::CreateRandomUuid(), this);
     video_sender->SetMediaChannel(video_media_channel());
     new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(), video_sender);
@@ -1738,7 +1738,7 @@
     RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind;
     return nullptr;
   }
-  new_sender->internal()->set_stream_ids(stream_ids);
+  new_sender->internal()->SetStreamIDs(stream_ids);
 
   return new_sender;
 }
@@ -2361,7 +2361,7 @@
       } else {
         // Get the StreamParams from the channel which could generate SSRCs.
         const std::vector<StreamParams>& streams = channel->local_streams();
-        transceiver->internal()->sender_internal()->set_stream_ids(
+        transceiver->internal()->sender_internal()->SetStreamIDs(
             streams[0].stream_ids());
         transceiver->internal()->sender_internal()->SetSsrc(
             streams[0].first_ssrc());
@@ -4010,7 +4010,7 @@
   if (sender) {
     // We already have a sender for this track, so just change the stream_id
     // so that it's correct in the next call to CreateOffer.
-    sender->internal()->set_stream_ids({stream->id()});
+    sender->internal()->SetStreamIDs({stream->id()});
     return;
   }
 
@@ -4055,7 +4055,7 @@
   if (sender) {
     // We already have a sender for this track, so just change the stream_id
     // so that it's correct in the next call to CreateOffer.
-    sender->internal()->set_stream_ids({stream->id()});
+    sender->internal()->SetStreamIDs({stream->id()});
     return;
   }
 
@@ -4987,7 +4987,7 @@
     return;
   }
 
-  sender->internal()->set_stream_ids({sender_info.stream_id});
+  sender->internal()->SetStreamIDs({sender_info.stream_id});
   sender->internal()->SetSsrc(sender_info.first_ssrc);
 }
 
@@ -7130,6 +7130,12 @@
   return ret;
 }
 
+void PeerConnection::OnSetStreamIDs() {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  if (IsUnifiedPlan())
+    UpdateNegotiationNeeded();
+}
+
 PeerConnectionObserver* PeerConnection::Observer() const {
   // In earlier production code, the pointer was not cleared on close,
   // which might have led to undefined behavior if the observer was not
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index 3009185..f636c00 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -25,6 +25,7 @@
 #include "pc/peer_connection_factory.h"
 #include "pc/peer_connection_internal.h"
 #include "pc/rtc_stats_collector.h"
+#include "pc/rtp_sender.h"
 #include "pc/rtp_transceiver.h"
 #include "pc/sctp_transport.h"
 #include "pc/stats_collector.h"
@@ -57,6 +58,7 @@
                        public DataChannelProviderInterface,
                        public DataChannelSink,
                        public JsepTransportController::Observer,
+                       public RtpSenderBase::SetStreamIDsObserver,
                        public rtc::MessageHandler,
                        public sigslot::has_slots<> {
  public:
@@ -1055,6 +1057,9 @@
                           rtc::scoped_refptr<DtlsTransport> dtls_transport,
                           MediaTransportInterface* media_transport) override;
 
+  // RtpSenderBase::SetStreamIDsObserver override.
+  void OnSetStreamIDs() override;
+
   // Returns the observer. Will crash on CHECK if the observer is removed.
   PeerConnectionObserver* Observer() const RTC_RUN_ON(signaling_thread());
 
diff --git a/pc/peer_connection_rtp_unittest.cc b/pc/peer_connection_rtp_unittest.cc
index 6925300..8e99727 100644
--- a/pc/peer_connection_rtp_unittest.cc
+++ b/pc/peer_connection_rtp_unittest.cc
@@ -491,15 +491,8 @@
   ASSERT_TRUE(callee->CreateAnswerAndSetAsLocal());
 
   // Change the stream ID in the offer.
-  // TODO(https://crbug.com/webrtc/10129): When RtpSenderInterface::SetStreams
-  // is supported, this can use that instead of munging the SDP.
-  auto offer = caller->CreateOffer();
-  auto contents = offer->description()->contents();
-  ASSERT_EQ(1u, contents.size());
-  auto& stream_params = contents[0].media_description()->mutable_streams();
-  ASSERT_EQ(1u, stream_params.size());
-  stream_params[0].set_stream_ids({"stream2"});
-  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+  caller->pc()->GetSenders()[0]->SetStreamIDs({"stream2"});
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
   ASSERT_EQ(1u, transceiver->receiver()->streams().size());
   EXPECT_EQ("stream2", transceiver->receiver()->streams()[0]->id());
 }
@@ -1797,12 +1790,7 @@
 
 // This test exercises the code path that fires a NegotiationNeeded
 // notification when the stream IDs of the local description differ from
-// the ones in the transceiver. Since SetStreams() is not yet available
-// on RtpSenderInterface, adding a track is used to trigger the check for
-// the NegotiationNeeded notification.
-// TODO(https://crbug.com/webrtc/10129): Replace this test with a test that
-// checks that calling SetStreams() on a sender fires the notification once
-// the method becomes available in RtpSenderInterface.
+// the ones in the transceiver.
 TEST_F(PeerConnectionRtpTestUnifiedPlan,
        ChangeAssociatedStreamsTriggersRenegotiation) {
   auto caller = CreatePeerConnection();
@@ -1817,18 +1805,15 @@
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
   caller->observer()->clear_negotiation_needed();
 
-  SessionDescriptionInterface* cld = const_cast<SessionDescriptionInterface*>(
-      caller->pc()->current_local_description());
-  ASSERT_EQ(cld->description()->contents().size(), 1u);
-
-  cricket::SessionDescription* description = cld->description();
-  cricket::ContentInfo& content_info = description->contents()[0];
-  ASSERT_EQ(content_info.media_description()->mutable_streams().size(), 1u);
-  content_info.media_description()->mutable_streams()[0].set_stream_ids(
-      {"stream3", "stream4", "stream5"});
-
-  ASSERT_TRUE(caller->AddTrack(caller->CreateAudioTrack("a2")));
+  transceiver->sender()->SetStreamIDs({"stream3", "stream4", "stream5"});
   EXPECT_TRUE(caller->observer()->negotiation_needed());
+
+  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+  auto callee_streams = callee->pc()->GetReceivers()[0]->streams();
+  ASSERT_EQ(3u, callee_streams.size());
+  EXPECT_EQ("stream3", callee_streams[0]->id());
+  EXPECT_EQ("stream4", callee_streams[1]->id());
+  EXPECT_EQ("stream5", callee_streams[2]->id());
 }
 
 INSTANTIATE_TEST_SUITE_P(PeerConnectionRtpTest,
diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc
index 22957d3..6097034 100644
--- a/pc/rtp_sender.cc
+++ b/pc/rtp_sender.cc
@@ -16,6 +16,7 @@
 #include "api/audio_options.h"
 #include "api/media_stream_interface.h"
 #include "media/base/media_engine.h"
+#include "pc/peer_connection.h"
 #include "pc/stats_collector.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/helpers.h"
@@ -118,8 +119,12 @@
   return false;
 }
 
-RtpSenderBase::RtpSenderBase(rtc::Thread* worker_thread, const std::string& id)
-    : worker_thread_(worker_thread), id_(id) {
+RtpSenderBase::RtpSenderBase(rtc::Thread* worker_thread,
+                             const std::string& id,
+                             SetStreamIDsObserver* set_stream_ids_observer)
+    : worker_thread_(worker_thread),
+      id_(id),
+      set_stream_ids_observer_(set_stream_ids_observer) {
   RTC_DCHECK(worker_thread);
   init_parameters_.encodings.emplace_back();
 }
@@ -215,6 +220,12 @@
   return result;
 }
 
+void RtpSenderBase::SetStreamIDs(const std::vector<std::string>& stream_ids) {
+  stream_ids_ = stream_ids;
+  if (set_stream_ids_observer_)
+    set_stream_ids_observer_->OnSetStreamIDs();
+}
+
 bool RtpSenderBase::SetTrack(MediaStreamTrackInterface* track) {
   TRACE_EVENT0("webrtc", "RtpSenderBase::SetTrack");
   if (stopped_) {
@@ -317,6 +328,7 @@
     RemoveTrackFromStats();
   }
   media_channel_ = nullptr;
+  set_stream_ids_observer_ = nullptr;
   stopped_ = true;
 }
 
@@ -395,15 +407,18 @@
 rtc::scoped_refptr<AudioRtpSender> AudioRtpSender::Create(
     rtc::Thread* worker_thread,
     const std::string& id,
-    StatsCollector* stats) {
+    StatsCollector* stats,
+    SetStreamIDsObserver* set_stream_ids_observer) {
   return rtc::scoped_refptr<AudioRtpSender>(
-      new rtc::RefCountedObject<AudioRtpSender>(worker_thread, id, stats));
+      new rtc::RefCountedObject<AudioRtpSender>(worker_thread, id, stats,
+                                                set_stream_ids_observer));
 }
 
 AudioRtpSender::AudioRtpSender(rtc::Thread* worker_thread,
                                const std::string& id,
-                               StatsCollector* stats)
-    : RtpSenderBase(worker_thread, id),
+                               StatsCollector* stats,
+                               SetStreamIDsObserver* set_stream_ids_observer)
+    : RtpSenderBase(worker_thread, id, set_stream_ids_observer),
       stats_(stats),
       dtmf_sender_proxy_(DtmfSenderProxy::Create(
           rtc::Thread::Current(),
@@ -539,14 +554,17 @@
 
 rtc::scoped_refptr<VideoRtpSender> VideoRtpSender::Create(
     rtc::Thread* worker_thread,
-    const std::string& id) {
+    const std::string& id,
+    SetStreamIDsObserver* set_stream_ids_observer) {
   return rtc::scoped_refptr<VideoRtpSender>(
-      new rtc::RefCountedObject<VideoRtpSender>(worker_thread, id));
+      new rtc::RefCountedObject<VideoRtpSender>(worker_thread, id,
+                                                set_stream_ids_observer));
 }
 
 VideoRtpSender::VideoRtpSender(rtc::Thread* worker_thread,
-                               const std::string& id)
-    : RtpSenderBase(worker_thread, id) {}
+                               const std::string& id,
+                               SetStreamIDsObserver* set_stream_ids_observer)
+    : RtpSenderBase(worker_thread, id, set_stream_ids_observer) {}
 
 VideoRtpSender::~VideoRtpSender() {
   Stop();
diff --git a/pc/rtp_sender.h b/pc/rtp_sender.h
index c786d90..73c6925 100644
--- a/pc/rtp_sender.h
+++ b/pc/rtp_sender.h
@@ -47,7 +47,7 @@
   // description).
   virtual void SetSsrc(uint32_t ssrc) = 0;
 
-  virtual void set_stream_ids(const std::vector<std::string>& stream_ids) = 0;
+  virtual void SetStreamIDs(const std::vector<std::string>& stream_ids) {}
   virtual void set_init_send_encodings(
       const std::vector<RtpEncodingParameters>& init_send_encodings) = 0;
   virtual void set_transport(
@@ -74,6 +74,12 @@
 // Shared implementation for RtpSenderInternal interface.
 class RtpSenderBase : public RtpSenderInternal, public ObserverInterface {
  public:
+  class SetStreamIDsObserver {
+   public:
+    virtual ~SetStreamIDsObserver() = default;
+    virtual void OnSetStreamIDs() = 0;
+  };
+
   // Sets the underlying MediaEngine channel associated with this RtpSender.
   // A VoiceMediaChannel should be used for audio RtpSenders and
   // a VideoMediaChannel should be used for video RtpSenders.
@@ -101,9 +107,7 @@
   uint32_t ssrc() const override { return ssrc_; }
 
   std::vector<std::string> stream_ids() const override { return stream_ids_; }
-  void set_stream_ids(const std::vector<std::string>& stream_ids) override {
-    stream_ids_ = stream_ids;
-  }
+  void SetStreamIDs(const std::vector<std::string>& stream_ids) override;
 
   std::string id() const override { return id_; }
 
@@ -143,7 +147,12 @@
   RTCError DisableEncodingLayers(const std::vector<std::string>& rid) override;
 
  protected:
-  RtpSenderBase(rtc::Thread* worker_thread, const std::string& id);
+  // If |set_stream_ids_observer| is not null, it is invoked when SetStreamIDs
+  // is called. |set_stream_ids_observer| is not owned by this object. If not
+  // null, it must be valid at least until this sender becomes stopped.
+  RtpSenderBase(rtc::Thread* worker_thread,
+                const std::string& id,
+                SetStreamIDsObserver* set_stream_ids_observer);
   // TODO(nisse): Since SSRC == 0 is technically valid, figure out
   // some other way to test if we have a valid SSRC.
   bool can_send_track() const { return track_ && ssrc_; }
@@ -183,6 +192,8 @@
   // const method.
   mutable absl::optional<std::string> last_transaction_id_;
   std::vector<std::string> disabled_rids_;
+
+  SetStreamIDsObserver* set_stream_ids_observer_ = nullptr;
 };
 
 // LocalAudioSinkAdapter receives data callback as a sink to the local
@@ -215,9 +226,14 @@
   // The sender is initialized with no track to send and no associated streams.
   // StatsCollector provided so that Add/RemoveLocalAudioTrack can be called
   // at the appropriate times.
-  static rtc::scoped_refptr<AudioRtpSender> Create(rtc::Thread* worker_thread,
-                                                   const std::string& id,
-                                                   StatsCollector* stats);
+  // If |set_stream_ids_observer| is not null, it is invoked when SetStreamIDs
+  // is called. |set_stream_ids_observer| is not owned by this object. If not
+  // null, it must be valid at least until this sender becomes stopped.
+  static rtc::scoped_refptr<AudioRtpSender> Create(
+      rtc::Thread* worker_thread,
+      const std::string& id,
+      StatsCollector* stats,
+      SetStreamIDsObserver* set_stream_ids_observer);
   virtual ~AudioRtpSender();
 
   // DtmfSenderProvider implementation.
@@ -240,7 +256,8 @@
  protected:
   AudioRtpSender(rtc::Thread* worker_thread,
                  const std::string& id,
-                 StatsCollector* stats);
+                 StatsCollector* stats,
+                 SetStreamIDsObserver* set_stream_ids_observer);
 
   void SetSend() override;
   void ClearSend() override;
@@ -274,8 +291,13 @@
  public:
   // Construct an RtpSender for video with the given sender ID.
   // The sender is initialized with no track to send and no associated streams.
-  static rtc::scoped_refptr<VideoRtpSender> Create(rtc::Thread* worker_thread,
-                                                   const std::string& id);
+  // If |set_stream_ids_observer| is not null, it is invoked when SetStreamIDs
+  // is called. |set_stream_ids_observer| is not owned by this object. If not
+  // null, it must be valid at least until this sender becomes stopped.
+  static rtc::scoped_refptr<VideoRtpSender> Create(
+      rtc::Thread* worker_thread,
+      const std::string& id,
+      SetStreamIDsObserver* set_stream_ids_observer);
   virtual ~VideoRtpSender();
 
   // ObserverInterface implementation
@@ -291,7 +313,9 @@
   rtc::scoped_refptr<DtmfSenderInterface> GetDtmfSender() const override;
 
  protected:
-  VideoRtpSender(rtc::Thread* worker_thread, const std::string& id);
+  VideoRtpSender(rtc::Thread* worker_thread,
+                 const std::string& id,
+                 SetStreamIDsObserver* set_stream_ids_observer);
 
   void SetSend() override;
   void ClearSend() override;
diff --git a/pc/rtp_sender_receiver_unittest.cc b/pc/rtp_sender_receiver_unittest.cc
index 90ca03f..be8c472 100644
--- a/pc/rtp_sender_receiver_unittest.cc
+++ b/pc/rtp_sender_receiver_unittest.cc
@@ -83,6 +83,13 @@
 static const uint32_t kVideoSsrcSimulcast = 102;
 static const uint32_t kVideoSimulcastLayerCount = 2;
 static const int kDefaultTimeout = 10000;  // 10 seconds.
+
+class MockSetStreamIDsObserver
+    : public webrtc::RtpSenderBase::SetStreamIDsObserver {
+ public:
+  MOCK_METHOD0(OnSetStreamIDs, void());
+};
+
 }  // namespace
 
 namespace webrtc {
@@ -187,10 +194,14 @@
       const rtc::scoped_refptr<LocalAudioSource>& source) {
     audio_track_ = AudioTrack::Create(kAudioTrackId, source);
     EXPECT_TRUE(local_stream_->AddTrack(audio_track_));
+    std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+        absl::make_unique<MockSetStreamIDsObserver>();
     audio_rtp_sender_ =
-        AudioRtpSender::Create(worker_thread_, audio_track_->id(), nullptr);
+        AudioRtpSender::Create(worker_thread_, audio_track_->id(), nullptr,
+                               set_stream_ids_observer.get());
     ASSERT_TRUE(audio_rtp_sender_->SetTrack(audio_track_));
-    audio_rtp_sender_->set_stream_ids({local_stream_->id()});
+    EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+    audio_rtp_sender_->SetStreamIDs({local_stream_->id()});
     audio_rtp_sender_->SetMediaChannel(voice_media_channel_);
     audio_rtp_sender_->SetSsrc(kAudioSsrc);
     audio_rtp_sender_->GetOnDestroyedSignal()->connect(
@@ -200,7 +211,7 @@
 
   void CreateAudioRtpSenderWithNoTrack() {
     audio_rtp_sender_ =
-        AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
+        AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr, nullptr);
     audio_rtp_sender_->SetMediaChannel(voice_media_channel_);
   }
 
@@ -248,16 +259,20 @@
 
   void CreateVideoRtpSender(bool is_screencast, uint32_t ssrc = kVideoSsrc) {
     AddVideoTrack(is_screencast);
-    video_rtp_sender_ =
-        VideoRtpSender::Create(worker_thread_, video_track_->id());
+    std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+        absl::make_unique<MockSetStreamIDsObserver>();
+    video_rtp_sender_ = VideoRtpSender::Create(
+        worker_thread_, video_track_->id(), set_stream_ids_observer.get());
     ASSERT_TRUE(video_rtp_sender_->SetTrack(video_track_));
-    video_rtp_sender_->set_stream_ids({local_stream_->id()});
+    EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+    video_rtp_sender_->SetStreamIDs({local_stream_->id()});
     video_rtp_sender_->SetMediaChannel(video_media_channel_);
     video_rtp_sender_->SetSsrc(ssrc);
     VerifyVideoChannelInput(ssrc);
   }
   void CreateVideoRtpSenderWithNoTrack() {
-    video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, /*id=*/"");
+    video_rtp_sender_ =
+        VideoRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
     video_rtp_sender_->SetMediaChannel(video_media_channel_);
   }
 
@@ -428,7 +443,7 @@
   void RunDisableSimulcastLayersWithoutMediaEngineTest(
       const std::vector<std::string>& all_layers,
       const std::vector<std::string>& disabled_layers) {
-    auto sender = VideoRtpSender::Create(rtc::Thread::Current(), "1");
+    auto sender = VideoRtpSender::Create(rtc::Thread::Current(), "1", nullptr);
     RtpParameters parameters;
     parameters.encodings.resize(all_layers.size());
     for (size_t i = 0; i < all_layers.size(); ++i) {
@@ -820,7 +835,7 @@
 
 TEST_F(RtpSenderReceiverTest, AudioSenderCanSetParametersBeforeNegotiation) {
   audio_rtp_sender_ =
-      AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
+      AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr, nullptr);
 
   RtpParameters params = audio_rtp_sender_->GetParameters();
   ASSERT_EQ(1u, params.encodings.size());
@@ -838,10 +853,14 @@
   audio_track_ = AudioTrack::Create(kAudioTrackId, nullptr);
   EXPECT_TRUE(local_stream_->AddTrack(audio_track_));
 
+  std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+      absl::make_unique<MockSetStreamIDsObserver>();
   audio_rtp_sender_ =
-      AudioRtpSender::Create(worker_thread_, audio_track_->id(), nullptr);
+      AudioRtpSender::Create(worker_thread_, audio_track_->id(), nullptr,
+                             set_stream_ids_observer.get());
   ASSERT_TRUE(audio_rtp_sender_->SetTrack(audio_track_));
-  audio_rtp_sender_->set_stream_ids({local_stream_->id()});
+  EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+  audio_rtp_sender_->SetStreamIDs({local_stream_->id()});
 
   std::vector<RtpEncodingParameters> init_encodings(1);
   init_encodings[0].max_bitrate_bps = 60000;
@@ -869,7 +888,7 @@
 TEST_F(RtpSenderReceiverTest,
        AudioSenderMustCallGetParametersBeforeSetParametersBeforeNegotiation) {
   audio_rtp_sender_ =
-      AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
+      AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr, nullptr);
 
   RtpParameters params;
   RTCError result = audio_rtp_sender_->SetParameters(params);
@@ -1048,7 +1067,8 @@
 }
 
 TEST_F(RtpSenderReceiverTest, VideoSenderCanSetParametersBeforeNegotiation) {
-  video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, /*id=*/"");
+  video_rtp_sender_ =
+      VideoRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
 
   RtpParameters params = video_rtp_sender_->GetParameters();
   ASSERT_EQ(1u, params.encodings.size());
@@ -1065,10 +1085,13 @@
 TEST_F(RtpSenderReceiverTest, VideoSenderInitParametersMovedAfterNegotiation) {
   AddVideoTrack(false);
 
-  video_rtp_sender_ =
-      VideoRtpSender::Create(worker_thread_, video_track_->id());
+  std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+      absl::make_unique<MockSetStreamIDsObserver>();
+  video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, video_track_->id(),
+                                             set_stream_ids_observer.get());
   ASSERT_TRUE(video_rtp_sender_->SetTrack(video_track_));
-  video_rtp_sender_->set_stream_ids({local_stream_->id()});
+  EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+  video_rtp_sender_->SetStreamIDs({local_stream_->id()});
 
   std::vector<RtpEncodingParameters> init_encodings(2);
   init_encodings[0].max_bitrate_bps = 60000;
@@ -1103,10 +1126,13 @@
        VideoSenderInitParametersMovedAfterManualSimulcastAndNegotiation) {
   AddVideoTrack(false);
 
-  video_rtp_sender_ =
-      VideoRtpSender::Create(worker_thread_, video_track_->id());
+  std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+      absl::make_unique<MockSetStreamIDsObserver>();
+  video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, video_track_->id(),
+                                             set_stream_ids_observer.get());
   ASSERT_TRUE(video_rtp_sender_->SetTrack(video_track_));
-  video_rtp_sender_->set_stream_ids({local_stream_->id()});
+  EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+  video_rtp_sender_->SetStreamIDs({local_stream_->id()});
 
   std::vector<RtpEncodingParameters> init_encodings(1);
   init_encodings[0].max_bitrate_bps = 60000;
@@ -1137,7 +1163,8 @@
 
 TEST_F(RtpSenderReceiverTest,
        VideoSenderMustCallGetParametersBeforeSetParametersBeforeNegotiation) {
-  video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, /*id=*/"");
+  video_rtp_sender_ =
+      VideoRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
 
   RtpParameters params;
   RTCError result = video_rtp_sender_->SetParameters(params);
@@ -1527,13 +1554,16 @@
 TEST_F(RtpSenderReceiverTest,
        PropagatesVideoTrackContentHintSetBeforeEnabling) {
   AddVideoTrack();
+  std::unique_ptr<MockSetStreamIDsObserver> set_stream_ids_observer =
+      absl::make_unique<MockSetStreamIDsObserver>();
   // Setting detailed overrides the default non-screencast mode. This should be
   // applied even if the track is set on construction.
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kDetailed);
-  video_rtp_sender_ =
-      VideoRtpSender::Create(worker_thread_, video_track_->id());
+  video_rtp_sender_ = VideoRtpSender::Create(worker_thread_, video_track_->id(),
+                                             set_stream_ids_observer.get());
   ASSERT_TRUE(video_rtp_sender_->SetTrack(video_track_));
-  video_rtp_sender_->set_stream_ids({local_stream_->id()});
+  EXPECT_CALL(*set_stream_ids_observer, OnSetStreamIDs());
+  video_rtp_sender_->SetStreamIDs({local_stream_->id()});
   video_rtp_sender_->SetMediaChannel(video_media_channel_);
   video_track_->set_enabled(true);
 
diff --git a/pc/test/mock_rtp_sender_internal.h b/pc/test/mock_rtp_sender_internal.h
index fa16220..181dcbb 100644
--- a/pc/test/mock_rtp_sender_internal.h
+++ b/pc/test/mock_rtp_sender_internal.h
@@ -46,7 +46,7 @@
   // RtpSenderInternal methods.
   MOCK_METHOD1(SetMediaChannel, void(cricket::MediaChannel*));
   MOCK_METHOD1(SetSsrc, void(uint32_t));
-  MOCK_METHOD1(set_stream_ids, void(const std::vector<std::string>&));
+  MOCK_METHOD1(SetStreamIDs, void(const std::vector<std::string>&));
   MOCK_METHOD1(set_init_send_encodings,
                void(const std::vector<RtpEncodingParameters>&));
   MOCK_METHOD0(Stop, void());