Bug 1425621 - Part 4: Move track event logic to JS. r=drno,jib,smaug
authorByron Campen [:bwc] <docfaraday@gmail.com>
Wed, 20 Dec 2017 17:00:40 -0600
changeset 453415 6fc395c1746df13107d9f1baffbdad8a2433f7fa
parent 453414 463ebca87767698554f417352fadf5d516a99d40
child 453416 2cdb3560d089d896e4fc720b5dc76d8742cc4aa2
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdrno, jib, smaug
bugs1425621
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1425621 - Part 4: Move track event logic to JS. r=drno,jib,smaug MozReview-Commit-ID: 8kUbYQnD3Oc
dom/media/PeerConnection.js
dom/webidl/MediaStreamTrack.webidl
dom/webidl/PeerConnectionObserver.webidl
dom/webidl/RTCRtpReceiver.webidl
media/webrtc/signaling/gtest/jsep_session_unittest.cpp
media/webrtc/signaling/src/jsep/JsepSession.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/jsep/JsepTrack.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1318,16 +1318,41 @@ class RTCPeerConnection {
 
     this._queueTaskWithClosedCheck(() => {
       if (this._negotiationNeeded) {
         this.dispatchEvent(new this._win.Event("negotiationneeded"));
       }
     });
   }
 
+  _processTrackAdditionsAndRemovals() {
+    let postProcessing = {
+      updateStreamFunctions: [],
+      muteTracks: [],
+      trackEvents: []
+    };
+
+    for (let transceiver of this._transceivers) {
+      transceiver.receiver.processTrackAdditionsAndRemovals(transceiver,
+                                                            postProcessing);
+    }
+
+    for (let f of postProcessing.updateStreamFunctions) {
+      f();
+    }
+
+    for (let t of postProcessing.muteTracks) {
+      t.mutedChanged(true);
+    }
+
+    for (let ev of postProcessing.trackEvents) {
+      this.dispatchEvent(ev);
+    }
+  }
+
   // TODO(Bug 1241291): Legacy event, remove eventually
   _fireLegacyAddStreamEvents() {
     for (let stream of this._newStreams) {
       let ev = new this._win.MediaStreamEvent("addstream", { stream });
       this.dispatchEvent(ev);
     }
     this._newStreams = [];
   }
@@ -1650,16 +1675,17 @@ class PeerConnectionObserver {
 
   onSetLocalDescriptionSuccess() {
     this._dompc._syncTransceivers();
     this._dompc._onSetLocalDescriptionSuccess();
   }
 
   onSetRemoteDescriptionSuccess() {
     this._dompc._syncTransceivers();
+    this._dompc._processTrackAdditionsAndRemovals();
     this._dompc._fireLegacyAddStreamEvents();
     this._dompc._onSetRemoteDescriptionSuccess();
   }
 
   onSetLocalDescriptionError(code, message) {
     this._localType = null;
     this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
   }
@@ -1816,49 +1842,16 @@ class PeerConnectionObserver {
                                                              { stream }));
   }
 
   _getTransceiverWithRecvTrack(webrtcTrackId) {
     return this._dompc.getTransceivers().find(
         transceiver => transceiver.remoteTrackIdIs(webrtcTrackId));
   }
 
-  onTrack(webrtcTrackId, streamIds) {
-    let pc = this._dompc;
-    let matchingTransceiver = this._getTransceiverWithRecvTrack(webrtcTrackId);
-
-    // Get or create MediaStreams, and add the new track to them.
-    let streams = streamIds.map(id => this._dompc._getOrCreateStream(id));
-
-    streams.forEach(stream => {
-      stream.addTrack(matchingTransceiver.receiver.track);
-      // Adding tracks from JS does not result in the stream getting
-      // onaddtrack, so we need to do that here. The mediacapture spec says
-      // this needs to be queued, also.
-      pc._queueTaskWithClosedCheck(() => {
-        stream.dispatchEvent(
-            new pc._win.MediaStreamTrackEvent(
-              "addtrack", { track: matchingTransceiver.receiver.track }));
-      });
-    });
-
-
-    let ev = new pc._win.RTCTrackEvent("track", {
-      receiver: matchingTransceiver.receiver,
-      track: matchingTransceiver.receiver.track,
-      streams,
-      transceiver: matchingTransceiver });
-    this.dispatchEvent(ev);
-
-    // Fire legacy event as well for a little bit.
-    ev = new pc._win.MediaStreamTrackEvent("addtrack",
-        { track: matchingTransceiver.receiver.track });
-    this.dispatchEvent(ev);
-  }
-
   onTransceiverNeeded(kind, transceiverImpl) {
     this._dompc._onTransceiverNeeded(kind, transceiverImpl);
   }
 
   notifyDataChannel(channel) {
     this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
                                                                 { channel }));
   }
