Bug 1208371 - Switch MediaPipeline to use direct listeners on tracks. r?jesup,bwc draft
authorAndreas Pehrson <pehrsons@gmail.com>
Fri, 18 Mar 2016 14:21:51 +0100
changeset 342151 2734e42f96a537cc91e4b9b531f3c6bf329dbd09
parent 342150 9600093016a6e524d2bf34dbcb27905e481ddc0d
child 342152 9c1c883ac38202c2e30133592a742f536016effa
push id13352
push userpehrsons@gmail.com
push dateFri, 18 Mar 2016 13:49:47 +0000
reviewersjesup, bwc
bugs1208371
milestone47.0a1
Bug 1208371 - Switch MediaPipeline to use direct listeners on tracks. r?jesup,bwc MozReview-Commit-ID: BSSfkTwXoVN
dom/media/MediaStreamGraph.cpp
dom/media/MediaStreamGraph.h
dom/media/MediaStreamTrack.cpp
dom/media/MediaStreamTrack.h
dom/media/TrackUnionStream.cpp
dom/media/TrackUnionStream.h
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/test/FakeMediaStreams.h
media/webrtc/signaling/test/FakeMediaStreamsImpl.h
media/webrtc/signaling/test/mediapipeline_unittest.cpp
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -2624,17 +2624,17 @@ SourceMediaStream::NotifyDirectConsumers
   }
 
   for (const TrackBound<MediaStreamTrackDirectListener>& source
          : mDirectTrackListeners) {
     if (aTrack->mID != source.mTrackID) {
       continue;
     }
     StreamTime offset = 0; // FIX! need a separate StreamTime.... or the end of the internal buffer
-    source.mListener->NotifyRealtimeTrackData(Graph(), offset, *aSegment);
+    source.mListener->NotifyRealtimeTrackDataAndApplyTrackDisabling(Graph(), offset, *aSegment);
   }
 }
 
 // These handle notifying all the listeners of an event
 void
 SourceMediaStream::NotifyListenersEventImpl(MediaStreamListener::MediaStreamGraphEvent aEvent)
 {
   for (uint32_t j = 0; j < mListeners.Length(); ++j) {
@@ -2790,16 +2790,39 @@ SourceMediaStream::FinishWithLockHeld()
   mMutex.AssertCurrentThreadOwns();
   mUpdateFinished = true;
   if (auto graph = GraphImpl()) {
     graph->EnsureNextIteration();
   }
 }
 
 void
+SourceMediaStream::SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled)
+{
+  MutexAutoLock lock(mMutex);
+  for (TrackBound<MediaStreamTrackDirectListener>& l: mDirectTrackListeners) {
+    if (l.mTrackID == aTrackID) {
+      bool oldEnabled = !mDisabledTrackIDs.Contains(aTrackID);
+      if (!oldEnabled && aEnabled) {
+        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
+                                     "direct listener enabled",
+                                     this, aTrackID));
+        l.mListener->DecreaseDisabled();
+      } else if (oldEnabled && !aEnabled) {
+        STREAM_LOG(LogLevel::Debug, ("SourceMediaStream %p track %d setting "
+                                     "direct listener disabled",
+                                     this, aTrackID));
+        l.mListener->IncreaseDisabled();
+      }
+    }
+  }
+  MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
+}
+
+void
 SourceMediaStream::EndAllTrackAndFinish()
 {
   MutexAutoLock lock(mMutex);
   for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
     SourceMediaStream::TrackData* data = &mUpdateTracks[i];
     data->mCommands |= TRACK_END;
   }
   mPendingTracks.Clear();
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -287,22 +287,29 @@ public:
  * Other types of streams will fail installation since they are not supported.
  *
  * Note that this listener and others for the same track will still get
  * NotifyQueuedChanges() callbacks from the MSG tread, so you must be careful
  * to ignore them if this listener was successfully installed.
  */
 class MediaStreamTrackDirectListener : public MediaStreamTrackListener
 {
+  friend class SourceMediaStream;
+  friend class TrackUnionStream;
+
 public:
   /*
    * This will be called on any MediaStreamTrackDirectListener added to a
    * SourceMediaStream when AppendToTrack() is called for the listener's bound
    * track. The MediaSegment will be the RawSegment (unresampled) if available
-   * in AppendToTrack(). Note that NotifyQueuedTrackChanges() calls will also
+   * in AppendToTrack().
+   * If the track is enabled at the source but has been disabled in one of the
+   * streams in between the source and where it was originally added, aMedia
+   * will be a disabled version of the one passed to AppendToTrack() as well.
+   * Note that NotifyQueuedTrackChanges() calls will also
    * still occur.
    */
   virtual void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
                                        StreamTime aTrackOffset,
                                        const MediaSegment& aMedia) {}
 
   /**
    * When a direct listener is processed for installation by the
@@ -327,16 +334,60 @@ public:
     STREAM_NOT_SUPPORTED,
     SUCCESS
   };
   virtual void NotifyDirectListenerInstalled(InstallationResult aResult) {}
   virtual void NotifyDirectListenerUninstalled() {}
 
 protected:
   virtual ~MediaStreamTrackDirectListener() {}
+
+  void MirrorAndDisableSegment(AudioSegment& aFrom, AudioSegment& aTo)
+  {
+    aTo.Clear();
+    aTo.AppendNullData(aFrom.GetDuration());
+  }
+
+  void NotifyRealtimeTrackDataAndApplyTrackDisabling(MediaStreamGraph* aGraph,
+                                                     StreamTime aTrackOffset,
+                                                     MediaSegment& aMedia)
+  {
+    if (mDisabledCount == 0) {
+      NotifyRealtimeTrackData(aGraph, aTrackOffset, aMedia);
+      return;
+    }
+
+    if (!mMedia) {
+      mMedia = aMedia.CreateEmptyClone();
+    }
+    if (aMedia.GetType() == MediaSegment::AUDIO) {
+      MirrorAndDisableSegment(static_cast<AudioSegment&>(aMedia),
+                              static_cast<AudioSegment&>(*mMedia));
+    } else {
+      MOZ_CRASH("Unsupported media type");
+    }
+    NotifyRealtimeTrackData(aGraph, aTrackOffset, *mMedia);
+  }
+
+  void IncreaseDisabled()
+  {
+    ++mDisabledCount;
+  }
+  void DecreaseDisabled()
+  {
+    --mDisabledCount;
+    MOZ_ASSERT(mDisabledCount >= 0, "Double decrease");
+  }
+
+  // Matches the number of disabled streams to which this listener is attached.
+  // The number of streams are those between the stream the listener was added
+  // and the SourceMediaStream that is the input of the data.
+  Atomic<int32_t> mDisabledCount;
+
+  nsAutoPtr<MediaSegment> mMedia;
 };
 
 /**
  * This is a base class for main-thread listener callbacks.
  * This callback is invoked on the main thread when the main-thread-visible
  * state of a stream has changed.
  *
  * These methods are called with the media graph monitor held, so
@@ -987,21 +1038,17 @@ public:
   void FinishWithLockHeld();
   void Finish()
   {
     MutexAutoLock lock(mMutex);
     FinishWithLockHeld();
   }
 
   // Overriding allows us to hold the mMutex lock while changing the track enable status
-  void
-  SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override {
-    MutexAutoLock lock(mMutex);
-    MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
-  }
+  void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
 
   // Overriding allows us to ensure mMutex is locked while changing the track enable status
   void
   ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment,
                       MediaSegment* aRawSegment = nullptr) override {
     mMutex.AssertCurrentThreadOwns();
     MediaStream::ApplyTrackDisabling(aTrackID, aSegment, aRawSegment);
   }
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -166,16 +166,22 @@ MediaStreamTrack::ApplyConstraints(const
 }
 
 MediaStreamGraph*
 MediaStreamTrack::Graph()
 {
   return GetOwnedStream()->Graph();
 }
 
+MediaStreamGraphImpl*
+MediaStreamTrack::GraphImpl()
+{
+  return GetOwnedStream()->GraphImpl();
+}
+
 void
 MediaStreamTrack::PrincipalChanged()
 {
   LOG(LogLevel::Info, ("MediaStreamTrack %p Principal changed. Now: "
                        "null=%d, codebase=%d, expanded=%d, system=%d", this,
                        GetPrincipal()->GetIsNullPrincipal(),
                        GetPrincipal()->GetIsCodebasePrincipal(),
                        GetPrincipal()->GetIsExpandedPrincipal(),
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -16,19 +16,21 @@
 
 namespace mozilla {
 
 class DOMMediaStream;
 class MediaEnginePhotoCallback;
 class MediaInputPort;
 class MediaStream;
 class MediaStreamGraph;
+class MediaStreamGraphImpl;
 class MediaStreamTrackListener;
 class MediaStreamTrackDirectListener;
 class PeerConnectionImpl;
+class PeerConnectionMedia;
 class PeerIdentity;
 class ProcessedMediaStream;
 class RemoteSourceStreamInfo;
 
 namespace dom {
 
 class AudioStreamTrack;
 class VideoStreamTrack;
@@ -199,16 +201,17 @@ class MediaStreamTrack : public DOMEvent
                          public MediaStreamTrackSource::Sink
 {
   // DOMMediaStream owns MediaStreamTrack instances, and requires access to
   // some internal state, e.g., GetInputStream(), GetOwnedStream().
   friend class mozilla::DOMMediaStream;
 
   // PeerConnection and friends need to know our owning DOMStream and track id.
   friend class mozilla::PeerConnectionImpl;
+  friend class mozilla::PeerConnectionMedia;
   friend class mozilla::RemoteSourceStreamInfo;
 
 public:
   /**
    * aTrackID is the MediaStreamGraph track ID for the track in the
    * MediaStream owned by aStream.
    */
   MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID,
@@ -254,16 +257,17 @@ public:
   CORSMode GetCORSMode() const { return GetSource().GetCORSMode(); }
 
   /**
    * Get this track's PeerIdentity.
    */
   const PeerIdentity* GetPeerIdentity() const { return GetSource().GetPeerIdentity(); }
 
   MediaStreamGraph* Graph();
+  MediaStreamGraphImpl* GraphImpl();
 
   MediaStreamTrackSource& GetSource() const
   {
     MOZ_RELEASE_ASSERT(mSource, "The track source is only removed on destruction");
     return *mSource;
   }
 
   // Webrtc allows the remote side to name tracks whatever it wants, and we
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -222,16 +222,20 @@ TrackUnionStream::TrackUnionStream(DOMMe
 
     for (int32_t i = mPendingDirectTrackListeners.Length() - 1; i >= 0; --i) {
       TrackBound<MediaStreamTrackDirectListener>& bound =
         mPendingDirectTrackListeners[i];
       if (bound.mTrackID != map->mOutputTrackID) {
         continue;
       }
       MediaStream* source = map->mInputPort->GetSource();
+      map->mOwnedDirectListeners.AppendElement(bound.mListener);
+      if (mDisabledTrackIDs.Contains(bound.mTrackID)) {
+        bound.mListener->IncreaseDisabled();
+      }
       STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p adding direct listener "
                                    "%p for track %d. Forwarding to input "
                                    "stream %p track %d.",
                                    this, bound.mListener.get(), bound.mTrackID,
                                    source, map->mInputTrackID));
       source->AddDirectTrackListenerImpl(bound.mListener.forget(),
                                          map->mInputTrackID);
       mPendingDirectTrackListeners.RemoveElementAt(i);
@@ -331,29 +335,58 @@ TrackUnionStream::TrackUnionStream(DOMMe
         }
         b.mListener->NotifyQueuedChanges(Graph(), outputStart, *segment);
       }
       outputTrack->GetSegment()->AppendFrom(segment);
     }
   }
 
 void
+TrackUnionStream::SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) {
+  for (TrackMapEntry& entry : mTrackMap) {
+    if (entry.mOutputTrackID == aTrackID) {
+      STREAM_LOG(LogLevel::Info, ("TrackUnionStream %p track %d was explicitly %s",
+                                   this, aTrackID, aEnabled ? "enabled" : "disabled"));
+      for (MediaStreamTrackDirectListener* listener : entry.mOwnedDirectListeners) {
+        bool oldEnabled = !mDisabledTrackIDs.Contains(aTrackID);
+        if (!oldEnabled && aEnabled) {
+          STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p track %d setting "
+                                       "direct listener enabled",
+                                       this, aTrackID));
+          listener->DecreaseDisabled();
+        } else if (oldEnabled && !aEnabled) {
+          STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p track %d setting "
+                                       "direct listener disabled",
+                                       this, aTrackID));
+          listener->IncreaseDisabled();
+        }
+      }
+    }
+  }
+  MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
+}
+
+void
 TrackUnionStream::AddDirectTrackListenerImpl(already_AddRefed<MediaStreamTrackDirectListener> aListener,
                                              TrackID aTrackID)
 {
   RefPtr<MediaStreamTrackDirectListener> listener = aListener;
 
   for (TrackMapEntry& entry : mTrackMap) {
     if (entry.mOutputTrackID == aTrackID) {
       MediaStream* source = entry.mInputPort->GetSource();
       STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p adding direct listener "
                                    "%p for track %d. Forwarding to input "
                                    "stream %p track %d.",
                                    this, listener.get(), aTrackID, source,
                                    entry.mInputTrackID));
+      entry.mOwnedDirectListeners.AppendElement(listener);
+      if (mDisabledTrackIDs.Contains(aTrackID)) {
+        listener->IncreaseDisabled();
+      }
       source->AddDirectTrackListenerImpl(listener.forget(),
                                          entry.mInputTrackID);
       return;
     }
   }
 
   TrackBound<MediaStreamTrackDirectListener>* bound =
     mPendingDirectTrackListeners.AppendElement();