@@ -2070,16 +2063,19 @@ setupPrototype(RTCRtpSender, {
 class RTCRtpReceiver {
   constructor(pc, transceiverImpl) {
     // We do not set the track here; that is done when _transceiverImpl is set
     Object.assign(this,
         {
           _pc: pc,
           _transceiverImpl: transceiverImpl,
           track: transceiverImpl.getReceiveTrack(),
+          _remoteSetSendBit: false,
+          _ontrackFired: false,
+          streamIds: [],
           // Sync and contributing sources must be kept cached so that timestamps
           // remain stable, as the timestamp offset can vary
           // note key = entry.source + entry.sourceType
           _rtpSources: new Map(),
           _rtpSourcesJsTimestamp: null,
         });
   }
 
@@ -2131,18 +2127,16 @@ class RTCRtpReceiver {
         removeKeys.push(entry.source + entry.sourceType);
       }
     }
     for (let delKey of removeKeys) {
       this._rtpSources.delete(delKey);
     }
   }
 
-
-
   _getRtpSourcesByType(type) {
     this._fetchRtpSources();
     // Only return the values from within the last 10 seconds as per the spec
     let cutoffTime = this._rtpSourcesJsTimestamp - 10 * 1000;
     let sources = [...this._rtpSources.values()].filter(
       (entry) => {
         return entry.sourceType == type &&
             (entry.timestamp + entry.sourceClockOffset) >= cutoffTime;
@@ -2157,16 +2151,77 @@ class RTCRtpReceiver {
   getContributingSources() {
     return this._getRtpSourcesByType("contributing");
   }
 
   getSynchronizationSources() {
     return this._getRtpSourcesByType("synchronization");
   }
 
+  setStreamIds(streamIds) {
+    this.streamIds = streamIds;
+  }
+
+  setRemoteSendBit(sendBit) {
+    this._remoteSetSendBit = sendBit;
+  }
+
+  processTrackAdditionsAndRemovals(transceiver,
+                                   {updateStreamFunctions, muteTracks, trackEvents}) {
+    let streamsWithTrack = this.streamIds
+      .map(id => this._pc._getOrCreateStream(id));
+
+    let streamsWithoutTrack = this._pc.getRemoteStreams()
+      .filter(s => !this.streamIds.includes(s.id));
+
+    updateStreamFunctions.push(...streamsWithTrack.map(stream => () => {
+      if (!stream.getTracks().includes(this.track)) {
+        stream.addTrack(this.track);
+        // Adding tracks from JS does not result in the stream getting
+        // onaddtrack, so we need to do that here.
+        stream.dispatchEvent(
+            new this._pc._win.MediaStreamTrackEvent(
+              "addtrack", { track: this.track }));
+      }
+    }));
+
+    updateStreamFunctions.push(...streamsWithoutTrack.map(stream => () => {
+      // Content JS might remove this track from the stream before this function fires (ugh)
+      if (stream.getTracks().includes(this.track)) {
+        stream.removeTrack(this.track);
+        // Removing tracks from JS does not result in the stream getting
+        // onremovetrack, so we need to do that here.
+        stream.dispatchEvent(
+            new this._pc._win.MediaStreamTrackEvent(
+              "removetrack", { track: this.track }));
+      }
+    }));
+
+    if (!this._remoteSetSendBit) {
+      // remote used "recvonly" or "inactive"
+      this._ontrackFired = false;
+      if (!this.track.muted) {
+        muteTracks.push(this.track);
+      }
+    } else if (!this._ontrackFired) {
+      // remote used "sendrecv" or "sendonly", and we haven't fired ontrack
+      let ev = new this._pc._win.RTCTrackEvent("track", {
+        receiver: this.__DOM_IMPL__,
+        track: this.track,
+        streams: streamsWithTrack,
+        transceiver });
+      trackEvents.push(ev);
+      this._ontrackFired = true;
+
+      // Fire legacy event as well for a little bit.
+      ev = new this._pc._win.MediaStreamTrackEvent("addtrack",
+          { track: this.track });
+      trackEvents.push(ev);
+    }
+  }
 }
 setupPrototype(RTCRtpReceiver, {
   classID: PC_RECEIVER_CID,
   contractID: PC_RECEIVER_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
 });
 
 class RTCRtpTransceiver {
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -90,9 +90,12 @@ interface MediaStreamTrack : EventTarget
 //  MediaTrackCapabilities getCapabilities ();
     MediaTrackConstraints  getConstraints ();
     [NeedsCallerType]
     MediaTrackSettings     getSettings ();
 
     [Throws, NeedsCallerType]
     Promise<void>          applyConstraints (optional MediaTrackConstraints constraints);
 //              attribute EventHandler          onoverconstrained;
+
+    [ChromeOnly]
+    void mutedChanged(boolean muted);
 };
--- a/dom/webidl/PeerConnectionObserver.webidl
+++ b/dom/webidl/PeerConnectionObserver.webidl
@@ -31,17 +31,16 @@ interface PeerConnectionObserver
   /* Data channel callbacks */
   void notifyDataChannel(DataChannel channel);
 
   /* Notification of one of several types of state changed */
   void onStateChange(PCObserverStateType state);
 
   /* Changes to MediaStreamTracks */
   void onRemoveStream(MediaStream stream);
-  void onTrack(DOMString webrtcTrackId, sequence<DOMString> streamIds);
 
   /* Transceiver management; called when setRemoteDescription causes a
      transceiver to be created on the C++ side */
   void onTransceiverNeeded(DOMString kind, TransceiverImpl transceiverImpl);
 
   /* DTMF callback */
   void onDTMFToneChange(MediaStreamTrack track, DOMString tone);
 
--- a/dom/webidl/RTCRtpReceiver.webidl
+++ b/dom/webidl/RTCRtpReceiver.webidl
@@ -9,9 +9,18 @@
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtpreceiver;1"]
 interface RTCRtpReceiver {
   readonly attribute MediaStreamTrack   track;
   Promise<RTCStatsReport>               getStats();
   sequence<RTCRtpContributingSource>    getContributingSources();
   sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
+
+  [ChromeOnly]
+  void setStreamIds(sequence<DOMString> streamIds);
+  [ChromeOnly]
+  void setRemoteSendBit(boolean sendBit);
+  [ChromeOnly]
+  void processTrackAdditionsAndRemovals(
+      RTCRtpTransceiver transceiver,
+      object postProcessing);
 };
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -508,23 +508,16 @@ protected:
   JsepTrack GetTrackOff(size_t index, SdpMediaSection::MediaType type) {
     return GetTrack(*mSessionOff, type, index);
   }
 
   JsepTrack GetTrackAns(size_t index, SdpMediaSection::MediaType type) {
     return GetTrack(*mSessionAns, type, index);
   }
 
-  size_t CountRtpTypes() const {
-    return std::count_if(
-        types.begin(), types.end(),
-        [](SdpMediaSection::MediaType type)
-          {return type != SdpMediaSection::MediaType::kApplication;});
-  }
-
   bool Equals(const SdpFingerprintAttributeList::Fingerprint& f1,
               const SdpFingerprintAttributeList::Fingerprint& f2) const {
     if (f1.hashFunc != f2.hashFunc) {
       return false;
     }
 
     if (f1.fingerprint != f2.fingerprint) {
       return false;
@@ -1634,57 +1627,37 @@ TEST_P(JsepSessionTest, GetDescriptions)
 
 TEST_P(JsepSessionTest, RenegotiationNoChange)
 {
   AddTracks(*mSessionOff);
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(CountRtpTypes(), added.size());
-  ASSERT_EQ(0U, removed.size());
-
   AddTracks(*mSessionAns);
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
   SetRemoteAnswer(answer);
 
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(CountRtpTypes(), added.size());
-  ASSERT_EQ(0U, removed.size());
-
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
   std::vector<RefPtr<JsepTransceiver>> origOffererTransceivers
     = DeepCopy(mSessionOff->GetTransceivers());
   std::vector<RefPtr<JsepTransceiver>> origAnswererTransceivers
     = DeepCopy(mSessionAns->GetTransceivers());
 
   std::string reoffer = CreateOffer();
   SetLocalOffer(reoffer);
   SetRemoteOffer(reoffer);
 
-  added = mSessionAns->GetRemoteTracksAdded();
-  removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
   std::string reanswer = CreateAnswer();
   SetLocalAnswer(reanswer);
   SetRemoteAnswer(reanswer);
 
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
   ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
@@ -1693,57 +1666,37 @@ TEST_P(JsepSessionTest, RenegotiationNoC
 // Disabled: See Bug 1329028
 TEST_P(JsepSessionTest, DISABLED_RenegotiationSwappedRolesNoChange)
 {
   AddTracks(*mSessionOff);
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(types.size(), added.size());
-  ASSERT_EQ(0U, removed.size());
-
   AddTracks(*mSessionAns);
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
   SetRemoteAnswer(answer);
 
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(types.size(), added.size());
-  ASSERT_EQ(0U, removed.size());
-
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
   auto offererTransceivers = DeepCopy(mSessionOff->GetTransceivers());
   auto answererTransceivers = DeepCopy(mSessionAns->GetTransceivers());
 
   SwapOfferAnswerRoles();
 
   std::string reoffer = CreateOffer();
   SetLocalOffer(reoffer);
   SetRemoteOffer(reoffer);
 
-  added = mSessionAns->GetRemoteTracksAdded();
-  removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
   std::string reanswer = CreateAnswer();
   SetLocalAnswer(reanswer);
   SetRemoteAnswer(reanswer);
 
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kPassive);
 
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_TRUE(Equals(offererTransceivers, newAnswererTransceivers));
   ASSERT_TRUE(Equals(answererTransceivers, newOffererTransceivers));
@@ -1771,28 +1724,16 @@ TEST_P(JsepSessionTest, RenegotiationOff
   AddTracks(*mSessionOff, extraTypes);
   types.insert(types.end(), extraTypes.begin(), extraTypes.end());
 
   OfferAnswer(CHECK_SUCCESS);
 
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(2U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
-  ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_LE(2U, newOffererTransceivers.size());
   newOffererTransceivers.resize(newOffererTransceivers.size() - 2);
   ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
 
   ASSERT_LE(2U, newAnswererTransceivers.size());
@@ -1833,28 +1774,16 @@ TEST_P(JsepSessionTest, RenegotiationAns
 
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer, CHECK_SUCCESS);
   SetRemoteAnswer(answer, CHECK_SUCCESS);
 
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(2U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
-  ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
-
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_LE(2U, newOffererTransceivers.size());
   newOffererTransceivers.resize(newOffererTransceivers.size() - 2);
   ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
 
   ASSERT_LE(2U, newAnswererTransceivers.size());
@@ -1884,30 +1813,16 @@ TEST_P(JsepSessionTest, RenegotiationBot
   AddTracks(*mSessionOff, extraTypes);
   types.insert(types.end(), extraTypes.begin(), extraTypes.end());
 
   OfferAnswer(CHECK_SUCCESS);
 
   ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
   ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(2U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
-  ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(2U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(SdpMediaSection::kAudio, added[0].GetMediaType());
-  ASSERT_EQ(SdpMediaSection::kVideo, added[1].GetMediaType());
-
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_LE(2U, newOffererTransceivers.size());
   newOffererTransceivers.resize(newOffererTransceivers.size() - 2);
   ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
 
   ASSERT_LE(2U, newAnswererTransceivers.size());
@@ -1981,42 +1896,33 @@ TEST_P(JsepSessionTest, RenegotiationOff
     return;
   }
 
   OfferAnswer();
 
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
 
-  JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+  RefPtr<JsepTransceiver> transceiver =
+    GetNegotiatedTransceiver(*mSessionOff, 0);
   ASSERT_TRUE(transceiver);
   std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
   std::string trackId = transceiver->mSendTrack.GetTrackId();
   std::string msidToReplace("a=msid:");
   msidToReplace += streamId;
   msidToReplace += " ";
   msidToReplace += trackId;
   size_t msidOffset = offer.find(msidToReplace);
   ASSERT_NE(std::string::npos, msidOffset);
   offer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
 
   SetRemoteOffer(offer);
-
-  std::vector<JsepTrack> removedTracks = mSessionAns->GetRemoteTracksRemoved();
-  std::vector<JsepTrack> addedTracks = mSessionAns->GetRemoteTracksAdded();
-
-  ASSERT_EQ(1U, removedTracks.size());
-  ASSERT_FALSE(IsNull(removedTracks[0]));
-  ASSERT_EQ(streamId, removedTracks[0].GetStreamIds()[0]);
-  ASSERT_EQ(trackId, removedTracks[0].GetTrackId());
-
-  ASSERT_EQ(1U, addedTracks.size());
-  ASSERT_FALSE(IsNull(addedTracks[0]));
-  ASSERT_EQ("foo", addedTracks[0].GetStreamIds()[0]);
-  ASSERT_EQ("bar", addedTracks[0].GetTrackId());
+  transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
+  ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
+  ASSERT_EQ("bar", transceiver->mRecvTrack.GetTrackId());
 
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
   SetRemoteAnswer(answer);
 }
 
 // The JSEP draft explicitly forbids changing the msid on an m-section, but
 // that is a new restriction that older versions of Firefox do not follow.
@@ -2031,42 +1937,34 @@ TEST_P(JsepSessionTest, RenegotiationAns
   OfferAnswer();
 
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
 
-  JsepTransceiver* transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
+  RefPtr<JsepTransceiver> transceiver =
+    GetNegotiatedTransceiver(*mSessionAns, 0);
   ASSERT_TRUE(transceiver);
   std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
   std::string trackId = transceiver->mSendTrack.GetTrackId();
   std::string msidToReplace("a=msid:");
   msidToReplace += streamId;
   msidToReplace += " ";
   msidToReplace += trackId;
   size_t msidOffset = answer.find(msidToReplace);
   ASSERT_NE(std::string::npos, msidOffset);
   answer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
 
   SetRemoteAnswer(answer);
 
-  std::vector<JsepTrack> removedTracks = mSessionOff->GetRemoteTracksRemoved();
-  std::vector<JsepTrack> addedTracks = mSessionOff->GetRemoteTracksAdded();
-
-  ASSERT_EQ(1U, removedTracks.size());
-  ASSERT_FALSE(IsNull(removedTracks[0]));
-  ASSERT_EQ(streamId, removedTracks[0].GetStreamIds()[0]);
-  ASSERT_EQ(trackId, removedTracks[0].GetTrackId());
-
-  ASSERT_EQ(1U, addedTracks.size());
-  ASSERT_FALSE(IsNull(addedTracks[0]));
-  ASSERT_EQ("foo", addedTracks[0].GetStreamIds()[0]);
-  ASSERT_EQ("bar", addedTracks[0].GetTrackId());
+  transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
+  ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
+  ASSERT_EQ("bar", transceiver->mRecvTrack.GetTrackId());
 }
 
 TEST_P(JsepSessionTest, RenegotiationOffererStopsTransceiver)
 {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
   if (types.back() == SdpMediaSection::kApplication) {
     return;
@@ -2080,30 +1978,16 @@ TEST_P(JsepSessionTest, RenegotiationOff
     DeepCopy(mSessionAns->GetTransceivers());
 
   // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
   mSessionOff->GetTransceivers().back()->Stop();
   JsepTrack removedTrack(mSessionOff->GetTransceivers().back()->mSendTrack);
 
   OfferAnswer(CHECK_SUCCESS);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(1U, removed.size());
-
-  ASSERT_EQ(removedTrack.GetMediaType(), removed[0].GetMediaType());
-  ASSERT_EQ(removedTrack.GetStreamIds(), removed[0].GetStreamIds());
-  ASSERT_EQ(removedTrack.GetTrackId(), removed[0].GetTrackId());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(1U, removed.size());
-
   // Last m-section should be disabled
   auto offer = GetParsedLocalDescription(*mSessionOff);
   const SdpMediaSection* msection =
     &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
   ASSERT_TRUE(msection);
   ValidateDisabledMSection(msection);
 
   // Last m-section should be disabled
@@ -2149,30 +2033,16 @@ TEST_P(JsepSessionTest, RenegotiationAns
     = DeepCopy(mSessionAns->GetTransceivers());
 
   // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
   mSessionAns->GetTransceivers().back()->Stop();
   JsepTrack removedTrack(mSessionAns->GetTransceivers().back()->mSendTrack);
 
   OfferAnswer(CHECK_SUCCESS);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(1U, removed.size());
-
-  ASSERT_EQ(removedTrack.GetMediaType(), removed[0].GetMediaType());
-  ASSERT_EQ(removedTrack.GetStreamIds(), removed[0].GetStreamIds());
-  ASSERT_EQ(removedTrack.GetTrackId(), removed[0].GetTrackId());
-
   // Last m-section should be sendrecv
   auto offer = GetParsedLocalDescription(*mSessionOff);
   const SdpMediaSection* msection =
     &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
   ASSERT_TRUE(msection);
   ASSERT_TRUE(msection->IsReceiving());
   ASSERT_TRUE(msection->IsSending());
 
@@ -2218,34 +2088,16 @@ TEST_P(JsepSessionTest, RenegotiationBot
   // Avoid bundle transport side effects; don't stop the BUNDLE-tag!
   mSessionOff->GetTransceivers().back()->Stop();
   JsepTrack removedTrackOffer(mSessionOff->GetTransceivers().back()->mSendTrack);
   mSessionAns->GetTransceivers().back()->Stop();
   JsepTrack removedTrackAnswer(mSessionAns->GetTransceivers().back()->mSendTrack);
 
   OfferAnswer(CHECK_SUCCESS);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(1U, removed.size());
-
-  ASSERT_EQ(removedTrackOffer.GetMediaType(), removed[0].GetMediaType());
-  ASSERT_EQ(removedTrackOffer.GetStreamIds(), removed[0].GetStreamIds());
-  ASSERT_EQ(removedTrackOffer.GetTrackId(), removed[0].GetTrackId());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(1U, removed.size());
-
-  ASSERT_EQ(removedTrackAnswer.GetMediaType(), removed[0].GetMediaType());
-  ASSERT_EQ(removedTrackAnswer.GetStreamIds(), removed[0].GetStreamIds());
-  ASSERT_EQ(removedTrackAnswer.GetTrackId(), removed[0].GetTrackId());
-
   // Last m-section should be disabled
   auto offer = GetParsedLocalDescription(*mSessionOff);
   const SdpMediaSection* msection =
     &offer->GetMediaSection(offer->GetMediaSectionCount() - 1);
   ASSERT_TRUE(msection);
   ValidateDisabledMSection(msection);
 
   // Last m-section should be disabled
@@ -2300,28 +2152,16 @@ TEST_P(JsepSessionTest, RenegotiationBot
   std::vector<SdpMediaSection::MediaType> extraTypes;
   extraTypes.push_back(removedType);
   AddTracks(*mSessionAns, extraTypes);
   AddTracks(*mSessionOff, extraTypes);
   types.insert(types.end(), extraTypes.begin(), extraTypes.end());
 
   OfferAnswer(CHECK_SUCCESS);
 
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(1U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(removedType, added[0].GetMediaType());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(1U, added.size());
-  ASSERT_EQ(0U, removed.size());
-  ASSERT_EQ(removedType, added[0].GetMediaType());
-
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
   auto newAnswererTransceivers = mSessionAns->GetTransceivers();
 
   ASSERT_EQ(origOffererTransceivers.size() + 1, newOffererTransceivers.size());
   ASSERT_EQ(origAnswererTransceivers.size() + 1,
             newAnswererTransceivers.size());
 
   // Ensure that the m-section was re-used; no gaps
@@ -2347,26 +2187,18 @@ TEST_P(JsepSessionTest, RenegotiationBot
   }
 
   OfferAnswer();
 
   mSessionOff->GetTransceivers()[0]->Stop();
   mSessionOff->GetTransceivers()[1]->Stop();
 
   OfferAnswer(CHECK_SUCCESS);
-
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(2U, removed.size());
-
-  added = mSessionOff->GetRemoteTracksAdded();
-  removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(2U, removed.size());
+  ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
+  ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsStopped());
 }
 
 TEST_P(JsepSessionTest, RenegotiationOffererReplacesTrack)
 {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   if (types.front() == SdpMediaSection::kApplication) {
@@ -2377,20 +2209,20 @@ TEST_P(JsepSessionTest, RenegotiationOff
 
   mSessionOff->GetTransceivers()[0]->mSendTrack.UpdateTrackIds(
       std::vector<std::string>(1, "newstream"), "newtrack");
 
   OfferAnswer(CHECK_SUCCESS);
 
   // Latest JSEP spec says the msid never changes, so the other side will not
   // notice track replacement.
-  auto added = mSessionAns->GetRemoteTracksAdded();
-  auto removed = mSessionAns->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
+  ASSERT_NE("newtrack",
+      mSessionAns->GetTransceivers()[0]->mRecvTrack.GetTrackId());
+  ASSERT_NE("newstream",
+      mSessionAns->GetTransceivers()[0]->mRecvTrack.GetStreamIds()[0]);
 }
 
 TEST_P(JsepSessionTest, RenegotiationAnswererReplacesTrack)
 {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   if (types.front() == SdpMediaSection::kApplication) {
@@ -2401,20 +2233,20 @@ TEST_P(JsepSessionTest, RenegotiationAns
 
   mSessionAns->GetTransceivers()[0]->mSendTrack.UpdateTrackIds(
       std::vector<std::string>(1, "newstream"), "newtrack");
 
   OfferAnswer(CHECK_SUCCESS);
 
   // Latest JSEP spec says the msid never changes, so the other side will not
   // notice track replacement.
-  auto added = mSessionOff->GetRemoteTracksAdded();
-  auto removed = mSessionOff->GetRemoteTracksRemoved();
-  ASSERT_EQ(0U, added.size());
-  ASSERT_EQ(0U, removed.size());
+  ASSERT_NE("newtrack",
+      mSessionOff->GetTransceivers()[0]->mRecvTrack.GetTrackId());
+  ASSERT_NE("newstream",
+      mSessionOff->GetTransceivers()[0]->mRecvTrack.GetStreamIds()[0]);
 }
 
 // Tests whether auto-assigned remote msids (ie; what happens when the other
 // side doesn't use msid attributes) are stable across renegotiation.
 TEST_P(JsepSessionTest, RenegotiationAutoAssignedMsidIsStable)
 {
   AddTracks(*mSessionOff);
   std::string offer = CreateOffer();
@@ -4739,17 +4571,19 @@ TEST_P(JsepSessionTest, TestRejectOfferR
 
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
 
   ASSERT_EQ(NS_OK,
             mSessionAns->SetRemoteDescription(kJsepSdpRollback, ""));
   ASSERT_EQ(kJsepStateStable, mSessionAns->GetState());
-  ASSERT_EQ(CountRtpTypes(), mSessionAns->GetRemoteTracksRemoved().size());
+  for (const auto& transceiver : mSessionAns->GetTransceivers()) {
+    ASSERT_EQ(0U, transceiver->mRecvTrack.GetStreamIds().size());
+  }
 
   ASSERT_EQ(NS_OK,
             mSessionOff->SetLocalDescription(kJsepSdpRollback, ""));
   ASSERT_EQ(kJsepStateStable, mSessionOff->GetState());
 
   OfferAnswer();
 }
 
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -127,20 +127,16 @@ public:
   {
     std::stable_sort(Codecs().begin(), Codecs().end(), sorter);
     for (auto& transceiver : GetTransceivers()) {
       transceiver->mSendTrack.SortCodecs(sorter);
       transceiver->mRecvTrack.SortCodecs(sorter);
     }
   }
 
-  // Helpful for firing events.
-  virtual std::vector<JsepTrack> GetRemoteTracksAdded() const = 0;
-  virtual std::vector<JsepTrack> GetRemoteTracksRemoved() const = 0;
-
   virtual const std::vector<RefPtr<JsepTransceiver>>&
     GetTransceivers() const = 0;
   virtual std::vector<RefPtr<JsepTransceiver>>& GetTransceivers() = 0;
   virtual nsresult AddTransceiver(RefPtr<JsepTransceiver> transceiver) = 0;
 
   // Basic JSEP operations.
   virtual nsresult CreateOffer(const JsepOfferOptions& options,
                                std::string* offer) = 0;
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -183,28 +183,16 @@ JsepSessionImpl::AddAudioRtpExtension(co
 
 nsresult
 JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName,
                                       SdpDirectionAttribute::Direction direction)
 {
   return AddRtpExtension(mVideoRtpExtensions, extensionName, direction);
 }
 
-std::vector<JsepTrack>
-JsepSessionImpl::GetRemoteTracksAdded() const
-{
-  return mRemoteTracksAdded;
-}
-
-std::vector<JsepTrack>
-JsepSessionImpl::GetRemoteTracksRemoved() const
-{
-  return mRemoteTracksRemoved;
-}
-
 nsresult
 JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options,
                                      JsepTransceiver& transceiver,
                                      Sdp* local)
 {
   JsepTrack& sendTrack(transceiver.mSendTrack);
   JsepTrack& recvTrack(transceiver.mRecvTrack);
 
@@ -1393,22 +1381,16 @@ JsepSessionImpl::SetRemoteDescriptionAns
   mCurrentLocalDescription = Move(mPendingLocalDescription);
   MOZ_ASSERT(mIsOfferer);
   mWasOffererLastTime = true;
 
   SetState(kJsepStateStable);
   return NS_OK;
 }
 
-static bool
-TrackIdCompare(const JsepTrack& t1, const JsepTrack& t2)
-{
-  return t1.GetTrackId() < t2.GetTrackId();
-}
-
 JsepTransceiver*
 JsepSessionImpl::GetTransceiverForLevel(size_t level)
 {
   for (RefPtr<JsepTransceiver>& transceiver : mTransceivers) {
     if (transceiver->HasLevel() && (transceiver->GetLevel() == level)) {
       return transceiver.get();
     }
   }
@@ -1478,88 +1460,53 @@ JsepSessionImpl::GetTransceiverForRemote
   nsresult rv = AddTransceiver(newTransceiver);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return mTransceivers.back().get();
 }
 
 nsresult
 JsepSessionImpl::UpdateTransceiversFromRemoteDescription(const Sdp& remote)
 {
-  std::vector<JsepTrack> oldRemoteTracks;
-  std::vector<JsepTrack> newRemoteTracks;
-
   // Iterate over the sdp, updating remote tracks as we go
   for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
     const SdpMediaSection& msection = remote.GetMediaSection(i);
 
     JsepTransceiver* transceiver(GetTransceiverForRemote(msection));
     if (!transceiver) {
       return NS_ERROR_FAILURE;
     }
 
-    bool isRtp =
-      msection.GetMediaType() != SdpMediaSection::MediaType::kApplication;
-
-    if (isRtp && transceiver->mRecvTrack.GetActive()) {
-      oldRemoteTracks.push_back(transceiver->mRecvTrack);
-    }
-
     if (!mSdpHelper.MsectionIsDisabled(msection)) {
       transceiver->Associate(msection.GetAttributeList().GetMid());
     } else {
       transceiver->Disassociate();
       // This cannot be rolled back.
       transceiver->Stop();
       continue;
     }
 
-    if (!isRtp) {
+    if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
       continue;
     }
 
     // Interop workaround for endpoints that don't support msid.
-    // If the receiver has no ids, set some initial values, one way or another.
+    // Ensures that there is a default track id set.
+    // TODO(bug 1426005): Remove this
     if (msection.IsSending() && transceiver->mRecvTrack.GetTrackId().empty()) {
       std::vector<std::string> streamIds;
       std::string trackId;
 
       nsresult rv = GetRemoteIds(remote, msection, &streamIds, &trackId);
       NS_ENSURE_SUCCESS(rv, rv);
       transceiver->mRecvTrack.UpdateTrackIds(streamIds, trackId);
     }
 
     transceiver->mRecvTrack.UpdateRecvTrack(remote, msection);
-
-    if (msection.IsSending()) {
-      newRemoteTracks.push_back(transceiver->mRecvTrack);
-    }
   }
 
-  std::sort(oldRemoteTracks.begin(), oldRemoteTracks.end(), TrackIdCompare);
-  std::sort(newRemoteTracks.begin(), newRemoteTracks.end(), TrackIdCompare);
-
-  mRemoteTracksAdded.clear();
-  mRemoteTracksRemoved.clear();
-
-  std::set_difference(
-      oldRemoteTracks.begin(),
-      oldRemoteTracks.end(),
-      newRemoteTracks.begin(),
-      newRemoteTracks.end(),
-      std::inserter(mRemoteTracksRemoved, mRemoteTracksRemoved.begin()),
-      TrackIdCompare);
-
-  std::set_difference(
-      newRemoteTracks.begin(),
-      newRemoteTracks.end(),
-      oldRemoteTracks.begin(),
-      oldRemoteTracks.end(),
-      std::inserter(mRemoteTracksAdded, mRemoteTracksAdded.begin()),
-      TrackIdCompare);
-
   return NS_OK;
 }
 
 
 bool
 JsepSessionImpl::WasMsectionDisabledLastNegotiation(size_t level) const
 {
   const Sdp* answer(GetAnswer());
@@ -1612,36 +1559,34 @@ JsepSessionImpl::RollbackRemoteOffer()
   for (size_t i = 0; i < mTransceivers.size(); ++i) {
     RefPtr<JsepTransceiver>& transceiver(mTransceivers[i]);
     if (i < mOldTransceivers.size()) {
       transceiver->Rollback(*mOldTransceivers[i]);
       continue;
     }
 
     // New transceiver!
-    if (!transceiver->HasAddTrackMagic() &&
-        transceiver->WasCreatedBySetRemote()) {
+    bool shouldRemove = !transceiver->HasAddTrackMagic() &&
+                        transceiver->WasCreatedBySetRemote();
+
+    // We rollback even for transceivers we will remove, just to ensure we end
+    // up at the starting state.
+    RefPtr<JsepTransceiver> temp(
+        new JsepTransceiver(transceiver->GetMediaType()));
+    transceiver->Rollback(*temp);
+
+    if (shouldRemove) {
       transceiver->Stop();
-      transceiver->Disassociate();
-      transceiver->ClearLevel();
       transceiver->SetRemoved();
       mTransceivers.erase(mTransceivers.begin() + i);
       --i;
-      continue;
     }
-
-    // Transceiver has been "touched" by addTrack; let it live, but unhook it
-    // from everything.
-    RefPtr<JsepTransceiver> temp(
-        new JsepTransceiver(transceiver->GetMediaType()));
-    transceiver->Rollback(*temp);
   }
 
   mOldTransceivers.clear();
-  std::swap(mRemoteTracksAdded, mRemoteTracksRemoved);
 }
 
 nsresult
 JsepSessionImpl::ValidateLocalDescription(const Sdp& description)
 {
   // TODO(bug 1095226): Better checking.
   if (!mGeneratedLocalDescription) {
     JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer"
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -90,20 +90,16 @@ public:
       SdpDirectionAttribute::Direction::kSendrecv) override;
 
   virtual std::vector<JsepCodecDescription*>&
   Codecs() override
   {
     return mSupportedCodecs.values;
   }
 
-  virtual std::vector<JsepTrack> GetRemoteTracksAdded() const override;
-
-  virtual std::vector<JsepTrack> GetRemoteTracksRemoved() const override;
-
   virtual nsresult CreateOffer(const JsepOfferOptions& options,
                                std::string* offer) override;
 
   virtual nsresult CreateAnswer(const JsepAnswerOptions& options,
                                 std::string* answer) override;
 
   virtual std::string GetLocalDescription(JsepDescriptionPendingOrCurrent type)
                                           const override;
@@ -248,19 +244,16 @@ private:
   nsresult EnableOfferMsection(SdpMediaSection* msection);
 
   mozilla::Sdp* GetParsedLocalDescription(JsepDescriptionPendingOrCurrent type)
                                           const;
   mozilla::Sdp* GetParsedRemoteDescription(JsepDescriptionPendingOrCurrent type)
                                            const;
   const Sdp* GetAnswer() const;
 
-  // By the most recent SetRemoteDescription
-  std::vector<JsepTrack> mRemoteTracksAdded;
-  std::vector<JsepTrack> mRemoteTracksRemoved;
   // !!!NOT INDEXED BY LEVEL!!! These are in the order they were created in. The
   // level mapping is done with JsepTransceiver::mLevel.
   std::vector<RefPtr<JsepTransceiver>> mTransceivers;
   // So we can rollback. Not as simple as just going back to the old, though...
   std::vector<RefPtr<JsepTransceiver>> mOldTransceivers;
 
   bool mIsOfferer;
   bool mWasOffererLastTime;
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -93,17 +93,18 @@ private:
 
 class JsepTrack
 {
 public:
   JsepTrack(mozilla::SdpMediaSection::MediaType type,
             sdp::Direction direction)
       : mType(type),
         mDirection(direction),
-        mActive(false)
+        mActive(false),
+        mRemoteSetSendBit(false)
   {
   }
 
   virtual ~JsepTrack() {}
 
   void UpdateTrackIds(const std::vector<std::string>& streamIds,
                       const std::string& trackId)
   {
@@ -120,18 +121,22 @@ public:
   void UpdateRecvTrack(const Sdp& sdp, const SdpMediaSection& msection)
   {
     MOZ_ASSERT(mDirection == sdp::kRecv);
     MOZ_ASSERT(
         msection.GetMediaType() != SdpMediaSection::MediaType::kApplication);
     std::string error;
     SdpHelper helper(&error);
 
+    mRemoteSetSendBit = msection.IsSending();
+
     if (msection.IsSending()) {
       (void)helper.GetIdsFromMsid(sdp, msection, &mStreamIds, &mTrackId);
+    } else {
+      mStreamIds.clear();
     }
 
     // We do this whether or not the track is active
     SetCNAME(helper.GetCNAME(msection));
     mSsrcs.clear();
     if (msection.GetAttributeList().HasAttribute(
           SdpAttribute::kSsrcAttribute)) {
       for (auto& ssrcAttr : msection.GetAttributeList().GetSsrc().mSsrcs) {
@@ -154,16 +159,17 @@ public:
       mType = rhs.mType;
       mStreamIds = rhs.mStreamIds;
       mTrackId = rhs.mTrackId;
       mCNAME = rhs.mCNAME;
       mDirection = rhs.mDirection;
       mJsEncodeConstraints = rhs.mJsEncodeConstraints;
       mSsrcs = rhs.mSsrcs;
       mActive = rhs.mActive;
+      mRemoteSetSendBit = rhs.mRemoteSetSendBit;
 
       for (const JsepCodecDescription* codec : rhs.mPrototypeCodecs.values) {
         mPrototypeCodecs.values.push_back(codec->Clone());
       }
       if (rhs.mNegotiatedDetails) {
         mNegotiatedDetails.reset(
           new JsepTrackNegotiatedDetails(*rhs.mNegotiatedDetails));
       }
@@ -222,16 +228,22 @@ public:
   }
 
   void
   SetActive(bool active)
   {
     mActive = active;
   }
 
+  bool
+  GetRemoteSetSendBit() const
+  {
+    return mRemoteSetSendBit;
+  }
+
   virtual void PopulateCodecs(
       const std::vector<JsepCodecDescription*>& prototype);
 
   template <class UnaryFunction>
   void ForEachCodec(UnaryFunction func)
   {
     std::for_each(mPrototypeCodecs.values.begin(),
                   mPrototypeCodecs.values.end(), func);
@@ -345,13 +357,14 @@ private:
   PtrVector<JsepCodecDescription> mPrototypeCodecs;
   // Holds encoding params/constraints from JS. Simulcast happens when there are
   // multiple of these. If there are none, we assume unconstrained unicast with
   // no rid.
   std::vector<JsConstraints> mJsEncodeConstraints;
   UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
   std::vector<uint32_t> mSsrcs;
   bool mActive;
+  bool mRemoteSetSendBit;
 };
 
 } // namespace mozilla
 
 #endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1769,46 +1769,16 @@ static void DeferredSetRemote(const std:
     if (!PeerConnectionCtx::GetInstance()->isReady()) {
       MOZ_CRASH("Why is DeferredSetRemote being executed when the "
                 "PeerConnectionCtx isn't ready?");
     }
     wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str());
   }
 }
 
-void
-PeerConnectionImpl::FireOnTrackEvents(RefPtr<PeerConnectionObserver>& aPco)
-{
-  for (auto& track : mJsepSession->GetRemoteTracksAdded()) {
-    if (track.GetMediaType() == mozilla::SdpMediaSection::kApplication) {
-      // Ignore datachannel
-      continue;
-    }
-
-    MOZ_ASSERT(!track.GetTrackId().empty());
-
-    nsString trackId = NS_ConvertUTF8toUTF16(track.GetTrackId().c_str());
-
-    dom::Sequence<nsString> streamIds;
-    for (const std::string& streamId : track.GetStreamIds()) {
-      // If this fails, oh well.
-      streamIds.AppendElement(
-          NS_ConvertASCIItoUTF16(streamId.c_str()), fallible);
-    }
-
-    JSErrorResult jrv;
-    aPco->OnTrack(trackId, streamIds, jrv);
-    if (jrv.Failed()) {
-      CSFLogError(LOGTAG, ": OnTrack(%s) failed! Error: %u",
-          track.GetTrackId().c_str(),
-          jrv.ErrorCodeAsInt());
-    }
-  }
-}
-
 NS_IMETHODIMP
 PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   if (!aSDP) {
     CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
     return NS_ERROR_FAILURE;
@@ -1925,21 +1895,16 @@ PeerConnectionImpl::SetRemoteDescription
                     __FUNCTION__, mHandle.c_str(), static_cast<int>(rv));
         MOZ_CRASH();
         return NS_ERROR_FAILURE;
       }
     }
 
     UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback);
 
-    // This needs to be done before we fire ontrack events
-    pco->SyncTransceivers(jrv);
-
-    FireOnTrackEvents(pco);
-
     pco->OnSetRemoteDescriptionSuccess(jrv);
 
     startCallTelem();
   }
 
   return NS_OK;
 }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -356,18 +356,16 @@ public:
 
   NS_IMETHODIMP SetLocalDescription (int32_t aAction, const char* aSDP);
 
   void SetLocalDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv)
   {
     rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
   }
 
-  void FireOnTrackEvents(RefPtr<PeerConnectionObserver>& aPco);
-
   NS_IMETHODIMP SetRemoteDescription (int32_t aAction, const char* aSDP);
 
   void SetRemoteDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv)
   {
     rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
   }
 
   NS_IMETHODIMP_TO_ERRORRESULT(GetStats, ErrorResult &rv,
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -517,31 +517,45 @@ TransceiverImpl::SyncWithJS(dom::RTCRtpT
     } else {
       if (mJsepTransceiver->mSendTrack.GetActive()) {
         aJsTransceiver.SetCurrentDirection(
             dom::RTCRtpTransceiverDirection::Sendonly, aRv);
       } else {
         aJsTransceiver.SetCurrentDirection(
             dom::RTCRtpTransceiverDirection::Inactive, aRv);
       }
-
-      // If negotiation stops a track from receiving (ie; m-section is
-      // negotiated "sendonly" or "inactive"), we mark the track muted.  We do
-      // _not_ do the reverse; we need to wait for RTP to unmute according to
-      // the spec. That happens in MediaPipeline.
-      if (!mReceiveTrack->Muted()) {
-        mReceiveTrack->MutedChanged(true);
-      }
     }
 
     if (aRv.Failed()) {
       return;
     }
   }
 
+  RefPtr<dom::RTCRtpReceiver> receiver = aJsTransceiver.GetReceiver(aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // receive stream ids from JSEP
+  dom::Sequence<nsString> receiveStreamIds;
+  for (const auto& id : mJsepTransceiver->mRecvTrack.GetStreamIds()) {
+    receiveStreamIds.AppendElement(NS_ConvertUTF8toUTF16(id.c_str()),
+                                   fallible);
+  }
+  receiver->SetStreamIds(receiveStreamIds, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  receiver->SetRemoteSendBit(mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit(),
+                             aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
   // AddTrack magic from JS
   if (aJsTransceiver.GetAddTrackMagic(aRv)) {
     mJsepTransceiver->SetAddTrackMagic();
   }
 
   if (aRv.Failed()) {
     return;
   }