@@ -365,16 +398,32 @@ void
 TrackUnionStream::RemoveDirectTrackListenerImpl(MediaStreamTrackDirectListener* aListener,
                                                 TrackID aTrackID)
 {
   for (TrackMapEntry& entry : mTrackMap) {
     // OutputTrackID is unique to this stream so we only need to do this once.
     if (entry.mOutputTrackID != aTrackID) {
       continue;
     }
+    for (size_t i = 0; i < entry.mOwnedDirectListeners.Length(); ++i) {
+      if (entry.mOwnedDirectListeners[i] == aListener) {
+        STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing direct "
+                                     "listener %p for track %d, forwarding "
+                                     "to input stream %p track %d",
+                                     this, aListener, aTrackID,
+                                     entry.mInputPort->GetSource(),
+                                     entry.mInputTrackID));
+        if (mDisabledTrackIDs.Contains(aTrackID)) {
+          // Reset the listener's state.
+          aListener->DecreaseDisabled();
+        }
+        entry.mOwnedDirectListeners.RemoveElementAt(i);
+        break;
+      }
+    }
     // Forward to the input
     MediaStream* source = entry.mInputPort->GetSource();
     source->RemoveDirectTrackListenerImpl(aListener, entry.mInputTrackID);
     return;
   }
 
   for (size_t i = 0; i < mPendingDirectTrackListeners.Length(); ++i) {
     TrackBound<MediaStreamTrackDirectListener>& bound =
--- a/dom/media/TrackUnionStream.h
+++ b/dom/media/TrackUnionStream.h
@@ -16,16 +16,18 @@ namespace mozilla {
  */
 class TrackUnionStream : public ProcessedMediaStream {
 public:
   explicit TrackUnionStream(DOMMediaStream* aWrapper);
 
   void RemoveInput(MediaInputPort* aPort) override;
   void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
 
+  void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) override;
+
 protected:
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
     StreamTime mEndOfConsumedInputTicks;
     // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
     // previous interval which was unblocked for both the input and output
@@ -39,16 +41,20 @@ protected:
     // We keep track IDs instead of track pointers because
     // tracks can be removed without us being notified (e.g.
     // when a finished track is forgotten.) When we need a Track*,
     // we call StreamBuffer::FindTrack, which will return null if
     // the track has been deleted.
     TrackID mInputTrackID;
     TrackID mOutputTrackID;
     nsAutoPtr<MediaSegment> mSegment;
+    // These are direct track listeners that have been added to this
+    // TrackUnionStream-track and forwarded to the input track. We will update
+    // these when this track's disabled status changes.
+    nsTArray<RefPtr<MediaStreamTrackDirectListener>> mOwnedDirectListeners;
   };
 
   // Add the track to this stream, retaining its TrackID if it has never
   // been previously used in this stream, allocating a new TrackID otherwise.
   uint32_t AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack,
                     GraphTime aFrom);
   void EndTrack(uint32_t aIndex);
   void CopyTrackData(StreamBuffer::Track* aInputTrack,
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -67,17 +67,16 @@ using namespace mozilla::layers;
 MOZ_MTLOG_MODULE("mediapipeline")
 
 namespace mozilla {
 
 static char kDTLSExporterLabel[] = "EXTRACTOR-dtls_srtp";
 
 MediaPipeline::~MediaPipeline() {
   ASSERT_ON_THREAD(main_thread_);
-  MOZ_ASSERT(!stream_);  // Check that we have shut down already.
   MOZ_MTLOG(ML_INFO, "Destroying MediaPipeline: " << description_);
 }
 
 nsresult MediaPipeline::Init() {
   ASSERT_ON_THREAD(main_thread_);
 
   if (direction_ == RECEIVE) {
     conduit_->SetReceiverTransport(transport_);
@@ -99,23 +98,16 @@ nsresult MediaPipeline::Init_s() {
 
   return AttachTransport_s();
 }
 
 
 // Disconnect us from the transport so that we can cleanly destruct the
 // pipeline on the main thread.  ShutdownMedia_m() must have already been
 // called
-void MediaPipeline::ShutdownTransport_s() {
-  ASSERT_ON_THREAD(sts_thread_);
-  MOZ_ASSERT(!stream_); // verifies that ShutdownMedia_m() has run
-
-  DetachTransport_s();
-}
-
 void
 MediaPipeline::DetachTransport_s()
 {
   ASSERT_ON_THREAD(sts_thread_);
 
   disconnect_all();
   transport_->Detach();
   rtp_.Detach();
@@ -663,48 +655,44 @@ void MediaPipelineTransmit::AttachToTrac
 
   description_ = pc_ + "| ";
   description_ += conduit_->type() == MediaSessionConduit::AUDIO ?
       "Transmit audio[" : "Transmit video[";
   description_ += track_id;
   description_ += "]";
 
   // TODO(ekr@rtfm.com): Check for errors
-  MOZ_MTLOG(ML_DEBUG, "Attaching pipeline to stream "
-            << static_cast<void *>(stream_) << " conduit type=" <<
+  MOZ_MTLOG(ML_DEBUG, "Attaching pipeline to track "
+            << static_cast<void *>(domtrack_) << " conduit type=" <<
             (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
 
-  stream_->AddListener(listener_);
-
-  // Is this a gUM mediastream?  If so, also register the Listener directly with
-  // the SourceMediaStream that's attached to the TrackUnion so we can get direct
-  // unqueued (and not resampled) data
-  listener_->direct_connect_ = domstream_->AddDirectListener(listener_);
+  // Register the Listener directly with the source if we can.
+  // We also register it as a non-direct listener so we fall back to that
+  // if installing the direct listener fails. As a direct listener we get access
+  // to direct unqueued (and not resampled) data.
+  domtrack_->AddDirectListener(listener_);
+  domtrack_->AddListener(listener_);
 
 #ifndef MOZILLA_INTERNAL_API
   // this enables the unit tests that can't fiddle with principals and the like
   listener_->SetEnabled(true);
 #endif
 }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
 void MediaPipelineTransmit::UpdateSinkIdentity_m(nsIPrincipal* principal,
                                                  const PeerIdentity* sinkIdentity) {
   ASSERT_ON_THREAD(main_thread_);
 
-  MediaStreamTrack* track =
-    domstream_->GetOwnedTrackById(NS_ConvertUTF8toUTF16(trackid().c_str()));
-  MOZ_RELEASE_ASSERT(track);
-
-  bool enableTrack = principal->Subsumes(track->GetPrincipal());
+  bool enableTrack = principal->Subsumes(domtrack_->GetPrincipal());
   if (!enableTrack) {
     // first try didn't work, but there's a chance that this is still available
     // if our track is bound to a peerIdentity, and the peer connection (our
     // sink) is bound to the same identity, then we can enable the track.
-    const PeerIdentity* trackIdentity = track->GetPeerIdentity();
+    const PeerIdentity* trackIdentity = domtrack_->GetPeerIdentity();
     if (sinkIdentity && trackIdentity) {
       enableTrack = (*sinkIdentity == *trackIdentity);
     }
   }
 
   listener_->SetEnabled(enableTrack);
 }
 #endif
@@ -717,31 +705,34 @@ nsresult MediaPipelineTransmit::Transpor
   // Should not be set for a transmitter
   if (&info == &rtp_) {
     listener_->SetActive(true);
   }
 
   return NS_OK;
 }
 
-nsresult MediaPipelineTransmit::ReplaceTrack(DOMMediaStream *domstream,
-                                             const std::string& track_id) {
+nsresult MediaPipelineTransmit::ReplaceTrack(MediaStreamTrack& domtrack) {
   // MainThread, checked in calls we make
-  MOZ_MTLOG(ML_DEBUG, "Reattaching pipeline " << description_ << " to stream "
-            << static_cast<void *>(domstream->GetOwnedStream())
+#if !defined(MOZILLA_EXTERNAL_LINKAGE)
+  nsString nsTrackId;
+  domtrack.GetId(nsTrackId);
+  std::string track_id(NS_ConvertUTF16toUTF8(nsTrackId).get());
+#else
+  std::string track_id = domtrack.GetId();
+#endif
+  MOZ_MTLOG(ML_DEBUG, "Reattaching pipeline " << description_ << " to track "
+            << static_cast<void *>(&domtrack)
             << " track " << track_id << " conduit type=" <<
             (conduit_->type() == MediaSessionConduit::AUDIO ?"audio":"video"));
 
-  if (domstream_) { // may be excessive paranoia
-    DetachMediaStream();
-  }
-  domstream_ = domstream; // Detach clears it
-  stream_ = domstream->GetOwnedStream();
+  DetachMedia();
+  domtrack_ = &domtrack; // Detach clears it
   // Unsets the track id after RemoveListener() takes effect.
-  listener_->UnsetTrackId(stream_->GraphImpl());
+  listener_->UnsetTrackId(domtrack_->GraphImpl());
   track_id_ = track_id;
   AttachToTrack(track_id);
   return NS_OK;
 }
 
 void MediaPipeline::DisconnectTransport_s(TransportInfo &info) {
   MOZ_ASSERT(info.transport_);
   ASSERT_ON_THREAD(sts_thread_);
@@ -893,71 +884,72 @@ UnsetTrackId(MediaStreamGraphImpl* graph
   };
   graph->AppendMessage(MakeUnique<Message>(this));
 #else
   UnsetTrackIdImpl();
 #endif
 }
 // Called if we're attached with AddDirectListener()
 void MediaPipelineTransmit::PipelineListener::
-NotifyRealtimeData(MediaStreamGraph* graph, TrackID tid,
-                   StreamTime offset,
-                   uint32_t events,
-                   const MediaSegment& media) {
-  MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyRealtimeData()");
+NotifyRealtimeTrackData(MediaStreamGraph* graph,
+                        StreamTime offset,
+                        const MediaSegment& media) {
+  MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyRealtimeTrackData() listener=" <<
+                      this << ", offset=" << offset <<
+                      ", duration=" << media.GetDuration());
 
-  NewData(graph, tid, offset, events, media);
+  NewData(graph, offset, media);
 }
 
 void MediaPipelineTransmit::PipelineListener::
-NotifyQueuedTrackChanges(MediaStreamGraph* graph, TrackID tid,
-                         StreamTime offset,
-                         uint32_t events,
-                         const MediaSegment& queued_media,
-                         MediaStream* aInputStream,
-                         TrackID aInputTrackID) {
-  MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyQueuedTrackChanges()");
+NotifyQueuedChanges(MediaStreamGraph* graph,
+                    StreamTime offset,
+                    const MediaSegment& queued_media) {
+  MOZ_MTLOG(ML_DEBUG, "MediaPipeline::NotifyQueuedChanges()");
 
   // ignore non-direct data if we're also getting direct data
   if (!direct_connect_) {
-    NewData(graph, tid, offset, events, queued_media);
+    NewData(graph, offset, queued_media);
   }
 }
 
+void MediaPipelineTransmit::PipelineListener::
+NotifyDirectListenerInstalled(InstallationResult aResult) {
+  MOZ_MTLOG(ML_INFO, "MediaPipeline::NotifyDirectListenerInstalled() listener= " <<
+                     this << ", result=" << static_cast<int32_t>(aResult));
+
+  direct_connect_ = InstallationResult::SUCCESS == aResult;
+}
+
+void MediaPipelineTransmit::PipelineListener::
+NotifyDirectListenerUninstalled() {
+  MOZ_MTLOG(ML_INFO, "MediaPipeline::NotifyDirectListenerUninstalled() listener=" << this);
+
+  direct_connect_ = false;
+}
+
 // I420 buffer size macros
 #define YSIZE(x,y) ((x)*(y))
 #define CRSIZE(x,y) ((((x)+1) >> 1) * (((y)+1) >> 1))
 #define I420SIZE(x,y) (YSIZE((x),(y)) + 2 * CRSIZE((x),(y)))
 
-// XXX NOTE: this code will have to change when we get support for multiple tracks of type
-// in a MediaStream and especially in a PeerConnection stream.  bug 1056650
-// It should be matching on the "correct" track for the pipeline, not just "any video track".
-
 void MediaPipelineTransmit::PipelineListener::
-NewData(MediaStreamGraph* graph, TrackID tid,
+NewData(MediaStreamGraph* graph,
         StreamTime offset,
-        uint32_t events,
         const MediaSegment& media) {
   if (!active_) {
     MOZ_MTLOG(ML_DEBUG, "Discarding packets because transport not ready");
     return;
   }
 
   if (conduit_->type() !=
       (media.GetType() == MediaSegment::AUDIO ? MediaSessionConduit::AUDIO :
                                                 MediaSessionConduit::VIDEO)) {
-    // Ignore data of wrong kind in case we have a muxed stream
-    return;
-  }
-
-  if (track_id_ == TRACK_INVALID) {
-    // Don't lock during normal media flow except on first sample
-    MutexAutoLock lock(mMutex);
-    track_id_ = track_id_external_ = tid;
-  } else if (tid != track_id_) {
+    MOZ_ASSERT(false, "The media type should always be correct since the "
+                      "listener is locked to a specific track");
     return;
   }
 
   // TODO(ekr@rtfm.com): For now assume that we have only one
   // track type and it's destined for us
   // See bug 784517
   if (media.GetType() == MediaSegment::AUDIO) {
     AudioSegment* audio = const_cast<AudioSegment *>(
@@ -1502,17 +1494,17 @@ nsresult MediaPipelineReceiveVideo::Init
   description_ = pc_ + "| Receive video[";
   description_ += track_id_;
   description_ += "]";
 
 #if defined(MOZILLA_INTERNAL_API)
   listener_->AddSelf(new VideoSegment());
 #endif
 
-  // Always happens before we can DetachMediaStream()
+  // Always happens before we can DetachMedia()
   static_cast<VideoSessionConduit *>(conduit_.get())->
       AttachRenderer(renderer_);
 
   return MediaPipelineReceive::Init();
 }
 
 MediaPipelineReceiveVideo::PipelineListener::PipelineListener(
   SourceMediaStream* source, TrackID track_id, bool queue_track)
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -9,16 +9,17 @@
 #define mediapipeline_h__
 
 #include "sigslot.h"
 
 #ifdef USE_FAKE_MEDIA_STREAMS
 #include "FakeMediaStreams.h"
 #else
 #include "DOMMediaStream.h"
+#include "MediaStreamTrack.h"
 #include "MediaStreamGraph.h"
 #include "VideoUtils.h"
 #endif
 #include "MediaConduitInterface.h"
 #include "MediaPipelineFilter.h"
 #include "AudioSegment.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Atomics.h"
@@ -72,25 +73,23 @@ class PeerIdentity;
 class MediaPipeline : public sigslot::has_slots<> {
  public:
   enum Direction { TRANSMIT, RECEIVE };
   enum State { MP_CONNECTING, MP_OPEN, MP_CLOSED };
   MediaPipeline(const std::string& pc,
                 Direction direction,
                 nsCOMPtr<nsIEventTarget> main_thread,
                 nsCOMPtr<nsIEventTarget> sts_thread,
-                MediaStream *stream,
                 const std::string& track_id,
                 int level,
                 RefPtr<MediaSessionConduit> conduit,
                 RefPtr<TransportFlow> rtp_transport,
                 RefPtr<TransportFlow> rtcp_transport,
                 nsAutoPtr<MediaPipelineFilter> filter)
       : direction_(direction),
-        stream_(stream),
         track_id_(track_id),
         level_(level),
         conduit_(conduit),
         rtp_(rtp_transport, rtcp_transport ? RTP : MUX),
         rtcp_(rtcp_transport ? rtcp_transport : rtp_transport,
               rtcp_transport ? RTCP : MUX),
         main_thread_(main_thread),
         sts_thread_(sts_thread),
@@ -109,31 +108,29 @@ class MediaPipeline : public sigslot::ha
       // both rtp and rtcp.
       MOZ_ASSERT(rtp_transport != rtcp_transport);
 
       // PipelineTransport() will access this->sts_thread_; moved here for safety
       transport_ = new PipelineTransport(this);
     }
 
   // Must be called on the STS thread.  Must be called after ShutdownMedia_m().
-  void ShutdownTransport_s();
+  void DetachTransport_s();
 
   // Must be called on the main thread.
-  void ShutdownMedia_m() {
+  void ShutdownMedia_m()
+  {
     ASSERT_ON_THREAD(main_thread_);
 
     if (direction_ == RECEIVE) {
       conduit_->StopReceiving();
     } else {
       conduit_->StopTransmitting();
     }
-
-    if (stream_) {
-      DetachMediaStream();
-    }
+    DetachMedia();
   }
 
   virtual nsresult Init();
 
   void UpdateTransport_m(int level,
                          RefPtr<TransportFlow> rtp_transport,
                          RefPtr<TransportFlow> rtcp_transport,
                          nsAutoPtr<MediaPipelineFilter> filter);
@@ -173,19 +170,18 @@ class MediaPipeline : public sigslot::ha
     RTP,
     RTCP,
     MUX,
     MAX_RTP_TYPE
   } RtpType;
 
  protected:
   virtual ~MediaPipeline();
-  virtual void DetachMediaStream() {}
+  virtual void DetachMedia() {}
   nsresult AttachTransport_s();
-  void DetachTransport_s();
 
   // Separate class to allow ref counting
   class PipelineTransport : public TransportInterface {
    public:
     // Implement the TransportInterface functions
     explicit PipelineTransport(MediaPipeline *pipeline)
         : pipeline_(pipeline),
           sts_thread_(pipeline->sts_thread_) {}
@@ -252,22 +248,19 @@ class MediaPipeline : public sigslot::ha
   void RtpPacketReceived(TransportLayer *layer, const unsigned char *data,
                          size_t len);
   void RtcpPacketReceived(TransportLayer *layer, const unsigned char *data,
                           size_t len);
   void PacketReceived(TransportLayer *layer, const unsigned char *data,
                       size_t len);
 
   Direction direction_;
-  RefPtr<MediaStream> stream_;  // A pointer to the stream we are servicing.
+  std::string track_id_;        // The track on the stream.
                                 // Written on the main thread.
                                 // Used on STS and MediaStreamGraph threads.
-                                // May be changed by rtpSender.replaceTrack()
-  std::string track_id_;        // The track on the stream.
-                                // Written and used as with the stream_;
                                 // Not used outside initialization in MediaPipelineTransmit
   // The m-line index (starting at 0, to match convention) Atomic because
   // this value is updated from STS, but read on main, and we don't want to
   // bother with dispatches just to get an int occasionally.
   Atomic<int> level_;
   RefPtr<MediaSessionConduit> conduit_;  // Our conduit. Written on the main
                                          // thread. Read on STS thread.
 
@@ -381,74 +374,67 @@ private:
 // A specialization of pipeline for reading from an input device
 // and transmitting to the network.
 class MediaPipelineTransmit : public MediaPipeline {
 public:
   // Set rtcp_transport to nullptr to use rtcp-mux
   MediaPipelineTransmit(const std::string& pc,
                         nsCOMPtr<nsIEventTarget> main_thread,
                         nsCOMPtr<nsIEventTarget> sts_thread,
-                        DOMMediaStream *domstream,
+                        dom::MediaStreamTrack* domtrack,
                         const std::string& track_id,
                         int level,
-                        bool is_video,
                         RefPtr<MediaSessionConduit> conduit,
                         RefPtr<TransportFlow> rtp_transport,
                         RefPtr<TransportFlow> rtcp_transport,
                         nsAutoPtr<MediaPipelineFilter> filter) :
-      MediaPipeline(pc, TRANSMIT, main_thread, sts_thread,
-                    domstream->GetOwnedStream(), track_id, level,
+      MediaPipeline(pc, TRANSMIT, main_thread, sts_thread, track_id, level,
                     conduit, rtp_transport, rtcp_transport, filter),
       listener_(new PipelineListener(conduit)),
-      domstream_(domstream),
-      is_video_(is_video)
+      domtrack_(domtrack)
   {}
 
   // Initialize (stuff here may fail)
   virtual nsresult Init() override;
 
   virtual void AttachToTrack(const std::string& track_id);
 
-  // Index used to refer to this before we know the TrackID
-  // Note: unlike MediaPipeline::trackid(), this is threadsafe
-  // Not set until first media is received
-  virtual TrackID trackid_locked() const { return listener_->trackid(); }
   // written and used from MainThread
-  virtual bool IsVideo() const override { return is_video_; }
+  virtual bool IsVideo() const override { return !!domtrack_->AsVideoStreamTrack(); }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // when the principal of the PeerConnection changes, it calls through to here
   // so that we can determine whether to enable stream transmission
   virtual void UpdateSinkIdentity_m(nsIPrincipal* principal,
                                     const PeerIdentity* sinkIdentity);
 #endif
 
   // Called on the main thread.
-  virtual void DetachMediaStream() override {
+  virtual void DetachMedia() override {
     ASSERT_ON_THREAD(main_thread_);
-    domstream_->RemoveDirectListener(listener_);
-    domstream_ = nullptr;
-    stream_->RemoveListener(listener_);
+    if (domtrack_) {
+      domtrack_->RemoveDirectListener(listener_);
+      domtrack_->RemoveListener(listener_);
+      domtrack_ = nullptr;
+    }
     // Let the listener be destroyed with the pipeline (or later).
-    stream_ = nullptr;
   }
 
   // Override MediaPipeline::TransportReady.
   virtual nsresult TransportReady_s(TransportInfo &info) override;
 
   // Replace a track with a different one
   // In non-compliance with the likely final spec, allow the new
   // track to be part of a different stream (since we don't support
   // multiple tracks of a type in a stream yet).  bug 1056650
-  virtual nsresult ReplaceTrack(DOMMediaStream *domstream,
-                                const std::string& track_id);
+  virtual nsresult ReplaceTrack(dom::MediaStreamTrack& domtrack);
 
 
   // Separate class to allow ref counting
-  class PipelineListener : public MediaStreamDirectListener {
+  class PipelineListener : public MediaStreamTrackDirectListener {
    friend class MediaPipelineTransmit;
    public:
     explicit PipelineListener(const RefPtr<MediaSessionConduit>& conduit)
       : conduit_(conduit),
         track_id_(TRACK_INVALID),
         mMutex("MediaPipelineTransmit::PipelineListener"),
         track_id_external_(TRACK_INVALID),
         active_(false),
@@ -475,45 +461,37 @@ public:
     // Dispatches setting the internal TrackID to TRACK_INVALID to the media
     // graph thread to keep it in sync with other MediaStreamGraph operations
     // like RemoveListener() and AddListener(). The TrackID will be updated on
     // the next NewData() callback.
     void UnsetTrackId(MediaStreamGraphImpl* graph);
 
     void SetActive(bool active) { active_ = active; }
     void SetEnabled(bool enabled) { enabled_ = enabled; }
-    TrackID trackid() {
-      MutexAutoLock lock(mMutex);
-      return track_id_external_;
-    }
+
+    // Implement MediaStreamTrackListener
+    void NotifyQueuedChanges(MediaStreamGraph* aGraph,
+                             StreamTime aTrackOffset,
+                             const MediaSegment& aQueuedMedia) override;
 
-    // Implement MediaStreamListener
-    virtual void NotifyQueuedTrackChanges(MediaStreamGraph* graph, TrackID tid,
-                                          StreamTime offset,
-                                          uint32_t events,
-                                          const MediaSegment& queued_media,
-                                          MediaStream* input_stream,
-                                          TrackID input_tid) override;
-    virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override {}
-
-    // Implement MediaStreamDirectListener
-    virtual void NotifyRealtimeData(MediaStreamGraph* graph, TrackID tid,
-                                    StreamTime offset,
-                                    uint32_t events,
-                                    const MediaSegment& media) override;
+    // Implement MediaStreamTrackDirectListener
+    void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
+                                 StreamTime aTrackOffset,
+                                 const MediaSegment& aMedia) override;
+    void NotifyDirectListenerInstalled(InstallationResult aResult) override;
+    void NotifyDirectListenerUninstalled() override;
 
    private:
     void UnsetTrackIdImpl() {
       MutexAutoLock lock(mMutex);
       track_id_ = track_id_external_ = TRACK_INVALID;
     }
 
-    void NewData(MediaStreamGraph* graph, TrackID tid,
+    void NewData(MediaStreamGraph* graph,
                  StreamTime offset,
-                 uint32_t events,
                  const MediaSegment& media);
 
     virtual void ProcessAudioChunk(AudioSessionConduit *conduit,
                                    TrackRate rate, AudioChunk& chunk);
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
     virtual void ProcessVideoChunk(VideoSessionConduit *conduit,
                                    VideoChunk& chunk);
 #endif
@@ -527,94 +505,102 @@ public:
     TrackID track_id_external_; // this is queried from other threads
 
     // active is true if there is a transport to send on
     mozilla::Atomic<bool> active_;
     // enabled is true if the media access control permits sending
     // actual content; when false you get black/silence
     mozilla::Atomic<bool> enabled_;
 
+    // Written and read on the MediaStreamGraph thread
     bool direct_connect_;
 
     nsAutoPtr<AudioPacketizer<int16_t, int16_t>> packetizer_;
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
     int32_t last_img_; // serial number of last Image
 #endif // MOZILLA_INTERNAL_API
   };
 
  private:
   RefPtr<PipelineListener> listener_;
-  DOMMediaStream *domstream_;
-  bool is_video_;
+  dom::MediaStreamTrack* domtrack_;
 };
 
 
 // A specialization of pipeline for reading from the network and
 // rendering video.
 class MediaPipelineReceive : public MediaPipeline {
  public:
   // Set rtcp_transport to nullptr to use rtcp-mux
   MediaPipelineReceive(const std::string& pc,
                        nsCOMPtr<nsIEventTarget> main_thread,
                        nsCOMPtr<nsIEventTarget> sts_thread,
-                       MediaStream *stream,
+                       SourceMediaStream *stream,
                        const std::string& track_id,
                        int level,
                        RefPtr<MediaSessionConduit> conduit,
                        RefPtr<TransportFlow> rtp_transport,
                        RefPtr<TransportFlow> rtcp_transport,
                        nsAutoPtr<MediaPipelineFilter> filter) :
       MediaPipeline(pc, RECEIVE, main_thread, sts_thread,
-                    stream, track_id, level, conduit, rtp_transport,
+                    track_id, level, conduit, rtp_transport,
                     rtcp_transport, filter),
+      stream_(stream),
       segments_added_(0) {
+    MOZ_ASSERT(stream_);
   }
 
   int segments_added() const { return segments_added_; }
 
  protected:
+  ~MediaPipelineReceive() {
+    MOZ_ASSERT(!stream_);  // Check that we have shut down already.
+  }
+
+  RefPtr<SourceMediaStream> stream_;
   int segments_added_;
 
  private:
 };
 
 
 // A specialization of pipeline for reading from the network and
 // rendering audio.
 class MediaPipelineReceiveAudio : public MediaPipelineReceive {
  public:
   MediaPipelineReceiveAudio(const std::string& pc,
                             nsCOMPtr<nsIEventTarget> main_thread,
                             nsCOMPtr<nsIEventTarget> sts_thread,
-                            MediaStream *stream,
+                            SourceMediaStream* stream,
                             // This comes from an msid attribute. Everywhere
                             // but MediaStreamGraph uses this.
                             const std::string& media_stream_track_id,
                             // This is an integer identifier that is only
                             // unique within a single DOMMediaStream, which is
                             // used by MediaStreamGraph
                             TrackID numeric_track_id,
                             int level,
                             RefPtr<AudioSessionConduit> conduit,
                             RefPtr<TransportFlow> rtp_transport,
                             RefPtr<TransportFlow> rtcp_transport,
                             nsAutoPtr<MediaPipelineFilter> filter,
                             bool queue_track) :
       MediaPipelineReceive(pc, main_thread, sts_thread,
                            stream, media_stream_track_id, level, conduit,
                            rtp_transport, rtcp_transport, filter),
-      listener_(new PipelineListener(stream->AsSourceStream(),
-                                     numeric_track_id, conduit, queue_track)) {
+      listener_(new PipelineListener(stream, numeric_track_id, conduit,
+                                     queue_track)) {
   }
 
-  virtual void DetachMediaStream() override {
+  virtual void DetachMedia() override {
     ASSERT_ON_THREAD(main_thread_);
-    listener_->EndTrack();
-    stream_->RemoveListener(listener_);
-    stream_ = nullptr;
+    if (stream_) {
+      stream_->RemoveListener(listener_);
+      stream_ = nullptr;
+    }
   }
 
   virtual nsresult Init() override;
   virtual bool IsVideo() const override { return false; }
 
  private:
   // Separate class to allow ref counting
   class PipelineListener : public GenericReceiveListener {
@@ -653,17 +639,17 @@ class MediaPipelineReceiveAudio : public
 
 // A specialization of pipeline for reading from the network and
 // rendering video.
 class MediaPipelineReceiveVideo : public MediaPipelineReceive {
  public:
   MediaPipelineReceiveVideo(const std::string& pc,
                             nsCOMPtr<nsIEventTarget> main_thread,
                             nsCOMPtr<nsIEventTarget> sts_thread,
-                            MediaStream *stream,
+                            SourceMediaStream *stream,
                             // This comes from an msid attribute. Everywhere
                             // but MediaStreamGraph uses this.
                             const std::string& media_stream_track_id,
                             // This is an integer identifier that is only
                             // unique within a single DOMMediaStream, which is
                             // used by MediaStreamGraph
                             TrackID numeric_track_id,
                             int level,
@@ -671,32 +657,33 @@ class MediaPipelineReceiveVideo : public
                             RefPtr<TransportFlow> rtp_transport,
                             RefPtr<TransportFlow> rtcp_transport,
                             nsAutoPtr<MediaPipelineFilter> filter,
                             bool queue_track) :
       MediaPipelineReceive(pc, main_thread, sts_thread,
                            stream, media_stream_track_id, level, conduit,
                            rtp_transport, rtcp_transport, filter),
       renderer_(new PipelineRenderer(this)),
-      listener_(new PipelineListener(stream->AsSourceStream(),
-                                     numeric_track_id, queue_track)) {
+      listener_(new PipelineListener(stream, numeric_track_id, queue_track)) {
   }
 
   // Called on the main thread.
-  virtual void DetachMediaStream() override {
+  virtual void DetachMedia() override {
     ASSERT_ON_THREAD(main_thread_);
 
     listener_->EndTrack();
     // stop generating video and thus stop invoking the PipelineRenderer
     // and PipelineListener - the renderer has a raw ptr to the Pipeline to
     // avoid cycles, and the render callbacks are invoked from a different
     // thread so simple null-checks would cause TSAN bugs without locks.
     static_cast<VideoSessionConduit*>(conduit_.get())->DetachRenderer();
-    stream_->RemoveListener(listener_);
-    stream_ = nullptr;
+    if (stream_) {
+      stream_->RemoveListener(listener_);
+      stream_ = nullptr;
+    }
   }
 
   virtual nsresult Init() override;
   virtual bool IsVideo() const override { return true; }
 
  private:
   class PipelineRenderer : public VideoRenderer {
    public:
--- a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp
@@ -433,21 +433,25 @@ MediaPipelineFactory::CreateOrUpdateMedi
   RefPtr<MediaPipeline> pipeline =
     stream->GetPipelineByTrackId_m(aTrack.GetTrackId());
 
   if (pipeline && pipeline->level() != static_cast<int>(level)) {
     MOZ_MTLOG(ML_WARNING, "Track " << aTrack.GetTrackId() <<
                           " has moved from level " << pipeline->level() <<
                           " to level " << level <<
                           ". This requires re-creating the MediaPipeline.");
+    RefPtr<dom::MediaStreamTrack> domTrack =
+      stream->GetTrackById(aTrack.GetTrackId());
+    MOZ_ASSERT(domTrack, "MediaPipeline existed for a track, but no MediaStreamTrack");
+
     // Since we do not support changing the conduit on a pre-existing
     // MediaPipeline
     pipeline = nullptr;
     stream->RemoveTrack(aTrack.GetTrackId());
-    stream->AddTrack(aTrack.GetTrackId());
+    stream->AddTrack(aTrack.GetTrackId(), domTrack);
   }
 
   if (pipeline) {
     pipeline->UpdateTransport_m(level, rtpFlow, rtcpFlow, filter);
     return NS_OK;
   }
 
   MOZ_MTLOG(ML_DEBUG,
@@ -497,31 +501,31 @@ MediaPipelineFactory::CreateMediaPipelin
   MOZ_MTLOG(ML_DEBUG, __FUNCTION__ << ": Creating pipeline for "
             << numericTrackId << " -> " << aTrack.GetTrackId());
 
   if (aTrack.GetMediaType() == SdpMediaSection::kAudio) {
     pipeline = new MediaPipelineReceiveAudio(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
-        stream->GetMediaStream()->GetInputStream(),
+        stream->GetMediaStream()->GetInputStream()->AsSourceStream(),
         aTrack.GetTrackId(),
         numericTrackId,
         aLevel,
         static_cast<AudioSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
         aFilter,
         queue_track);
   } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) {
     pipeline = new MediaPipelineReceiveVideo(
         mPC->GetHandle(),
         mPC->GetMainThread().get(),
         mPC->GetSTSThread(),
-        stream->GetMediaStream()->GetInputStream(),
+        stream->GetMediaStream()->GetInputStream()->AsSourceStream(),
         aTrack.GetTrackId(),
         numericTrackId,
         aLevel,
         static_cast<VideoSessionConduit*>(aConduit.get()), // Ugly downcast.
         aRtpFlow,
         aRtcpFlow,
         aFilter,
         queue_track);
@@ -561,25 +565,28 @@ MediaPipelineFactory::CreateMediaPipelin
     const RefPtr<MediaSessionConduit>& aConduit)
 {
   nsresult rv;
 
   // This is checked earlier
   RefPtr<LocalSourceStreamInfo> stream =
       mPCMedia->GetLocalStreamById(aTrack.GetStreamId());
 
+  dom::MediaStreamTrack* track =
+    stream->GetTrackById(aTrack.GetTrackId());
+  MOZ_ASSERT(track);
+
   // Now we have all the pieces, create the pipeline
   RefPtr<MediaPipelineTransmit> pipeline = new MediaPipelineTransmit(
       mPC->GetHandle(),
       mPC->GetMainThread().get(),
       mPC->GetSTSThread(),
-      stream->GetMediaStream(),
+      track,
       aTrack.GetTrackId(),
       aLevel,
-      aTrack.GetMediaType() == SdpMediaSection::kVideo,
       aConduit,
       aRtpFlow,
       aRtcpFlow,
       aFilter);
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // implement checking for peerIdentity (where failure == black/silence)
   nsIDocument* doc = mPC->GetWindow()->GetExtantDoc();
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1801,36 +1801,37 @@ PeerConnectionImpl::CreateNewRemoteTrack
         if (track->GetMediaType() == SdpMediaSection::kAudio) {
           ++numNewAudioTracks;
         } else if (track->GetMediaType() == SdpMediaSection::kVideo) {
           ++numNewVideoTracks;
         } else {
           MOZ_ASSERT(false);
           continue;
         }
-        info->AddTrack(track->GetTrackId());
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
         RefPtr<RemoteTrackSource> source = new RemoteTrackSource(principal);
-        DebugOnly<bool> sourceSet =
-          info->SetTrackSource(track->GetTrackId(), source);
-        NS_ASSERTION(sourceSet, "TrackSource was added in the wrong order. "
-                                "Did someone add a track from elsewhere?");
-        TrackID trackID = info->GetNumericTrackId(track->GetTrackId());
+#else
+        RefPtr<MediaStreamTrackSource> source = new MediaStreamTrackSource();
+#endif
+        TrackID trackID = info->GetNextAvailableNumericTrackId();
+        RefPtr<MediaStreamTrack> domTrack;
         if (track->GetMediaType() == SdpMediaSection::kAudio) {
-          info->GetMediaStream()->CreateDOMTrack(trackID,
-                                                 MediaSegment::AUDIO,
-                                                 nsString(),
-                                                 source);
+          domTrack =
+            info->GetMediaStream()->CreateDOMTrack(trackID,
+                                                   MediaSegment::AUDIO,
+                                                   nsString(),
+                                                   source);
         } else {
-          info->GetMediaStream()->CreateDOMTrack(trackID,
-                                                 MediaSegment::VIDEO,
-                                                 nsString(),
-                                                 source);
+          domTrack =
+            info->GetMediaStream()->CreateDOMTrack(trackID,
+                                                   MediaSegment::VIDEO,
+                                                   nsString(),
+                                                   source);
         }
-#endif
+        info->AddTrack(track->GetTrackId(), domTrack);
         CSFLogDebug(logTag, "Added remote track %s/%s",
                     info->GetId().c_str(), track->GetTrackId().c_str());
       } else {
         ++numPreexistingTrackIds;
       }
     }
 
     // Now that the streams are all set up, notify about track availability.
@@ -2248,17 +2249,17 @@ PeerConnectionImpl::AddTrack(MediaStream
   if (!aMediaStream.HasTrack(aTrack)) {
     CSFLogError(logTag, "%s: Track is not in stream", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
   uint32_t num = mMedia->LocalStreamsLength();
 
   std::string streamId = PeerConnectionImpl::GetStreamId(aMediaStream);
   std::string trackId = PeerConnectionImpl::GetTrackId(aTrack);
-  nsresult res = mMedia->AddTrack(&aMediaStream, streamId, trackId);
+  nsresult res = mMedia->AddTrack(aMediaStream, streamId, aTrack, trackId);
   if (NS_FAILED(res)) {
     return res;
   }
 
   CSFLogDebug(logTag, "Added track (%s) to stream %s",
                       trackId.c_str(), streamId.c_str());
 
   if (num != mMedia->LocalStreamsLength()) {
@@ -2403,17 +2404,17 @@ PeerConnectionImpl::ReplaceTrack(MediaSt
       CSFLogError(logTag, "Error firing replaceTrack error callback");
       return NS_ERROR_UNEXPECTED;
     }
     return NS_OK;
   }
 
   rv = media()->ReplaceTrack(origStreamId,
                              origTrackId,
-                             aWithTrack.mOwningStream,
+                             aWithTrack,
                              newStreamId,
                              newTrackId);
 
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "Unexpected error in ReplaceTrack: %d",
                         static_cast<int>(rv));
     pco->OnReplaceTrackError(kInvalidMediastreamTrack,
                              ObString("Failed to replace track"),
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -18,16 +18,23 @@
 #include "AudioConduit.h"
 #include "VideoConduit.h"
 #include "runnable_utils.h"
 #include "transportlayerice.h"
 #include "transportlayerdtls.h"
 #include "signaling/src/jsep/JsepSession.h"
 #include "signaling/src/jsep/JsepTransport.h"
 
+#ifdef USE_FAKE_STREAMS
+#include "DOMMediaStream.h"
+#include "FakeMediaStreams.h"
+#else
+#include "MediaSegment.h"
+#endif
+
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIURI.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsICancelable.h"
 #include "nsIDocument.h"
 #include "nsILoadInfo.h"
 #include "nsIContentPolicy.h"
@@ -50,53 +57,54 @@
 namespace mozilla {
 using namespace dom;
 
 static const char* logTag = "PeerConnectionMedia";
 
 nsresult
 PeerConnectionMedia::ReplaceTrack(const std::string& aOldStreamId,
                                   const std::string& aOldTrackId,
-                                  DOMMediaStream* aNewStream,
+                                  MediaStreamTrack& aNewTrack,
                                   const std::string& aNewStreamId,
                                   const std::string& aNewTrackId)
 {
   RefPtr<LocalSourceStreamInfo> oldInfo(GetLocalStreamById(aOldStreamId));
 
   if (!oldInfo) {
     CSFLogError(logTag, "Failed to find stream id %s", aOldStreamId.c_str());
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  nsresult rv = AddTrack(aNewStream, aNewStreamId, aNewTrackId);
+  nsresult rv = AddTrack(*aNewTrack.mOwningStream, aNewStreamId,
+                         aNewTrack, aNewTrackId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<LocalSourceStreamInfo> newInfo(GetLocalStreamById(aNewStreamId));
 
   if (!newInfo) {
     CSFLogError(logTag, "Failed to add track id %s", aNewTrackId.c_str());
     MOZ_ASSERT(false);
     return NS_ERROR_FAILURE;
   }
 
-  rv = newInfo->TakePipelineFrom(oldInfo, aOldTrackId, aNewTrackId);
+  rv = newInfo->TakePipelineFrom(oldInfo, aOldTrackId, aNewTrack, aNewTrackId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return RemoveLocalTrack(aOldStreamId, aOldTrackId);
 }
 
 static void
 PipelineReleaseRef_m(RefPtr<MediaPipeline> pipeline)
 {}
 
 static void
 PipelineDetachTransport_s(RefPtr<MediaPipeline> pipeline,
                           nsCOMPtr<nsIThread> mainThread)
 {
-  pipeline->ShutdownTransport_s();
+  pipeline->DetachTransport_s();
   mainThread->Dispatch(
       // Make sure we let go of our reference before dispatching
       // If the dispatch fails, well, we're hosed anyway.
       WrapRunnableNM(PipelineReleaseRef_m, pipeline.forget()),
       NS_DISPATCH_NORMAL);
 }
 
 void
@@ -116,17 +124,17 @@ SourceStreamInfo::RemoveTrack(const std:
 }
 
 void SourceStreamInfo::DetachTransport_s()
 {
   ASSERT_ON_THREAD(mParent->GetSTSThread());
   // walk through all the MediaPipelines and call the shutdown
   // transport functions. Must be on the STS thread.
   for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) {
-    it->second->ShutdownTransport_s();
+    it->second->DetachTransport_s();
   }
 }
 
 void SourceStreamInfo::DetachMedia_m()
 {
   ASSERT_ON_THREAD(mParent->GetMainThread());
 
   // walk through all the MediaPipelines and call the shutdown
@@ -673,38 +681,34 @@ PeerConnectionMedia::EnsureIceGathering_
 
   // If there are no streams, we're probably in a situation where we've rolled
   // back while still waiting for our proxy configuration to come back. Make
   // sure content knows that the rollback has stuck wrt gathering.
   IceGatheringStateChange_s(mIceCtx.get(), NrIceCtx::ICE_CTX_GATHER_COMPLETE);
 }
 
 nsresult
-PeerConnectionMedia::AddTrack(DOMMediaStream* aMediaStream,
+PeerConnectionMedia::AddTrack(DOMMediaStream& aMediaStream,
                               const std::string& streamId,
+                              MediaStreamTrack& aTrack,
                               const std::string& trackId)
 {
   ASSERT_ON_THREAD(mMainThread);
 
-  if (!aMediaStream) {
-    CSFLogError(logTag, "%s - aMediaStream is NULL", __FUNCTION__);
-    return NS_ERROR_FAILURE;
-  }
-
-  CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, aMediaStream);
+  CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, &aMediaStream);
 
   RefPtr<LocalSourceStreamInfo> localSourceStream =
     GetLocalStreamById(streamId);
 
   if (!localSourceStream) {
-    localSourceStream = new LocalSourceStreamInfo(aMediaStream, this, streamId);
+    localSourceStream = new LocalSourceStreamInfo(&aMediaStream, this, streamId);
     mLocalSourceStreams.AppendElement(localSourceStream);
   }
 
-  localSourceStream->AddTrack(trackId);
+  localSourceStream->AddTrack(trackId, &aTrack);
   return NS_OK;
 }
 
 nsresult
 PeerConnectionMedia::RemoveLocalTrack(const std::string& streamId,
                                       const std::string& trackId)
 {
   ASSERT_ON_THREAD(mMainThread);
@@ -1156,16 +1160,17 @@ PeerConnectionMedia::ConnectDtlsListener
   if (dtls) {
     dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected_s);
   }
 }
 
 nsresult
 LocalSourceStreamInfo::TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info,
                                         const std::string& oldTrackId,
+                                        MediaStreamTrack& aNewTrack,
                                         const std::string& newTrackId)
 {
   if (mPipelines.count(newTrackId)) {
     CSFLogError(logTag, "%s: Pipeline already exists for %s/%s",
                 __FUNCTION__, mId.c_str(), newTrackId.c_str());
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -1175,18 +1180,17 @@ LocalSourceStreamInfo::TakePipelineFrom(
     // Replacetrack can potentially happen in the middle of offer/answer, before
     // the pipeline has been created.
     CSFLogInfo(logTag, "%s: Replacing track before the pipeline has been "
                        "created, nothing to do.", __FUNCTION__);
     return NS_OK;
   }
 
   nsresult rv =
-    static_cast<MediaPipelineTransmit*>(pipeline.get())->ReplaceTrack(
-        mMediaStream, newTrackId);
+    static_cast<MediaPipelineTransmit*>(pipeline.get())->ReplaceTrack(aNewTrack);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mPipelines[newTrackId] = pipeline;
 
   return NS_OK;
 }
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
@@ -1249,19 +1253,21 @@ LocalSourceStreamInfo::UpdateSinkIdentit
   }
 }
 
 void RemoteSourceStreamInfo::UpdatePrincipal_m(nsIPrincipal* aPrincipal)
 {
   // This blasts away the existing principal.
   // We only do this when we become certain that the all tracks are safe to make
   // accessible to the script principal.
-  for (RefPtr<RemoteTrackSource>& source : mTrackSources) {
-    MOZ_RELEASE_ASSERT(source);
-    source->SetPrincipal(aPrincipal);
+  for (auto& trackPair : mTracks) {
+    MOZ_RELEASE_ASSERT(trackPair.second);
+    RemoteTrackSource& source =
+      static_cast<RemoteTrackSource&>(trackPair.second->GetSource());
+    source.SetPrincipal(aPrincipal);
   }
 }
 #endif // MOZILLA_INTERNAL_API
 
 bool
 PeerConnectionMedia::AnyCodecHasPluginID(uint64_t aPluginID)
 {
   for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -12,23 +12,16 @@
 #include "nspr.h"
 #include "prlock.h"
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIProtocolProxyCallback.h"
 
-#ifdef USE_FAKE_MEDIA_STREAMS
-#include "FakeMediaStreams.h"
-#else
-#include "DOMMediaStream.h"
-#include "MediaSegment.h"
-#endif
-
 #include "signaling/src/jsep/JsepSession.h"
 #include "AudioSegment.h"
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
 #include "Layers.h"
 #include "VideoUtils.h"
 #include "ImageLayers.h"
 #include "VideoSegment.h"
@@ -82,44 +75,57 @@ public:
 
   DOMMediaStream* GetMediaStream() const {
     return mMediaStream;
   }
 
   nsresult StorePipeline(const std::string& trackId,
                          const RefPtr<MediaPipeline>& aPipeline);
 
-  virtual void AddTrack(const std::string& trackId) { mTracks.insert(trackId); }
+  virtual void AddTrack(const std::string& trackId,
+                        const RefPtr<dom::MediaStreamTrack>& aTrack)
+  {
+    mTracks.insert(std::make_pair(trackId, aTrack));
+  }
   void RemoveTrack(const std::string& trackId);
   bool HasTrack(const std::string& trackId) const
   {
     return !!mTracks.count(trackId);
   }
   size_t GetTrackCount() const { return mTracks.size(); }
 
   // This method exists for stats and the unittests.
   // It allows visibility into the pipelines and flows.
   const std::map<std::string, RefPtr<MediaPipeline>>&
   GetPipelines() const { return mPipelines; }
   RefPtr<MediaPipeline> GetPipelineByTrackId_m(const std::string& trackId);
+  dom::MediaStreamTrack* GetTrackById(const std::string& trackId)
+  {
+    auto it = mTracks.find(trackId);
+    if (it == mTracks.end()) {
+      return nullptr;
+    }
+
+    return it->second;
+  }
   const std::string& GetId() const { return mId; }
 
   void DetachTransport_s();
   void DetachMedia_m();
   bool AnyCodecHasPluginID(uint64_t aPluginID);
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   RefPtr<mozilla::dom::VideoStreamTrack> GetVideoTrackByTrackId(const std::string& trackId);
 #endif
 protected:
   RefPtr<DOMMediaStream> mMediaStream;
   PeerConnectionMedia *mParent;
   const std::string mId;
   // These get set up before we generate our local description, the pipelines
   // and conduits are set up once offer/answer completes.
-  std::set<std::string> mTracks;
+  std::map<std::string, RefPtr<dom::MediaStreamTrack>> mTracks;
   std::map<std::string, RefPtr<MediaPipeline>> mPipelines;
 };
 
 // TODO(ekr@rtfm.com): Refactor {Local,Remote}SourceStreamInfo
 // bug 837539.
 class LocalSourceStreamInfo : public SourceStreamInfo {
   ~LocalSourceStreamInfo() {
     mMediaStream = nullptr;
@@ -127,16 +133,17 @@ class LocalSourceStreamInfo : public Sou
 public:
   LocalSourceStreamInfo(DOMMediaStream *aMediaStream,
                         PeerConnectionMedia *aParent,
                         const std::string& aId)
      : SourceStreamInfo(aMediaStream, aParent, aId) {}
 
   nsresult TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info,
                             const std::string& oldTrackId,
+                            dom::MediaStreamTrack& aNewTrack,
                             const std::string& newTrackId);
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   void UpdateSinkIdentity_m(nsIPrincipal* aPrincipal,
                             const PeerIdentity* aSinkIdentity);
 #endif
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LocalSourceStreamInfo)
@@ -190,36 +197,31 @@ class RemoteSourceStreamInfo : public So
       mReceiving(false)
   {
   }
 
   void SyncPipeline(RefPtr<MediaPipelineReceive> aPipeline);
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   void UpdatePrincipal_m(nsIPrincipal* aPrincipal);
-
-  // Track sources may only be set in the same order as tracks were added.
-  // Returns true if the source was added, false otherwise.
-  bool SetTrackSource(const std::string& track, RemoteTrackSource* source)
-  {
-    size_t nextIndex = mTrackSources.size();
-    if (mTrackIdMap.size() < nextIndex || mTrackIdMap[nextIndex] != track) {
-      return false;
-    }
-    mTrackSources.push_back(source);
-    return true;
-  }
 #endif
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteSourceStreamInfo)
 
-  virtual void AddTrack(const std::string& track) override
+  void AddTrack(const std::string& trackId,
+                const RefPtr<dom::MediaStreamTrack>& aTrack) override
   {
-    mTrackIdMap.push_back(track);
-    SourceStreamInfo::AddTrack(track);
+    mTrackIdMap.push_back(trackId);
+    MOZ_RELEASE_ASSERT(GetNumericTrackId(trackId) == aTrack->mTrackID);
+    SourceStreamInfo::AddTrack(trackId, aTrack);
+  }
+
+  TrackID GetNextAvailableNumericTrackId() const
+  {
+    return mTrackIdMap.size() + 1;
   }
 
   TrackID GetNumericTrackId(const std::string& trackId) const
   {
     for (size_t i = 0; i < mTrackIdMap.size(); ++i) {
       if (mTrackIdMap[i] == trackId) {
         return static_cast<TrackID>(i + 1);
       }
@@ -311,18 +313,19 @@ class PeerConnectionMedia : public sigsl
   // Process a trickle ICE candidate.
   void AddIceCandidate(const std::string& candidate, const std::string& mid,
                        uint32_t aMLine);
 
   // Handle complete media pipelines.
   nsresult UpdateMediaPipelines(const JsepSession& session);
 
   // Add a track (main thread only)
-  nsresult AddTrack(DOMMediaStream* aMediaStream,
+  nsresult AddTrack(DOMMediaStream& aMediaStream,
                     const std::string& streamId,
+                    dom::MediaStreamTrack& aTrack,
                     const std::string& trackId);
 
   nsresult RemoveLocalTrack(const std::string& streamId,
                             const std::string& trackId);
   nsresult RemoveRemoteTrack(const std::string& streamId,
                             const std::string& trackId);
 
   nsresult GetRemoteTrackId(const std::string streamId,
@@ -346,21 +349,21 @@ class PeerConnectionMedia : public sigsl
 
   RemoteSourceStreamInfo* GetRemoteStreamByIndex(size_t index);
   RemoteSourceStreamInfo* GetRemoteStreamById(const std::string& id);
   RemoteSourceStreamInfo* GetRemoteStreamByTrackId(const std::string& id);
 
   // Add a remote stream.
   nsresult AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo);
 
-  nsresult ReplaceTrack(const std::string& oldStreamId,
-                        const std::string& oldTrackId,
-                        DOMMediaStream* aNewStream,
-                        const std::string& newStreamId,
-                        const std::string& aNewTrack);
+  nsresult ReplaceTrack(const std::string& aOldStreamId,
+                        const std::string& aOldTrackId,
+                        dom::MediaStreamTrack& aNewTrack,
+                        const std::string& aNewStreamId,
+                        const std::string& aNewTrackId);
 
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   // In cases where the peer isn't yet identified, we disable the pipeline (not
   // the stream, that would potentially affect others), so that it sends
   // black/silence.  Once the peer is identified, re-enable those streams.
   void UpdateSinkIdentity_m(nsIPrincipal* aPrincipal,
                             const PeerIdentity* aSinkIdentity);
   // this determines if any stream is peerIdentity constrained
--- a/media/webrtc/signaling/test/FakeMediaStreams.h
+++ b/media/webrtc/signaling/test/FakeMediaStreams.h
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef FAKE_MEDIA_STREAM_H_
 #define FAKE_MEDIA_STREAM_H_
 
 #include <set>
 #include <string>
 #include <sstream>
+#include <vector>
 
 #include "nsNetCID.h"
 #include "nsITimer.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIComponentManager.h"
 #include "nsIComponentRegistrar.h"
 #include "nsISupportsImpl.h"
 #include "nsServiceManagerUtils.h"
@@ -27,16 +28,17 @@
 #include "nsISupportsImpl.h"
 
 class nsPIDOMWindowInner;
 
 namespace mozilla {
    class MediaStreamGraphImpl;
    class MediaSegment;
    class PeerConnectionImpl;
+   class PeerConnectionMedia;
    class RemoteSourceStreamInfo;
 };
 
 
 namespace mozilla {
 
 class MediaStreamGraph;
 
@@ -99,45 +101,101 @@ public:
 class Fake_MediaStreamDirectListener : public Fake_MediaStreamListener
 {
 protected:
   virtual ~Fake_MediaStreamDirectListener() {}
 
 public:
   virtual void NotifyRealtimeData(mozilla::MediaStreamGraph* graph, mozilla::TrackID tid,
                                   mozilla::StreamTime offset,
-                                  uint32_t events,
                                   const mozilla::MediaSegment& media) = 0;
 };
 
+class Fake_MediaStreamTrackListener
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrackListener)
+
+protected:
+  virtual ~Fake_MediaStreamTrackListener() {}
+
+public:
+  virtual void NotifyQueuedChanges(mozilla::MediaStreamGraph* aGraph,
+                                   mozilla::StreamTime aTrackOffset,
+                                   const mozilla::MediaSegment& aQueuedMedia) = 0;
+};
+
+class Fake_MediaStreamTrackDirectListener : public Fake_MediaStreamTrackListener
+{
+protected:
+  virtual ~Fake_MediaStreamTrackDirectListener() {}
+
+public:
+  virtual void NotifyRealtimeTrackData(mozilla::MediaStreamGraph* aGraph,
+                                       mozilla::StreamTime aTrackOffset,
+                                       const mozilla::MediaSegment& aMedia) = 0;
+  enum class InstallationResult {
+    STREAM_NOT_SUPPORTED,
+    SUCCESS
+  };
+  virtual void NotifyDirectListenerInstalled(InstallationResult aResult) = 0;
+  virtual void NotifyDirectListenerUninstalled() = 0;
+};
+
 // Note: only one listener supported
 class Fake_MediaStream {
  protected:
   virtual ~Fake_MediaStream() { Stop(); }
 
+  struct BoundTrackListener
+  {
+    BoundTrackListener(Fake_MediaStreamTrackListener* aListener,
+                       mozilla::TrackID aTrackID)
+      : mListener(aListener), mTrackID(aTrackID) {}
+    RefPtr<Fake_MediaStreamTrackListener> mListener;
+    mozilla::TrackID mTrackID;
+  };
+
  public:
-  Fake_MediaStream () : mListeners(), mMutex("Fake MediaStream") {}
+  Fake_MediaStream () : mListeners(), mTrackListeners(), mMutex("Fake MediaStream") {}
 
   static uint32_t GraphRate() { return 16000; }
 
   void AddListener(Fake_MediaStreamListener *aListener) {
     mozilla::MutexAutoLock lock(mMutex);
     mListeners.insert(aListener);
   }
 
   void RemoveListener(Fake_MediaStreamListener *aListener) {
     mozilla::MutexAutoLock lock(mMutex);
     mListeners.erase(aListener);
   }
 
+  void AddTrackListener(Fake_MediaStreamTrackListener *aListener,
+                        mozilla::TrackID aTrackID) {
+    mozilla::MutexAutoLock lock(mMutex);
+    mTrackListeners.push_back(BoundTrackListener(aListener, aTrackID));
+  }
+
+  void RemoveTrackListener(Fake_MediaStreamTrackListener *aListener,
+                           mozilla::TrackID aTrackID) {
+    mozilla::MutexAutoLock lock(mMutex);
+    for (std::vector<BoundTrackListener>::iterator it = mTrackListeners.begin();
+         it != mTrackListeners.end(); ++it) {
+      if (it->mListener == aListener && it->mTrackID == aTrackID) {
+        mTrackListeners.erase(it);
+        return;
+      }
+    }
+  }
+
   void NotifyPull(mozilla::MediaStreamGraph* graph,
                   mozilla::StreamTime aDesiredTime) {
 
     mozilla::MutexAutoLock lock(mMutex);
-    std::set<Fake_MediaStreamListener *>::iterator it;
+    std::set<RefPtr<Fake_MediaStreamListener>>::iterator it;
     for (it = mListeners.begin(); it != mListeners.end(); ++it) {
       (*it)->NotifyPull(graph, aDesiredTime);
     }
   }
 
   virtual Fake_SourceMediaStream *AsSourceStream() { return nullptr; }
 
   virtual mozilla::MediaStreamGraphImpl *GraphImpl() { return nullptr; }
@@ -149,17 +207,18 @@ class Fake_MediaStream {
 
   double StreamTimeToSeconds(mozilla::StreamTime aTime);
   mozilla::StreamTime
   TicksToTimeRoundDown(mozilla::TrackRate aRate, mozilla::TrackTicks aTicks);
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStream);
 
  protected:
-  std::set<Fake_MediaStreamListener *> mListeners;
+  std::set<RefPtr<Fake_MediaStreamListener>> mListeners;
+  std::vector<BoundTrackListener> mTrackListeners;
   mozilla::Mutex mMutex;  // Lock to prevent the listener list from being modified while
                           // executing Periodic().
 };
 
 class Fake_MediaPeriodic : public nsITimerCallback {
 public:
   explicit Fake_MediaPeriodic(Fake_MediaStream *aStream) : mStream(aStream),
                                                            mCount(0) {}
@@ -294,16 +353,17 @@ class Fake_MediaStreamTrackSource
 
 protected:
   virtual ~Fake_MediaStreamTrackSource() {}
 };
 
 class Fake_MediaStreamTrack
 {
   friend class mozilla::PeerConnectionImpl;
+  friend class mozilla::PeerConnectionMedia;
   friend class mozilla::RemoteSourceStreamInfo;
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Fake_MediaStreamTrack)
 
   Fake_MediaStreamTrack(bool aIsVideo, Fake_DOMMediaStream* aOwningStream) :
     mIsVideo (aIsVideo),
     mOwningStream (aOwningStream),
     mTrackID(mIsVideo ? 1 : 0)
@@ -311,43 +371,57 @@ public:
     static size_t counter = 0;
     std::ostringstream os;
     os << counter++;
     mID = os.str();
   }
 
   std::string GetId() const { return mID; }
   void AssignId(const std::string& id) { mID = id; }
+  mozilla::MediaStreamGraphImpl* GraphImpl() { return nullptr; }
   const Fake_MediaStreamTrack* AsVideoStreamTrack() const
   {
     return mIsVideo? this : nullptr;
   }
   const Fake_MediaStreamTrack* AsAudioStreamTrack() const
   {
     return mIsVideo? nullptr : this;
   }
   uint32_t typeSize () const
   {
     return sizeof(Fake_MediaStreamTrack);
   }
   const char* typeName () const
   {
     return "Fake_MediaStreamTrack";
   }
+  void AddListener(Fake_MediaStreamTrackListener *aListener);
+  void RemoveListener(Fake_MediaStreamTrackListener *aListener);
+  void AddDirectListener(Fake_MediaStreamTrackDirectListener *aListener)
+  {
+    AddListener(aListener);
+    aListener->NotifyDirectListenerInstalled(
+      Fake_MediaStreamTrackDirectListener::InstallationResult::STREAM_NOT_SUPPORTED);
+  }
+  void RemoveDirectListener(Fake_MediaStreamTrackDirectListener *aListener)
+  {
+    RemoveListener(aListener);
+  }
 private:
   ~Fake_MediaStreamTrack() {}
 
   const bool mIsVideo;
   Fake_DOMMediaStream* mOwningStream;
   mozilla::TrackID mTrackID;
   std::string mID;
 };
 
 class Fake_DOMMediaStream : public nsISupports
 {
+  friend class mozilla::PeerConnectionMedia;
 protected:
   virtual ~Fake_DOMMediaStream() {
     // Note: memory leak
     mMediaStream->Stop();
   }
 
 public:
   explicit Fake_DOMMediaStream(Fake_MediaStream *stream = nullptr)
@@ -381,16 +455,34 @@ public:
   virtual void RemoveDirectListener(Fake_MediaStreamListener *aListener) {}
 
   Fake_MediaStream *GetInputStream() { return mMediaStream; }
   Fake_MediaStream *GetOwnedStream() { return mMediaStream; }
   Fake_MediaStream *GetPlaybackStream() { return mMediaStream; }
   Fake_MediaStream *GetStream() { return mMediaStream; }
   std::string GetId() const { return mID; }
   void AssignId(const std::string& id) { mID = id; }
+  Fake_MediaStreamTrack* GetTrackById(const std::string& aId)
+  {
+    if (mHintContents & HINT_CONTENTS_AUDIO) {
+      if (mAudioTrack && mAudioTrack->GetId() == aId) {
+        return mAudioTrack;
+      }
+    }
+    if (mHintContents & HINT_CONTENTS_VIDEO) {
+      if (mVideoTrack && mVideoTrack->GetId() == aId) {
+        return mVideoTrack;
+      }
+    }
+    return nullptr;
+  }
+  Fake_MediaStreamTrack* GetOwnedTrackById(const std::string& aId)
+  {
+    return GetTrackById(aId);
+  }
 
   // Hints to tell the SDP generator about whether this
   // MediaStream probably has audio and/or video
   typedef uint8_t TrackTypeHints;
   enum {
     HINT_CONTENTS_AUDIO = 0x01,
     HINT_CONTENTS_VIDEO = 0x02
   };
@@ -503,16 +595,18 @@ class Fake_VideoStreamSource : public Fa
 };
 
 
 namespace mozilla {
 typedef Fake_MediaStream MediaStream;
 typedef Fake_SourceMediaStream SourceMediaStream;
 typedef Fake_MediaStreamListener MediaStreamListener;
 typedef Fake_MediaStreamDirectListener MediaStreamDirectListener;
+typedef Fake_MediaStreamTrackListener MediaStreamTrackListener;
+typedef Fake_MediaStreamTrackDirectListener MediaStreamTrackDirectListener;
 typedef Fake_DOMMediaStream DOMMediaStream;
 typedef Fake_DOMMediaStream DOMLocalMediaStream;
 
 namespace dom {
 typedef Fake_MediaStreamTrack MediaStreamTrack;
 typedef Fake_MediaStreamTrackSource MediaStreamTrackSource;
 }
 }
--- a/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
+++ b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
@@ -53,24 +53,34 @@ nsresult Fake_SourceMediaStream::Stop() 
 void Fake_SourceMediaStream::Periodic() {
   mozilla::MutexAutoLock lock(mMutex);
   // Pull more audio-samples iff pulling is enabled
   // and we are not asked by the signaling agent to stop
   //pulling data.
   if (mPullEnabled && !mStop) {
     // 100 ms matches timer interval and AUDIO_BUFFER_SIZE @ 16000 Hz
     mDesiredTime += 100;
-    for (std::set<Fake_MediaStreamListener *>::iterator it =
+    for (std::set<RefPtr<Fake_MediaStreamListener>>::iterator it =
              mListeners.begin(); it != mListeners.end(); ++it) {
       (*it)->NotifyPull(nullptr, TicksToTimeRoundDown(1000 /* ms per s */,
                                                       mDesiredTime));
     }
   }
 }
 
+// Fake_MediaStreamTrack
+void Fake_MediaStreamTrack::AddListener(Fake_MediaStreamTrackListener *aListener)
+{
+  mOwningStream->GetInputStream()->AddTrackListener(aListener, mTrackID);
+}
+void Fake_MediaStreamTrack::RemoveListener(Fake_MediaStreamTrackListener *aListener)
+{
+  mOwningStream->GetInputStream()->RemoveTrackListener(aListener, mTrackID);
+}
+
 // Fake_MediaStreamBase
 nsresult Fake_MediaStreamBase::Start() {
   mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   if (!mTimer) {
     return NS_ERROR_FAILURE;
   }
 
   mTimer->InitWithCallback(mPeriodic, 100, nsITimer::TYPE_REPEATING_SLACK);
@@ -105,26 +115,32 @@ void Fake_AudioStreamSource::Periodic() 
     mCount++;
   }
 
   mozilla::AudioSegment segment;
   AutoTArray<const int16_t *,1> channels;
   channels.AppendElement(data);
   segment.AppendFrames(samples.forget(), channels, AUDIO_BUFFER_SIZE);
 
-  for(std::set<Fake_MediaStreamListener *>::iterator it = mListeners.begin();
+  for(std::set<RefPtr<Fake_MediaStreamListener>>::iterator it = mListeners.begin();
        it != mListeners.end(); ++it) {
     (*it)->NotifyQueuedTrackChanges(nullptr, // Graph
                                     0, // TrackID
                                     0, // Offset TODO(ekr@rtfm.com) fix
                                     0, // ???
                                     segment,
                                     nullptr, // Input stream
                                     -1);     // Input track id
   }
+  for(std::vector<BoundTrackListener>::iterator it = mTrackListeners.begin();
+       it != mTrackListeners.end(); ++it) {
+    it->mListener->NotifyQueuedChanges(nullptr, // Graph
+                                       0, // Offset TODO(ekr@rtfm.com) fix
+                                       segment);
+  }
 }
 
 
 // Fake_MediaPeriodic
 NS_IMPL_ISUPPORTS(Fake_MediaPeriodic, nsITimerCallback)
 
 NS_IMETHODIMP
 Fake_MediaPeriodic::Notify(nsITimer *timer) {
--- a/media/webrtc/signaling/test/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/test/mediapipeline_unittest.cpp
@@ -187,17 +187,17 @@ class TestAgent {
       WrapRunnable(this, &TestAgent::StopInt));
   }
 
   void Shutdown_s() {
     audio_rtp_transport_.Shutdown();
     audio_rtcp_transport_.Shutdown();
     bundle_transport_.Shutdown();
     if (audio_pipeline_)
-      audio_pipeline_->ShutdownTransport_s();
+      audio_pipeline_->DetachTransport_s();
   }
 
   void Shutdown() {
     if (audio_pipeline_)
       audio_pipeline_->ShutdownMedia_m();
 
     mozilla::SyncRunnable::DispatchToThread(
       test_utils->sts_target(),
@@ -246,16 +246,21 @@ class TestAgent {
 };
 
 class TestAgentSend : public TestAgent {
  public:
   TestAgentSend() : use_bundle_(false) {}
 
   virtual void CreatePipelines_s(bool aIsRtcpMux) {
     audio_ = new Fake_DOMMediaStream(new Fake_AudioStreamSource());
+    audio_->SetHintContents(Fake_DOMMediaStream::HINT_CONTENTS_AUDIO);
+
+    nsTArray<RefPtr<Fake_MediaStreamTrack>> tracks;
+    audio_->GetAudioTracks(tracks);
+    ASSERT_EQ(1U, tracks.Length());
 
     mozilla::MediaConduitErrorCode err =
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
         ConfigureSendMediaCodec(&audio_config_);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
 
     std::string test_pc("PC");
 
@@ -270,20 +275,19 @@ class TestAgentSend : public TestAgent {
       rtp = bundle_transport_.flow_;
       rtcp = nullptr;
     }
 
     audio_pipeline_ = new mozilla::MediaPipelineTransmit(
         test_pc,
         nullptr,
         test_utils->sts_target(),
-        audio_,
+        tracks[0],
         "audio_track_fake_uuid",
         1,
-        false,
         audio_conduit_,
         rtp,
         rtcp,
         nsAutoPtr<MediaPipelineFilter>());
 
     audio_pipeline_->Init();
   }
 
@@ -321,17 +325,17 @@ class TestAgentReceive : public TestAgen
     if (aIsRtcpMux) {
       ASSERT_FALSE(audio_rtcp_transport_.flow_);
     }
 
     audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
         test_pc,
         nullptr,
         test_utils->sts_target(),
-        audio_->GetStream(), "audio_track_fake_uuid", 1, 1,
+        audio_->GetStream()->AsSourceStream(), "audio_track_fake_uuid", 1, 1,
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()),
         audio_rtp_transport_.flow_,
         audio_rtcp_transport_.flow_,
         bundle_filter_,
         false);
 
     audio_pipeline_->Init();
   }