Bug 1494301: Single API for mtransport. r=mjf
authorByron Campen [:bwc] <docfaraday@gmail.com>
Fri, 26 Oct 2018 00:39:07 +0000
changeset 499516 11819394462eb9695db2fb3021c1dec220e52c8f
parent 499515 f3d23a7bcbb6e30a5d7368882fdf1b04498d9bdf
child 499517 81837314ba09b55b0dfe6423b8308e5808bafd32
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmjf
bugs1494301
milestone65.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 1494301: Single API for mtransport. r=mjf Differential Revision: https://phabricator.services.mozilla.com/D7212
media/mtransport/mediapacket.cpp
media/mtransport/mediapacket.h
media/mtransport/nricectx.cpp
media/mtransport/nricemediastream.cpp
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/transport_unittests.cpp
media/mtransport/transportflow.cpp
media/mtransport/transportlayerdtls.cpp
media/mtransport/transportlayerice.cpp
media/mtransport/transportlayerice.h
media/mtransport/transportlayersrtp.cpp
media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/mediapipeline/TransportLayerPacketDumper.cpp
media/webrtc/signaling/src/mediapipeline/TransportLayerPacketDumper.h
media/webrtc/signaling/src/mediapipeline/moz.build
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
media/webrtc/signaling/src/peerconnection/moz.build
netwerk/sctp/datachannel/DataChannel.cpp
netwerk/sctp/datachannel/DataChannel.h
netwerk/sctp/datachannel/moz.build
--- a/media/mtransport/mediapacket.cpp
+++ b/media/mtransport/mediapacket.cpp
@@ -17,10 +17,65 @@ MediaPacket::Copy(const uint8_t* data, s
     capacity = len;
   }
   data_.reset(new uint8_t[capacity]);
   len_ = len;
   capacity_ = capacity;
   memcpy(data_.get(), data, len);
 }
 
+static bool IsRtp(const uint8_t* data, size_t len)
+{
+  if (len < 2)
+    return false;
+
+  // Check if this is a RTCP packet. Logic based on the types listed in
+  // media/webrtc/trunk/src/modules/rtp_rtcp/source/rtp_utility.cc
+
+  // Anything outside this range is RTP.
+  if ((data[1] < 192) || (data[1] > 207))
+    return true;
+
+  if (data[1] == 192) // FIR
+    return false;
+
+  if (data[1] == 193) // NACK, but could also be RTP. This makes us sad
+    return true;      // but it's how webrtc.org behaves.
+
+  if (data[1] == 194)
+    return true;
+
+  if (data[1] == 195) // IJ.
+    return false;
+
+  if ((data[1] > 195) && (data[1] < 200)) // the > 195 is redundant
+    return true;
+
+  if ((data[1] >= 200) && (data[1] <= 207)) // SR, RR, SDES, BYE,
+    return false;                           // APP, RTPFB, PSFB, XR
+
+  MOZ_ASSERT(false); // Not reached, belt and suspenders.
+  return true;
+}
+
+void
+MediaPacket::Categorize()
+{
+  SetType(MediaPacket::UNCLASSIFIED);
+
+  if (!data_ || len_ < 4) {
+    return;
+  }
+
+  if (data_[0] >= 20 && data_[0] <= 63) {
+    // DTLS per RFC 7983
+    SetType(MediaPacket::DTLS);
+  } else if (data_[0] > 127 && data_[0] < 192) {
+    // RTP/RTCP per RFC 7983
+    if (IsRtp(data_.get(), len_)) {
+      SetType(MediaPacket::SRTP);
+    } else {
+      SetType(MediaPacket::SRTCP);
+    }
+  }
+}
 } // namespace mozilla
 
--- a/media/mtransport/mediapacket.h
+++ b/media/mtransport/mediapacket.h
@@ -81,21 +81,26 @@ class MediaPacket {
 
     size_t encrypted_len() const
     {
       return encrypted_len_;
     }
 
     enum Type {
       UNCLASSIFIED,
+      SRTP,
+      SRTCP,
+      DTLS,
       RTP,
       RTCP,
       SCTP
     };
 
+    void Categorize();
+
     void SetType(Type type)
     {
       type_ = type;
     }
 
     Type type() const
     {
       return type_;
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -781,17 +781,16 @@ NrIceStats NrIceCtx::Destroy() {
   ice_handler_vtbl_ = nullptr;
   ice_handler_ = nullptr;
   streams_.clear();
 
   return stats;
 }
 
 NrIceCtx::~NrIceCtx() {
-  Destroy();
 }
 
 void NrIceCtx::destroy_peer_ctx() {
   nr_ice_peer_ctx_destroy(&peer_);
 }
 
 nsresult NrIceCtx::SetControlling(Controlling controlling) {
   if (!ice_controlling_set_) {
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -472,16 +472,20 @@ nsresult NrIceMediaStream::GetCandidateP
 
   return NS_OK;
 }
 
 nsresult NrIceMediaStream::GetDefaultCandidate(
     int component,
     NrIceCandidate* candidate) const {
 
+  if (!stream_) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nr_ice_candidate *cand;
 
   int r = nr_ice_media_stream_get_default_candidate(stream_, component, &cand);
   if (r) {
     if (r == R_NOT_FOUND) {
       MOZ_MTLOG(ML_INFO, "Couldn't get default ICE candidate for '"
                 << name_ << "', no candidates.");
     } else {
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -974,16 +974,17 @@ class IceTestPeer : public sigslot::has_
     std::cerr << name_ << " Shutdown" << std::endl;
     shutting_down_ = true;
     for (auto& controlled_trickle_candidate : controlled_trickle_candidates_) {
       for (auto& cand : controlled_trickle_candidate.second) {
         delete cand;
       }
     }
 
+    ice_ctx_->Destroy();
     ice_ctx_ = nullptr;
 
     if (remote_) {
       remote_->UnsetRemote();
       remote_ = nullptr;
     }
   }
 
--- a/media/mtransport/test/transport_unittests.cpp
+++ b/media/mtransport/test/transport_unittests.cpp
@@ -477,16 +477,17 @@ class TransportTestPeer : public sigslot
 
 
   void DestroyFlow() {
     disconnect_all();
     if (flow_) {
       loopback_->Disconnect();
       flow_ = nullptr;
     }
+    ice_ctx_->Destroy();
     ice_ctx_ = nullptr;
     streams_.clear();
   }
 
   void DisconnectDestroyFlow() {
     loopback_->Disconnect();
     disconnect_all();  // Disconnect from the signals;
      flow_ = nullptr;
--- a/media/mtransport/transportflow.cpp
+++ b/media/mtransport/transportflow.cpp
@@ -7,36 +7,38 @@
 // Original author: ekr@rtfm.com
 #include <deque>
 
 #include "logging.h"
 #include "runnable_utils.h"
 #include "transportflow.h"
 #include "transportlayer.h"
 
+#include "mozilla/DebugOnly.h"
+
 namespace mozilla {
 
 NS_IMPL_ISUPPORTS0(TransportFlow)
 
 // There are some hacks here to allow destruction off of
 // the main thread.
 TransportFlow::~TransportFlow() {
   // Push the destruction onto the STS thread. Note that there
   // is still some possibility that someone is accessing this
   // object simultaneously, but as long as smart pointer discipline
   // is maintained, it shouldn't be possible to access and
   // destroy it simultaneously. The conversion to an nsAutoPtr
   // ensures automatic destruction of the queue at exit of
   // DestroyFinal.
-  if (target_) {
-    nsAutoPtr<std::deque<TransportLayer*>> layers_tmp(layers_.release());
-    RUN_ON_THREAD(target_,
-                  WrapRunnableNM(&TransportFlow::DestroyFinal, layers_tmp),
-                  NS_DISPATCH_NORMAL);
-  }
+  MOZ_RELEASE_ASSERT(target_);
+  nsAutoPtr<std::deque<TransportLayer*>> layers_tmp(layers_.release());
+  DebugOnly<nsresult> rv = target_->Dispatch(
+      WrapRunnableNM(&TransportFlow::DestroyFinal, layers_tmp),
+      NS_DISPATCH_NORMAL);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 void TransportFlow::DestroyFinal(nsAutoPtr<std::deque<TransportLayer *> > layers) {
   ClearLayers(layers.get());
 }
 
 void TransportFlow::ClearLayers(std::deque<TransportLayer *>* layers) {
   while (!layers->empty()) {
--- a/media/mtransport/transportlayerdtls.cpp
+++ b/media/mtransport/transportlayerdtls.cpp
@@ -95,16 +95,17 @@ int32_t TransportLayerNSPRAdapter::Write
   if (!enabled_) {
     MOZ_MTLOG(ML_WARNING, "Writing to disabled transport layer");
     return -1;
   }
 
   MediaPacket packet;
   // Copies. Oh well.
   packet.Copy(static_cast<const uint8_t*>(buf), static_cast<size_t>(length));
+  packet.SetType(MediaPacket::DTLS);
 
   TransportResult r = output_->SendPacket(packet);
   if (r >= 0) {
     return r;
   }
 
   if (r == TE_WOULDBLOCK) {
     PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
@@ -1012,18 +1013,17 @@ void TransportLayerDtls::PacketReceived(
     return;
   }
 
   if (!packet.data()) {
     // Something ate this, probably the SRTP layer
     return;
   }
 
-  // not DTLS per RFC 7983
-  if (packet.data()[0] < 20 || packet.data()[0] > 63) {
+  if (packet.type() != MediaPacket::DTLS) {
     return;
   }
 
   nspr_io_adapter_->PacketReceived(packet);
   GetDecryptedPackets();
 }
 
 void
@@ -1043,16 +1043,17 @@ TransportLayerDtls::GetDecryptedPackets(
       // Can we peek to get a better idea of the actual size?
       static const size_t kBufferSize = 9216;
       auto buffer = MakeUnique<uint8_t[]>(kBufferSize);
       rv = PR_Recv(ssl_fd_.get(), buffer.get(), kBufferSize, 0, PR_INTERVAL_NO_WAIT);
       if (rv > 0) {
         // We have data
         MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS");
         MediaPacket packet;
+        packet.SetType(MediaPacket::SCTP);
         packet.Take(std::move(buffer), static_cast<size_t>(rv));
         SignalPacketReceived(this, packet);
       } else if (rv == 0) {
         TL_SET_STATE(TS_CLOSED);
       } else {
         int32_t err = PR_GetError();
 
         if (err == PR_WOULD_BLOCK_ERROR) {
--- a/media/mtransport/transportlayerice.cpp
+++ b/media/mtransport/transportlayerice.cpp
@@ -120,16 +120,17 @@ void TransportLayerIce::PostSetup() {
                                         &TransportLayerIce::IcePacketReceived);
   if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
     TL_SET_STATE(TS_OPEN);
   }
 }
 
 TransportResult TransportLayerIce::SendPacket(MediaPacket& packet) {
   CheckThread();
+  SignalPacketSending(this, packet);
   nsresult res = stream_->SendPacket(component_,
                                      packet.data(),
                                      packet.len());
 
   if (!NS_SUCCEEDED(res)) {
     return (res == NS_BASE_STREAM_WOULD_BLOCK) ?
         TE_WOULDBLOCK : TE_ERROR;
   }
@@ -177,12 +178,14 @@ void TransportLayerIce::IcePacketReceive
 
   MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << stream->name() << ","
     << component << "," << len << ")");
   // Might be useful to allow MediaPacket to borrow a buffer (ie; not take
   // ownership, but copy it if the MediaPacket is moved). This could be a
   // footgun though with MediaPackets that end up on the heap.
   MediaPacket packet;
   packet.Copy(data, len);
+  packet.Categorize();
+
   SignalPacketReceived(this, packet);
 }
 
 }  // close namespace
--- a/media/mtransport/transportlayerice.h
+++ b/media/mtransport/transportlayerice.h
@@ -45,16 +45,19 @@ class TransportLayerIce : public Transpo
 
   // Slots for ICE
   void IceCandidate(NrIceMediaStream *stream, const std::string&);
   void IceReady(NrIceMediaStream *stream);
   void IceFailed(NrIceMediaStream *stream);
   void IcePacketReceived(NrIceMediaStream *stream, int component,
                          const unsigned char *data, int len);
 
+  // Useful for capturing encrypted packets
+  sigslot::signal2<TransportLayer*, MediaPacket&> SignalPacketSending;
+
   TRANSPORT_LAYER_ID("ice")
 
  private:
   DISALLOW_COPY_ASSIGN(TransportLayerIce);
   void PostSetup();
 
   RefPtr<NrIceMediaStream> stream_;
   int component_;
--- a/media/mtransport/transportlayersrtp.cpp
+++ b/media/mtransport/transportlayersrtp.cpp
@@ -51,68 +51,36 @@ TransportLayerSrtp::Setup()
   }
 
   // downward_ is the TransportLayerIce
   downward_->SignalPacketReceived.connect(this, &TransportLayerSrtp::PacketReceived);
 
   return true;
 }
 
-static bool IsRtp(const unsigned char* data, size_t len)
-{
-  if (len < 2)
-    return false;
-
-  // Check if this is a RTCP packet. Logic based on the types listed in
-  // media/webrtc/trunk/src/modules/rtp_rtcp/source/rtp_utility.cc
-
-  // Anything outside this range is RTP.
-  if ((data[1] < 192) || (data[1] > 207))
-    return true;
-
-  if (data[1] == 192) // FIR
-    return false;
-
-  if (data[1] == 193) // NACK, but could also be RTP. This makes us sad
-    return true;      // but it's how webrtc.org behaves.
-
-  if (data[1] == 194)
-    return true;
-
-  if (data[1] == 195) // IJ.
-    return false;
-
-  if ((data[1] > 195) && (data[1] < 200)) // the > 195 is redundant
-    return true;
-
-  if ((data[1] >= 200) && (data[1] <= 207)) // SR, RR, SDES, BYE,
-    return false;                           // APP, RTPFB, PSFB, XR
-
-  MOZ_ASSERT(false); // Not reached, belt and suspenders.
-  return true;
-}
-
 TransportResult
 TransportLayerSrtp::SendPacket(MediaPacket& packet)
 {
   if (packet.len() < 4) {
     MOZ_ASSERT(false);
     return TE_ERROR;
   }
 
   MOZ_ASSERT(packet.capacity() - packet.len() >= SRTP_MAX_EXPANSION);
 
   int out_len;
   nsresult res;
   switch (packet.type()) {
     case MediaPacket::RTP:
       res = mSendSrtp->ProtectRtp(packet.data(), packet.len(), packet.capacity(), &out_len);
+      packet.SetType(MediaPacket::SRTP);
       break;
     case MediaPacket::RTCP:
       res = mSendSrtp->ProtectRtcp(packet.data(), packet.len(), packet.capacity(), &out_len);
+      packet.SetType(MediaPacket::SRTCP);
       break;
     default:
       MOZ_CRASH("SRTP layer asked to send packet that is neither RTP or RTCP");
   }
 
   if (NS_FAILED(res)) {
     MOZ_MTLOG(ML_ERROR,
                 "Error protecting "
@@ -222,31 +190,27 @@ TransportLayerSrtp::PacketReceived(Trans
     return;
   }
 
   if (!packet.data()) {
     // Something ate this, probably the DTLS layer
     return;
   }
 
-  if (packet.len() < 4) {
-    return;
-  }
-
-  // not RTP/RTCP per RFC 7983
-  if (packet.data()[0] <= 127 || packet.data()[0] >= 192) {
+  if (packet.type() != MediaPacket::SRTP &&
+      packet.type() != MediaPacket::SRTCP) {
     return;
   }
 
   // We want to keep the encrypted packet around for packet dumping
   packet.CopyDataToEncrypted();
   int outLen;
   nsresult res;
 
-  if (IsRtp(packet.data(), packet.len())) {
+  if (packet.type() == MediaPacket::SRTP) {
     packet.SetType(MediaPacket::RTP);
     res = mRecvSrtp->UnprotectRtp(packet.data(), packet.len(), packet.len(), &outLen);
   } else {
     packet.SetType(MediaPacket::RTCP);
     res = mRecvSrtp->UnprotectRtcp(packet.data(), packet.len(), packet.len(), &outLen);
   }
 
   if (NS_SUCCEEDED(res)) {
--- a/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/gtest/mediapipeline_unittest.cpp
@@ -21,16 +21,17 @@
 #include "MediaStreamTrack.h"
 #include "transportflow.h"
 #include "transportlayerloopback.h"
 #include "transportlayerdtls.h"
 #include "transportlayersrtp.h"
 #include "mozilla/SyncRunnable.h"
 #include "mtransport_test_utils.h"
 #include "SharedBuffer.h"
+#include "MediaTransportHandler.h"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 
 using namespace mozilla;
 MOZ_MTLOG_MODULE("mediapipeline")
 
 static MtransportTestUtils *test_utils;
@@ -159,112 +160,127 @@ public:
                            PRINCIPAL_HANDLE_NONE);
 
       for (auto& listener: mst->mListeners) {
         listener->NotifyQueuedChanges(nullptr, 0, segment);
       }
     }
 };
 
-class TransportInfo {
+class LoopbackTransport : public MediaTransportBase {
  public:
-  TransportInfo() :
-    flow_(nullptr),
-    loopback_(nullptr) {}
+  LoopbackTransport()
+  {
+    SetState("mux", TransportLayer::TS_INIT, false);
+    SetState("mux", TransportLayer::TS_INIT, true);
+    SetState("non-mux", TransportLayer::TS_INIT, false);
+    SetState("non-mux", TransportLayer::TS_INIT, true);
+  }
 
-  static void InitAndConnect(TransportInfo &client, TransportInfo &server) {
-    client.Init(true);
-    server.Init(false);
+  static void InitAndConnect(LoopbackTransport &client, LoopbackTransport &server) {
     client.Connect(&server);
     server.Connect(&client);
   }
 
-  void Init(bool client) {
-    UniquePtr<TransportLayerLoopback> loopback(new TransportLayerLoopback);
-    UniquePtr<TransportLayerDtls> dtls(new TransportLayerDtls);
-    UniquePtr<TransportLayerSrtp> srtp(new TransportLayerSrtp(*dtls));
-
-    std::vector<uint16_t> ciphers;
-    ciphers.push_back(kDtlsSrtpAeadAes256Gcm);
-    dtls->SetSrtpCiphers(ciphers);
-    dtls->SetIdentity(DtlsIdentity::Generate());
-    dtls->SetRole(client ? TransportLayerDtls::CLIENT :
-      TransportLayerDtls::SERVER);
-    dtls->SetVerificationAllowAll();
-
-    ASSERT_EQ(NS_OK, loopback->Init());
-    ASSERT_EQ(NS_OK, dtls->Init());
-    ASSERT_EQ(NS_OK, srtp->Init());
-
-    dtls->Chain(loopback.get());
-    srtp->Chain(loopback.get());
-
-    flow_ = new TransportFlow();
-    loopback_ = loopback.release();
-    flow_->PushLayer(loopback_);
-    flow_->PushLayer(dtls.release());
-    flow_->PushLayer(srtp.release());
-  }
-
-  void Connect(TransportInfo* peer) {
-    MOZ_ASSERT(loopback_);
-    MOZ_ASSERT(peer->loopback_);
-
-    loopback_->Connect(peer->loopback_);
+  void Connect(LoopbackTransport* peer) {
+    peer_ = peer;
   }
 
   void Shutdown() {
-    if (loopback_) {
-      loopback_->Disconnect();
-    }
-    loopback_ = nullptr;
-    flow_ = nullptr;
+    peer_ = nullptr;
+  }
+
+  nsresult SendPacket(const std::string& aTransportId,
+                      MediaPacket& aPacket) override
+  {
+    peer_->SignalPacketReceived(aTransportId, aPacket);
+    return NS_OK;
   }
 
-  RefPtr<TransportFlow> flow_;
-  TransportLayerLoopback *loopback_;
+  TransportLayer::State GetState(const std::string& aTransportId,
+                                 bool aRtcp) const override
+  {
+    if (aRtcp) {
+      auto it = mRtcpStates.find(aTransportId);
+      if (it != mRtcpStates.end()) {
+        return it->second;
+      }
+    } else {
+      auto it = mRtpStates.find(aTransportId);
+      if (it != mRtpStates.end()) {
+        return it->second;
+      }
+    }
+
+    return TransportLayer::TS_NONE;
+  }
+
+  void SetState(const std::string& aTransportId,
+                TransportLayer::State aState,
+                bool aRtcp)
+  {
+    if (aRtcp) {
+      mRtcpStates[aTransportId] = aState;
+      SignalRtcpStateChange(aTransportId, aState);
+    } else {
+      mRtpStates[aTransportId] = aState;
+      SignalStateChange(aTransportId, aState);
+    }
+  }
+
+private:
+  RefPtr<MediaTransportBase> peer_;
+  std::map<std::string, TransportLayer::State> mRtpStates;
+  std::map<std::string, TransportLayer::State> mRtcpStates;
 };
 
 class TestAgent {
  public:
   TestAgent() :
       audio_config_(109, "opus", 48000, 960, 2, 64000, false),
       audio_conduit_(mozilla::AudioSessionConduit::Create()),
       audio_pipeline_(),
-      use_bundle_(false) {
+      transport_(new LoopbackTransport) {
   }
 
-  static void ConnectRtp(TestAgent *client, TestAgent *server) {
-    TransportInfo::InitAndConnect(client->audio_rtp_transport_,
-                                  server->audio_rtp_transport_);
+  static void Connect(TestAgent *client, TestAgent *server) {
+    LoopbackTransport::InitAndConnect(*client->transport_,
+                                      *server->transport_);
   }
 
-  static void ConnectRtcp(TestAgent *client, TestAgent *server) {
-    TransportInfo::InitAndConnect(client->audio_rtcp_transport_,
-                                  server->audio_rtcp_transport_);
+  virtual void CreatePipeline(const std::string& aTransportId) = 0;
+
+  void SetState(const std::string& aTransportId,
+                TransportLayer::State aState,
+                bool aRtcp)
+  {
+    mozilla::SyncRunnable::DispatchToThread(
+        test_utils->sts_target(),
+        WrapRunnable(transport_,
+          &LoopbackTransport::SetState, aTransportId, aState, aRtcp));
   }
 
-  static void ConnectBundle(TestAgent *client, TestAgent *server) {
-    TransportInfo::InitAndConnect(client->bundle_transport_,
-                                  server->bundle_transport_);
+  void UpdateTransport(const std::string& aTransportId,
+                       nsAutoPtr<MediaPipelineFilter> aFilter)
+  {
+    mozilla::SyncRunnable::DispatchToThread(
+        test_utils->sts_target(),
+        WrapRunnable(audio_pipeline_,
+          &MediaPipeline::UpdateTransport_s, aTransportId, aFilter));
   }
 
-  virtual void CreatePipeline(bool aIsRtcpMux) = 0;
-
   void Stop() {
     MOZ_MTLOG(ML_DEBUG, "Stopping");
 
     if (audio_pipeline_)
       audio_pipeline_->Stop();
   }
 
   void Shutdown_s() {
-    audio_rtp_transport_.Shutdown();
-    audio_rtcp_transport_.Shutdown();
-    bundle_transport_.Shutdown();
+    transport_->Shutdown();
   }
 
   void Shutdown() {
     if (audio_pipeline_)
       audio_pipeline_->Shutdown_m();
     if (audio_stream_track_)
       audio_stream_track_->Stop();
 
@@ -296,77 +312,58 @@ class TestAgent {
   int GetAudioRtcpCountSent() {
     return audio_pipeline_->RtcpPacketsSent();
   }
 
   int GetAudioRtcpCountReceived() {
     return audio_pipeline_->RtcpPacketsReceived();
   }
 
-
-  void SetUsingBundle(bool use_bundle) {
-    use_bundle_ = use_bundle;
-  }
-
  protected:
   mozilla::AudioCodecConfig audio_config_;
   RefPtr<mozilla::MediaSessionConduit> audio_conduit_;
   RefPtr<FakeAudioStreamTrack> audio_stream_track_;
   // TODO(bcampen@mozilla.com): Right now this does not let us test RTCP in
   // both directions; only the sender's RTCP is sent, but the receiver should
   // be sending it too.
   RefPtr<mozilla::MediaPipeline> audio_pipeline_;
-  TransportInfo audio_rtp_transport_;
-  TransportInfo audio_rtcp_transport_;
-  TransportInfo bundle_transport_;
-  bool use_bundle_;
+  RefPtr<LoopbackTransport> transport_;
 };
 
 class TestAgentSend : public TestAgent {
  public:
   TestAgentSend() {
     mozilla::MediaConduitErrorCode err =
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
         ConfigureSendMediaCodec(&audio_config_);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
 
     audio_stream_track_ = new FakeAudioStreamTrack();
   }
 
-  virtual void CreatePipeline(bool aIsRtcpMux) {
+  virtual void CreatePipeline(const std::string& aTransportId) {
 
     std::string test_pc;
 
-    if (aIsRtcpMux) {
-      ASSERT_FALSE(audio_rtcp_transport_.flow_);
-    }
-
     RefPtr<MediaPipelineTransmit> audio_pipeline =
       new mozilla::MediaPipelineTransmit(
         test_pc,
+        transport_,
         nullptr,
         test_utils->sts_target(),
         false,
         audio_conduit_);
 
     audio_pipeline->SetTrack(audio_stream_track_.get());
     audio_pipeline->Start();
 
     audio_pipeline_ = audio_pipeline;
 
-    RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
-    RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
-
-    if (use_bundle_) {
-      rtp = bundle_transport_.flow_;
-      rtcp = nullptr;
-    }
-
-    audio_pipeline_->UpdateTransport_m(
-        rtp, rtcp, nsAutoPtr<MediaPipelineFilter>(nullptr));
+    audio_pipeline_->UpdateTransport_m(aTransportId,
+                                       nsAutoPtr<MediaPipelineFilter>(nullptr));
   }
 };
 
 
 class TestAgentReceive : public TestAgent {
  public:
 
   TestAgentReceive() {
@@ -374,52 +371,39 @@ class TestAgentReceive : public TestAgen
     codecs.push_back(&audio_config_);
 
     mozilla::MediaConduitErrorCode err =
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get())->
         ConfigureRecvMediaCodecs(codecs);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
   }
 
-  virtual void CreatePipeline(bool aIsRtcpMux) {
+  virtual void CreatePipeline(const std::string& aTransportId) {
     std::string test_pc;
 
-    if (aIsRtcpMux) {
-      ASSERT_FALSE(audio_rtcp_transport_.flow_);
-    }
-
     audio_pipeline_ = new mozilla::MediaPipelineReceiveAudio(
         test_pc,
+        transport_,
         nullptr,
         test_utils->sts_target(),
         static_cast<mozilla::AudioSessionConduit *>(audio_conduit_.get()),
         nullptr);
 
     audio_pipeline_->Start();
 
-    RefPtr<TransportFlow> rtp(audio_rtp_transport_.flow_);
-    RefPtr<TransportFlow> rtcp(audio_rtcp_transport_.flow_);
-
-    if (use_bundle_) {
-      rtp = bundle_transport_.flow_;
-      rtcp = nullptr;
-    }
-
-    audio_pipeline_->UpdateTransport_m(rtp, rtcp, bundle_filter_);
+    audio_pipeline_->UpdateTransport_m(aTransportId, bundle_filter_);
   }
 
   void SetBundleFilter(nsAutoPtr<MediaPipelineFilter> filter) {
     bundle_filter_ = filter;
   }
 
-  void UpdateFilter_s(
+  void UpdateTransport_s(const std::string& aTransportId,
       nsAutoPtr<MediaPipelineFilter> filter) {
-    audio_pipeline_->UpdateTransport_s(audio_rtp_transport_.flow_,
-                                       audio_rtcp_transport_.flow_,
-                                       filter);
+    audio_pipeline_->UpdateTransport_s(aTransportId, filter);
   }
 
  private:
   nsAutoPtr<MediaPipelineFilter> bundle_filter_;
 };
 
 
 class MediaPipelineTest : public ::testing::Test {
@@ -431,34 +415,20 @@ class MediaPipelineTest : public ::testi
 
   static void SetUpTestCase() {
     test_utils = new MtransportTestUtils();
     NSS_NoDB_Init(nullptr);
     NSS_SetDomesticPolicy();
   }
 
   // Setup transport.
-  void InitTransports(bool aIsRtcpMux) {
-    // RTP, p1_ is server, p2_ is client
+  void InitTransports() {
     mozilla::SyncRunnable::DispatchToThread(
       test_utils->sts_target(),
-      WrapRunnableNM(&TestAgent::ConnectRtp, &p2_, &p1_));
-
-    // Create RTCP flows separately if we are not muxing them.
-    if(!aIsRtcpMux) {
-      // RTCP, p1_ is server, p2_ is client
-      mozilla::SyncRunnable::DispatchToThread(
-        test_utils->sts_target(),
-        WrapRunnableNM(&TestAgent::ConnectRtcp, &p2_, &p1_));
-    }
-
-    // BUNDLE, p1_ is server, p2_ is client
-    mozilla::SyncRunnable::DispatchToThread(
-      test_utils->sts_target(),
-      WrapRunnableNM(&TestAgent::ConnectBundle, &p2_, &p1_));
+      WrapRunnableNM(&TestAgent::Connect, &p2_, &p1_));
   }
 
   // Verify RTP and RTCP
   void TestAudioSend(bool aIsRtcpMux,
                      nsAutoPtr<MediaPipelineFilter> initialFilter =
                         nsAutoPtr<MediaPipelineFilter>(nullptr),
                      nsAutoPtr<MediaPipelineFilter> refinedFilter =
                         nsAutoPtr<MediaPipelineFilter>(nullptr),
@@ -468,37 +438,51 @@ class MediaPipelineTest : public ::testi
     bool bundle = !!(initialFilter);
     // We do not support testing bundle without rtcp mux, since that doesn't
     // make any sense.
     ASSERT_FALSE(!aIsRtcpMux && bundle);
 
     p2_.SetBundleFilter(initialFilter);
 
     // Setup transport flows
-    InitTransports(aIsRtcpMux);
+    InitTransports();
+
+    std::string transportId = aIsRtcpMux ? "mux" : "non-mux";
+    p1_.CreatePipeline(transportId);
+    p2_.CreatePipeline(transportId);
 
-    p1_.CreatePipeline(aIsRtcpMux);
-    p2_.CreatePipeline(aIsRtcpMux);
+    // Set state of transports to CONNECTING. MediaPipeline doesn't really care
+    // about this transition, but we're trying to simluate what happens in a
+    // real case.
+    p1_.SetState(transportId, TransportLayer::TS_CONNECTING, false);
+    p1_.SetState(transportId, TransportLayer::TS_CONNECTING, true);
+    p2_.SetState(transportId, TransportLayer::TS_CONNECTING, false);
+    p2_.SetState(transportId, TransportLayer::TS_CONNECTING, true);
+
+    PR_Sleep(10);
+
+    // Set state of transports to OPEN (ie; connected). This should result in
+    // media flowing.
+    p1_.SetState(transportId, TransportLayer::TS_OPEN, false);
+    p1_.SetState(transportId, TransportLayer::TS_OPEN, true);
+    p2_.SetState(transportId, TransportLayer::TS_OPEN, false);
+    p2_.SetState(transportId, TransportLayer::TS_OPEN, true);
 
     if (bundle) {
       PR_Sleep(ms_until_filter_update);
 
       // Leaving refinedFilter not set implies we want to just update with
       // the other side's SSRC
       if (!refinedFilter) {
         refinedFilter = new MediaPipelineFilter;
         // Might not be safe, strictly speaking.
         refinedFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
       }
 
-      mozilla::SyncRunnable::DispatchToThread(
-          test_utils->sts_target(),
-          WrapRunnable(&p2_,
-                       &TestAgentReceive::UpdateFilter_s,
-                       refinedFilter));
+      p2_.UpdateTransport(transportId, refinedFilter);
     }
 
     // wait for some RTP/RTCP tx and rx to happen
     PR_Sleep(ms_of_traffic_after_answer);
 
     p1_.Stop();
     p2_.Stop();
 
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -39,23 +39,18 @@
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/ImageUtils.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/gfx/Types.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
-#include "nspr.h"
 #include "runnable_utils.h"
-#include "srtp.h"
-#include "transportflow.h"
-#include "transportlayer.h"
-#include "transportlayerdtls.h"
-#include "transportlayerice.h"
+#include "signaling/src/peerconnection/MediaTransportHandler.h"
 #include "Tracing.h"
 
 #include "webrtc/base/bind.h"
 #include "webrtc/base/keep_ref_until_done.h"
 #include "webrtc/common_video/include/i420_buffer_pool.h"
 #include "webrtc/common_video/include/video_frame_buffer.h"
 #include "webrtc/video_frame.h"
 
@@ -597,25 +592,25 @@ protected:
   // A buffer to hold a single packet of audio.
   UniquePtr<int16_t[]> mPacket;
   nsTArray<int16_t> mInterleavedAudio;
   AlignedShortBuffer mOutputAudio;
   UniquePtr<AudioConverter> mAudioConverter;
 };
 
 MediaPipeline::MediaPipeline(const std::string& aPc,
+                             MediaTransportBase* aTransportHandler,
                              DirectionType aDirection,
                              nsCOMPtr<nsIEventTarget> aMainThread,
                              nsCOMPtr<nsIEventTarget> aStsThread,
                              RefPtr<MediaSessionConduit> aConduit)
   : mDirection(aDirection)
   , mLevel(0)
+  , mTransportHandler(aTransportHandler)
   , mConduit(aConduit)
-  , mRtp(nullptr, RTP)
-  , mRtcp(nullptr, RTCP)
   , mMainThread(aMainThread)
   , mStsThread(aStsThread)
   , mTransport(new PipelineTransport(aStsThread))
   , mRtpPacketsSent(0)
   , mRtcpPacketsSent(0)
   , mRtpPacketsReceived(0)
   , mRtcpPacketsReceived(0)
   , mRtpBytesSent(0)
@@ -655,84 +650,59 @@ void
 MediaPipeline::DetachTransport_s()
 {
   ASSERT_ON_THREAD(mStsThread);
 
   MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
           ("%s in %s", mDescription.c_str(), __FUNCTION__));
 
   disconnect_all();
+  mRtpState = TransportLayer::TS_NONE;
+  mRtcpState = TransportLayer::TS_NONE;
+  mTransportId.clear();
   mTransport->Detach();
-  mRtp.Detach();
-  mRtcp.Detach();
 
   // Make sure any cycles are broken
   mPacketDumper = nullptr;
 }
 
-nsresult
-MediaPipeline::AttachTransport_s()
-{
-  ASSERT_ON_THREAD(mStsThread);
-  nsresult res;
-  MOZ_ASSERT(mRtp.mTransport);
-  MOZ_ASSERT(mRtcp.mTransport);
-  res = ConnectTransport_s(mRtp);
-  if (NS_FAILED(res)) {
-    return res;
-  }
-
-  if (mRtcp.mTransport != mRtp.mTransport) {
-    res = ConnectTransport_s(mRtcp);
-    if (NS_FAILED(res)) {
-      return res;
-    }
-  }
-
-  mTransport->Attach(this);
-
-  return NS_OK;
-}
-
 void
-MediaPipeline::UpdateTransport_m(RefPtr<TransportFlow> aRtpTransport,
-                                 RefPtr<TransportFlow> aRtcpTransport,
+MediaPipeline::UpdateTransport_m(const std::string& aTransportId,
                                  nsAutoPtr<MediaPipelineFilter> aFilter)
 {
   RUN_ON_THREAD(mStsThread,
                 WrapRunnable(RefPtr<MediaPipeline>(this),
                              &MediaPipeline::UpdateTransport_s,
-                             aRtpTransport,
-                             aRtcpTransport,
+                             aTransportId,
                              aFilter),
                 NS_DISPATCH_NORMAL);
 }
 
 void
-MediaPipeline::UpdateTransport_s(RefPtr<TransportFlow> aRtpTransport,
-                                 RefPtr<TransportFlow> aRtcpTransport,
+MediaPipeline::UpdateTransport_s(const std::string& aTransportId,
                                  nsAutoPtr<MediaPipelineFilter> aFilter)
 {
-  bool rtcp_mux = false;
-  if (!aRtcpTransport) {
-    aRtcpTransport = aRtpTransport;
-    rtcp_mux = true;
+  ASSERT_ON_THREAD(mStsThread);
+  if (!mSignalsConnected) {
+    mTransportHandler->SignalStateChange.connect(
+        this, &MediaPipeline::RtpStateChange);
+    mTransportHandler->SignalRtcpStateChange.connect(
+        this, &MediaPipeline::RtcpStateChange);
+    mTransportHandler->SignalEncryptedSending.connect(
+        this, &MediaPipeline::EncryptedPacketSending);
+    mTransportHandler->SignalPacketReceived.connect(
+        this, &MediaPipeline::PacketReceived);
+    mSignalsConnected = true;
   }
 
-  if ((aRtpTransport != mRtp.mTransport) ||
-      (aRtcpTransport != mRtcp.mTransport)) {
-    disconnect_all();
-    mTransport->Detach();
-    mRtp.Detach();
-    mRtcp.Detach();
-    if (aRtpTransport && aRtcpTransport) {
-      mRtp = TransportInfo(aRtpTransport, rtcp_mux ? MUX : RTP);
-      mRtcp = TransportInfo(aRtcpTransport, rtcp_mux ? MUX : RTCP);
-      AttachTransport_s();
-    }
+  if (aTransportId != mTransportId) {
+    mTransportId = aTransportId;
+    mRtpState = mTransportHandler->GetState(mTransportId, false);
+    mRtcpState = mTransportHandler->GetState(mTransportId, true);
+    CheckTransportStates();
   }
 
   if (mFilter && aFilter) {
     // Use the new filter, but don't forget any remote SSRCs that we've learned
     // by receiving traffic.
     mFilter->Update(*aFilter);
   } else {
     mFilter = aFilter;
@@ -785,204 +755,159 @@ MediaPipeline::GetContributingSourceStat
       RTCRTPContributingSourceStats stats;
       info.second.GetWebidlInstance(stats, aInboundRtpStreamId);
       aArr.AppendElement(stats, fallible);
     }
   }
 }
 
 void
-MediaPipeline::StateChange(TransportLayer* aLayer, TransportLayer::State aState)
-{
-  TransportInfo* info = GetTransportInfo_s(aLayer);
-  MOZ_ASSERT(info);
-
-  if (aState == TransportLayer::TS_OPEN) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Info, ("Flow is ready"));
-    TransportReady_s(*info);
-  } else if (aState == TransportLayer::TS_CLOSED ||
-             aState == TransportLayer::TS_ERROR) {
-    TransportFailed_s(*info);
-  }
-}
-
-static bool
-MakeRtpTypeToStringArray(const char** aArray)
+MediaPipeline::RtpStateChange(const std::string& aTransportId,
+                              TransportLayer::State aState)
 {
-  static const char* RTP_str = "RTP";
-  static const char* RTCP_str = "RTCP";
-  static const char* MUX_str = "RTP/RTCP mux";
-  aArray[MediaPipeline::RTP] = RTP_str;
-  aArray[MediaPipeline::RTCP] = RTCP_str;
-  aArray[MediaPipeline::MUX] = MUX_str;
-  return true;
-}
-
-static const char*
-ToString(MediaPipeline::RtpType type)
-{
-  static const char* array[(int)MediaPipeline::MAX_RTP_TYPE] = { nullptr };
-  // Dummy variable to cause init to happen only on first call
-  static bool dummy = MakeRtpTypeToStringArray(array);
-  (void)dummy;
-  return array[type];
+  if (mTransportId != aTransportId) {
+    return;
+  }
+  mRtpState = aState;
+  CheckTransportStates();
 }
 
-nsresult
-MediaPipeline::TransportReady_s(TransportInfo& aInfo)
+void
+MediaPipeline::RtcpStateChange(const std::string& aTransportId,
+                              TransportLayer::State aState)
 {
-  // TODO(ekr@rtfm.com): implement some kind of notification on
-  // failure. bug 852665.
-  if (aInfo.mState != StateType::MP_CONNECTING) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-            ("Transport ready for flow in wrong state:%s :%s",
-             mDescription.c_str(),
-             ToString(aInfo.mType)));
-    return NS_ERROR_FAILURE;
+  if (mTransportId != aTransportId) {
+    return;
   }
-
-  MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-          ("Transport ready for pipeline %p flow %s: %s",
-           this,
-           mDescription.c_str(),
-            ToString(aInfo.mType)));
-
-  if (mDirection == DirectionType::RECEIVE) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-            ("Listening for %s packets received on %p",
-             ToString(aInfo.mType),
-             aInfo.mSrtp));
-
-    aInfo.mSrtp->SignalPacketReceived.connect(
-        this, &MediaPipeline::PacketReceived);
-  }
-
-  aInfo.mState = StateType::MP_OPEN;
-  UpdateRtcpMuxState(aInfo);
-  return NS_OK;
+  mRtcpState = aState;
+  CheckTransportStates();
 }
 
-nsresult
-MediaPipeline::TransportFailed_s(TransportInfo& aInfo)
+void
+MediaPipeline::CheckTransportStates()
 {
   ASSERT_ON_THREAD(mStsThread);
 
-  aInfo.mState = StateType::MP_CLOSED;
-  UpdateRtcpMuxState(aInfo);
-
-  MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-          ("Transport closed for flow %s", ToString(aInfo.mType)));
-
-  NS_WARNING(
-    "MediaPipeline Transport failed. This is not properly cleaned up yet");
+  if (mRtpState == TransportLayer::TS_CLOSED ||
+      mRtpState == TransportLayer::TS_ERROR ||
+      mRtcpState == TransportLayer::TS_CLOSED ||
+      mRtcpState == TransportLayer::TS_ERROR) {
+    MOZ_LOG(gMediaPipelineLog, LogLevel::Warning,
+            ("RTP Transport failed for pipeline %p flow %s",
+             this,
+             mDescription.c_str()));
 
-  // TODO(ekr@rtfm.com): SECURITY: Figure out how to clean up if the
-  // connection was good and now it is bad.
-  // TODO(ekr@rtfm.com): Report up so that the PC knows we
-  // have experienced an error.
-
-  return NS_OK;
-}
+    NS_WARNING(
+      "MediaPipeline Transport failed. This is not properly cleaned up yet");
+    // TODO(ekr@rtfm.com): SECURITY: Figure out how to clean up if the
+    // connection was good and now it is bad.
+    // TODO(ekr@rtfm.com): Report up so that the PC knows we
+    // have experienced an error.
+    mTransport->Detach();
+    return;
+  }
 
-void
-MediaPipeline::UpdateRtcpMuxState(TransportInfo& aInfo)
-{
-  if (aInfo.mType == MUX) {
-    if (aInfo.mTransport == mRtcp.mTransport) {
-      mRtcp.mState = aInfo.mState;
-    }
+  if (mRtpState == TransportLayer::TS_OPEN) {
+    MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+            ("RTP Transport ready for pipeline %p flow %s",
+             this,
+             mDescription.c_str()));
+  }
+
+  if (mRtcpState == TransportLayer::TS_OPEN) {
+    MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+            ("RTCP Transport ready for pipeline %p flow %s",
+             this,
+             mDescription.c_str()));
+  }
+
+  if (mRtpState == TransportLayer::TS_OPEN && mRtcpState == mRtpState) {
+    mTransport->Attach(this);
+    TransportReady_s();
   }
 }
 
 nsresult
-MediaPipeline::SendPacket(TransportLayer* aLayer, MediaPacket& packet)
+MediaPipeline::SendPacket(MediaPacket& packet)
 {
   ASSERT_ON_THREAD(mStsThread);
-
-  int len = packet.len();
-  TransportResult res = aLayer->SendPacket(packet);
+  MOZ_ASSERT(mRtpState == TransportLayer::TS_OPEN);
+  MOZ_ASSERT(!mTransportId.empty());
+  nsresult rv = mTransportHandler->SendPacket(mTransportId, packet);
 
-  if (res != len) {
-    // Ignore blocking indications
-    if (res == TE_WOULDBLOCK)
-      return NS_OK;
-
+  if (NS_FAILED(rv)) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
             ("Failed write on stream %s", mDescription.c_str()));
     return NS_BASE_STREAM_CLOSED;
   }
 
   return NS_OK;
 }
 
 void
 MediaPipeline::IncrementRtpPacketsSent(int32_t aBytes)
 {
   ++mRtpPacketsSent;
   mRtpBytesSent += aBytes;
 
   if (!(mRtpPacketsSent % 100)) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-            ("RTP sent packet count for %s Pipeline %p Flow: %p: %u (%" PRId64
+            ("RTP sent packet count for %s Pipeline %p: %u (%" PRId64
              " bytes)",
              mDescription.c_str(),
              this,
-             static_cast<void*>(mRtp.mTransport),
              mRtpPacketsSent,
              mRtpBytesSent));
   }
 }
 
 void
 MediaPipeline::IncrementRtcpPacketsSent()
 {
   ++mRtcpPacketsSent;
   if (!(mRtcpPacketsSent % 100)) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-            ("RTCP sent packet count for %s Pipeline %p Flow: %p: %u",
+            ("RTCP sent packet count for %s Pipeline %p: %u",
              mDescription.c_str(),
              this,
-             static_cast<void*>(mRtp.mTransport),
              mRtcpPacketsSent));
   }
 }
 
 void
 MediaPipeline::IncrementRtpPacketsReceived(int32_t aBytes)
 {
   ++mRtpPacketsReceived;
   mRtpBytesReceived += aBytes;
   if (!(mRtpPacketsReceived % 100)) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-            ("RTP received packet count for %s Pipeline %p Flow: %p: %u (%"
+            ("RTP received packet count for %s Pipeline %p: %u (%"
              PRId64 " bytes)",
              mDescription.c_str(),
              this,
-             static_cast<void*>(mRtp.mTransport),
              mRtpPacketsReceived,
              mRtpBytesReceived));
   }
 }
 
 void
 MediaPipeline::IncrementRtcpPacketsReceived()
 {
   ++mRtcpPacketsReceived;
   if (!(mRtcpPacketsReceived % 100)) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
-            ("RTCP received packet count for %s Pipeline %p Flow: %p: %u",
+            ("RTCP received packet count for %s Pipeline %p: %u",
              mDescription.c_str(),
              this,
-             static_cast<void*>(mRtp.mTransport),
              mRtcpPacketsReceived));
   }
 }
 
 void
-MediaPipeline::RtpPacketReceived(TransportLayer* aLayer, MediaPacket& packet)
+MediaPipeline::RtpPacketReceived(MediaPacket& packet)
 {
   if (mDirection == DirectionType::TRANSMIT) {
     return;
   }
 
   if (!mTransport->Pipeline()) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
             ("Discarding incoming packet; transport disconnected"));
@@ -990,28 +915,16 @@ MediaPipeline::RtpPacketReceived(Transpo
   }
 
   if (!mConduit) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
             ("Discarding incoming packet; media disconnected"));
     return;
   }
 
-  if (mRtp.mState != StateType::MP_OPEN) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-            ("Discarding incoming packet; pipeline not open"));
-    return;
-  }
-
-  if (mRtp.mSrtp->state() != TransportLayer::TS_OPEN) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-            ("Discarding incoming packet; transport not open"));
-    return;
-  }
-
   if (!packet.len()) {
     return;
   }
 
   webrtc::RTPHeader header;
   if (!mRtpParser->Parse(packet.data(), packet.len(), &header, true)) {
     return;
   }
@@ -1073,42 +986,30 @@ MediaPipeline::RtpPacketReceived(Transpo
   mPacketDumper->Dump(
     mLevel, dom::mozPacketDumpType::Rtp, false, packet.data(), packet.len());
 
   (void)mConduit->ReceivedRTPPacket(
     packet.data(), packet.len(), header.ssrc); // Ignore error codes
 }
 
 void
-MediaPipeline::RtcpPacketReceived(TransportLayer* aLayer, MediaPacket& packet)
+MediaPipeline::RtcpPacketReceived(MediaPacket& packet)
 {
   if (!mTransport->Pipeline()) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
             ("Discarding incoming packet; transport disconnected"));
     return;
   }
 
   if (!mConduit) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
             ("Discarding incoming packet; media disconnected"));
     return;
   }
 
-  if (mRtcp.mState != StateType::MP_OPEN) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
-            ("Discarding incoming packet; pipeline not open"));
-    return;
-  }
-
-  if (mRtcp.mSrtp->state() != TransportLayer::TS_OPEN) {
-    MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-            ("Discarding incoming packet; transport not open"));
-    return;
-  }
-
   if (!packet.len()) {
     return;
   }
 
   // We do not filter RTCP. This is because a compound RTCP packet can contain
   // any collection of RTCP packets, and webrtc.org already knows how to filter
   // out what it is interested in, and what it is not. Maybe someday we should
   // have a TransportLayer that breaks up compound RTCP so we can filter them
@@ -1126,33 +1027,59 @@ MediaPipeline::RtcpPacketReceived(Transp
     mLevel, dom::mozPacketDumpType::Srtcp, false, packet.encrypted_data(), packet.encrypted_len());
 
   mPacketDumper->Dump(mLevel, dom::mozPacketDumpType::Rtcp, false, packet.data(), packet.len());
 
   (void)mConduit->ReceivedRTCPPacket(packet.data(), packet.len()); // Ignore error codes
 }
 
 void
-MediaPipeline::PacketReceived(TransportLayer* aLayer, MediaPacket& packet)
+MediaPipeline::PacketReceived(const std::string& aTransportId,
+                              MediaPacket& packet)
 {
+  if (mTransportId != aTransportId) {
+    return;
+  }
+
   if (!mTransport->Pipeline()) {
     MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
             ("Discarding incoming packet; transport disconnected"));
     return;
   }
 
   switch (packet.type()) {
     case MediaPacket::RTP:
-      RtpPacketReceived(aLayer, packet);
+      RtpPacketReceived(packet);
       break;
     case MediaPacket::RTCP:
-      RtcpPacketReceived(aLayer, packet);
+      RtcpPacketReceived(packet);
       break;
     default:
-      MOZ_CRASH("TransportLayerSrtp let something other than RTP/RTCP through");
+      ;
+  }
+}
+
+void
+MediaPipeline::EncryptedPacketSending(const std::string& aTransportId,
+                                      MediaPacket& aPacket)
+{
+  if (mTransportId == aTransportId) {
+    dom::mozPacketDumpType type;
+    if (aPacket.type() == MediaPacket::SRTP) {
+      type = dom::mozPacketDumpType::Srtp;
+    } else if (aPacket.type() == MediaPacket::SRTCP) {
+      type = dom::mozPacketDumpType::Srtcp;
+    } else if (aPacket.type() == MediaPacket::DTLS) {
+      // TODO(bug 1497936): Implement packet dump for DTLS
+      return;
+    } else {
+      MOZ_ASSERT(false);
+      return;
+    }
+    mPacketDumper->Dump(Level(), type, true, aPacket.data(), aPacket.len());
   }
 }
 
 class MediaPipelineTransmit::PipelineListener : public MediaStreamVideoSink
 {
   friend class MediaPipelineTransmit;
 
 public:
@@ -1268,21 +1195,23 @@ protected:
   virtual ~VideoFrameFeeder() { MOZ_COUNT_DTOR(VideoFrameFeeder); }
 
   Mutex mMutex; // Protects the member below.
   RefPtr<PipelineListener> mListener;
 };
 
 MediaPipelineTransmit::MediaPipelineTransmit(
   const std::string& aPc,
+  MediaTransportBase* aTransportHandler,
   nsCOMPtr<nsIEventTarget> aMainThread,
   nsCOMPtr<nsIEventTarget> aStsThread,
   bool aIsVideo,
   RefPtr<MediaSessionConduit> aConduit)
   : MediaPipeline(aPc,
+                  aTransportHandler,
                   DirectionType::TRANSMIT,
                   aMainThread,
                   aStsThread,
                   aConduit)
   , mIsVideo(aIsVideo)
   , mListener(new PipelineListener(aConduit))
   , mFeeder(aIsVideo ? MakeAndAddRef<VideoFrameFeeder>(mListener)
                      : nullptr) // For video we send frames to an
@@ -1463,29 +1392,23 @@ MediaPipelineTransmit::UpdateSinkIdentit
 void
 MediaPipelineTransmit::DetachMedia()
 {
   ASSERT_ON_THREAD(mMainThread);
   mDomTrack = nullptr;
   // Let the listener be destroyed with the pipeline (or later).
 }
 
-nsresult
-MediaPipelineTransmit::TransportReady_s(TransportInfo& aInfo)
+void
+MediaPipelineTransmit::TransportReady_s()
 {
   ASSERT_ON_THREAD(mStsThread);
   // Call base ready function.
-  MediaPipeline::TransportReady_s(aInfo);
-
-  // Should not be set for a transmitter
-  if (&aInfo == &mRtp) {
-    mListener->SetActive(true);
-  }
-
-  return NS_OK;
+  MediaPipeline::TransportReady_s();
+  mListener->SetActive(true);
 }
 
 nsresult
 MediaPipelineTransmit::SetTrack(MediaStreamTrack* aDomTrack)
 {
   // MainThread, checked in calls we make
   if (aDomTrack) {
     nsString nsTrackId;
@@ -1506,61 +1429,16 @@ MediaPipelineTransmit::SetTrack(MediaStr
 
   if (wasTransmitting) {
     Start();
   }
   return NS_OK;
 }
 
 nsresult
-MediaPipeline::ConnectTransport_s(TransportInfo& aInfo)
-{
-  MOZ_ASSERT(aInfo.mTransport);
-  MOZ_ASSERT(aInfo.mSrtp);
-  ASSERT_ON_THREAD(mStsThread);
-
-  // Look to see if the transport is ready
-  if (aInfo.mSrtp->state() == TransportLayer::TS_OPEN) {
-    nsresult res = TransportReady_s(aInfo);
-    if (NS_FAILED(res)) {
-      MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-              ("Error calling TransportReady(); res=%u in %s",
-                static_cast<uint32_t>(res),
-                __FUNCTION__));
-      return res;
-    }
-  } else if (aInfo.mSrtp->state() == TransportLayer::TS_ERROR) {
-      MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
-              ("%s transport is already in error state",
-               ToString(aInfo.mType)));
-    TransportFailed_s(aInfo);
-    return NS_ERROR_FAILURE;
-  }
-
-  aInfo.mSrtp->SignalStateChange.connect(this, &MediaPipeline::StateChange);
-
-  return NS_OK;
-}
-
-MediaPipeline::TransportInfo*
-MediaPipeline::GetTransportInfo_s(TransportLayer* aLayer)
-{
-  ASSERT_ON_THREAD(mStsThread);
-  if (aLayer == mRtp.mSrtp) {
-    return &mRtp;
-  }
-
-  if (aLayer == mRtcp.mSrtp) {
-    return &mRtcp;
-  }
-
-  return nullptr;
-}
-
-nsresult
 MediaPipeline::PipelineTransport::SendRtpPacket(const uint8_t* aData, size_t aLen)
 {
   nsAutoPtr<MediaPacket> packet(new MediaPacket);
   packet->Copy(aData, aLen, aLen + SRTP_MAX_EXPANSION);
   packet->SetType(MediaPacket::RTP);
 
   RUN_ON_THREAD(
     mStsThread,
@@ -1578,25 +1456,23 @@ MediaPipeline::PipelineTransport::SendRt
 {
   bool isRtp = aPacket->type() == MediaPacket::RTP;
 
   ASSERT_ON_THREAD(mStsThread);
   if (!mPipeline) {
     return NS_OK; // Detached
   }
 
-  TransportInfo& transport = isRtp ? mPipeline->mRtp : mPipeline->mRtcp;
-
-  if (transport.mSrtp->state() != TransportLayer::TS_OPEN) {
-    // SRTP not ready yet.
+  if (isRtp && mPipeline->mRtpState != TransportLayer::TS_OPEN) {
     return NS_OK;
   }
 
-  MOZ_ASSERT(transport.mTransport);
-  NS_ENSURE_TRUE(transport.mTransport, NS_ERROR_NULL_POINTER);
+  if (!isRtp && mPipeline->mRtcpState != TransportLayer::TS_OPEN) {
+    return NS_OK;
+  }
 
   MediaPacket packet(std::move(*aPacket));
   packet.sdp_level() = Some(mPipeline->Level());
 
   if (RtpLogger::IsPacketLoggingOn()) {
     RtpLogger::LogPacket(packet, false, mPipeline->mDescription);
   }
 
@@ -1616,17 +1492,17 @@ MediaPipeline::PipelineTransport::SendRt
     mPipeline->IncrementRtcpPacketsSent();
   }
 
   MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
           ("%s sending %s packet",
            mPipeline->mDescription.c_str(),
            (isRtp ? "RTP" : "RTCP")));
 
-  return mPipeline->SendPacket(transport.mSrtp, packet);
+  return mPipeline->SendPacket(packet);
 }
 
 nsresult
 MediaPipeline::PipelineTransport::SendRtcpPacket(const uint8_t* aData,
                                                  size_t aLen)
 {
   nsAutoPtr<MediaPacket> packet(new MediaPacket);
   packet->Copy(aData, aLen, aLen + SRTP_MAX_EXPANSION);
@@ -1893,21 +1769,24 @@ protected:
   const TrackID mTrackId;
   const RefPtr<SourceMediaStream> mSource;
   TrackTicks mPlayedTicks;
   PrincipalHandle mPrincipalHandle;
   bool mListening;
   Atomic<bool> mMaybeTrackNeedsUnmute;
 };
 
-MediaPipelineReceive::MediaPipelineReceive(const std::string& aPc,
-                                           nsCOMPtr<nsIEventTarget> aMainThread,
-                                           nsCOMPtr<nsIEventTarget> aStsThread,
-                                           RefPtr<MediaSessionConduit> aConduit)
+MediaPipelineReceive::MediaPipelineReceive(
+    const std::string& aPc,
+    MediaTransportBase* aTransportHandler,
+    nsCOMPtr<nsIEventTarget> aMainThread,
+    nsCOMPtr<nsIEventTarget> aStsThread,
+    RefPtr<MediaSessionConduit> aConduit)
   : MediaPipeline(aPc,
+                  aTransportHandler,
                   DirectionType::RECEIVE,
                   aMainThread,
                   aStsThread,
                   aConduit)
 {
 }
 
 MediaPipelineReceive::~MediaPipelineReceive() {}
@@ -2043,21 +1922,26 @@ private:
   // 48kHz, mRate is capped to 48kHz. If mRate does not match the graph rate,
   // audio is resampled to the graph rate.
   const TrackRate mRate;
   const RefPtr<TaskQueue> mTaskQueue;
 };
 
 MediaPipelineReceiveAudio::MediaPipelineReceiveAudio(
   const std::string& aPc,
+  MediaTransportBase* aTransportHandler,
   nsCOMPtr<nsIEventTarget> aMainThread,
   nsCOMPtr<nsIEventTarget> aStsThread,
   RefPtr<AudioSessionConduit> aConduit,
   dom::MediaStreamTrack* aTrack)
-  : MediaPipelineReceive(aPc, aMainThread, aStsThread, aConduit)
+  : MediaPipelineReceive(aPc,
+                         aTransportHandler,
+                         aMainThread,
+                         aStsThread,
+                         aConduit)
   , mListener(aTrack ? new PipelineListener(aTrack, mConduit) : nullptr)
 {
   mDescription = mPc + "| Receive audio";
 }
 
 void
 MediaPipelineReceiveAudio::DetachMedia()
 {
@@ -2231,21 +2115,26 @@ public:
   }
 
 private:
   MediaPipelineReceiveVideo* mPipeline; // Raw pointer to avoid cycles
 };
 
 MediaPipelineReceiveVideo::MediaPipelineReceiveVideo(
   const std::string& aPc,
+  MediaTransportBase* aTransportHandler,
   nsCOMPtr<nsIEventTarget> aMainThread,
   nsCOMPtr<nsIEventTarget> aStsThread,
   RefPtr<VideoSessionConduit> aConduit,
   dom::MediaStreamTrack* aTrack)
-  : MediaPipelineReceive(aPc, aMainThread, aStsThread, aConduit)
+  : MediaPipelineReceive(aPc,
+                         aTransportHandler,
+                         aMainThread,
+                         aStsThread,
+                         aConduit)
   , mRenderer(new PipelineRenderer(this))
   , mListener(aTrack ? new PipelineListener(aTrack) : nullptr)
 {
   mDescription = mPc + "| Receive video";
   aConduit->AttachRenderer(mRenderer);
 }
 
 void
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -6,38 +6,39 @@
 // Original author: ekr@rtfm.com
 
 #ifndef mediapipeline_h__
 #define mediapipeline_h__
 
 #include <map>
 
 #include "sigslot.h"
+#include "transportlayer.h" // For TransportLayer::State
 
 #include "signaling/src/media-conduit/MediaConduitInterface.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Atomics.h"
-#include "SrtpFlow.h"
+#include "SrtpFlow.h" // For SRTP_MAX_EXPANSION
 #include "mediapacket.h"
 #include "mtransport/runnable_utils.h"
-#include "mtransport/transportflow.h"
 #include "AudioPacketizer.h"
 #include "StreamTracks.h"
 #include "signaling/src/peerconnection/PacketDumper.h"
 
 #include "webrtc/modules/rtp_rtcp/include/rtp_header_parser.h"
 
 // Should come from MediaEngine.h, but that's a pain to include here
 // because of the MOZILLA_EXTERNAL_LINKAGE stuff.
 #define WEBRTC_MAX_SAMPLE_RATE 48000
 
 class nsIPrincipal;
 
 namespace mozilla {
 class MediaPipelineFilter;
+class MediaTransportBase;
 class PeerIdentity;
 class AudioProxyThread;
 class VideoFrameConverter;
 
 namespace dom {
 class MediaStreamTrack;
 struct RTCRTPContributingSourceStats;
 } // namespace dom
@@ -78,60 +79,51 @@ class SourceMediaStream;
 class MediaPipeline : public sigslot::has_slots<>
 {
 public:
   enum class DirectionType
   {
     TRANSMIT,
     RECEIVE
   };
-  enum class StateType
-  {
-    MP_CONNECTING,
-    MP_OPEN,
-    MP_CLOSED
-  };
   MediaPipeline(const std::string& aPc,
+                MediaTransportBase* aTransportHandler,
                 DirectionType aDirection,
                 nsCOMPtr<nsIEventTarget> aMainThread,
                 nsCOMPtr<nsIEventTarget> aStsThread,
                 RefPtr<MediaSessionConduit> aConduit);
 
   virtual void Start() = 0;
   virtual void Stop() = 0;
   virtual void DetachMedia() {}
 
   void SetLevel(size_t aLevel) { mLevel = aLevel; }
 
   // Must be called on the main thread.
   void Shutdown_m();
 
-  void UpdateTransport_m(RefPtr<TransportFlow> aRtpTransport,
-                         RefPtr<TransportFlow> aRtcpTransport,
+  void UpdateTransport_m(const std::string& aTransportId,
                          nsAutoPtr<MediaPipelineFilter> aFilter);
 
-  void UpdateTransport_s(RefPtr<TransportFlow> aRtpTransport,
-                         RefPtr<TransportFlow> aRtcpTransport,
+  void UpdateTransport_s(const std::string& aTransportId,
                          nsAutoPtr<MediaPipelineFilter> aFilter);
 
   // Used only for testing; adds RTP header extension for RTP Stream Id with
   // the given id.
   void AddRIDExtension_m(size_t aExtensionId);
   void AddRIDExtension_s(size_t aExtensionId);
   // Used only for testing; installs a MediaPipelineFilter that filters
   // everything but the given RID
   void AddRIDFilter_m(const std::string& aRid);
   void AddRIDFilter_s(const std::string& aRid);
 
   virtual DirectionType Direction() const { return mDirection; }
   int Level() const { return mLevel; }
   virtual bool IsVideo() const = 0;
 
-  bool IsDoingRtcpMux() const { return mRtp.mType == MUX; }
-
   class RtpCSRCStats
   {
   public:
     // Gets an expiration time for CRC info given a reference time,
     //   this reference time would normally be the time of calling.
     //   This value can then be used to check if a RtpCSRCStats
     //   has expired via Expired(...)
     static DOMHighResTimeStamp GetExpiryFromTime(
@@ -173,18 +165,16 @@ public:
   int64_t RtpBytesReceived() const { return mRtpBytesReceived; }
   int32_t RtcpPacketsReceived() const { return mRtcpPacketsReceived; }
 
   MediaSessionConduit* Conduit() const { return mConduit; }
 
   // Thread counting
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPipeline)
 
-  typedef enum { RTP, RTCP, MUX, MAX_RTP_TYPE } RtpType;
-
   // Separate class to allow ref counting
   class PipelineTransport : public TransportInterface
   {
   public:
     // Implement the TransportInterface functions
     explicit PipelineTransport(nsIEventTarget* aStsThread)
       : mPipeline(nullptr)
       , mStsThread(aStsThread)
@@ -203,87 +193,63 @@ public:
 
     // Creates a cycle, which we break with Detach
     RefPtr<MediaPipeline> mPipeline;
     const nsCOMPtr<nsIEventTarget> mStsThread;
   };
 
 protected:
   virtual ~MediaPipeline();
-  nsresult AttachTransport_s();
   friend class PipelineTransport;
 
-  struct TransportInfo
-  {
-    TransportInfo(RefPtr<TransportFlow> aFlow, RtpType aType)
-      : mTransport(aFlow)
-      , mSrtp(mTransport ? mTransport->GetLayer("srtp") : nullptr)
-      , mState(StateType::MP_CONNECTING)
-      , mType(aType)
-    {
-    }
-
-    void Detach()
-    {
-      mTransport = nullptr;
-      mSrtp = nullptr;
-    }
-
-    RefPtr<TransportFlow> mTransport;
-    TransportLayer* mSrtp;
-    StateType mState;
-    RtpType mType;
-  };
-
-  // The transport is down
-  virtual nsresult TransportFailed_s(TransportInfo& aInfo);
   // The transport is ready
-  virtual nsresult TransportReady_s(TransportInfo& aInfo);
-  void UpdateRtcpMuxState(TransportInfo& aInfo);
-
-  nsresult ConnectTransport_s(TransportInfo& aInfo);
-
-  TransportInfo* GetTransportInfo_s(TransportLayer* aLayer);
+  virtual void TransportReady_s() {}
 
   void IncrementRtpPacketsSent(int aBytes);
   void IncrementRtcpPacketsSent();
   void IncrementRtpPacketsReceived(int aBytes);
   virtual void OnRtpPacketReceived() {};
   void IncrementRtcpPacketsReceived();
 
-  virtual nsresult SendPacket(TransportLayer* aLayer,
-                              MediaPacket& packet);
+  virtual nsresult SendPacket(MediaPacket& packet);
 
   // Process slots on transports
-  void StateChange(TransportLayer* aLayer, TransportLayer::State);
-  void RtpPacketReceived(TransportLayer* aLayer, MediaPacket& packet);
-  void RtcpPacketReceived(TransportLayer* aLayer, MediaPacket& packet);
-  void PacketReceived(TransportLayer* aLayer, MediaPacket& packet);
+  void RtpStateChange(const std::string& aTransportId, TransportLayer::State);
+  void RtcpStateChange(const std::string& aTransportId, TransportLayer::State);
+  virtual void CheckTransportStates();
+  void PacketReceived(const std::string& aTransportId, MediaPacket& packet);
+
+  void RtpPacketReceived(MediaPacket& packet);
+  void RtcpPacketReceived(MediaPacket& packet);
+
+  void EncryptedPacketSending(const std::string& aTransportId,
+                              MediaPacket& aPacket);
 
   void SetDescription_s(const std::string& description);
 
   const DirectionType mDirection;
   size_t mLevel;
+  std::string mTransportId;
+  RefPtr<MediaTransportBase> mTransportHandler;
   RefPtr<MediaSessionConduit> mConduit; // Our conduit. Written on the main
                                         // thread. Read on STS thread.
 
-  // The transport objects. Read/written on STS thread.
-  TransportInfo mRtp;
-  TransportInfo mRtcp;
+  TransportLayer::State mRtpState = TransportLayer::TS_NONE;
+  TransportLayer::State mRtcpState = TransportLayer::TS_NONE;
+  bool mSignalsConnected = false;
 
   // Pointers to the threads we need. Initialized at creation
   // and used all over the place.
   const nsCOMPtr<nsIEventTarget> mMainThread;
   const nsCOMPtr<nsIEventTarget> mStsThread;
 
   // Created in c'tor. Referenced by the conduit.
   RefPtr<PipelineTransport> mTransport;
 
   // Only safe to access from STS thread.
-  // Build into TransportInfo?
   int32_t mRtpPacketsSent;
   int32_t mRtcpPacketsSent;
   int32_t mRtpPacketsReceived;
   int32_t mRtcpPacketsReceived;
   int64_t mRtpBytesSent;
   int64_t mRtpBytesReceived;
 
   // Only safe to access from STS thread.
@@ -310,16 +276,17 @@ private:
 
 // A specialization of pipeline for reading from an input device
 // and transmitting to the network.
 class MediaPipelineTransmit : public MediaPipeline
 {
 public:
   // Set aRtcpTransport to nullptr to use rtcp-mux
   MediaPipelineTransmit(const std::string& aPc,
+                        MediaTransportBase* aTransportHandler,
                         nsCOMPtr<nsIEventTarget> aMainThread,
                         nsCOMPtr<nsIEventTarget> aStsThread,
                         bool aIsVideo,
                         RefPtr<MediaSessionConduit> aConduit);
 
   bool Transmitting() const;
 
   void Start() override;
@@ -333,18 +300,18 @@ public:
   // `track` has to be null or equal `mDomTrack` for us to apply the update.
   virtual void UpdateSinkIdentity_m(const dom::MediaStreamTrack* aTrack,
                                     nsIPrincipal* aPrincipal,
                                     const PeerIdentity* aSinkIdentity);
 
   // Called on the main thread.
   void DetachMedia() override;
 
-  // Override MediaPipeline::TransportReady.
-  nsresult TransportReady_s(TransportInfo& aInfo) override;
+  // Override MediaPipeline::TransportReady_s.
+  void TransportReady_s() 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 SetTrack(dom::MediaStreamTrack* aDomTrack);
 
   // Separate classes to allow ref counting
@@ -368,16 +335,17 @@ private:
 
 // A specialization of pipeline for reading from the network and
 // rendering media.
 class MediaPipelineReceive : public MediaPipeline
 {
 public:
   // Set aRtcpTransport to nullptr to use rtcp-mux
   MediaPipelineReceive(const std::string& aPc,
+                       MediaTransportBase* aTransportHandler,
                        nsCOMPtr<nsIEventTarget> aMainThread,
                        nsCOMPtr<nsIEventTarget> aStsThread,
                        RefPtr<MediaSessionConduit> aConduit);
 
   // Sets the PrincipalHandle we set on the media chunks produced by this
   // pipeline. Must be called on the main thread.
   virtual void SetPrincipalHandle_m(
     const PrincipalHandle& aPrincipalHandle) = 0;
@@ -387,16 +355,17 @@ protected:
 };
 
 // A specialization of pipeline for reading from the network and
 // rendering audio.
 class MediaPipelineReceiveAudio : public MediaPipelineReceive
 {
 public:
   MediaPipelineReceiveAudio(const std::string& aPc,
+                            MediaTransportBase* aTransportHandler,
                             nsCOMPtr<nsIEventTarget> aMainThread,
                             nsCOMPtr<nsIEventTarget> aStsThread,
                             RefPtr<AudioSessionConduit> aConduit,
                             dom::MediaStreamTrack* aTrack);
 
   void DetachMedia() override;
 
   bool IsVideo() const override { return false; }
@@ -416,16 +385,17 @@ private:
 };
 
 // A specialization of pipeline for reading from the network and
 // rendering video.
 class MediaPipelineReceiveVideo : public MediaPipelineReceive
 {
 public:
   MediaPipelineReceiveVideo(const std::string& aPc,
+                            MediaTransportBase* aTransportHandler,
                             nsCOMPtr<nsIEventTarget> aMainThread,
                             nsCOMPtr<nsIEventTarget> aStsThread,
                             RefPtr<VideoSessionConduit> aConduit,
                             dom::MediaStreamTrack* aTrack);
 
   // Called on the main thread.
   void DetachMedia() override;
 
deleted file mode 100644
--- a/media/webrtc/signaling/src/mediapipeline/TransportLayerPacketDumper.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// Original author: ekr@rtfm.com
-
-#include "TransportLayerPacketDumper.h"
-
-#include "logging.h"
-#include "nsError.h"
-#include "mozilla/Assertions.h"
-
-namespace mozilla {
-
-MOZ_MTLOG_MODULE("mtransport")
-
-TransportLayerPacketDumper::TransportLayerPacketDumper(
-    nsAutoPtr<PacketDumper>&& aPacketDumper, dom::mozPacketDumpType aType) :
-  mPacketDumper(std::move(aPacketDumper)),
-  mType(aType)
-{}
-
-void
-TransportLayerPacketDumper::WasInserted()
-{
-  CheckThread();
-  if (!downward_) {
-    MOZ_MTLOG(ML_ERROR, "Packet dumper with nothing below. This is useless");
-    TL_SET_STATE(TS_ERROR);
-  }
-
-  downward_->SignalStateChange.connect(this,
-      &TransportLayerPacketDumper::StateChange);
-  downward_->SignalPacketReceived.connect(this,
-      &TransportLayerPacketDumper::PacketReceived);
-}
-
-TransportResult
-TransportLayerPacketDumper::SendPacket(MediaPacket& packet)
-{
-  if (packet.sdp_level().isSome()) {
-    dom::mozPacketDumpType dumpType = mType;
-    if (mType == dom::mozPacketDumpType::Srtp &&
-        packet.type() == MediaPacket::RTCP) {
-      dumpType = dom::mozPacketDumpType::Srtcp;
-    }
-
-    mPacketDumper->Dump(*packet.sdp_level(),
-                        dumpType,
-                        true,
-                        packet.data(),
-                        packet.len());
-  }
-  return downward_->SendPacket(packet);
-}
-
-void
-TransportLayerPacketDumper::StateChange(TransportLayer* aLayer, State aState)
-{
-  TL_SET_STATE(aState);
-}
-
-void
-TransportLayerPacketDumper::PacketReceived(TransportLayer* aLayer,
-                                           MediaPacket& packet)
-{
-  // There's no way to know the level yet, so we can't use the packet dumper
-  // yet. We rely on the SRTP layer saving the encrypted packet in
-  // MediaPacket::encrypted_, to allow MediaPipeline to dump it later.
-  SignalPacketReceived(this, packet);
-}
-
-} // namespace mozilla
-
-
deleted file mode 100644
--- a/media/webrtc/signaling/src/mediapipeline/TransportLayerPacketDumper.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef transportlayerpacketdumper_h__
-#define transportlayerpacketdumper_h__
-
-#include "transportlayer.h"
-#include "signaling/src/peerconnection/PacketDumper.h"
-#include "mozilla/dom/RTCPeerConnectionBinding.h"
-
-namespace mozilla {
-
-class TransportLayerPacketDumper final : public TransportLayer {
-  public:
-    explicit TransportLayerPacketDumper(nsAutoPtr<PacketDumper>&& aPacketDumper,
-                                        dom::mozPacketDumpType aType);
-    virtual ~TransportLayerPacketDumper() {};
-
-    // Transport layer overrides.
-    void WasInserted() override;
-    TransportResult SendPacket(MediaPacket& packet) override;
-
-    // Signals
-    void StateChange(TransportLayer *aLayer, State state);
-    void PacketReceived(TransportLayer* aLayer, MediaPacket& packet);
-
-    TRANSPORT_LAYER_ID("packet-dumper")
-
-  private:
-    DISALLOW_COPY_ASSIGN(TransportLayerPacketDumper);
-    nsAutoPtr<PacketDumper> mPacketDumper;
-    dom::mozPacketDumpType mType;
-};
-
-
-}  // close namespace
-#endif
--- a/media/webrtc/signaling/src/mediapipeline/moz.build
+++ b/media/webrtc/signaling/src/mediapipeline/moz.build
@@ -17,14 +17,13 @@ LOCAL_INCLUDES += [
     '/netwerk/srtp/src/crypto/include',
     '/netwerk/srtp/src/include',
 ]
 
 UNIFIED_SOURCES += [
     'MediaPipeline.cpp',
     'MediaPipelineFilter.cpp',
     'RtpLogger.cpp',
-    'TransportLayerPacketDumper.cpp',
 ]
 
 DEFINES['TRACING'] = True
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.cpp
@@ -0,0 +1,906 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaTransportHandler.h"
+#include "nricemediastream.h"
+#include "nriceresolver.h"
+#include "transportflow.h"
+#include "transportlayerice.h"
+#include "transportlayerdtls.h"
+#include "transportlayersrtp.h"
+
+// Config stuff
+#include "nsIPrefService.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+
+// Parsing STUN/TURN URIs
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsIURLParser.h"
+
+// Logging stuff
+#include "CSFLog.h"
+
+// DTLS
+#include "signaling/src/sdp/SdpAttribute.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/dom/RTCStatsReportBinding.h"
+
+#include <string>
+#include <vector>
+
+namespace mozilla {
+
+static const char* mthLogTag = "MediaTransportHandler";
+#ifdef LOGTAG
+#undef LOGTAG
+#endif
+#define LOGTAG mthLogTag
+
+MediaTransportHandler::MediaTransportHandler()
+{}
+
+MediaTransportHandler::~MediaTransportHandler()
+{}
+
+static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy)
+{
+  switch (aPolicy) {
+    case dom::RTCIceTransportPolicy::Relay:
+      return NrIceCtx::ICE_POLICY_RELAY;
+    case dom::RTCIceTransportPolicy::All:
+      if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) {
+        return NrIceCtx::ICE_POLICY_NO_HOST;
+      } else {
+        return NrIceCtx::ICE_POLICY_ALL;
+      }
+    default:
+      MOZ_CRASH();
+  }
+  return NrIceCtx::ICE_POLICY_ALL;
+}
+
+static nsresult addNrIceServer(const nsString& aIceUrl,
+                               const dom::RTCIceServer& aIceServer,
+                               std::vector<NrIceStunServer>* aStunServersOut,
+                               std::vector<NrIceTurnServer>* aTurnServersOut)
+{
+  // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than
+  // nsStandardURL. To parse STUN/TURN URI's to spec
+  // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3
+  // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3
+  // we parse out the query-string, and use ParseAuthority() on the rest
+  RefPtr<nsIURI> url;
+  nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl);
+  NS_ENSURE_SUCCESS(rv, rv);
+  bool isStun = false, isStuns = false, isTurn = false, isTurns = false;
+  url->SchemeIs("stun", &isStun);
+  url->SchemeIs("stuns", &isStuns);
+  url->SchemeIs("turn", &isTurn);
+  url->SchemeIs("turns", &isTurns);
+  if (!(isStun || isStuns || isTurn || isTurns)) {
+    return NS_ERROR_FAILURE;
+  }
+  if (isStuns) {
+    return NS_OK; // TODO: Support STUNS (Bug 1056934)
+  }
+
+  nsAutoCString spec;
+  rv = url->GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509)
+  int32_t port;
+  nsAutoCString host;
+  nsAutoCString transport;
+  {
+    uint32_t hostPos;
+    int32_t hostLen;
+    nsAutoCString path;
+    rv = url->GetPathQueryRef(path);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Tolerate query-string + parse 'transport=[udp|tcp]' by hand.
+    int32_t questionmark = path.FindChar('?');
+    if (questionmark >= 0) {
+      const nsCString match = NS_LITERAL_CSTRING("transport=");
+
+      for (int32_t i = questionmark, endPos; i >= 0; i = endPos) {
+        endPos = path.FindCharInSet("&", i + 1);
+        const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1,
+            endPos);
+        if (StringBeginsWith(fieldvaluepair, match)) {
+          transport = Substring(fieldvaluepair, match.Length());
+          ToLowerCase(transport);
+        }
+      }
+      path.SetLength(questionmark);
+    }
+
+    rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(),
+        nullptr,  nullptr,
+        nullptr,  nullptr,
+        &hostPos,  &hostLen, &port);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!hostLen) {
+      return NS_ERROR_FAILURE;
+    }
+    if (hostPos > 1)  /* The username was removed */
+      return NS_ERROR_FAILURE;
+    path.Mid(host, hostPos, hostLen);
+  }
+  if (port == -1)
+    port = (isStuns || isTurns)? 5349 : 3478;
+
+  if (isStuns || isTurns) {
+    // Should we barf if transport is set to udp or something?
+    transport = kNrIceTransportTls;
+  }
+
+  if (transport.IsEmpty()) {
+    transport = kNrIceTransportUdp;
+  }
+
+  if (isTurn || isTurns) {
+    std::string pwd(NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get());
+    std::string username(NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get());
+
+    std::vector<unsigned char> password(pwd.begin(), pwd.end());
+
+    UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(host.get(), port, username, password, transport.get()));
+    if (!server) {
+      return NS_ERROR_FAILURE;
+    }
+    aTurnServersOut->emplace_back(std::move(*server));
+  } else {
+    UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(host.get(), port, transport.get()));
+    if (!server) {
+      return NS_ERROR_FAILURE;
+    }
+    aStunServersOut->emplace_back(std::move(*server));
+  }
+  return NS_OK;
+}
+
+nsresult
+MediaTransportHandler::Init(const std::string& aName,
+                            const dom::RTCConfiguration& aConfiguration)
+{
+  bool allowIceLoopback = Preferences::GetBool(
+    "media.peerconnection.ice.loopback", false);
+  bool iceTcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
+  bool allowIceLinkLocal = Preferences::GetBool(
+    "media.peerconnection.ice.link_local", false);
+
+  NrIceCtx::InitializeGlobals(allowIceLoopback, iceTcp, allowIceLinkLocal);
+
+  bool allowLoopback = Preferences::GetBool(
+    "media.peerconnection.ice.loopback", false);
+  bool tcpEnabled = Preferences::GetBool(
+      "media.peerconnection.ice.tcp", false);
+  bool allowLinkLocal = Preferences::GetBool(
+    "media.peerconnection.ice.link_local", false);
+
+  mIceCtx = NrIceCtx::Create(aName, allowLoopback, tcpEnabled, allowLinkLocal,
+      toNrIcePolicy(aConfiguration.mIceTransportPolicy));
+  if (!mIceCtx) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mProxyOnly = Preferences::GetBool(
+      "media.peerconnection.ice.proxy_only", false);
+
+  mIceCtx->SignalGatheringStateChange.connect(
+      this, &MediaTransportHandler::OnGatheringStateChange);
+  mIceCtx->SignalConnectionStateChange.connect(
+      this, &MediaTransportHandler::OnConnectionStateChange);
+
+  std::vector<NrIceStunServer> stunServers;
+  std::vector<NrIceTurnServer> turnServers;
+
+  nsresult rv;
+  if (aConfiguration.mIceServers.WasPassed()) {
+    for (const auto& iceServer : aConfiguration.mIceServers.Value()) {
+      NS_ENSURE_STATE(iceServer.mUrls.WasPassed());
+      NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence());
+      for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) {
+        rv = addNrIceServer(iceUrl, iceServer, &stunServers, &turnServers);
+        if (NS_FAILED(rv)) {
+          CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s",
+                      __FUNCTION__, NS_ConvertUTF16toUTF8(iceUrl).get());
+          return rv;
+        }
+      }
+    }
+  }
+
+  if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) {
+    CSFLogError(LOGTAG, "%s: Failed to set stun servers", __FUNCTION__);
+    return rv;
+  }
+  // Give us a way to globally turn off TURN support
+  bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false);
+  if (!disabled) {
+    if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) {
+      CSFLogError(LOGTAG, "%s: Failed to set turn servers", __FUNCTION__);
+      return rv;
+    }
+  } else if (!turnServers.empty()) {
+    CSFLogError(LOGTAG, "%s: Setting turn servers disabled", __FUNCTION__);
+  }
+
+  mDNSResolver = new NrIceResolver;
+  if (NS_FAILED(rv = mDNSResolver->Init())) {
+    CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver", __FUNCTION__);
+    return rv;
+  }
+  if (NS_FAILED(rv = mIceCtx->SetResolver(mDNSResolver->AllocateResolver()))) {
+    CSFLogError(LOGTAG, "%s: Failed to get dns resolver", __FUNCTION__);
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+MediaTransportHandler::Destroy()
+{
+  disconnect_all();
+  NrIceStats stats = mIceCtx->Destroy();
+  CSFLogDebug(LOGTAG, "Ice Telemetry: stun (retransmits: %d)"
+                      "   turn (401s: %d   403s: %d   438s: %d)",
+              stats.stun_retransmits, stats.turn_401s, stats.turn_403s,
+              stats.turn_438s);
+
+  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_STUN_RETRANSMITS,
+                       stats.stun_retransmits);
+  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_401S,
+                       stats.turn_401s);
+  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_403S,
+                       stats.turn_403s);
+  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_438S,
+                       stats.turn_438s);
+}
+
+nsresult
+MediaTransportHandler::SetProxyServer(const std::string& aProxyHost,
+                                      uint16_t aProxyPort,
+                                      const std::string& aAlpnProtocols)
+{
+  NrIceProxyServer proxyServer(aProxyHost, aProxyPort, aAlpnProtocols);
+  return mIceCtx->SetProxyServer(proxyServer);
+}
+
+void
+MediaTransportHandler::EnsureProvisionalTransport(
+    const std::string& aTransportId,
+    const std::string& aUfrag,
+    const std::string& aPwd,
+    size_t aComponentCount)
+{
+  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+  if (!stream) {
+    CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%u",
+                mIceCtx->name().c_str(),
+                aTransportId.c_str(),
+                static_cast<unsigned>(aComponentCount));
+
+    std::ostringstream os;
+    os << mIceCtx->name() << " transport-id=" << aTransportId;
+    stream = mIceCtx->CreateStream(aTransportId,
+                                   os.str(),
+                                   aComponentCount);
+
+    if (!stream) {
+      CSFLogError(LOGTAG, "Failed to create ICE stream.");
+      return;
+    }
+
+    stream->SignalCandidate.connect(this,
+                                    &MediaTransportHandler::OnCandidateFound);
+  }
+
+  // Begins an ICE restart if this stream has a different ufrag/pwd
+  stream->SetIceCredentials(aUfrag, aPwd);
+
+  // Make sure there's an entry in mTransports
+  mTransports[aTransportId];
+}
+
+nsresult
+MediaTransportHandler::ActivateTransport(
+    const std::string& aTransportId,
+    const std::string& aLocalUfrag,
+    const std::string& aLocalPwd,
+    size_t aComponentCount,
+    const std::string& aUfrag,
+    const std::string& aPassword,
+    const std::vector<std::string>& aCandidateList,
+    RefPtr<DtlsIdentity> aDtlsIdentity,
+    bool aDtlsClient,
+    const SdpFingerprintAttributeList& aFingerprints,
+    bool aPrivacyRequested)
+{
+  MOZ_ASSERT(aComponentCount);
+  MOZ_ASSERT(aDtlsIdentity);
+
+  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+  if (!stream) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u",
+              mIceCtx->name().c_str(),
+              aTransportId.c_str(),
+              static_cast<unsigned>(aComponentCount));
+
+  std::vector<std::string> attrs;
+  attrs.reserve(aCandidateList.size() + 2 /* ufrag + pwd */);
+  for (const auto& candidate : aCandidateList) {
+    attrs.push_back("candidate:" + candidate);
+  }
+  attrs.push_back("ice-ufrag:" + aUfrag);
+  attrs.push_back("ice-pwd:" + aPassword);
+
+  // If we started an ICE restart in EnsureProvisionalTransport, this is where
+  // we decide whether to commit or rollback.
+  nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u",
+                        static_cast<unsigned>(rv));
+    MOZ_ASSERT(false);
+    return rv;
+  }
+
+  Transport transport = mTransports[aTransportId];
+  if (!transport.mFlow) {
+    transport.mFlow = CreateTransportFlow(aTransportId, false, aDtlsIdentity,
+      aDtlsClient, aFingerprints, aPrivacyRequested);
+    if (!transport.mFlow) {
+      return NS_ERROR_FAILURE;
+    }
+    TransportLayer* dtls = transport.mFlow->GetLayer(TransportLayerDtls::ID());
+    dtls->SignalStateChange.connect(
+        this, &MediaTransportHandler::OnStateChange);
+    if (aComponentCount < 2) {
+      dtls->SignalStateChange.connect(
+          this, &MediaTransportHandler::OnRtcpStateChange);
+    }
+  }
+
+  if (aComponentCount == 2) {
+    if (!transport.mRtcpFlow) {
+      transport.mRtcpFlow = CreateTransportFlow(aTransportId, true,
+          aDtlsIdentity, aDtlsClient, aFingerprints, aPrivacyRequested);
+      if (!transport.mRtcpFlow) {
+        return NS_ERROR_FAILURE;
+      }
+      TransportLayer* dtls = transport.mRtcpFlow->GetLayer(
+          TransportLayerDtls::ID());
+      dtls->SignalStateChange.connect(
+          this, &MediaTransportHandler::OnRtcpStateChange);
+    }
+  } else {
+    transport.mRtcpFlow = nullptr;
+    // components are 1-indexed
+    stream->DisableComponent(2);
+  }
+
+  mTransports[aTransportId] = transport;
+  return NS_OK;
+}
+
+void
+MediaTransportHandler::StartIceGathering(
+    bool aDefaultRouteOnly,
+    const nsTArray<NrIceStunAddr>& aStunAddrs)
+{
+  // Belt and suspenders - in e10s mode, the call below to SetStunAddrs
+  // needs to have the proper flags set on ice ctx.  For non-e10s,
+  // setting those flags happens in StartGathering.  We could probably
+  // just set them here, and only do it here.
+  mIceCtx->SetCtxFlags(aDefaultRouteOnly, mProxyOnly);
+
+  if (aStunAddrs.Length()) {
+    mIceCtx->SetStunAddrs(aStunAddrs);
+  }
+
+  // Start gathering, but only if there are streams
+  if (!mIceCtx->GetStreams().empty()) {
+    mIceCtx->StartGathering(aDefaultRouteOnly, mProxyOnly);
+    return;
+  }
+
+  CSFLogWarn(LOGTAG,
+             "%s: No streams to start gathering on. Can happen with rollback",
+             __FUNCTION__);
+
+  // 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.
+  SignalGatheringStateChange(dom::PCImplIceGatheringState::Complete);
+}
+
+nsresult
+MediaTransportHandler::StartIceChecks(
+    bool aIsControlling,
+    bool aIsOfferer,
+    const std::vector<std::string>& aIceOptions)
+{
+  nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "%s: couldn't parse global parameters", __FUNCTION__ );
+    return rv;
+  }
+
+  rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING :
+                                                NrIceCtx::ICE_CONTROLLED);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "%s: couldn't set controlling to %d",
+                __FUNCTION__, aIsControlling);
+    return rv;
+  }
+
+  rv = mIceCtx->StartChecks(aIsOfferer);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__);
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MediaTransportHandler::AddIceCandidate(const std::string& aTransportId,
+                                       const std::string& aCandidate)
+{
+  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+  if (!stream) {
+    CSFLogError(LOGTAG, "No ICE stream for candidate with transport id %s: %s",
+                        aTransportId.c_str(), aCandidate.c_str());
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsresult rv = stream->ParseTrickleCandidate(aCandidate);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "Couldn't process ICE candidate with transport id %s: "
+                        "%s",
+                        aTransportId.c_str(), aCandidate.c_str());
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+void
+MediaTransportHandler::UpdateNetworkState(bool aOnline)
+{
+  mIceCtx->UpdateNetworkState(aOnline);
+}
+
+void
+MediaTransportHandler::RemoveTransportsExcept(
+    const std::set<std::string>& aTransportIds)
+{
+  for (auto it = mTransports.begin(); it != mTransports.end();) {
+    if (!aTransportIds.count(it->first)) {
+      if (it->second.mFlow) {
+        SignalStateChange(it->first, TransportLayer::TS_NONE);
+        SignalRtcpStateChange(it->first, TransportLayer::TS_NONE);
+      }
+      mIceCtx->DestroyStream(it->first);
+      it = mTransports.erase(it);
+    } else {
+      MOZ_ASSERT(it->second.mFlow);
+      ++it;
+    }
+  }
+}
+
+nsresult
+MediaTransportHandler::SendPacket(const std::string& aTransportId,
+                                  MediaPacket& aPacket)
+{
+  MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED);
+  RefPtr<TransportFlow> flow = GetTransportFlow(
+      aTransportId, aPacket.type() == MediaPacket::RTCP);
+
+  if (!flow) {
+    CSFLogError(LOGTAG, "%s: No such transport flow (%s) for outgoing packet",
+                mIceCtx->name().c_str(), aTransportId.c_str());
+    MOZ_ASSERT(false);
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  TransportLayer* layer = nullptr;
+  switch (aPacket.type()) {
+    case MediaPacket::SCTP:
+      layer = flow->GetLayer(TransportLayerDtls::ID());
+      break;
+    case MediaPacket::RTP:
+    case MediaPacket::RTCP:
+      layer = flow->GetLayer(TransportLayerSrtp::ID());
+      break;
+    default:
+      // Maybe it would be useful to allow the injection of other packet types
+      // for testing?
+      MOZ_ASSERT(false);
+      return NS_ERROR_FAILURE;
+  }
+
+  MOZ_ASSERT(layer);
+
+  if (layer->SendPacket(aPacket) < 0) {
+    CSFLogError(LOGTAG, "%s: Transport flow (%s) failed to send packet",
+                mIceCtx->name().c_str(), aTransportId.c_str());
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+TransportLayer::State
+MediaTransportHandler::GetState(const std::string& aTransportId,
+                                bool aRtcp) const
+{
+  RefPtr<TransportFlow> flow = GetTransportFlow(aTransportId, aRtcp);
+  if (flow) {
+    return flow->GetLayer(TransportLayerDtls::ID())->state();
+  }
+  return TransportLayer::TS_NONE;
+}
+
+void
+MediaTransportHandler::GetAllIceStats(bool aInternalStats,
+                                      DOMHighResTimeStamp aNow,
+                                      dom::RTCStatsReportInternal* aReport)
+{
+  for (const auto& stream : mIceCtx->GetStreams()) {
+    GetIceStats(*stream, aInternalStats, aNow, aReport);
+  }
+}
+
+void
+MediaTransportHandler::GetIceStats(const std::string& aTransportId,
+                                   bool aInternalStats,
+                                   DOMHighResTimeStamp aNow,
+                                   dom::RTCStatsReportInternal* aReport)
+{
+  auto stream = mIceCtx->GetStream(aTransportId);
+  if (stream) {
+    GetIceStats(*stream, aInternalStats, aNow, aReport);
+  }
+}
+
+static void ToRTCIceCandidateStats(
+    const std::vector<NrIceCandidate>& candidates,
+    dom::RTCStatsType candidateType,
+    const nsString& componentId,
+    DOMHighResTimeStamp now,
+    dom::RTCStatsReportInternal* report) {
+
+  MOZ_ASSERT(report);
+  for (const auto& candidate : candidates) {
+    dom::RTCIceCandidateStats cand;
+    cand.mType.Construct(candidateType);
+    NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
+    cand.mComponentId.Construct(componentId);
+    cand.mId.Construct(codeword);
+    cand.mTimestamp.Construct(now);
+    cand.mCandidateType.Construct(
+        dom::RTCStatsIceCandidateType(candidate.type));
+    cand.mIpAddress.Construct(
+        NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
+    cand.mPortNumber.Construct(candidate.cand_addr.port);
+    cand.mTransport.Construct(
+        NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
+    if (candidateType == dom::RTCStatsType::Local_candidate) {
+      cand.mMozLocalTransport.Construct(
+          NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
+      if (dom::RTCStatsIceCandidateType(candidate.type) ==
+            dom::RTCStatsIceCandidateType::Relayed) {
+        cand.mRelayProtocol.Construct(
+            NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
+      }
+    }
+    report->mIceCandidateStats.Value().AppendElement(cand, fallible);
+    if (candidate.trickled) {
+      report->mTrickledIceCandidateStats.Value().AppendElement(cand, fallible);
+    }
+  }
+}
+
+void
+MediaTransportHandler::GetIceStats(
+    const NrIceMediaStream& aStream,
+    bool aInternalStats,
+    DOMHighResTimeStamp aNow,
+    dom::RTCStatsReportInternal* aReport) const
+{
+  NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str());
+
+  std::vector<NrIceCandidatePair> candPairs;
+  nsresult res = aStream.GetCandidatePairs(&candPairs);
+  if (NS_FAILED(res)) {
+    CSFLogError(LOGTAG,
+        "%s: Error getting candidate pairs for transport id \"%s\"",
+        __FUNCTION__, aStream.GetId().c_str());
+    return;
+  }
+
+  for (auto& candPair : candPairs) {
+    NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str());
+    NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str());
+    NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str());
+    // Only expose candidate-pair statistics to chrome, until we've thought
+    // through the implications of exposing it to content.
+
+    dom::RTCIceCandidatePairStats s;
+    s.mId.Construct(codeword);
+    s.mTransportId.Construct(transportId);
+    s.mTimestamp.Construct(aNow);
+    s.mType.Construct(dom::RTCStatsType::Candidate_pair);
+    s.mLocalCandidateId.Construct(localCodeword);
+    s.mRemoteCandidateId.Construct(remoteCodeword);
+    s.mNominated.Construct(candPair.nominated);
+    s.mWritable.Construct(candPair.writable);
+    s.mReadable.Construct(candPair.readable);
+    s.mPriority.Construct(candPair.priority);
+    s.mSelected.Construct(candPair.selected);
+    s.mBytesSent.Construct(candPair.bytes_sent);
+    s.mBytesReceived.Construct(candPair.bytes_recvd);
+    s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send);
+    s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv);
+    s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state));
+    s.mComponentId.Construct(candPair.component_id);
+    aReport->mIceCandidatePairStats.Value().AppendElement(s, fallible);
+  }
+
+  std::vector<NrIceCandidate> candidates;
+  if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) {
+    ToRTCIceCandidateStats(candidates,
+                           dom::RTCStatsType::Local_candidate,
+                           transportId,
+                           aNow,
+                           aReport);
+    // add the local candidates unparsed string to a sequence
+    for (const auto& candidate : candidates) {
+      aReport->mRawLocalCandidates.Value().AppendElement(
+          NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible);
+    }
+  }
+  candidates.clear();
+
+  if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) {
+    ToRTCIceCandidateStats(candidates,
+                           dom::RTCStatsType::Remote_candidate,
+                           transportId,
+                           aNow,
+                           aReport);
+    // add the remote candidates unparsed string to a sequence
+    for (const auto& candidate : candidates) {
+      aReport->mRawRemoteCandidates.Value().AppendElement(
+          NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible);
+    }
+  }
+}
+
+RefPtr<TransportFlow>
+MediaTransportHandler::GetTransportFlow(const std::string& aTransportId,
+                                        bool aIsRtcp) const
+{
+  auto it = mTransports.find(aTransportId);
+  if (it == mTransports.end()) {
+    return nullptr;
+  }
+
+  if (aIsRtcp) {
+    return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow;;
+  }
+
+  return it->second.mFlow;
+}
+
+RefPtr<TransportFlow>
+MediaTransportHandler::CreateTransportFlow(
+    const std::string& aTransportId,
+    bool aIsRtcp,
+    RefPtr<DtlsIdentity> aDtlsIdentity,
+    bool aDtlsClient,
+    const SdpFingerprintAttributeList& aFingerprints,
+    bool aPrivacyRequested)
+{
+  nsresult rv;
+  RefPtr<TransportFlow> flow = new TransportFlow(aTransportId);
+
+  // The media streams are made on STS so we need to defer setup.
+  auto ice = MakeUnique<TransportLayerIce>();
+  auto dtls = MakeUnique<TransportLayerDtls>();
+  auto srtp = MakeUnique<TransportLayerSrtp>(*dtls);
+  dtls->SetRole(aDtlsClient
+                    ? TransportLayerDtls::CLIENT
+                    : TransportLayerDtls::SERVER);
+
+  dtls->SetIdentity(aDtlsIdentity);
+
+  for (const auto& fingerprint : aFingerprints.mFingerprints) {
+    std::ostringstream ss;
+    ss << fingerprint.hashFunc;
+    rv = dtls->SetVerificationDigest(ss.str(), &fingerprint.fingerprint[0],
+                                     fingerprint.fingerprint.size());
+    if (NS_FAILED(rv)) {
+      CSFLogError(LOGTAG, "Could not set fingerprint");
+      return nullptr;
+    }
+  }
+
+  std::vector<uint16_t> srtpCiphers = TransportLayerDtls::GetDefaultSrtpCiphers();
+
+  rv = dtls->SetSrtpCiphers(srtpCiphers);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "Couldn't set SRTP ciphers");
+    return nullptr;
+  }
+
+  // Always permits negotiation of the confidential mode.
+  // Only allow non-confidential (which is an allowed default),
+  // if we aren't confidential.
+  std::set<std::string> alpn;
+  std::string alpnDefault = "";
+  alpn.insert("c-webrtc");
+  if (!aPrivacyRequested) {
+    alpnDefault = "webrtc";
+    alpn.insert(alpnDefault);
+  }
+  rv = dtls->SetAlpn(alpn, alpnDefault);
+  if (NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "Couldn't set ALPN");
+    return nullptr;
+  }
+
+  ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ? 2 : 1);
+  NS_ENSURE_SUCCESS(ice->Init(), nullptr);
+  NS_ENSURE_SUCCESS(dtls->Init(), nullptr);
+  NS_ENSURE_SUCCESS(srtp->Init(), nullptr);
+  dtls->Chain(ice.get());
+  srtp->Chain(ice.get());
+
+  dtls->SignalPacketReceived.connect(
+      this, &MediaTransportHandler::PacketReceived);
+  srtp->SignalPacketReceived.connect(
+      this, &MediaTransportHandler::PacketReceived);
+  ice->SignalPacketSending.connect(
+      this, &MediaTransportHandler::EncryptedPacketSending);
+  flow->PushLayer(ice.release());
+  flow->PushLayer(dtls.release());
+  flow->PushLayer(srtp.release());
+  return flow;
+}
+
+static mozilla::dom::PCImplIceGatheringState
+toDomIceGatheringState(NrIceCtx::GatheringState aState)
+{
+  switch (aState) {
+    case NrIceCtx::ICE_CTX_GATHER_INIT:
+      return dom::PCImplIceGatheringState::New;
+    case NrIceCtx::ICE_CTX_GATHER_STARTED:
+      return dom::PCImplIceGatheringState::Gathering;
+    case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
+      return dom::PCImplIceGatheringState::Complete;
+  }
+  MOZ_CRASH();
+}
+
+void
+MediaTransportHandler::OnGatheringStateChange(NrIceCtx* aIceCtx,
+                                              NrIceCtx::GatheringState aState)
+{
+  if (aState == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+    for (const auto& stream : mIceCtx->GetStreams()) {
+      OnCandidateFound(stream, "");
+    }
+  }
+  SignalGatheringStateChange(toDomIceGatheringState(aState));
+}
+
+static mozilla::dom::PCImplIceConnectionState
+toDomIceConnectionState(NrIceCtx::ConnectionState aState)
+{
+  switch (aState) {
+    case NrIceCtx::ICE_CTX_INIT:
+      return dom::PCImplIceConnectionState::New;
+    case NrIceCtx::ICE_CTX_CHECKING:
+      return dom::PCImplIceConnectionState::Checking;
+    case NrIceCtx::ICE_CTX_CONNECTED:
+      return dom::PCImplIceConnectionState::Connected;
+    case NrIceCtx::ICE_CTX_COMPLETED:
+      return dom::PCImplIceConnectionState::Completed;
+    case NrIceCtx::ICE_CTX_FAILED:
+      return dom::PCImplIceConnectionState::Failed;
+    case NrIceCtx::ICE_CTX_DISCONNECTED:
+      return dom::PCImplIceConnectionState::Disconnected;
+    case NrIceCtx::ICE_CTX_CLOSED:
+      return dom::PCImplIceConnectionState::Closed;
+  }
+  MOZ_CRASH();
+}
+
+void
+MediaTransportHandler::OnConnectionStateChange(NrIceCtx* aIceCtx,
+                                               NrIceCtx::ConnectionState aState)
+{
+  SignalConnectionStateChange(toDomIceConnectionState(aState));
+}
+
+// The stuff below here will eventually go into the MediaTransportChild class
+void
+MediaTransportHandler::OnCandidateFound(NrIceMediaStream* aStream,
+                                        const std::string& aCandidate)
+{
+  CandidateInfo info;
+  info.mCandidate = aCandidate;
+  NrIceCandidate defaultRtpCandidate;
+  NrIceCandidate defaultRtcpCandidate;
+  nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate);
+  if (NS_SUCCEEDED(rv)) {
+    info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host;
+    info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port;
+  } else {
+    CSFLogError(LOGTAG, "%s: GetDefaultCandidates failed for transport id %s, "
+                        "res=%u",
+                        __FUNCTION__,
+                        aStream->GetId().c_str(),
+                        static_cast<unsigned>(rv));
+  }
+
+  // Optional; component won't exist if doing rtcp-mux
+  if (NS_SUCCEEDED(aStream->GetDefaultCandidate(2, &defaultRtcpCandidate))) {
+    info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host;
+    info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port;
+  }
+
+  SignalCandidate(aStream->GetId(), info);
+}
+
+void
+MediaTransportHandler::OnStateChange(TransportLayer* aLayer,
+                                     TransportLayer::State aState)
+{
+  if (aState == TransportLayer::TS_OPEN) {
+    MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
+    TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
+    SignalAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn());
+  }
+
+  // DTLS state indicates the readiness of the transport as a whole, because
+  // SRTP uses the keys from the DTLS handshake.
+  SignalStateChange(aLayer->flow_id(), aState);
+}
+
+void
+MediaTransportHandler::OnRtcpStateChange(TransportLayer* aLayer,
+                                         TransportLayer::State aState)
+{
+  SignalRtcpStateChange(aLayer->flow_id(), aState);
+}
+
+void
+MediaTransportHandler::PacketReceived(TransportLayer* aLayer,
+                                      MediaPacket& aPacket)
+{
+  SignalPacketReceived(aLayer->flow_id(), aPacket);
+}
+
+void
+MediaTransportHandler::EncryptedPacketSending(TransportLayer* aLayer,
+                                              MediaPacket& aPacket)
+{
+  SignalEncryptedSending(aLayer->flow_id(), aPacket);
+}
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/peerconnection/MediaTransportHandler.h
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTHANDLER_H__
+#define _MTRANSPORTHANDLER_H__
+
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "sigslot.h"
+#include "transportlayer.h" // Need the State enum
+#include "mozilla/dom/PeerConnectionImplEnumsBinding.h"
+#include "nricectx.h" // Need some enums
+#include "nsDOMNavigationTiming.h" // DOMHighResTimeStamp
+
+#include <map>
+#include <string>
+#include <set>
+#include <vector>
+
+namespace mozilla {
+class DtlsIdentity; // TODO(bug 1494311) Use IPC type
+class NrIceCtx;
+class NrIceMediaStream;
+class NrIceResolver;
+class SdpFingerprintAttributeList; // TODO(bug 1494311) Use IPC type
+class TransportFlow;
+
+namespace dom {
+struct RTCConfiguration;
+struct RTCStatsReportInternal;
+}
+
+// Base-class, makes some testing easier
+class MediaTransportBase {
+  public:
+    virtual nsresult SendPacket(const std::string& aTransportId,
+                                MediaPacket& aPacket) = 0;
+
+    virtual TransportLayer::State GetState(const std::string& aTransportId,
+                                           bool aRtcp) const = 0;
+    sigslot::signal2<const std::string&, MediaPacket&> SignalPacketReceived;
+    sigslot::signal2<const std::string&, MediaPacket&> SignalEncryptedSending;
+    sigslot::signal2<const std::string&, TransportLayer::State>
+      SignalStateChange;
+    sigslot::signal2<const std::string&, TransportLayer::State>
+      SignalRtcpStateChange;
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaTransportBase)
+
+  protected:
+    virtual ~MediaTransportBase() {}
+};
+
+class MediaTransportHandler : public MediaTransportBase,
+                              public sigslot::has_slots<> {
+  public:
+    MediaTransportHandler();
+    nsresult Init(const std::string& aName,
+                  const dom::RTCConfiguration& aConfiguration);
+    void Destroy();
+
+    // We will probably be able to move the proxy lookup stuff into
+    // this class once we move mtransport to its own process.
+    nsresult SetProxyServer(const std::string& aProxyHost,
+                            uint16_t aProxyPort,
+                            const std::string& aAlpnProtocols);
+
+    void EnsureProvisionalTransport(const std::string& aTransportId,
+                                    const std::string& aLocalUfrag,
+                                    const std::string& aLocalPwd,
+                                    size_t aComponentCount);
+
+    // We set default-route-only as late as possible because it depends on what
+    // capture permissions have been granted on the window, which could easily
+    // change between Init (ie; when the PC is created) and StartIceGathering
+    // (ie; when we set the local description).
+    void StartIceGathering(bool aDefaultRouteOnly,
+                           // This will go away once mtransport moves to its
+                           // own process, because we won't need to get this
+                           // via IPC anymore
+                           const nsTArray<NrIceStunAddr>& aStunAddrs);
+
+
+    nsresult ActivateTransport(const std::string& aTransportId,
+                               const std::string& aLocalUfrag,
+                               const std::string& aLocalPwd,
+                               size_t aComponentCount,
+                               const std::string& aUfrag,
+                               const std::string& aPassword,
+                               const std::vector<std::string>& aCandidateList,
+                               // TODO(bug 1494311): Use an IPC type.
+                               RefPtr<DtlsIdentity> aDtlsIdentity,
+                               bool aDtlsClient,
+                               // TODO(bug 1494311): Use IPC type
+                               const SdpFingerprintAttributeList& aFingerprints,
+                               bool aPrivacyRequested);
+
+    void RemoveTransportsExcept(const std::set<std::string>& aTransportIds);
+
+    nsresult StartIceChecks(bool aIsControlling,
+                            bool aIsOfferer,
+                            const std::vector<std::string>& aIceOptions);
+
+    nsresult AddIceCandidate(const std::string& aTransportId,
+                             const std::string& aCandidate);
+
+    void UpdateNetworkState(bool aOnline);
+
+    nsresult SendPacket(const std::string& aTransportId,
+                        MediaPacket& aPacket) override;
+
+    // TODO(bug 1494312): Figure out how this fits with an async API. Maybe we
+    // cache on the content process.
+    TransportLayer::State GetState(const std::string& aTransportId,
+                                   bool aRtcp) const override;
+
+    // TODO(bug 1494312): Stats stuff needs to be async.
+    void GetAllIceStats(bool internalStats,
+                        DOMHighResTimeStamp now,
+                        dom::RTCStatsReportInternal* report);
+
+    // TODO(bug 1494312): Stats stuff needs to be async.
+    void GetIceStats(const std::string& aTransportId,
+                     bool internalStats,
+                     DOMHighResTimeStamp now,
+                     dom::RTCStatsReportInternal* report);
+
+    // TODO(bug 1494311) Use IPC type
+    struct CandidateInfo {
+      std::string mCandidate;
+      std::string mDefaultHostRtp;
+      uint16_t mDefaultPortRtp = 0;
+      std::string mDefaultHostRtcp;
+      uint16_t mDefaultPortRtcp = 0;
+    };
+
+    sigslot::signal2<const std::string&, const CandidateInfo&> SignalCandidate;
+    sigslot::signal1<const std::string&> SignalAlpnNegotiated;
+    sigslot::signal1<dom::PCImplIceGatheringState> SignalGatheringStateChange;
+    sigslot::signal1<dom::PCImplIceConnectionState> SignalConnectionStateChange;
+
+  private:
+    RefPtr<TransportFlow> CreateTransportFlow(
+        const std::string& aTransportId,
+        bool aIsRtcp,
+        RefPtr<DtlsIdentity> aDtlsIdentity,
+        bool aDtlsClient,
+        // TODO(bug 1494312) Use IPC type
+        const SdpFingerprintAttributeList& aFingerprints,
+        bool aPrivacyRequested);
+
+    struct Transport {
+        RefPtr<TransportFlow> mFlow;
+        RefPtr<TransportFlow> mRtcpFlow;
+    };
+
+    void OnGatheringStateChange(NrIceCtx* aIceCtx,
+                                NrIceCtx::GatheringState aState);
+    void OnConnectionStateChange(NrIceCtx* aIceCtx,
+                                 NrIceCtx::ConnectionState aState);
+    void OnCandidateFound(NrIceMediaStream* aStream,
+                          const std::string& aCandidate);
+    void OnStateChange(TransportLayer* aLayer, TransportLayer::State);
+    void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State);
+    void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket);
+    void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket);
+    RefPtr<TransportFlow> GetTransportFlow(const std::string& aId,
+                                           bool aIsRtcp) const;
+    void GetIceStats(const NrIceMediaStream& aStream,
+                     bool aInternalStats,
+                     DOMHighResTimeStamp aNow,
+                     dom::RTCStatsReportInternal* aReport) const;
+
+
+    ~MediaTransportHandler() override;
+    RefPtr<NrIceCtx> mIceCtx;
+    RefPtr<NrIceResolver> mDNSResolver;
+    std::map<std::string, Transport> mTransports;
+    bool mProxyOnly = false;
+};
+}
+
+#endif //_MTRANSPORTHANDLER_H__
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -314,23 +314,19 @@ bool IsPrivateBrowsing(nsPIDOMWindowInne
 }
 
 PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
 : mTimeCard(MOZ_LOG_TEST(logModuleInfo,LogLevel::Error) ?
             create_timecard() : nullptr)
   , mSignalingState(PCImplSignalingState::SignalingStable)
   , mIceConnectionState(PCImplIceConnectionState::New)
   , mIceGatheringState(PCImplIceGatheringState::New)
-  , mDtlsConnected(false)
   , mWindow(nullptr)
   , mCertificate(nullptr)
-  , mPrivacyRequested(false)
   , mSTSThread(nullptr)
-  , mAllowIceLoopback(false)
-  , mAllowIceLinkLocal(false)
   , mForceIceTcp(false)
   , mMedia(nullptr)
   , mUuidGen(MakeUnique<PCUuidGenerator>())
   , mIceRestartCount(0)
   , mIceRollbackCount(0)
   , mHaveConfiguredCodecs(false)
   , mAddCandidateErrorCount(0)
   , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
@@ -351,20 +347,16 @@ PeerConnectionImpl::PeerConnectionImpl(c
       log->EnterPrivateMode();
     }
     mWindow->AddPeerConnection();
     mActiveOnWindow = true;
   }
   CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s",
              __FUNCTION__, mHandle.c_str());
   STAMP_TIMECARD(mTimeCard, "Constructor Completed");
-  mAllowIceLoopback = Preferences::GetBool(
-    "media.peerconnection.ice.loopback", false);
-  mAllowIceLinkLocal = Preferences::GetBool(
-    "media.peerconnection.ice.link_local", false);
   mForceIceTcp = Preferences::GetBool(
     "media.peerconnection.ice.force_ice_tcp", false);
   memset(mMaxReceiving, 0, sizeof(mMaxReceiving));
   memset(mMaxSending, 0, sizeof(mMaxSending));
 }
 
 PeerConnectionImpl::~PeerConnectionImpl()
 {
@@ -502,19 +494,16 @@ PeerConnectionImpl::Initialize(PeerConne
 
   // Connect ICE slots.
   mMedia->SignalIceGatheringStateChange.connect(
       this,
       &PeerConnectionImpl::IceGatheringStateChange);
   mMedia->SignalUpdateDefaultCandidate.connect(
       this,
       &PeerConnectionImpl::UpdateDefaultCandidate);
-  mMedia->SignalEndOfLocalCandidates.connect(
-      this,
-      &PeerConnectionImpl::EndOfLocalCandidates);
   mMedia->SignalIceConnectionStateChange.connect(
       this,
       &PeerConnectionImpl::IceConnectionStateChange);
 
   mMedia->SignalCandidate.connect(this, &PeerConnectionImpl::CandidateReady);
 
   // Initialize the media object.
   res = mMedia->Init(aConfiguration);
@@ -577,17 +566,17 @@ PeerConnectionImpl::Initialize(PeerConne
   nsresult res = Initialize(aObserver, &aWindow, aConfiguration, aThread);
   if (NS_FAILED(res)) {
     rv.Throw(res);
     return;
   }
 
   if (!aConfiguration.mPeerIdentity.IsEmpty()) {
     mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
-    mPrivacyRequested = true;
+    mPrivacyRequested = Some(true);
   }
 }
 
 void
 PeerConnectionImpl::SetCertificate(mozilla::dom::RTCCertificate& aCertificate)
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
   MOZ_ASSERT(!mCertificate, "This can only be called once");
@@ -917,34 +906,36 @@ PeerConnectionImpl::EnsureDataConnection
     CSFLogDebug(LOGTAG,"%s DataConnection already connected",__FUNCTION__);
     mDataConnection->SetMaxMessageSize(aMMSSet, aMaxMessageSize);
     return NS_OK;
   }
 
   nsCOMPtr<nsIEventTarget> target = mWindow
       ? mWindow->EventTargetFor(TaskCategory::Other)
       : nullptr;
-  mDataConnection = new DataChannelConnection(this, target);
+  mDataConnection = new DataChannelConnection(
+      this, target, mMedia->mTransportHandler);
   if (!mDataConnection->Init(aLocalPort, aNumstreams, aMMSSet, aMaxMessageSize)) {
     CSFLogError(LOGTAG,"%s DataConnection Init Failed",__FUNCTION__);
     return NS_ERROR_FAILURE;
   }
   CSFLogDebug(LOGTAG,"%s DataChannelConnection %p attached to %s",
               __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str());
   return NS_OK;
 }
 
 nsresult
 PeerConnectionImpl::GetDatachannelParameters(
     uint32_t* channels,
     uint16_t* localport,
     uint16_t* remoteport,
     uint32_t* remotemaxmessagesize,
     bool*     mmsset,
-    std::string* transportId) const {
+    std::string* transportId,
+    bool* client) const {
 
   for (const auto& transceiver : mJsepSession->GetTransceivers()) {
     bool dataChannel =
       transceiver->GetMediaType() == SdpMediaSection::kApplication;
 
     if (dataChannel && transceiver->mSendTrack.GetNegotiatedDetails()) {
       // This will release assert if there is no such index, and that's ok
       const JsepTrackEncoding& encoding =
@@ -985,16 +976,19 @@ PeerConnectionImpl::GetDatachannelParame
         *remoteport =
           static_cast<const JsepApplicationCodecDescription*>(codec)->mRemotePort;
         *remotemaxmessagesize = static_cast<const JsepApplicationCodecDescription*>
           (codec)->mRemoteMaxMessageSize;
         *mmsset = static_cast<const JsepApplicationCodecDescription*>
           (codec)->mRemoteMMSSet;
         MOZ_ASSERT(!transceiver->mTransport.mTransportId.empty());
         *transportId = transceiver->mTransport.mTransportId;
+        *client =
+          transceiver->mTransport.mDtls->GetRole() ==
+              JsepDtlsTransport::kJsepDtlsClient;
         return NS_OK;
       }
     }
   }
 
   *channels = 0;
   *localport = 0;
   *remoteport = 0;
@@ -1110,40 +1104,34 @@ PeerConnectionImpl::InitializeDataChanne
   CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
 
   uint32_t channels = 0;
   uint16_t localport = 0;
   uint16_t remoteport = 0;
   uint32_t remotemaxmessagesize = 0;
   bool mmsset = false;
   std::string transportId;
+  bool client = false;
   nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport,
-                                         &remotemaxmessagesize, &mmsset, &transportId);
+      &remotemaxmessagesize, &mmsset, &transportId, &client);
 
   if (NS_FAILED(rv)) {
     CSFLogDebug(LOGTAG, "%s: We did not negotiate datachannel", __FUNCTION__);
     return NS_OK;
   }
 
   if (channels > MAX_NUM_STREAMS) {
     channels = MAX_NUM_STREAMS;
   }
 
   rv = EnsureDataConnection(localport, channels, remotemaxmessagesize, mmsset);
   if (NS_SUCCEEDED(rv)) {
-    // use the specified TransportFlow
-    RefPtr<TransportFlow> flow = mMedia->GetTransportFlow(transportId, false).get();
-    CSFLogDebug(LOGTAG, "Transportflow[%s] = %p",
-                        transportId.c_str(), flow.get());
-    if (flow) {
-      if (mDataConnection->ConnectViaTransportFlow(flow,
-                                                   localport,
-                                                   remoteport)) {
-        return NS_OK;
-      }
+    if (mDataConnection->ConnectToTransport(
+          transportId, client, localport, remoteport)) {
+      return NS_OK;
     }
     // If we inited the DataConnection, call Destroy() before releasing it
     mDataConnection->Destroy();
   }
   mDataConnection = nullptr;
   return NS_ERROR_FAILURE;
 }
 
@@ -1438,18 +1426,19 @@ PeerConnectionImpl::SetLocalDescription(
   JSErrorResult rv;
   RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (!pco) {
     return NS_OK;
   }
 
   STAMP_TIMECARD(mTimeCard, "Set Local Description");
 
-  bool isolated = mMedia->AnyLocalTrackHasPeerIdentity();
-  mPrivacyRequested = mPrivacyRequested || isolated;
+  if (mMedia->AnyLocalTrackHasPeerIdentity()) {
+    mPrivacyRequested = Some(true);
+  }
 
   mLocalRequestedSDP = aSDP;
 
   bool wasRestartingIce = mJsepSession->IsIceRestarting();
   JsepSdpType sdpType;
   switch (aAction) {
     case IPeerConnection::kActionOffer:
       sdpType = mozilla::kJsepSdpOffer;
@@ -1815,35 +1804,39 @@ PeerConnectionImpl::SetPeerIdentity(cons
     }
     MediaStreamTrack* allTracks = nullptr;
     mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(), mPeerIdentity);
   }
   return NS_OK;
 }
 
 nsresult
-PeerConnectionImpl::SetDtlsConnected(bool aPrivacyRequested)
+PeerConnectionImpl::OnAlpnNegotiated(const std::string& aAlpn)
 {
   PC_AUTO_ENTER_API_CALL(false);
+  if (mPrivacyRequested.isSome()) {
+    return NS_OK;
+  }
+
+  mPrivacyRequested = Some(aAlpn == "c-webrtc");
 
   // For this, as with mPrivacyRequested, once we've connected to a peer, we
   // fixate on that peer.  Dealing with multiple peers or connections is more
   // than this run-down wreck of an object can handle.
   // Besides, this is only used to say if we have been connected ever.
-  if (!mPrivacyRequested && !aPrivacyRequested && !mDtlsConnected) {
-    // now we know that privacy isn't needed for sure
+  if (!*mPrivacyRequested) {
+    // Neither side wants privacy
     nsIDocument* doc = GetWindow()->GetExtantDoc();
     if (!doc) {
       CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone");
       return NS_ERROR_FAILURE;
     }
     mMedia->UpdateRemoteStreamPrincipals_m(doc->NodePrincipal());
   }
-  mDtlsConnected = true;
-  mPrivacyRequested = mPrivacyRequested || aPrivacyRequested;
+
   return NS_OK;
 }
 
 void
 PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) {
   nsIDocument* doc = GetWindow()->GetExtantDoc();
   if (doc) {
     mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity);
@@ -2064,17 +2057,17 @@ PeerConnectionImpl::CreateReceiveTrack(S
   CSFLogDebug(LOGTAG, "Created media stream %p, inner: %p", stream.get(), stream->GetInputStream());
 
   // Set the principal used for creating the tracks. This makes the stream
   // data (audio/video samples) accessible to the receiving page. We're
   // only certain that privacy hasn't been requested if we're connected.
   nsCOMPtr<nsIPrincipal> principal;
   nsIDocument* doc = GetWindow()->GetExtantDoc();
   MOZ_ASSERT(doc);
-  if (mDtlsConnected && !PrivacyRequested()) {
+  if (mPrivacyRequested.isSome() && !*mPrivacyRequested) {
     principal = doc->NodePrincipal();
   } else {
     // we're either certain that we need isolation for the streams, OR
     // we're not sure and we can fix the stream in SetDtlsConnected
     principal =  NullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal());
   }
 
   RefPtr<MediaStreamTrack> track;
@@ -2664,16 +2657,21 @@ PeerConnectionImpl::GetName()
   return mName;
 }
 
 void
 PeerConnectionImpl::CandidateReady(const std::string& candidate,
                                    const std::string& transportId) {
   PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
 
+  if (candidate.empty()) {
+    mJsepSession->EndOfLocalCandidates(transportId);
+    return;
+  }
+
   if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) {
     CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str());
     return;
   }
 
   // One of the very few places we still use level; required by the JSEP API
   uint16_t level = 0;
   std::string mid;
@@ -2881,22 +2879,16 @@ PeerConnectionImpl::UpdateDefaultCandida
   CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
   mJsepSession->UpdateDefaultCandidate(defaultAddr,
                                        defaultPort,
                                        defaultRtcpAddr,
                                        defaultRtcpPort,
                                        transportId);
 }
 
-void
-PeerConnectionImpl::EndOfLocalCandidates(const std::string& transportId) {
-  CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
-  mJsepSession->EndOfLocalCandidates(transportId);
-}
-
 nsresult
 PeerConnectionImpl::BuildStatsQuery_m(
     mozilla::dom::MediaStreamTrack *aSelector,
     RTCStatsQuery *query) {
 
   if (!HasMedia()) {
     return NS_ERROR_UNEXPECTED;
   }
@@ -2907,20 +2899,16 @@ PeerConnectionImpl::BuildStatsQuery_m(
   }
 
   nsresult rv = GetTimeSinceEpoch(&(query->now));
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG, "Could not build stats query, could not get timestamp");
     return rv;
   }
 
-  // Note: mMedia->ice_ctx() is deleted on STS thread; so make sure we grab and hold
-  // a ref instead of making multiple calls.  NrIceCtx uses threadsafe refcounting.
-  // NOTE: Do this after all other failure tests, to ensure we don't
-  // accidentally release the Ctx on Mainthread.
   query->media = mMedia;
   if (!query->media) {
     CSFLogError(LOGTAG, "Could not build stats query, no ice_ctx");
     return NS_ERROR_UNEXPECTED;
   }
 
   // We do not use the pcHandle here, since that's risky to expose to content.
   query->report = new RTCStatsReportInternalConstruct(
@@ -3194,25 +3182,27 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
         // Fill in Contributing Source statistics
         mp.GetContributingSourceStats(localId,
             query->report->mRtpContributingSourceStats.Value());
         break;
       }
     }
   }
 
-  if (query->grabAllLevels) {
-    query->media->GetAllIceStats_s(query->internalStats,
-                                   query->now,
-                                   query->report);
-  } else {
-    query->media->GetIceStats_s(query->transportId,
-                                query->internalStats,
-                                query->now,
-                                query->report);
+  if (query->media->mTransportHandler) {
+    if (query->grabAllLevels) {
+      query->media->mTransportHandler->GetAllIceStats(query->internalStats,
+                                                      query->now,
+                                                      query->report);
+    } else {
+      query->media->mTransportHandler->GetIceStats(query->transportId,
+                                                   query->internalStats,
+                                                   query->now,
+                                                   query->report);
+    }
   }
 
   return NS_OK;
 }
 
 void PeerConnectionImpl::GetStatsForPCObserver_s(
     const std::string& pcHandle, // The Runnable holds the memory
     nsAutoPtr<RTCStatsQuery> query) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -206,39 +206,30 @@ public:
     ;
 
   // Get the media object
   const RefPtr<PeerConnectionMedia>& media() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mMedia;
   }
 
-  // Configure the ability to use localhost.
-  void SetAllowIceLoopback(bool val) { mAllowIceLoopback = val; }
-  bool GetAllowIceLoopback() const { return mAllowIceLoopback; }
-
-  // Configure the ability to use IPV6 link-local addresses.
-  void SetAllowIceLinkLocal(bool val) { mAllowIceLinkLocal = val; }
-  bool GetAllowIceLinkLocal() const { return mAllowIceLinkLocal; }
-
   // Handle system to allow weak references to be passed through C code
   virtual const std::string& GetHandle();
 
   // Name suitable for exposing to content
   virtual const std::string& GetName();
 
   // ICE events
   void IceConnectionStateChange(dom::PCImplIceConnectionState state);
   void IceGatheringStateChange(dom::PCImplIceGatheringState state);
   void UpdateDefaultCandidate(const std::string& defaultAddr,
                               uint16_t defaultPort,
                               const std::string& defaultRtcpAddr,
                               uint16_t defaultRtcpPort,
                               const std::string& transportId);
-  void EndOfLocalCandidates(const std::string& transportId);
 
   static void ListenThread(void *aData);
   static void ConnectThread(void *aData);
 
   // Get the main thread
   nsCOMPtr<nsIThread> GetMainThread() {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mThread;
@@ -442,17 +433,20 @@ public:
   }
 
   void SetId(const nsAString& id)
   {
     mName = NS_ConvertUTF16toUTF8(id).get();
   }
 
   // this method checks to see if we've made a promise to protect media.
-  bool PrivacyRequested() const { return mPrivacyRequested; }
+  bool PrivacyRequested() const
+  {
+    return mPrivacyRequested.isSome() && *mPrivacyRequested;
+  }
 
   NS_IMETHODIMP GetFingerprint(char** fingerprint);
   void GetFingerprint(nsAString& fingerprint)
   {
     char *tmp;
     GetFingerprint(&tmp);
     fingerprint.AssignASCII(tmp);
     delete[] tmp;
@@ -536,17 +530,17 @@ public:
   void SetSignalingState_m(mozilla::dom::PCImplSignalingState aSignalingState,
                            bool rollback = false);
 
   // Updates the RTC signaling state based on the JsepSession state
   void UpdateSignalingState(bool rollback = false);
 
   bool IsClosed() const;
   // called when DTLS connects; we only need this once
-  nsresult SetDtlsConnected(bool aPrivacyRequested);
+  nsresult OnAlpnNegotiated(const std::string& aAlpn);
 
   bool HasMedia() const;
 
   // initialize telemetry for when calls start
   void startCallTelem();
 
   nsresult BuildStatsQuery_m(
       mozilla::dom::MediaStreamTrack *aSelector,
@@ -606,17 +600,18 @@ private:
                                       const std::string& candidate);
 
   nsresult GetDatachannelParameters(
       uint32_t* channels,
       uint16_t* localport,
       uint16_t* remoteport,
       uint32_t* maxmessagesize,
       bool*     mmsset,
-      std::string* transportId) const;
+      std::string* transportId,
+      bool* client) const;
 
   nsresult AddRtpTransceiverToJsepSession(RefPtr<JsepTransceiver>& transceiver);
   already_AddRefed<TransceiverImpl> CreateTransceiverImpl(
       JsepTransceiver* aJsepTransceiver,
       dom::MediaStreamTrack* aSendTrack,
       ErrorResult& aRv);
 
   static void GetStatsForPCObserver_s(
@@ -644,20 +639,16 @@ private:
   Timecard *mTimeCard;
 
   mozilla::dom::PCImplSignalingState mSignalingState;
 
   // ICE State
   mozilla::dom::PCImplIceConnectionState mIceConnectionState;
   mozilla::dom::PCImplIceGatheringState mIceGatheringState;
 
-  // DTLS
-  // this is true if we have been connected ever, see SetDtlsConnected
-  bool mDtlsConnected;
-
   nsCOMPtr<nsIThread> mThread;
   // TODO: Remove if we ever properly wire PeerConnection for cycle-collection.
   nsWeakPtr mPCObserver;
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 
   // The SDP sent in from JS
   std::string mLocalRequestedSDP;
@@ -674,32 +665,30 @@ private:
   // The certificate we are using.
   RefPtr<mozilla::dom::RTCCertificate> mCertificate;
   // Whether an app should be prevented from accessing media produced by the PC
   // If this is true, then media will not be sent until mPeerIdentity matches
   // local streams PeerIdentity; and remote streams are protected from content
   //
   // This can be false if mPeerIdentity is set, in the case where identity is
   // provided, but the media is not protected from the app on either side
-  bool mPrivacyRequested;
+  Maybe<bool> mPrivacyRequested;
 
   // A handle to refer to this PC with
   std::string mHandle;
 
   // A name for this PC that we are willing to expose to content.
   std::string mName;
 
   // The target to run stuff on
   nsCOMPtr<nsIEventTarget> mSTSThread;
 
   // DataConnection that's used to get all the DataChannels
   RefPtr<mozilla::DataChannelConnection> mDataConnection;
 
-  bool mAllowIceLoopback;
-  bool mAllowIceLinkLocal;
   bool mForceIceTcp;
   RefPtr<PeerConnectionMedia> mMedia;
 
   // The JSEP negotiation session.
   mozilla::UniquePtr<PCUuidGenerator> mUuidGen;
   mozilla::UniquePtr<mozilla::JsepSession> mJsepSession;
   unsigned long mIceRestartCount;
   unsigned long mIceRollbackCount;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -1,51 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include <ostream>
-#include <string>
-#include <vector>
-
 #include "CSFLog.h"
 
-#include "nspr.h"
-
-#include "nricectx.h"
-#include "nricemediastream.h"
 #include "MediaPipelineFilter.h"
 #include "MediaPipeline.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionMedia.h"
 #include "runnable_utils.h"
-#include "transportlayerice.h"
-#include "transportlayerdtls.h"
-#include "transportlayersrtp.h"
 #include "signaling/src/jsep/JsepSession.h"
 #include "signaling/src/jsep/JsepTransport.h"
-#include "signaling/src/mediapipeline/TransportLayerPacketDumper.h"
-#include "signaling/src/peerconnection/PacketDumper.h"
 
 #include "nsContentUtils.h"
-#include "nsNetCID.h"
-#include "nsNetUtil.h"
 #include "nsIURI.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsICancelable.h"
 #include "nsILoadInfo.h"
 #include "nsIContentPolicy.h"
 #include "nsIProxyInfo.h"
 #include "nsIProtocolProxyService.h"
 
-#include "nsProxyRelease.h"
-
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
-#include "mozilla/Telemetry.h"
 #include "MediaManager.h"
 #include "WebrtcGmpVideoCodec.h"
 
 namespace mozilla {
 using namespace dom;
 
 static const char* pcmLogTag = "PeerConnectionMedia";
 #ifdef LOGTAG
@@ -93,31 +75,19 @@ PeerConnectionMedia::ProtocolProxyQueryH
   }
 
   rv = proxyinfo.GetPort(&httpsProxyPort);
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG, "%s: Failed to get proxy server port", __FUNCTION__);
     return;
   }
 
-  if (pcm_->mIceCtx.get()) {
-    assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
-    // Note that this could check if PrivacyRequested() is set on the PC and
-    // remove "webrtc" from the ALPN list.  But that would only work if the PC
-    // was constructed with a peerIdentity constraint, not when isolated
-    // streams are added.  If we ever need to signal to the proxy that the
-    // media is isolated, then we would need to restructure this code.
-    pcm_->mProxyServer.reset(
-      new NrIceProxyServer(httpsProxyHost.get(),
-                           static_cast<uint16_t>(httpsProxyPort),
-                           "webrtc,c-webrtc"));
-  } else {
-    CSFLogError(LOGTAG, "%s: Failed to set proxy server (ICE ctx unavailable)",
-        __FUNCTION__);
-  }
+  assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16));
+  pcm_->mProxyHost = httpsProxyHost.get();
+  pcm_->mProxyPort = static_cast<uint16_t>(httpsProxyPort);
 }
 
 NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback)
 
 void
 PeerConnectionMedia::StunAddrsHandler::OnStunAddrsAvailable(
     const mozilla::net::NrIceStunAddrArray& addrs)
 {
@@ -134,22 +104,20 @@ PeerConnectionMedia::StunAddrsHandler::O
       pcm_->SignalIceConnectionStateChange(dom::PCImplIceConnectionState::Failed);
     }
 
     pcm_ = nullptr;
   }
 }
 
 PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent)
-    : mParent(parent),
+    : mTransportHandler(nullptr),
+      mParent(parent),
       mParentHandle(parent->GetHandle()),
       mParentName(parent->GetName()),
-      mIceCtx(nullptr),
-      mDNSResolver(new NrIceResolver()),
-      mUuidGen(MakeUnique<PCUuidGenerator>()),
       mMainThread(mParent->GetMainThread()),
       mSTSThread(mParent->GetSTSThread()),
       mProxyResolveCompleted(false),
       mLocalAddrsCompleted(false) {
 }
 
 PeerConnectionMedia::~PeerConnectionMedia()
 {
@@ -234,293 +202,85 @@ PeerConnectionMedia::InitProxy()
   if (NS_FAILED(rv)) {
     CSFLogError(LOGTAG, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv);
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
-static nsresult addNrIceServer(const nsString& aIceUrl,
-                               const dom::RTCIceServer& aIceServer,
-                               std::vector<NrIceStunServer>* aStunServersOut,
-                               std::vector<NrIceTurnServer>* aTurnServersOut)
-{
-  // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than
-  // nsStandardURL. To parse STUN/TURN URI's to spec
-  // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3
-  // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3
-  // we parse out the query-string, and use ParseAuthority() on the rest
-  RefPtr<nsIURI> url;
-  nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl);
-  NS_ENSURE_SUCCESS(rv, rv);
-  bool isStun = false, isStuns = false, isTurn = false, isTurns = false;
-  url->SchemeIs("stun", &isStun);
-  url->SchemeIs("stuns", &isStuns);
-  url->SchemeIs("turn", &isTurn);
-  url->SchemeIs("turns", &isTurns);
-  if (!(isStun || isStuns || isTurn || isTurns)) {
-    return NS_ERROR_FAILURE;
-  }
-  if (isStuns) {
-    return NS_OK; // TODO: Support STUNS (Bug 1056934)
-  }
-
-  nsAutoCString spec;
-  rv = url->GetSpec(spec);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509)
-  int32_t port;
-  nsAutoCString host;
-  nsAutoCString transport;
-  {
-    uint32_t hostPos;
-    int32_t hostLen;
-    nsAutoCString path;
-    rv = url->GetPathQueryRef(path);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Tolerate query-string + parse 'transport=[udp|tcp]' by hand.
-    int32_t questionmark = path.FindChar('?');
-    if (questionmark >= 0) {
-      const nsCString match = NS_LITERAL_CSTRING("transport=");
-
-      for (int32_t i = questionmark, endPos; i >= 0; i = endPos) {
-        endPos = path.FindCharInSet("&", i + 1);
-        const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1,
-            endPos);
-        if (StringBeginsWith(fieldvaluepair, match)) {
-          transport = Substring(fieldvaluepair, match.Length());
-          ToLowerCase(transport);
-        }
-      }
-      path.SetLength(questionmark);
-    }
-
-    rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(),
-        nullptr,  nullptr,
-        nullptr,  nullptr,
-        &hostPos,  &hostLen, &port);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!hostLen) {
-      return NS_ERROR_FAILURE;
-    }
-    if (hostPos > 1) {
-       /* The username was removed */
-      return NS_ERROR_FAILURE;
-    }
-    path.Mid(host, hostPos, hostLen);
-  }
-  if (port == -1) {
-    port = (isStuns || isTurns)? 5349 : 3478;
-  }
-
-  if (isStuns || isTurns) {
-    // Should we barf if transport is set to udp or something?
-    transport = kNrIceTransportTls;
-  }
-
-  if (transport.IsEmpty()) {
-    transport = kNrIceTransportUdp;
-  }
-
-  if (isTurn || isTurns) {
-    std::string pwd(NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get());
-    std::string username(NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get());
-
-    std::vector<unsigned char> password(pwd.begin(), pwd.end());
-
-    UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(host.get(), port, username, password, transport.get()));
-    if (!server) {
-      return NS_ERROR_FAILURE;
-    }
-    aTurnServersOut->emplace_back(std::move(*server));
-  } else {
-    UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(host.get(), port, transport.get()));
-    if (!server) {
-      return NS_ERROR_FAILURE;
-    }
-    aStunServersOut->emplace_back(std::move(*server));
-  }
-  return NS_OK;
-}
-
-static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy)
-{
-  switch (aPolicy) {
-    case dom::RTCIceTransportPolicy::Relay:
-      return NrIceCtx::ICE_POLICY_RELAY;
-    case dom::RTCIceTransportPolicy::All:
-      if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) {
-        return NrIceCtx::ICE_POLICY_NO_HOST;
-      } else {
-        return NrIceCtx::ICE_POLICY_ALL;
-      }
-    default:
-      MOZ_CRASH();
-  }
-  return NrIceCtx::ICE_POLICY_ALL;
-}
-
 nsresult PeerConnectionMedia::Init(const dom::RTCConfiguration& aConfiguration)
 {
   nsresult rv = InitProxy();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false);
-
   // setup the stun local addresses IPC async call
   InitLocalAddrs();
 
-  NrIceCtx::InitializeGlobals(mParent->GetAllowIceLoopback(),
-      ice_tcp,
-      mParent->GetAllowIceLinkLocal());
-
-  // TODO(ekr@rtfm.com): need some way to set not offerer later
-  // Looks like a bug in the NrIceCtx API.
-  mIceCtx = NrIceCtx::Create("PC:" + mParentName,
-      toNrIcePolicy(aConfiguration.mIceTransportPolicy));
-  if(!mIceCtx) {
-    CSFLogError(LOGTAG, "%s: Failed to create Ice Context", __FUNCTION__);
+  mTransportHandler = new MediaTransportHandler;
+  rv = mTransportHandler->Init("PC:" + mParentName, aConfiguration);
+  if(NS_FAILED(rv)) {
+    CSFLogError(LOGTAG, "%s: Failed to init mtransport", __FUNCTION__);
     return NS_ERROR_FAILURE;
   }
 
-  std::vector<NrIceStunServer> stunServers;
-  std::vector<NrIceTurnServer> turnServers;
-
-  if (aConfiguration.mIceServers.WasPassed()) {
-    for (const auto& iceServer : aConfiguration.mIceServers.Value()) {
-      NS_ENSURE_STATE(iceServer.mUrls.WasPassed());
-      NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence());
-      for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) {
-        rv = addNrIceServer(iceUrl, iceServer, &stunServers, &turnServers);
-        if (NS_FAILED(rv)) {
-          CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s",
-                      __FUNCTION__, NS_ConvertUTF16toUTF8(iceUrl).get());
-          return rv;
-        }
-      }
-    }
-  }
-
-  if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) {
-    CSFLogError(LOGTAG, "%s: Failed to set stun servers", __FUNCTION__);
-    return rv;
-  }
-  // Give us a way to globally turn off TURN support
-  bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false);
-  if (!disabled) {
-    if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) {
-      CSFLogError(LOGTAG, "%s: Failed to set turn servers", __FUNCTION__);
-      return rv;
-    }
-  } else if (!turnServers.empty()) {
-    CSFLogError(LOGTAG, "%s: Setting turn servers disabled", __FUNCTION__);
-  }
-  if (NS_FAILED(rv = mDNSResolver->Init())) {
-    CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver", __FUNCTION__);
-    return rv;
-  }
-  if (NS_FAILED(rv =
-        mIceCtx->SetResolver(mDNSResolver->AllocateResolver()))) {
-    CSFLogError(LOGTAG, "%s: Failed to get dns resolver", __FUNCTION__);
-    return rv;
-  }
-  ConnectSignals(mIceCtx.get());
+  ConnectSignals();
   return NS_OK;
 }
 
 void
 PeerConnectionMedia::EnsureTransports(const JsepSession& aSession)
 {
   for (const auto& transceiver : aSession.GetTransceivers()) {
     if (transceiver->HasOwnTransport()) {
       RUN_ON_THREAD(
           GetSTSThread(),
-          WrapRunnable(RefPtr<PeerConnectionMedia>(this),
-                       &PeerConnectionMedia::EnsureTransport_s,
+          WrapRunnable(mTransportHandler,
+                       &MediaTransportHandler::EnsureProvisionalTransport,
                        transceiver->mTransport.mTransportId,
                        transceiver->mTransport.mLocalUfrag,
                        transceiver->mTransport.mLocalPwd,
                        transceiver->mTransport.mComponents),
           NS_DISPATCH_NORMAL);
     }
   }
 
   GatherIfReady();
 }
 
-void
-PeerConnectionMedia::EnsureTransport_s(const std::string& aTransportId,
-                                       const std::string& aUfrag,
-                                       const std::string& aPwd,
-                                       size_t aComponentCount)
-{
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
-  if (!stream) {
-    CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%u",
-                mParentHandle.c_str(),
-                aTransportId.c_str(),
-                static_cast<unsigned>(aComponentCount));
-
-    std::ostringstream os;
-    os << mParentName << " transport-id=" << aTransportId;
-    stream = mIceCtx->CreateStream(aTransportId,
-                                   os.str(),
-                                   aComponentCount);
-
-    if (!stream) {
-      CSFLogError(LOGTAG, "Failed to create ICE stream.");
-      return;
-    }
-
-    stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
-    stream->SignalCandidate.connect(this,
-                                    &PeerConnectionMedia::OnCandidateFound_s);
-  }
-  // This might begin an ICE restart
-  stream->SetIceCredentials(aUfrag, aPwd);
-}
-
 nsresult
 PeerConnectionMedia::UpdateTransports(const JsepSession& aSession,
                                       const bool forceIceTcp)
 {
   std::set<std::string> finalTransports;
   for (const auto& transceiver : aSession.GetTransceivers()) {
     if (transceiver->HasOwnTransport()) {
       finalTransports.insert(transceiver->mTransport.mTransportId);
       UpdateTransport(*transceiver, forceIceTcp);
     }
   }
 
   RUN_ON_THREAD(
       GetSTSThread(),
-      WrapRunnable(RefPtr<PeerConnectionMedia>(this),
-                   &PeerConnectionMedia::RemoveTransportsExcept_s,
+      WrapRunnable(mTransportHandler,
+                   &MediaTransportHandler::RemoveTransportsExcept,
                    finalTransports),
       NS_DISPATCH_NORMAL);
 
   for (const auto& transceiverImpl : mTransceivers) {
-    transceiverImpl->UpdateTransport(*this);
+    transceiverImpl->UpdateTransport();
   }
 
   return NS_OK;
 }
 
-nsresult
+void
 PeerConnectionMedia::UpdateTransport(const JsepTransceiver& aTransceiver,
                                      bool aForceIceTcp)
 {
-  nsresult rv = UpdateTransportFlows(aTransceiver);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
   std::string ufrag;
   std::string pwd;
   std::vector<std::string> candidates;
   size_t components = 0;
 
   const JsepTransport& transport = aTransceiver.mTransport;
   unsigned level = aTransceiver.GetLevel();
 
@@ -538,82 +298,31 @@ PeerConnectionMedia::UpdateTransport(con
                                     [](const std::string & s) {
                                       return s.find(" UDP ") != std::string::npos ||
                                              s.find(" udp ") != std::string::npos; }),
                      candidates.end());
   }
 
   RUN_ON_THREAD(
       GetSTSThread(),
-      WrapRunnable(RefPtr<PeerConnectionMedia>(this),
-                   &PeerConnectionMedia::ActivateTransport_s,
+      WrapRunnable(mTransportHandler,
+                   &MediaTransportHandler::ActivateTransport,
                    transport.mTransportId,
                    transport.mLocalUfrag,
                    transport.mLocalPwd,
                    components,
                    ufrag,
                    pwd,
-                   candidates),
+                   candidates,
+                   mParent->Identity(),
+                   transport.mDtls->GetRole() ==
+                       JsepDtlsTransport::kJsepDtlsClient,
+                   transport.mDtls->GetFingerprints(),
+                   mParent->PrivacyRequested()),
       NS_DISPATCH_NORMAL);
-
-  return NS_OK;
-}
-
-void
-PeerConnectionMedia::ActivateTransport_s(
-    const std::string& aTransportId,
-    const std::string& aLocalUfrag,
-    const std::string& aLocalPwd,
-    size_t aComponentCount,
-    const std::string& aUfrag,
-    const std::string& aPassword,
-    const std::vector<std::string>& aCandidateList) {
-
-  MOZ_ASSERT(aComponentCount);
-
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
-  if (!stream) {
-    MOZ_ASSERT(false);
-    return;
-  }
-
-  CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u",
-              mParentHandle.c_str(),
-              aTransportId.c_str(),
-              static_cast<unsigned>(aComponentCount));
-
-  std::vector<std::string> attrs;
-  attrs.reserve(aCandidateList.size() + 2 /* ufrag + pwd */);
-  for (const auto& candidate : aCandidateList) {
-    attrs.push_back("candidate:" + candidate);
-  }
-  attrs.push_back("ice-ufrag:" + aUfrag);
-  attrs.push_back("ice-pwd:" + aPassword);
-
-  nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u",
-                        static_cast<unsigned>(rv));
-  }
-
-  for (size_t c = aComponentCount; c < stream->components(); ++c) {
-    // components are 1-indexed
-    stream->DisableComponent(c + 1);
-  }
-}
-
-void
-PeerConnectionMedia::RemoveTransportsExcept_s(
-    const std::set<std::string>& aIds)
-{
-  for (const auto& stream : mIceCtx->GetStreams()) {
-    if (!aIds.count(stream->GetId())) {
-      mIceCtx->DestroyStream(stream->GetId());
-    }
-  }
 }
 
 nsresult
 PeerConnectionMedia::UpdateMediaPipelines()
 {
   // The GMP code is all the way on the other side of webrtc.org, and it is not
   // feasible to plumb error information all the way back. So, we set up a
   // handle to the PC (for the duration of this call) in a global variable.
@@ -634,156 +343,16 @@ PeerConnectionMedia::UpdateMediaPipeline
       // TODO: If there is no audio, we should probably de-sync. However, this
       // has never been done before, and it is unclear whether it is safe...
     }
   }
 
   return NS_OK;
 }
 
-nsresult
-PeerConnectionMedia::UpdateTransportFlows(const JsepTransceiver& aTransceiver)
-{
-  nsresult rv = UpdateTransportFlow(false, aTransceiver.mTransport);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  return UpdateTransportFlow(true, aTransceiver.mTransport);
-}
-
-// Accessing the PCMedia should be safe here because we shouldn't
-// have enqueued this function unless it was still active and
-// the ICE data is destroyed on the STS.
-static void
-FinalizeTransportFlow_s(const RefPtr<NrIceCtx>& aIceCtx,
-                        nsAutoPtr<PacketDumper> aPacketDumper,
-                        const RefPtr<TransportFlow>& aFlow,
-                        const std::string& aId,
-                        bool aIsRtcp,
-                        TransportLayerIce* aIceLayer,
-                        TransportLayerDtls* aDtlsLayer,
-                        TransportLayerSrtp* aSrtpLayer)
-{
-  TransportLayerPacketDumper* srtpDumper(new TransportLayerPacketDumper(
-        std::move(aPacketDumper), dom::mozPacketDumpType::Srtp));
-
-  aIceLayer->SetParameters(aIceCtx->GetStream(aId), aIsRtcp ? 2 : 1);
-  // TODO(bug 854518): Process errors.
-  (void)aIceLayer->Init();
-  (void)aDtlsLayer->Init();
-  (void)srtpDumper->Init();
-  (void)aSrtpLayer->Init();
-  aDtlsLayer->Chain(aIceLayer);
-  srtpDumper->Chain(aIceLayer);
-  aSrtpLayer->Chain(srtpDumper);
-  aFlow->PushLayer(aIceLayer);
-  aFlow->PushLayer(aDtlsLayer);
-  aFlow->PushLayer(srtpDumper);
-  aFlow->PushLayer(aSrtpLayer);
-}
-
-nsresult
-PeerConnectionMedia::UpdateTransportFlow(bool aIsRtcp,
-                                         const JsepTransport& aTransport)
-{
-  if (aIsRtcp && aTransport.mComponents < 2) {
-    RemoveTransportFlow(aTransport.mTransportId, aIsRtcp);
-    return NS_OK;
-  }
-
-  if (!aIsRtcp && !aTransport.mComponents) {
-    RemoveTransportFlow(aTransport.mTransportId, aIsRtcp);
-    return NS_OK;
-  }
-
-  MOZ_ASSERT(!aTransport.mTransportId.empty());
-
-  nsresult rv;
-
-  RefPtr<TransportFlow> flow = GetTransportFlow(aTransport.mTransportId, aIsRtcp);
-  if (flow) {
-    return NS_OK;
-  }
-
-  std::ostringstream osId;
-  osId << mParentHandle << ":" << aTransport.mTransportId << ","
-    << (aIsRtcp ? "rtcp" : "rtp");
-  flow = new TransportFlow(osId.str());
-
-  // The media streams are made on STS so we need to defer setup.
-  auto ice = MakeUnique<TransportLayerIce>();
-  auto dtls = MakeUnique<TransportLayerDtls>();
-  auto srtp = MakeUnique<TransportLayerSrtp>(*dtls);
-  dtls->SetRole(aTransport.mDtls->GetRole() ==
-                        JsepDtlsTransport::kJsepDtlsClient
-                    ? TransportLayerDtls::CLIENT
-                    : TransportLayerDtls::SERVER);
-
-  RefPtr<DtlsIdentity> pcid = mParent->Identity();
-  if (!pcid) {
-    CSFLogError(LOGTAG, "Failed to get DTLS identity.");
-    return NS_ERROR_FAILURE;
-  }
-  dtls->SetIdentity(pcid);
-
-  const SdpFingerprintAttributeList& fingerprints =
-      aTransport.mDtls->GetFingerprints();
-  for (const auto& fingerprint : fingerprints.mFingerprints) {
-    std::ostringstream ss;
-    ss << fingerprint.hashFunc;
-    rv = dtls->SetVerificationDigest(ss.str(), &fingerprint.fingerprint[0],
-                                     fingerprint.fingerprint.size());
-    if (NS_FAILED(rv)) {
-      CSFLogError(LOGTAG, "Could not set fingerprint");
-      return rv;
-    }
-  }
-
-  std::vector<uint16_t> srtpCiphers = TransportLayerDtls::GetDefaultSrtpCiphers();
-
-  rv = dtls->SetSrtpCiphers(srtpCiphers);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "Couldn't set SRTP ciphers");
-    return rv;
-  }
-
-  // Always permits negotiation of the confidential mode.
-  // Only allow non-confidential (which is an allowed default),
-  // if we aren't confidential.
-  std::set<std::string> alpn;
-  std::string alpnDefault = "";
-  alpn.insert("c-webrtc");
-  if (!mParent->PrivacyRequested()) {
-    alpnDefault = "webrtc";
-    alpn.insert(alpnDefault);
-  }
-  rv = dtls->SetAlpn(alpn, alpnDefault);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "Couldn't set ALPN");
-    return rv;
-  }
-
-  nsAutoPtr<PacketDumper> packetDumper(new PacketDumper(mParent));
-
-  rv = GetSTSThread()->Dispatch(
-      WrapRunnableNM(FinalizeTransportFlow_s, mIceCtx, packetDumper, flow,
-                     aTransport.mTransportId, aIsRtcp,
-                     ice.release(), dtls.release(), srtp.release()),
-      NS_DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "Failed to dispatch FinalizeTransportFlow_s");
-    return rv;
-  }
-
-  AddTransportFlow(aTransport.mTransportId, aIsRtcp, flow);
-
-  return NS_OK;
-}
-
 void
 PeerConnectionMedia::StartIceChecks(const JsepSession& aSession)
 {
   nsCOMPtr<nsIRunnable> runnable(
       WrapRunnable(
         RefPtr<PeerConnectionMedia>(this),
         &PeerConnectionMedia::StartIceChecks_s,
         aSession.IsIceControlling(),
@@ -811,129 +380,80 @@ PeerConnectionMedia::StartIceChecks_s(
 
   if (!aIceOptionsList.empty()) {
     attributes.push_back("ice-options:");
     for (const auto& option : aIceOptionsList) {
       attributes.back() += option + ' ';
     }
   }
 
-  nsresult rv = mIceCtx->ParseGlobalAttributes(attributes);
+  nsresult rv = mTransportHandler->StartIceChecks(aIsControlling,
+                                                  aIsOfferer,
+                                                  attributes);
   if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "%s: couldn't parse global parameters", __FUNCTION__ );
+    CSFLogError(LOGTAG, "%s: couldn't start ICE", __FUNCTION__ );
   }
-
-  mIceCtx->SetControlling(aIsControlling ?
-                                     NrIceCtx::ICE_CONTROLLING :
-                                     NrIceCtx::ICE_CONTROLLED);
-
-  mIceCtx->StartChecks(aIsOfferer);
 }
 
 bool
 PeerConnectionMedia::GetPrefDefaultAddressOnly() const
 {
   ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
 
   uint64_t winId = mParent->GetWindow()->WindowID();
 
   bool default_address_only = Preferences::GetBool(
     "media.peerconnection.ice.default_address_only", false);
   default_address_only |=
     !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
   return default_address_only;
 }
 
-bool
-PeerConnectionMedia::GetPrefProxyOnly() const
+void
+PeerConnectionMedia::ConnectSignals()
 {
-  ASSERT_ON_THREAD(mMainThread); // will crash on STS thread
-
-  return Preferences::GetBool("media.peerconnection.ice.proxy_only", false);
+  mTransportHandler->SignalGatheringStateChange.connect(
+      this,
+      &PeerConnectionMedia::IceGatheringStateChange_s);
+  mTransportHandler->SignalConnectionStateChange.connect(
+      this,
+      &PeerConnectionMedia::IceConnectionStateChange_s);
+  mTransportHandler->SignalCandidate.connect(
+      this,
+      &PeerConnectionMedia::OnCandidateFound_s);
+  mTransportHandler->SignalAlpnNegotiated.connect(
+      this,
+      &PeerConnectionMedia::AlpnNegotiated_s);
 }
 
 void
-PeerConnectionMedia::ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx)
+PeerConnectionMedia::AddIceCandidate(const std::string& aCandidate,
+                                     const std::string& aTransportId)
 {
-  aCtx->SignalGatheringStateChange.connect(
-      this,
-      &PeerConnectionMedia::IceGatheringStateChange_s);
-  aCtx->SignalConnectionStateChange.connect(
-      this,
-      &PeerConnectionMedia::IceConnectionStateChange_s);
-
-  if (aOldCtx) {
-    MOZ_ASSERT(aCtx != aOldCtx);
-    aOldCtx->SignalGatheringStateChange.disconnect(this);
-    aOldCtx->SignalConnectionStateChange.disconnect(this);
-
-    // if the old and new connection state and/or gathering state is
-    // different fire the state update.  Note: we don't fire the update
-    // if the state is *INIT since updates for the INIT state aren't
-    // sent during the normal flow. (mjf)
-    if (aOldCtx->connection_state() != aCtx->connection_state() &&
-        aCtx->connection_state() != NrIceCtx::ICE_CTX_INIT) {
-      aCtx->SignalConnectionStateChange(aCtx, aCtx->connection_state());
-    }
-
-    if (aOldCtx->gathering_state() != aCtx->gathering_state() &&
-        aCtx->gathering_state() != NrIceCtx::ICE_CTX_GATHER_INIT) {
-      aCtx->SignalGatheringStateChange(aCtx, aCtx->gathering_state());
-    }
-  }
-}
-
-void
-PeerConnectionMedia::AddIceCandidate(const std::string& candidate,
-                                     const std::string& aTransportId) {
   MOZ_ASSERT(!aTransportId.empty());
   RUN_ON_THREAD(GetSTSThread(),
                 WrapRunnable(
-                    RefPtr<PeerConnectionMedia>(this),
-                    &PeerConnectionMedia::AddIceCandidate_s,
-                    std::string(candidate), // Make copies.
-                    std::string(aTransportId)),
+                    mTransportHandler,
+                    &MediaTransportHandler::AddIceCandidate,
+                    aTransportId,
+                    aCandidate),
                 NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
-                                       const std::string& aTransportId) {
-  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
-  if (!stream) {
-    CSFLogError(LOGTAG, "No ICE stream for candidate with transport id %s: %s",
-                        aTransportId.c_str(), aCandidate.c_str());
-    return;
-  }
-
-  nsresult rv = stream->ParseTrickleCandidate(aCandidate);
-  if (NS_FAILED(rv)) {
-    CSFLogError(LOGTAG, "Couldn't process ICE candidate with transport id %s: "
-                        "%s",
-                        aTransportId.c_str(), aCandidate.c_str());
-    return;
-  }
-}
-
-void
 PeerConnectionMedia::UpdateNetworkState(bool online) {
   RUN_ON_THREAD(GetSTSThread(),
                 WrapRunnable(
-                    RefPtr<PeerConnectionMedia>(this),
-                    &PeerConnectionMedia::UpdateNetworkState_s,
+                    mTransportHandler,
+                    &MediaTransportHandler::UpdateNetworkState,
                     online),
                 NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::UpdateNetworkState_s(bool online) {
-  mIceCtx->UpdateNetworkState(online);
-}
-
-void
 PeerConnectionMedia::FlushIceCtxOperationQueueIfReady()
 {
   ASSERT_ON_THREAD(mMainThread);
 
   if (IsIceCtxReady()) {
     for (auto& mQueuedIceCtxOperation : mQueuedIceCtxOperations) {
       GetSTSThread()->Dispatch(mQueuedIceCtxOperation, NS_DISPATCH_NORMAL);
     }
@@ -952,72 +472,51 @@ PeerConnectionMedia::PerformOrEnqueueIce
     mQueuedIceCtxOperations.push_back(runnable);
   }
 }
 
 void
 PeerConnectionMedia::GatherIfReady() {
   ASSERT_ON_THREAD(mMainThread);
 
+  // If we had previously queued gathering or ICE start, unqueue them
+  mQueuedIceCtxOperations.clear();
   nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
         RefPtr<PeerConnectionMedia>(this),
         &PeerConnectionMedia::EnsureIceGathering_s,
-        GetPrefDefaultAddressOnly(),
-        GetPrefProxyOnly()));
+        GetPrefDefaultAddressOnly()));
 
   PerformOrEnqueueIceCtxOperation(runnable);
 }
 
 void
-PeerConnectionMedia::EnsureIceGathering_s(bool aDefaultRouteOnly,
-                                          bool aProxyOnly) {
-  if (mProxyServer) {
-    mIceCtx->SetProxyServer(*mProxyServer);
-  } else if (aProxyOnly) {
-    IceGatheringStateChange_s(mIceCtx.get(),
-                              NrIceCtx::ICE_CTX_GATHER_COMPLETE);
-    return;
+PeerConnectionMedia::EnsureIceGathering_s(bool aDefaultRouteOnly) {
+  if (!mProxyHost.empty()) {
+    // Note that this could check if PrivacyRequested() is set on the PC and
+    // remove "webrtc" from the ALPN list.  But that would only work if the PC
+    // was constructed with a peerIdentity constraint, not when isolated
+    // streams are added.  If we ever need to signal to the proxy that the
+    // media is isolated, then we would need to restructure this code.
+    mTransportHandler->SetProxyServer(
+        mProxyHost, mProxyPort, "webrtc,c-webrtc");
   }
 
-  // Make sure we don't call NrIceCtx::StartGathering if we're in e10s mode
+  // Make sure we don't call StartIceGathering if we're in e10s mode
   // and we received no STUN addresses from the parent process.  In the
-  // absence of previously provided STUN addresses, StartGathering will
+  // absence of previously provided STUN addresses, StartIceGathering will
   // attempt to gather them (as in non-e10s mode), and this will cause a
   // sandboxing exception in e10s mode.
   if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
     CSFLogInfo(LOGTAG,
                "%s: No STUN addresses returned from parent process",
                __FUNCTION__);
     return;
   }
 
-  // Belt and suspenders - in e10s mode, the call below to SetStunAddrs
-  // needs to have the proper flags set on ice ctx.  For non-e10s,
-  // setting those flags happens in StartGathering.  We could probably
-  // just set them here, and only do it here.
-  mIceCtx->SetCtxFlags(aDefaultRouteOnly, aProxyOnly);
-
-  if (mStunAddrs.Length()) {
-    mIceCtx->SetStunAddrs(mStunAddrs);
-  }
-
-  // Start gathering, but only if there are streams
-  if (!mIceCtx->GetStreams().empty()) {
-    mIceCtx->StartGathering(aDefaultRouteOnly, aProxyOnly);
-    return;
-  }
-
-  CSFLogWarn(LOGTAG,
-             "%s: No streams to start gathering on. Can happen with rollback",
-             __FUNCTION__);
-  // 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);
+  mTransportHandler->StartIceGathering(aDefaultRouteOnly, mStunAddrs);
 }
 
 void
 PeerConnectionMedia::SelfDestruct()
 {
   ASSERT_ON_THREAD(mMainThread);
 
   CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__);
@@ -1066,197 +565,39 @@ PeerConnectionMedia::SelfDestruct_m()
 void
 PeerConnectionMedia::ShutdownMediaTransport_s()
 {
   ASSERT_ON_THREAD(mSTSThread);
 
   CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__);
 
   disconnect_all();
-  mTransportFlows.clear();
 
-#if !defined(MOZILLA_EXTERNAL_LINKAGE)
-  NrIceStats stats = mIceCtx->Destroy();
-
-  CSFLogDebug(LOGTAG, "Ice Telemetry: stun (retransmits: %d)"
-                      "   turn (401s: %d   403s: %d   438s: %d)",
-              stats.stun_retransmits, stats.turn_401s, stats.turn_403s,
-              stats.turn_438s);
-
-  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_STUN_RETRANSMITS,
-                       stats.stun_retransmits);
-  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_401S,
-                       stats.turn_401s);
-  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_403S,
-                       stats.turn_403s);
-  Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_NICER_TURN_438S,
-                       stats.turn_438s);
-#endif
-
-  mIceCtx = nullptr;
+  mTransportHandler->Destroy();
+  mTransportHandler = nullptr;
 
   // we're holding a ref to 'this' that's released by SelfDestruct_m
   mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
                         NS_DISPATCH_NORMAL);
 }
 
-
-void
-PeerConnectionMedia::GetIceStats_s(
-    const std::string& aTransportId,
-    bool internalStats,
-    DOMHighResTimeStamp now,
-    RTCStatsReportInternal* report) const
-{
-  if (mIceCtx) {
-    auto stream = mIceCtx->GetStream(aTransportId);
-    if (stream) {
-      GetIceStats_s(*stream, internalStats, now, report);
-    }
-  }
-}
-
-void
-PeerConnectionMedia::GetAllIceStats_s(
-    bool internalStats,
-    DOMHighResTimeStamp now,
-    RTCStatsReportInternal* report) const
-{
-  if (mIceCtx) {
-    for (const auto& stream : mIceCtx->GetStreams()) {
-      GetIceStats_s(*stream, internalStats, now, report);
-    }
-  }
-}
-
-static void ToRTCIceCandidateStats(
-    const std::vector<NrIceCandidate>& candidates,
-    RTCStatsType candidateType,
-    const nsString& componentId,
-    DOMHighResTimeStamp now,
-    RTCStatsReportInternal* report) {
-
-  MOZ_ASSERT(report);
-  for (const auto& candidate : candidates) {
-    RTCIceCandidateStats cand;
-    cand.mType.Construct(candidateType);
-    NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
-    cand.mComponentId.Construct(componentId);
-    cand.mId.Construct(codeword);
-    cand.mTimestamp.Construct(now);
-    cand.mCandidateType.Construct(
-        RTCStatsIceCandidateType(candidate.type));
-    cand.mIpAddress.Construct(
-        NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
-    cand.mPortNumber.Construct(candidate.cand_addr.port);
-    cand.mTransport.Construct(
-        NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
-    if (candidateType == RTCStatsType::Local_candidate) {
-      cand.mMozLocalTransport.Construct(
-          NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
-      if (RTCStatsIceCandidateType(candidate.type) == RTCStatsIceCandidateType::Relayed) {
-        cand.mRelayProtocol.Construct(
-            NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
-      }
-    }
-    report->mIceCandidateStats.Value().AppendElement(cand, fallible);
-    if (candidate.trickled) {
-      report->mTrickledIceCandidateStats.Value().AppendElement(cand, fallible);
-    }
-  }
-}
-
-void
-PeerConnectionMedia::GetIceStats_s(
-    const NrIceMediaStream& aStream,
-    bool internalStats,
-    DOMHighResTimeStamp now,
-    RTCStatsReportInternal* report) const
-{
-  NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str());
-
-  std::vector<NrIceCandidatePair> candPairs;
-  nsresult res = aStream.GetCandidatePairs(&candPairs);
-  if (NS_FAILED(res)) {
-    CSFLogError(LOGTAG,
-        "%s: Error getting candidate pairs for transport id \"%s\"",
-        __FUNCTION__, aStream.GetId().c_str());
-    return;
-  }
-
-  for (auto& candPair : candPairs) {
-    NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str());
-    NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str());
-    NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str());
-    // Only expose candidate-pair statistics to chrome, until we've thought
-    // through the implications of exposing it to content.
-
-    RTCIceCandidatePairStats s;
-    s.mId.Construct(codeword);
-    s.mTransportId.Construct(transportId);
-    s.mTimestamp.Construct(now);
-    s.mType.Construct(RTCStatsType::Candidate_pair);
-    s.mLocalCandidateId.Construct(localCodeword);
-    s.mRemoteCandidateId.Construct(remoteCodeword);
-    s.mNominated.Construct(candPair.nominated);
-    s.mWritable.Construct(candPair.writable);
-    s.mReadable.Construct(candPair.readable);
-    s.mPriority.Construct(candPair.priority);
-    s.mSelected.Construct(candPair.selected);
-    s.mBytesSent.Construct(candPair.bytes_sent);
-    s.mBytesReceived.Construct(candPair.bytes_recvd);
-    s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send);
-    s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv);
-    s.mState.Construct(RTCStatsIceCandidatePairState(candPair.state));
-    s.mComponentId.Construct(candPair.component_id);
-    report->mIceCandidatePairStats.Value().AppendElement(s, fallible);
-  }
-
-  std::vector<NrIceCandidate> candidates;
-  if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) {
-    ToRTCIceCandidateStats(candidates,
-                           RTCStatsType::Local_candidate,
-                           transportId,
-                           now,
-                           report);
-    // add the local candidates unparsed string to a sequence
-    for (const auto& candidate : candidates) {
-      report->mRawLocalCandidates.Value().AppendElement(
-          NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible);
-    }
-  }
-  candidates.clear();
-
-  if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) {
-    ToRTCIceCandidateStats(candidates,
-                           RTCStatsType::Remote_candidate,
-                           transportId,
-                           now,
-                           report);
-    // add the remote candidates unparsed string to a sequence
-    for (const auto& candidate : candidates) {
-      report->mRawRemoteCandidates.Value().AppendElement(
-          NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible);
-    }
-  }
-}
-
 nsresult
 PeerConnectionMedia::AddTransceiver(
     JsepTransceiver* aJsepTransceiver,
     dom::MediaStreamTrack& aReceiveTrack,
     dom::MediaStreamTrack* aSendTrack,
     RefPtr<TransceiverImpl>* aTransceiverImpl)
 {
   if (!mCall) {
     mCall = WebRtcCallWrapper::Create();
   }
 
   RefPtr<TransceiverImpl> transceiver = new TransceiverImpl(
       mParent->GetHandle(),
+      mTransportHandler,
       aJsepTransceiver,
       mMainThread.get(),
       mSTSThread.get(),
       &aReceiveTrack,
       aSendTrack,
       mCall.get());
 
   if (!transceiver->IsValid()) {
@@ -1345,291 +686,118 @@ PeerConnectionMedia::AddRIDFilter(MediaS
       trackFound = true;
     }
   }
   MOZ_ASSERT(trackFound);
   return NS_OK;
 }
 
 void
-PeerConnectionMedia::IceGatheringStateChange_s(NrIceCtx* ctx,
-                                               NrIceCtx::GatheringState state)
+PeerConnectionMedia::IceGatheringStateChange_s(
+    dom::PCImplIceGatheringState aState)
 {
   ASSERT_ON_THREAD(mSTSThread);
 
-  if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
-    // Fire off EndOfLocalCandidates for each stream
-    for (auto& stream : ctx->GetStreams()) {
-      NrIceCandidate candidate;
-      NrIceCandidate rtcpCandidate;
-      GetDefaultCandidates(*stream, &candidate, &rtcpCandidate);
-      EndOfLocalCandidates(candidate.cand_addr.host,
-                           candidate.cand_addr.port,
-                           rtcpCandidate.cand_addr.host,
-                           rtcpCandidate.cand_addr.port,
-                           stream->GetId());
-    }
-  }
-
   // ShutdownMediaTransport_s has not run yet because it unhooks this function
   // from its signal, which means that SelfDestruct_m has not been dispatched
   // yet either, so this PCMedia will still be around when this dispatch reaches
   // main.
   GetMainThread()->Dispatch(
     WrapRunnable(this,
                  &PeerConnectionMedia::IceGatheringStateChange_m,
-                 ctx,
-                 state),
+                 aState),
     NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::IceConnectionStateChange_s(NrIceCtx* ctx,
-                                                NrIceCtx::ConnectionState state)
+PeerConnectionMedia::IceConnectionStateChange_s(
+    dom::PCImplIceConnectionState aState)
 {
   ASSERT_ON_THREAD(mSTSThread);
   // ShutdownMediaTransport_s has not run yet because it unhooks this function
   // from its signal, which means that SelfDestruct_m has not been dispatched
   // yet either, so this PCMedia will still be around when this dispatch reaches
   // main.
   GetMainThread()->Dispatch(
     WrapRunnable(this,
-                 &PeerConnectionMedia::IceConnectionStateChange_m,
-                 ctx,
-                 state),
+                 &PeerConnectionMedia::IceConnectionStateChange_m, aState),
     NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::OnCandidateFound_s(NrIceMediaStream *aStream,
-                                        const std::string &aCandidateLine)
+PeerConnectionMedia::OnCandidateFound_s(
+    const std::string& aTransportId,
+    const MediaTransportHandler::CandidateInfo& aCandidateInfo)
 {
   ASSERT_ON_THREAD(mSTSThread);
-  MOZ_ASSERT(aStream);
-  MOZ_ASSERT(!aStream->GetId().empty());
-  MOZ_RELEASE_ASSERT(mIceCtx);
+  MOZ_RELEASE_ASSERT(mTransportHandler);
 
-  CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aStream->name().c_str());
-
-  NrIceCandidate candidate;
-  NrIceCandidate rtcpCandidate;
-  GetDefaultCandidates(*aStream, &candidate, &rtcpCandidate);
+  CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str());
 
   // ShutdownMediaTransport_s has not run yet because it unhooks this function
   // from its signal, which means that SelfDestruct_m has not been dispatched
   // yet either, so this PCMedia will still be around when this dispatch reaches
   // main.
   GetMainThread()->Dispatch(
     WrapRunnable(this,
                  &PeerConnectionMedia::OnCandidateFound_m,
-                 aCandidateLine,
-                 candidate.cand_addr.host,
-                 candidate.cand_addr.port,
-                 rtcpCandidate.cand_addr.host,
-                 rtcpCandidate.cand_addr.port,
-                 aStream->GetId()),
-    NS_DISPATCH_NORMAL);
-}
-
-void
-PeerConnectionMedia::EndOfLocalCandidates(const std::string& aDefaultAddr,
-                                          uint16_t aDefaultPort,
-                                          const std::string& aDefaultRtcpAddr,
-                                          uint16_t aDefaultRtcpPort,
-                                          const std::string& aTransportId)
-{
-  GetMainThread()->Dispatch(
-    WrapRunnable(this,
-                 &PeerConnectionMedia::EndOfLocalCandidates_m,
-                 aDefaultAddr,
-                 aDefaultPort,
-                 aDefaultRtcpAddr,
-                 aDefaultRtcpPort,
-                 aTransportId),
+                 aTransportId,
+                 aCandidateInfo),
     NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::GetDefaultCandidates(const NrIceMediaStream& aStream,
-                                          NrIceCandidate* aCandidate,
-                                          NrIceCandidate* aRtcpCandidate)
+PeerConnectionMedia::IceGatheringStateChange_m(
+    dom::PCImplIceGatheringState aState)
 {
-  nsresult res = aStream.GetDefaultCandidate(1, aCandidate);
-  // Optional; component won't exist if doing rtcp-mux
-  if (NS_FAILED(aStream.GetDefaultCandidate(2, aRtcpCandidate))) {
-    aRtcpCandidate->cand_addr.host.clear();
-    aRtcpCandidate->cand_addr.port = 0;
-  }
-  if (NS_FAILED(res)) {
-    aCandidate->cand_addr.host.clear();
-    aCandidate->cand_addr.port = 0;
-    CSFLogError(LOGTAG, "%s: GetDefaultCandidates failed for transport id %s, "
-                        "res=%u",
-                        __FUNCTION__,
-                        aStream.GetId().c_str(),
-                        static_cast<unsigned>(res));
-  }
-}
-
-static mozilla::dom::PCImplIceConnectionState
-toDomIceConnectionState(NrIceCtx::ConnectionState state) {
-  switch (state) {
-    case NrIceCtx::ICE_CTX_INIT:
-      return PCImplIceConnectionState::New;
-    case NrIceCtx::ICE_CTX_CHECKING:
-      return PCImplIceConnectionState::Checking;
-    case NrIceCtx::ICE_CTX_CONNECTED:
-      return PCImplIceConnectionState::Connected;
-    case NrIceCtx::ICE_CTX_COMPLETED:
-      return PCImplIceConnectionState::Completed;
-    case NrIceCtx::ICE_CTX_FAILED:
-      return PCImplIceConnectionState::Failed;
-    case NrIceCtx::ICE_CTX_DISCONNECTED:
-      return PCImplIceConnectionState::Disconnected;
-    case NrIceCtx::ICE_CTX_CLOSED:
-      return PCImplIceConnectionState::Closed;
-  }
-  MOZ_CRASH();
-}
-
-static mozilla::dom::PCImplIceGatheringState
-toDomIceGatheringState(NrIceCtx::GatheringState state) {
-  switch (state) {
-    case NrIceCtx::ICE_CTX_GATHER_INIT:
-      return PCImplIceGatheringState::New;
-    case NrIceCtx::ICE_CTX_GATHER_STARTED:
-      return PCImplIceGatheringState::Gathering;
-    case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
-      return PCImplIceGatheringState::Complete;
-  }
-  MOZ_CRASH();
+  ASSERT_ON_THREAD(mMainThread);
+  SignalIceGatheringStateChange(aState);
 }
 
 void
-PeerConnectionMedia::IceGatheringStateChange_m(NrIceCtx* ctx,
-                                               NrIceCtx::GatheringState state)
+PeerConnectionMedia::IceConnectionStateChange_m(
+    dom::PCImplIceConnectionState aState)
 {
   ASSERT_ON_THREAD(mMainThread);
-  SignalIceGatheringStateChange(toDomIceGatheringState(state));
+  SignalIceConnectionStateChange(aState);
 }
 
 void
-PeerConnectionMedia::IceConnectionStateChange_m(NrIceCtx* ctx,
-                                                NrIceCtx::ConnectionState state)
+PeerConnectionMedia::OnCandidateFound_m(
+    const std::string& aTransportId,
+    const MediaTransportHandler::CandidateInfo& aCandidateInfo)
 {
   ASSERT_ON_THREAD(mMainThread);
-  SignalIceConnectionStateChange(toDomIceConnectionState(state));
-}
-
-void
-PeerConnectionMedia::IceStreamReady_s(NrIceMediaStream *aStream)
-{
-  MOZ_ASSERT(aStream);
-
-  CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aStream->name().c_str());
+  if (!aCandidateInfo.mDefaultHostRtp.empty()) {
+    SignalUpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp,
+                                 aCandidateInfo.mDefaultPortRtp,
+                                 aCandidateInfo.mDefaultHostRtcp,
+                                 aCandidateInfo.mDefaultPortRtcp,
+                                 aTransportId);
+  }
+  SignalCandidate(aCandidateInfo.mCandidate, aTransportId);
 }
 
 void
-PeerConnectionMedia::OnCandidateFound_m(const std::string& aCandidateLine,
-                                        const std::string& aDefaultAddr,
-                                        uint16_t aDefaultPort,
-                                        const std::string& aDefaultRtcpAddr,
-                                        uint16_t aDefaultRtcpPort,
-                                        const std::string& aTransportId)
+PeerConnectionMedia::AlpnNegotiated_s(const std::string& aAlpn)
 {
-  ASSERT_ON_THREAD(mMainThread);
-  if (!aDefaultAddr.empty()) {
-    SignalUpdateDefaultCandidate(aDefaultAddr,
-                                 aDefaultPort,
-                                 aDefaultRtcpAddr,
-                                 aDefaultRtcpPort,
-                                 aTransportId);
-  }
-  SignalCandidate(aCandidateLine, aTransportId);
-}
-
-void
-PeerConnectionMedia::EndOfLocalCandidates_m(const std::string& aDefaultAddr,
-                                            uint16_t aDefaultPort,
-                                            const std::string& aDefaultRtcpAddr,
-                                            uint16_t aDefaultRtcpPort,
-                                            const std::string& aTransportId) {
-  ASSERT_ON_THREAD(mMainThread);
-  if (!aDefaultAddr.empty()) {
-    SignalUpdateDefaultCandidate(aDefaultAddr,
-                                 aDefaultPort,
-                                 aDefaultRtcpAddr,
-                                 aDefaultRtcpPort,
-                                 aTransportId);
-  }
-  SignalEndOfLocalCandidates(aTransportId);
-}
-
-void
-PeerConnectionMedia::DtlsConnected_s(TransportLayer *layer,
-                                     TransportLayer::State state)
-{
-  MOZ_ASSERT(layer->id() == "dtls");
-  TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(layer);
-  dtlsLayer->SignalStateChange.disconnect(this);
-
-  bool privacyRequested = (dtlsLayer->GetNegotiatedAlpn() == "c-webrtc");
   GetMainThread()->Dispatch(
-    WrapRunnableNM(&PeerConnectionMedia::DtlsConnected_m,
-                   mParentHandle, privacyRequested),
+    WrapRunnableNM(&PeerConnectionMedia::AlpnNegotiated_m,
+                   mParentHandle, aAlpn),
     NS_DISPATCH_NORMAL);
 }
 
 void
-PeerConnectionMedia::DtlsConnected_m(const std::string& aParentHandle,
-                                     bool aPrivacyRequested)
+PeerConnectionMedia::AlpnNegotiated_m(const std::string& aParentHandle,
+                                      const std::string& aAlpn)
 {
   PeerConnectionWrapper pcWrapper(aParentHandle);
   PeerConnectionImpl* pc = pcWrapper.impl();
   if (pc) {
-    pc->SetDtlsConnected(aPrivacyRequested);
-  }
-}
-
-void
-PeerConnectionMedia::AddTransportFlow(const std::string& aId, bool aRtcp,
-                                      const RefPtr<TransportFlow> &aFlow)
-{
-  auto& flows = aRtcp ? mRtcpTransportFlows : mTransportFlows;
-
-  if (flows.count(aId)) {
-    MOZ_ASSERT(false);
-    return;
-  }
-  flows[aId] = aFlow;
-
-  GetSTSThread()->Dispatch(
-    WrapRunnable(this, &PeerConnectionMedia::ConnectDtlsListener_s, aFlow),
-    NS_DISPATCH_NORMAL);
-}
-
-void
-PeerConnectionMedia::RemoveTransportFlow(const std::string& aId, bool aRtcp)
-{
-  auto& flows = aRtcp ? mRtcpTransportFlows : mTransportFlows;
-  auto it = flows.find(aId);
-  if (it != flows.end()) {
-    NS_ProxyRelease(
-      "PeerConnectionMedia::mTransportFlows[aId] or mRtcpTransportFlows[aId]",
-      GetSTSThread(), it->second.forget());
-    flows.erase(it);
-  }
-}
-
-void
-PeerConnectionMedia::ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow)
-{
-  TransportLayer* dtls = aFlow->GetLayer(TransportLayerDtls::ID());
-  if (dtls) {
-    dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected_s);
+    pc->OnAlpnNegotiated(aAlpn);
   }
 }
 
 /**
  * Tells you if any local track is isolated to a specific peer identity.
  * Obviously, we want all the tracks to be isolated equally so that they can
  * all be sent or not.  We check once when we are setting a local description
  * and that determines if we flip the "privacy requested" bit on.  Once the bit
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -8,27 +8,26 @@
 #include <string>
 #include <vector>
 #include <map>
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/net/StunAddrsRequestChild.h"
 #include "nsIProtocolProxyCallback.h"
+#include "MediaTransportHandler.h"
 
 #include "TransceiverImpl.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 class DataChannel;
 class PeerIdentity;
 namespace dom {
-struct RTCInboundRTPStreamStats;
-struct RTCOutboundRTPStreamStats;
 class MediaStreamTrack;
 }
 }
 
 #include "nriceresolver.h"
 #include "nricemediastream.h"
 
 namespace mozilla {
@@ -44,29 +43,20 @@ class JsepSession;
 // will be a class that handles just the transport stuff, and we can rename it
 // to something more explanatory (say, PeerConnectionTransportManager).
 class PeerConnectionMedia : public sigslot::has_slots<> {
   ~PeerConnectionMedia();
 
  public:
   explicit PeerConnectionMedia(PeerConnectionImpl *parent);
 
-  PeerConnectionImpl* GetPC() { return mParent; }
   nsresult Init(const dom::RTCConfiguration& aConfiguration);
   // WARNING: This destroys the object!
   void SelfDestruct();
 
-  void GetIceStats_s(const std::string& aTransportId,
-                     bool internalStats,
-                     DOMHighResTimeStamp now,
-                     RTCStatsReportInternal* report) const;
-  void GetAllIceStats_s(bool internalStats,
-                        DOMHighResTimeStamp now,
-                        RTCStatsReportInternal* report) const;
-
   // Ensure ICE transports exist that we might need when offer/answer concludes
   void EnsureTransports(const JsepSession& aSession);
 
   // Activate ICE transports at the conclusion of offer/answer,
   // or when rollback occurs.
   nsresult UpdateTransports(const JsepSession& aSession,
                             const bool forceIceTcp);
 
@@ -124,63 +114,46 @@ class PeerConnectionMedia : public sigsl
   // on streams
   void UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal);
 
   bool AnyCodecHasPluginID(uint64_t aPluginID);
 
   const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; }
   const nsCOMPtr<nsIEventTarget>& GetSTSThread() const { return mSTSThread; }
 
-  // Get a transport flow either RTP/RTCP for a particular stream
-  // A stream can be of audio/video/datachannel/budled(?) types
-  RefPtr<TransportFlow> GetTransportFlow(const std::string& aId,
-                                         bool aIsRtcp) {
-    auto& flows = aIsRtcp ? mRtcpTransportFlows : mTransportFlows;
-    auto it = flows.find(aId);
-    if (it == flows.end()) {
-      return nullptr;
-    }
-
-    return it->second;
-  }
-
   // Used by PCImpl in a couple of places. Might be good to move that code in
   // here.
   std::vector<RefPtr<TransceiverImpl>>& GetTransceivers()
   {
     return mTransceivers;
   }
 
-  // Add a transport flow
-  void AddTransportFlow(const std::string& aId, bool aRtcp,
-                        const RefPtr<TransportFlow> &aFlow);
-  void RemoveTransportFlow(const std::string& aId, bool aRtcp);
-  void ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow);
-  void DtlsConnected_s(TransportLayer* aFlow,
-                       TransportLayer::State state);
-  static void DtlsConnected_m(const std::string& aParentHandle,
-                              bool aPrivacyRequested);
+  void AlpnNegotiated_s(const std::string& aAlpn);
+  static void AlpnNegotiated_m(const std::string& aParentHandle,
+                               const std::string& aAlpn);
 
   // ICE state signals
   sigslot::signal1<mozilla::dom::PCImplIceGatheringState>
       SignalIceGatheringStateChange;
   sigslot::signal1<mozilla::dom::PCImplIceConnectionState>
       SignalIceConnectionStateChange;
   // This passes a candidate:... attribute and transport id
+  // end-of-candidates is signaled with the empty string
   sigslot::signal2<const std::string&, const std::string&> SignalCandidate;
   // This passes address, port, transport id of the default candidate.
   sigslot::signal5<const std::string&, uint16_t,
                    const std::string&, uint16_t, const std::string&>
       SignalUpdateDefaultCandidate;
-  sigslot::signal1<const std::string&>
-      SignalEndOfLocalCandidates;
 
   // TODO: Move to PeerConnectionImpl
   RefPtr<WebRtcCallWrapper> mCall;
 
+  // mtransport objects
+  RefPtr<MediaTransportHandler> mTransportHandler;
+
  private:
   void InitLocalAddrs(); // for stun local address IPC request
   nsresult InitProxy();
   class ProtocolProxyQueryHandler : public nsIProtocolProxyCallback {
    public:
     explicit ProtocolProxyQueryHandler(PeerConnectionMedia *pcm) :
       pcm_(pcm) {}
 
@@ -210,119 +183,57 @@ class PeerConnectionMedia : public sigsl
   // Shutdown media transport. Must be called on STS thread.
   void ShutdownMediaTransport_s();
 
   // Final destruction of the media stream. Must be called on the main
   // thread.
   void SelfDestruct_m();
 
   // Manage ICE transports.
-  nsresult UpdateTransport(const JsepTransceiver& aTransceiver,
-                           bool aForceIceTcp);
-
-  void EnsureTransport_s(const std::string& aTransportId,
-                         const std::string& aUfrag,
-                         const std::string& aPwd,
-                         size_t aComponentCount);
-  void ActivateTransport_s(const std::string& aTransportId,
-                           const std::string& aLocalUfrag,
-                           const std::string& aLocalPwd,
-                           size_t aComponentCount,
-                           const std::string& aUfrag,
-                           const std::string& aPassword,
-                           const std::vector<std::string>& aCandidateList);
-  void RemoveTransportsExcept_s(const std::set<std::string>& aTransportIds);
-  nsresult UpdateTransportFlows(const JsepTransceiver& transceiver);
-  nsresult UpdateTransportFlow(bool aIsRtcp,
-                               const JsepTransport& aTransport);
+  void UpdateTransport(const JsepTransceiver& aTransceiver,
+                       bool aForceIceTcp);
 
   void GatherIfReady();
   void FlushIceCtxOperationQueueIfReady();
   void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
-  void EnsureIceGathering_s(bool aDefaultRouteOnly, bool aProxyOnly);
+  void EnsureIceGathering_s(bool aDefaultRouteOnly);
   void StartIceChecks_s(bool aIsControlling,
                         bool aIsOfferer,
                         bool aIsIceLite,
                         const std::vector<std::string>& aIceOptionsList);
 
   bool GetPrefDefaultAddressOnly() const;
-  bool GetPrefProxyOnly() const;
 
-  void ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx=nullptr);
-
-  // Process a trickle ICE candidate.
-  void AddIceCandidate_s(const std::string& aCandidate,
-                         const std::string& aTransportId);
-
-  void UpdateNetworkState_s(bool online);
+  void ConnectSignals();
 
   // ICE events
-  void IceGatheringStateChange_s(NrIceCtx* ctx,
-                               NrIceCtx::GatheringState state);
-  void IceConnectionStateChange_s(NrIceCtx* ctx,
-                                NrIceCtx::ConnectionState state);
-  void IceStreamReady_s(NrIceMediaStream *aStream);
-  void OnCandidateFound_s(NrIceMediaStream *aStream,
-                          const std::string& aCandidate);
-  void EndOfLocalCandidates(const std::string& aDefaultAddr,
-                            uint16_t aDefaultPort,
-                            const std::string& aDefaultRtcpAddr,
-                            uint16_t aDefaultRtcpPort,
-                            const std::string& aTransportId);
-  void GetDefaultCandidates(const NrIceMediaStream& aStream,
-                            NrIceCandidate* aCandidate,
-                            NrIceCandidate* aRtcpCandidate);
+  void IceGatheringStateChange_s(dom::PCImplIceGatheringState aState);
+  void IceConnectionStateChange_s(dom::PCImplIceConnectionState aState);
+  void OnCandidateFound_s(
+      const std::string& aTransportId,
+      const MediaTransportHandler::CandidateInfo& aCandidateInfo);
 
-  void IceGatheringStateChange_m(NrIceCtx* ctx,
-                                 NrIceCtx::GatheringState state);
-  void IceConnectionStateChange_m(NrIceCtx* ctx,
-                                  NrIceCtx::ConnectionState state);
-  void OnCandidateFound_m(const std::string& aCandidateLine,
-                          const std::string& aDefaultAddr,
-                          uint16_t aDefaultPort,
-                          const std::string& aDefaultRtcpAddr,
-                          uint16_t aDefaultRtcpPort,
-                          const std::string& aTransportId);
-  void EndOfLocalCandidates_m(const std::string& aDefaultAddr,
-                              uint16_t aDefaultPort,
-                              const std::string& aDefaultRtcpAddr,
-                              uint16_t aDefaultRtcpPort,
-                              const std::string& aTransportId);
+  void IceGatheringStateChange_m(dom::PCImplIceGatheringState aState);
+  void IceConnectionStateChange_m(dom::PCImplIceConnectionState aState);
+  void OnCandidateFound_m(
+      const std::string& aTransportId,
+      const MediaTransportHandler::CandidateInfo& aCandidateInfo);
+
   bool IsIceCtxReady() const {
     return mProxyResolveCompleted && mLocalAddrsCompleted;
   }
 
-  void GetIceStats_s(const NrIceMediaStream& aStream,
-                     bool internalStats,
-                     DOMHighResTimeStamp now,
-                     RTCStatsReportInternal* report) const;
-
   // The parent PC
   PeerConnectionImpl *mParent;
   // and a loose handle on it for event driven stuff
   std::string mParentHandle;
   std::string mParentName;
 
   std::vector<RefPtr<TransceiverImpl>> mTransceivers;
 
-  // ICE objects
-  RefPtr<NrIceCtx> mIceCtx;
-
-  // DNS
-  RefPtr<NrIceResolver> mDNSResolver;
-
-  // Transport flows for RTP and RTP/RTCP mux
-  std::map<std::string, RefPtr<TransportFlow> > mTransportFlows;
-
-  // Transport flows for standalone RTCP (rarely used)
-  std::map<std::string, RefPtr<TransportFlow> > mRtcpTransportFlows;
-
-  // UUID Generator
-  UniquePtr<PCUuidGenerator> mUuidGen;
-
   // The main thread.
   nsCOMPtr<nsIThread> mMainThread;
 
   // The STS thread.
   nsCOMPtr<nsIEventTarget> mSTSThread;
 
   // Used whenever we need to dispatch a runnable to STS to tweak something
   // on our ICE ctx, but are not ready to do so at the moment (eg; we are
@@ -332,17 +243,18 @@ class PeerConnectionMedia : public sigsl
 
   // Used to cancel any ongoing proxy request.
   nsCOMPtr<nsICancelable> mProxyRequest;
 
   // Used to track the state of the request.
   bool mProxyResolveCompleted;
 
   // Used to store the result of the request.
-  UniquePtr<NrIceProxyServer> mProxyServer;
+  std::string mProxyHost;
+  uint16_t mProxyPort;
 
   // Used to cancel incoming stun addrs response
   RefPtr<net::StunAddrsRequestChild> mStunAddrsRequest;
 
   // Used to track the state of the stun addr IPC request
   bool mLocalAddrsCompleted;
 
   // Used to store the result of the stun addr IPC request
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -1,53 +1,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TransceiverImpl.h"
-#include "mtransport/runnable_utils.h"
 #include "mozilla/UniquePtr.h"
-#include <sstream>
 #include <string>
 #include <vector>
-#include <queue>
 #include "AudioConduit.h"
 #include "VideoConduit.h"
 #include "MediaStreamGraph.h"
 #include "MediaPipeline.h"
 #include "MediaPipelineFilter.h"
 #include "signaling/src/jsep/JsepTrack.h"
 #include "MediaStreamGraphImpl.h"
 #include "logging.h"
 #include "MediaEngine.h"
 #include "nsIPrincipal.h"
 #include "MediaSegment.h"
 #include "RemoteTrackSource.h"
 #include "MediaConduitInterface.h"
-#include "PeerConnectionMedia.h"
+#include "MediaTransportHandler.h"
 #include "mozilla/dom/RTCRtpReceiverBinding.h"
 #include "mozilla/dom/RTCRtpSenderBinding.h"
 #include "mozilla/dom/RTCRtpTransceiverBinding.h"
 #include "mozilla/dom/TransceiverImplBinding.h"
 
 namespace mozilla {
 
 MOZ_MTLOG_MODULE("transceiverimpl")
 
 using LocalDirection = MediaSessionConduitLocalDirection;
 
 TransceiverImpl::TransceiverImpl(
     const std::string& aPCHandle,
+    MediaTransportHandler* aTransportHandler,
     JsepTransceiver* aJsepTransceiver,
     nsIEventTarget* aMainThread,
     nsIEventTarget* aStsThread,
     dom::MediaStreamTrack* aReceiveTrack,
     dom::MediaStreamTrack* aSendTrack,
     WebRtcCallWrapper* aCallWrapper) :
   mPCHandle(aPCHandle),
+  mTransportHandler(aTransportHandler),
   mJsepTransceiver(aJsepTransceiver),
   mHaveStartedReceiving(false),
   mHaveSetupTransport(false),
   mMainThread(aMainThread),
   mStsThread(aStsThread),
   mReceiveTrack(aReceiveTrack),
   mSendTrack(aSendTrack),
   mCallWrapper(aCallWrapper)
@@ -61,16 +60,17 @@ TransceiverImpl::TransceiverImpl(
   if (!IsValid()) {
     return;
   }
 
   mConduit->SetPCHandle(mPCHandle);
 
   mTransmitPipeline = new MediaPipelineTransmit(
       mPCHandle,
+      mTransportHandler,
       mMainThread.get(),
       mStsThread.get(),
       IsVideo(),
       mConduit);
 
   mTransmitPipeline->SetTrack(mSendTrack);
 }
 
@@ -88,16 +88,17 @@ TransceiverImpl::InitAudio()
                         ": Failed to create AudioSessionConduit");
     // TODO(bug 1422897): We need a way to record this when it happens in the
     // wild.
     return;
   }
 
   mReceivePipeline = new MediaPipelineReceiveAudio(
       mPCHandle,
+      mTransportHandler,
       mMainThread.get(),
       mStsThread.get(),
       static_cast<AudioSessionConduit*>(mConduit.get()),
       mReceiveTrack);
 }
 
 void
 TransceiverImpl::InitVideo()
@@ -109,16 +110,17 @@ TransceiverImpl::InitVideo()
                         ": Failed to create VideoSessionConduit");
     // TODO(bug 1422897): We need a way to record this when it happens in the
     // wild.
     return;
   }
 
   mReceivePipeline = new MediaPipelineReceiveVideo(
       mPCHandle,
+      mTransportHandler,
       mMainThread.get(),
       mStsThread.get(),
       static_cast<VideoSessionConduit*>(mConduit.get()),
       mReceiveTrack);
 }
 
 nsresult
 TransceiverImpl::UpdateSinkIdentity(const dom::MediaStreamTrack* aTrack,
@@ -135,57 +137,53 @@ TransceiverImpl::UpdateSinkIdentity(cons
 
 void
 TransceiverImpl::Shutdown_m()
 {
   mReceivePipeline->Shutdown_m();
   mTransmitPipeline->Shutdown_m();
   mReceivePipeline = nullptr;
   mTransmitPipeline = nullptr;
+  mTransportHandler = nullptr;
   mSendTrack = nullptr;
   if (mConduit) {
     mConduit->DeleteStreams();
   }
   mConduit = nullptr;
-  RUN_ON_THREAD(mStsThread, WrapRelease(mRtpFlow.forget()), NS_DISPATCH_NORMAL);
-  RUN_ON_THREAD(mStsThread, WrapRelease(mRtcpFlow.forget()), NS_DISPATCH_NORMAL);
 }
 
 nsresult
 TransceiverImpl::UpdateSendTrack(dom::MediaStreamTrack* aSendTrack)
 {
   if (mJsepTransceiver->IsStopped()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ <<
                       "(" << aSendTrack << ")");
   mSendTrack = aSendTrack;
   return mTransmitPipeline->SetTrack(mSendTrack);
 }
 
 nsresult
-TransceiverImpl::UpdateTransport(PeerConnectionMedia& aTransportManager)
+TransceiverImpl::UpdateTransport()
 {
   if (!mJsepTransceiver->HasLevel()) {
     return NS_OK;
   }
 
   if (!mHaveSetupTransport) {
     mReceivePipeline->SetLevel(mJsepTransceiver->GetLevel());
     mTransmitPipeline->SetLevel(mJsepTransceiver->GetLevel());
     mHaveSetupTransport = true;
   }
 
   ASSERT_ON_THREAD(mMainThread);
   nsAutoPtr<MediaPipelineFilter> filter;
 
-  mRtpFlow = aTransportManager.GetTransportFlow(GetTransportId(), false);
-  mRtcpFlow = aTransportManager.GetTransportFlow(GetTransportId(), true);
-
   if (mJsepTransceiver->HasBundleLevel() &&
       mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()) {
     filter = new MediaPipelineFilter;
 
     // Add remote SSRCs so we can distinguish which RTP packets actually
     // belong to this pipeline (also RTCP sender reports).
     for (unsigned int ssrc : mJsepTransceiver->mRecvTrack.GetSsrcs()) {
       filter->AddRemoteSSRC(ssrc);
@@ -196,18 +194,20 @@ TransceiverImpl::UpdateTransport(PeerCon
     // Add unique payload types as a last-ditch fallback
     auto uniquePts =
       mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()->GetUniquePayloadTypes();
     for (unsigned char& uniquePt : uniquePts) {
       filter->AddUniquePT(uniquePt);
     }
   }
 
-  mReceivePipeline->UpdateTransport_m(mRtpFlow, mRtcpFlow, filter);
-  mTransmitPipeline->UpdateTransport_m(mRtpFlow, mRtcpFlow, nsAutoPtr<MediaPipelineFilter>());
+  mReceivePipeline->UpdateTransport_m(
+      mJsepTransceiver->mTransport.mTransportId, filter);
+  mTransmitPipeline->UpdateTransport_m(
+      mJsepTransceiver->mTransport.mTransportId, filter);
   return NS_OK;
 }
 
 nsresult
 TransceiverImpl::UpdateConduit()
 {
   if (mJsepTransceiver->IsStopped()) {
     return NS_OK;
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
@@ -4,60 +4,59 @@
 #ifndef _TRANSCEIVERIMPL_H_
 #define _TRANSCEIVERIMPL_H_
 
 #include <string>
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIEventTarget.h"
 #include "nsTArray.h"
-#include "mozilla/OwningNonNull.h"
 #include "mozilla/dom/MediaStreamTrack.h"
 #include "ErrorList.h"
-#include "mtransport/transportflow.h"
 #include "signaling/src/jsep/JsepTransceiver.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 class PeerIdentity;
-class PeerConnectionMedia;
 class JsepTransceiver;
 enum class MediaSessionConduitLocalDirection : int;
 class MediaSessionConduit;
 class VideoSessionConduit;
 class AudioSessionConduit;
 class MediaPipelineReceive;
 class MediaPipelineTransmit;
 class MediaPipeline;
 class MediaPipelineFilter;
+class MediaTransportHandler;
 class WebRtcCallWrapper;
 class JsepTrackNegotiatedDetails;
 
 namespace dom {
 class RTCRtpTransceiver;
 struct RTCRtpSourceEntry;
 }
 
 /**
  * This is what ties all the various pieces that make up a transceiver
  * together. This includes:
  * MediaStreamTrack for rendering and capture
- * TransportFlow for RTP transmission/reception
+ * MediaTransportHandler for RTP transmission/reception
  * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and
  * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP.
 */
 class TransceiverImpl : public nsISupports {
 public:
   /**
    * |aReceiveStream| is always set; this holds even if the remote end has not
    * negotiated one for this transceiver. |aSendTrack| might or might not be
    * set.
    */
   TransceiverImpl(const std::string& aPCHandle,
+                  MediaTransportHandler* aTransportHandler,
                   JsepTransceiver* aJsepTransceiver,
                   nsIEventTarget* aMainThread,
                   nsIEventTarget* aStsThread,
                   dom::MediaStreamTrack* aReceiveTrack,
                   dom::MediaStreamTrack* aSendTrack,
                   WebRtcCallWrapper* aCallWrapper);
 
   bool IsValid() const
@@ -66,17 +65,17 @@ public:
   }
 
   nsresult UpdateSendTrack(dom::MediaStreamTrack* aSendTrack);
 
   nsresult UpdateSinkIdentity(const dom::MediaStreamTrack* aTrack,
                               nsIPrincipal* aPrincipal,
                               const PeerIdentity* aSinkIdentity);
 
-  nsresult UpdateTransport(PeerConnectionMedia& aTransportManager);
+  nsresult UpdateTransport();
 
   nsresult UpdateConduit();
 
   nsresult UpdatePrincipal(nsIPrincipal* aPrincipal);
 
   // TODO: We probably need to de-Sync when transceivers are stopped.
   nsresult SyncWithMatchingVideoConduits(
       std::vector<RefPtr<TransceiverImpl>>& transceivers);
@@ -139,28 +138,27 @@ private:
   nsresult UpdateAudioConduit();
   nsresult UpdateVideoConduit();
   nsresult ConfigureVideoCodecMode(VideoSessionConduit& aConduit);
   void UpdateConduitRtpExtmap(const JsepTrackNegotiatedDetails& aDetails,
                               const MediaSessionConduitLocalDirection aDir);
   void Stop();
 
   const std::string mPCHandle;
+  RefPtr<MediaTransportHandler> mTransportHandler;
   RefPtr<JsepTransceiver> mJsepTransceiver;
   std::string mMid;
   bool mHaveStartedReceiving;
   bool mHaveSetupTransport;
   nsCOMPtr<nsIEventTarget> mMainThread;
   nsCOMPtr<nsIEventTarget> mStsThread;
   RefPtr<dom::MediaStreamTrack> mReceiveTrack;
   RefPtr<dom::MediaStreamTrack> mSendTrack;
   // state for webrtc.org that is shared between all transceivers
   RefPtr<WebRtcCallWrapper> mCallWrapper;
-  RefPtr<TransportFlow> mRtpFlow;
-  RefPtr<TransportFlow> mRtcpFlow;
   RefPtr<MediaSessionConduit> mConduit;
   RefPtr<MediaPipelineReceive> mReceivePipeline;
   RefPtr<MediaPipelineTransmit> mTransmitPipeline;
 };
 
 } // namespace mozilla
 
 #endif // _TRANSCEIVERIMPL_H_
--- a/media/webrtc/signaling/src/peerconnection/moz.build
+++ b/media/webrtc/signaling/src/peerconnection/moz.build
@@ -17,16 +17,17 @@ LOCAL_INCLUDES += [
     '/media/webrtc/signaling/src/common/time_profiling',
     '/media/webrtc/signaling/src/media-conduit',
     '/media/webrtc/signaling/src/mediapipeline',
     '/media/webrtc/trunk',
     '/netwerk/srtp/src/include',
 ]
 
 UNIFIED_SOURCES += [
+    'MediaTransportHandler.cpp',
     'PacketDumper.cpp',
     'PeerConnectionCtx.cpp',
     'PeerConnectionImpl.cpp',
     'PeerConnectionMedia.cpp',
     'TransceiverImpl.cpp',
     'WebrtcGlobalInformation.cpp',
 ]
 
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -45,16 +45,18 @@
 #include "nsAutoPtr.h"
 #include "nsNetUtil.h"
 #include "nsNetCID.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/Unused.h"
 #ifdef MOZ_PEERCONNECTION
 #include "mtransport/runnable_utils.h"
+#include "signaling/src/peerconnection/MediaTransportHandler.h"
+#include "mediapacket.h"
 #endif
 
 #define DATACHANNEL_LOG(args) LOG(args)
 #include "DataChannel.h"
 #include "DataChannelProtocol.h"
 
 // Let us turn on and off important assertions in non-debug builds
 #ifdef DEBUG
@@ -299,31 +301,32 @@ debug_printf(const char *format, ...)
 #endif
       SCTP_LOG(("%s", buffer));
     }
     va_end(ap);
   }
 }
 
 DataChannelConnection::DataChannelConnection(DataConnectionListener *listener,
-                                             nsIEventTarget *aTarget)
+                                             nsIEventTarget *aTarget,
+                                             MediaTransportHandler* aHandler)
   : NeckoTargetHolder(aTarget)
   , mLock("netwerk::sctp::DataChannelConnection")
   , mSendInterleaved(false)
   , mPpidFragmentation(false)
   , mMaxMessageSizeSet(false)
   , mMaxMessageSize(0)
   , mAllocateEven(false)
+  , mTransportHandler(aHandler)
 {
   mCurrentStream = 0;
   mState = CLOSED;
   mSocket = nullptr;
   mMasterSocket = nullptr;
   mListener = listener;
-  mDtls = nullptr;
   mLocalPort = 0;
   mRemotePort = 0;
   mPendingType = PENDING_NONE;
   LOG(("Constructor DataChannelConnection=%p, listener=%p", this, mListener.get()));
   mInternalIOThread = nullptr;
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   mShutdown = false;
 #endif
@@ -331,20 +334,17 @@ DataChannelConnection::DataChannelConnec
 
 DataChannelConnection::~DataChannelConnection()
 {
   LOG(("Deleting DataChannelConnection %p", (void *) this));
   // This may die on the MainThread, or on the STS thread
   ASSERT_WEBRTC(mState == CLOSED);
   MOZ_ASSERT(!mMasterSocket);
   MOZ_ASSERT(mPending.GetSize() == 0);
-  MOZ_ASSERT(!mDtls);
-
-  // Already disconnected from sigslot/mTransportFlow
-  // TransportFlows must be released from the STS thread
+
   if (!IsSTSThread()) {
     ASSERT_WEBRTC(NS_IsMainThread());
 
     if (mInternalIOThread) {
       // Avoid spinning the event thread from here (which if we're mainthread
       // is in the event loop already)
       nsCOMPtr<nsIRunnable> r = WrapRunnable(nsCOMPtr<nsIThread>(mInternalIOThread),
                                              &nsIThread::Shutdown);
@@ -416,18 +416,17 @@ void DataChannelConnection::DestroyOnSTS
   // DON'T use RUN_ON_THREAD, it queue-jumps!
   mSTS->Dispatch(WrapRunnable(RefPtr<DataChannelConnection>(this),
                               &DataChannelConnection::DestroyOnSTSFinal),
                  NS_DISPATCH_NORMAL);
 }
 
 void DataChannelConnection::DestroyOnSTSFinal()
 {
-  mTransportFlow = nullptr;
-  mDtls = nullptr;
+  mTransportHandler = nullptr;
   sDataChannelShutdown->CreateConnectionShutdown(this);
 }
 
 bool
 DataChannelConnection::Init(unsigned short aPort, uint16_t aNumStreams, bool aMaxMessageSizeSet,
                             uint64_t aMaxMessageSize)
 {
   struct sctp_initmsg initmsg;
@@ -681,70 +680,81 @@ DataChannelConnection::SetMaxMessageSize
 
 uint64_t
 DataChannelConnection::GetMaxMessageSize()
 {
   return mMaxMessageSize;
 }
 
 #ifdef MOZ_PEERCONNECTION
-void
-DataChannelConnection::SetEvenOdd()
-{
-  ASSERT_WEBRTC(IsSTSThread());
-
-  MOZ_ASSERT(mDtls);  // DTLS is mandatory
-  mAllocateEven = (mDtls->role() == TransportLayerDtls::CLIENT);
-}
 
 bool
-DataChannelConnection::ConnectViaTransportFlow(TransportFlow *aFlow, uint16_t localport, uint16_t remoteport)
+DataChannelConnection::ConnectToTransport(
+    const std::string& aTransportId,
+    bool aClient,
+    uint16_t localport, uint16_t remoteport)
 {
   LOG(("Connect DTLS local %u, remote %u", localport, remoteport));
 
-  MOZ_ASSERT(mMasterSocket, "SCTP wasn't initialized before ConnectViaTransportFlow!");
-  if (NS_WARN_IF(!aFlow)) {
+  MOZ_ASSERT(mMasterSocket, "SCTP wasn't initialized before ConnectToTransport!");
+  if (NS_WARN_IF(aTransportId.empty())) {
     return false;
   }
 
-  mTransportFlow = aFlow;
   mLocalPort = localport;
   mRemotePort = remoteport;
   mState = CONNECTING;
 
   RUN_ON_THREAD(mSTS, WrapRunnable(RefPtr<DataChannelConnection>(this),
-                                   &DataChannelConnection::SetSignals),
+                                   &DataChannelConnection::SetSignals,
+                                   aTransportId,
+                                   aClient),
                 NS_DISPATCH_NORMAL);
   return true;
 }
 
 void
-DataChannelConnection::SetSignals()
+DataChannelConnection::SetSignals(const std::string& aTransportId,
+                                  bool aClient)
 {
   ASSERT_WEBRTC(IsSTSThread());
-  mDtls = static_cast<TransportLayerDtls*>(mTransportFlow->GetLayer("dtls"));
-  ASSERT_WEBRTC(mDtls);
-  LOG(("Setting transport signals, state: %d", mDtls->state()));
-  mDtls->SignalPacketReceived.connect(this, &DataChannelConnection::SctpDtlsInput);
+  mTransportId = aTransportId;
+  mAllocateEven = aClient;
+  mTransportHandler->SignalPacketReceived.connect(
+      this, &DataChannelConnection::SctpDtlsInput);
   // SignalStateChange() doesn't call you with the initial state
-  mDtls->SignalStateChange.connect(this, &DataChannelConnection::CompleteConnect);
-  CompleteConnect(mDtls, mDtls->state());
+  if (mTransportHandler->GetState(mTransportId, false)
+        == TransportLayer::TS_OPEN) {
+    LOG(("Setting transport signals, dtls already open"));
+    CompleteConnect();
+  } else {
+    LOG(("Setting transport signals, dtls not open yet"));
+    mTransportHandler->SignalStateChange.connect(
+        this, &DataChannelConnection::TransportStateChange);
+  }
 }
 
 void
-DataChannelConnection::CompleteConnect(TransportLayer *layer, TransportLayer::State state)
+DataChannelConnection::TransportStateChange(const std::string& aTransportId,
+                                            TransportLayer::State aState)
 {
-  LOG(("Data transport state: %d", state));
+  if (aState == TransportLayer::TS_OPEN) {
+    CompleteConnect();
+  }
+}
+
+void
+DataChannelConnection::CompleteConnect()
+{
+  LOG(("dtls open"));
   MutexAutoLock lock(mLock);
   ASSERT_WEBRTC(IsSTSThread());
-  // We should abort connection on TS_ERROR.
-  // Note however that the association will also fail (perhaps with a delay) and
-  // notify us in that way
-  if (state != TransportLayer::TS_OPEN || !mMasterSocket)
+  if (!mMasterSocket) {
     return;
+  }
 
   struct sockaddr_conn addr;
   memset(&addr, 0, sizeof(addr));
   addr.sconn_family = AF_CONN;
 #if defined(__Userspace_os_Darwin)
   addr.sconn_len = sizeof(addr);
 #endif
   addr.sconn_port = htons(mLocalPort);
@@ -790,18 +800,17 @@ DataChannelConnection::CompleteConnect(T
     if (r < 0) {
       if (errno == EINPROGRESS) {
         // non-blocking
         return;
       }
       LOG(("usrsctp_connect failed: %d", errno));
       mState = CLOSED;
     } else {
-      // We set Even/Odd and fire ON_CONNECTION via SCTP_COMM_UP when we get that
-      // This also avoids issues with calling TransportFlow stuff on Mainthread
+      // We fire ON_CONNECTION via SCTP_COMM_UP when we get that
       return;
     }
   }
   // Note: currently this doesn't actually notify the application
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
              DataChannelOnMessageAvailable::ON_CONNECTION,
              this)));
 }
@@ -833,18 +842,23 @@ DataChannelConnection::ProcessQueuedOpen
     } else {
       NS_ASSERTION(false, "How did a DataChannel get queued without the FINISH_OPEN flag?");
     }
   }
 
 }
 
 void
-DataChannelConnection::SctpDtlsInput(TransportLayer *layer, MediaPacket& packet)
+DataChannelConnection::SctpDtlsInput(const std::string& aTransportId,
+                                     MediaPacket& packet)
 {
+  if ((packet.type() != MediaPacket::SCTP) || (mTransportId != aTransportId)) {
+    return;
+  }
+
   if (MOZ_LOG_TEST(gSCTPLog, LogLevel::Debug)) {
     char *buf;
 
     if ((buf = usrsctp_dumppacket((void *)packet.data(),
                                   packet.len(),
                                   SCTP_DUMP_INBOUND)) != nullptr) {
       SCTP_LOG(("%s", buf));
       usrsctp_freedumpbuffer(buf);
@@ -854,18 +868,19 @@ DataChannelConnection::SctpDtlsInput(Tra
   MutexAutoLock lock(mLock);
   usrsctp_conninput(static_cast<void *>(this), packet.data(), packet.len(), 0);
 }
 
 int
 DataChannelConnection::SendPacket(nsAutoPtr<MediaPacket> packet)
 {
   //LOG(("%p: SCTP/DTLS sent %ld bytes", this, len));
-  if (mDtls) {
-    return mDtls->SendPacket(*packet) < 0 ? 1 : 0;
+  if (!mTransportId.empty()) {
+    nsresult rv = mTransportHandler->SendPacket(mTransportId, *packet);
+    return NS_FAILED(rv) ? 1 : 0;
   }
   return 0;
 }
 
 /* static */
 int
 DataChannelConnection::SctpDtlsOutput(void *addr, void *buffer, size_t length,
                                       uint8_t tos, uint8_t set_df)
@@ -883,16 +898,17 @@ DataChannelConnection::SctpDtlsOutput(vo
   }
 
   // We're async proxying even if on the STSThread because this is called
   // with internal SCTP locks held in some cases (such as in usrsctp_connect()).
   // SCTP has an option for Apple, on IP connections only, to release at least
   // one of the locks before calling a packet output routine; with changes to
   // the underlying SCTP stack this might remove the need to use an async proxy.
   nsAutoPtr<MediaPacket> packet(new MediaPacket);
+  packet->SetType(MediaPacket::SCTP);
   packet->Copy(static_cast<const uint8_t*>(buffer), length);
 
   // XXX It might be worthwhile to add an assertion against the thread
   // somehow getting into the DataChannel/SCTP code again, as
   // DISPATCH_SYNC is not fully blocking.  This may be tricky, as it
   // needs to be a per-thread check, not a global.
   peer->mSTS->Dispatch(WrapRunnable(
                          RefPtr<DataChannelConnection>(peer),
@@ -901,18 +917,16 @@ DataChannelConnection::SctpDtlsOutput(vo
   return 0; // cheat!  Packets can always be dropped later anyways
 }
 #endif
 
 #ifdef ALLOW_DIRECT_SCTP_LISTEN_CONNECT
 // listen for incoming associations
 // Blocks! - Don't call this from main thread!
 
-#error This code will not work as-is since SetEvenOdd() runs on Mainthread
-
 bool
 DataChannelConnection::Listen(unsigned short port)
 {
   struct sockaddr_in addr;
   socklen_t addr_len;
 
   NS_WARNING_ASSERTION(!NS_IsMainThread(),
                        "Blocks, do not call from main thread!!!");
@@ -947,18 +961,16 @@ DataChannelConnection::Listen(unsigned s
   struct linger l;
   l.l_onoff = 1;
   l.l_linger = 0;
   if (usrsctp_setsockopt(mSocket, SOL_SOCKET, SO_LINGER,
                          (const void *)&l, (socklen_t)sizeof(struct linger)) < 0) {
     LOG(("Couldn't set SO_LINGER on SCTP socket"));
   }
 
-  SetEvenOdd();
-
   // Notify Connection open
   // XXX We need to make sure connection sticks around until the message is delivered
   LOG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
              DataChannelOnMessageAvailable::ON_CONNECTION,
              this, (DataChannel *) nullptr)));
   return true;
 }
@@ -1026,18 +1038,16 @@ DataChannelConnection::Connect(const cha
   }
 #endif
 
   mSocket = mMasterSocket;
 
   LOG(("connect() succeeded!  Entering connected mode"));
   mState = OPEN;
 
-  SetEvenOdd();
-
   // Notify Connection open
   // XXX We need to make sure connection sticks around until the message is delivered
   LOG(("%s: sending ON_CONNECTION for %p", __FUNCTION__, this));
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
              DataChannelOnMessageAvailable::ON_CONNECTION,
              this, (DataChannel *) nullptr)));
   return true;
 }
@@ -1805,18 +1815,16 @@ DataChannelConnection::HandleAssociation
       // Check for older Firefox by looking at the amount of incoming streams
       LOG(("Negotiated number of incoming streams: %" PRIu16, sac->sac_inbound_streams));
       if (!mMaxMessageSizeSet
           && sac->sac_inbound_streams == WEBRTC_DATACHANNEL_STREAMS_OLDER_FIREFOX) {
         LOG(("Older Firefox detected, using PPID-based fragmentation"));
         mPpidFragmentation = true;
       }
 
-      SetEvenOdd();
-
       Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
                  DataChannelOnMessageAvailable::ON_CONNECTION,
                  this)));
       LOG(("DTLS connect() succeeded!  Entering connected mode"));
 
       // Open any streams pending...
       ProcessQueuedOpens();
 
--- a/netwerk/sctp/datachannel/DataChannel.h
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -22,19 +22,17 @@
 #include "nsDeque.h"
 #include "nsIInputStream.h"
 #include "mozilla/Mutex.h"
 #include "DataChannelProtocol.h"
 #include "DataChannelListener.h"
 #include "mozilla/net/NeckoTargetHolder.h"
 #ifdef SCTP_DTLS_SUPPORTED
 #include "mtransport/sigslot.h"
-#include "mtransport/transportflow.h"
-#include "mtransport/transportlayer.h"
-#include "mtransport/transportlayerdtls.h"
+#include "mtransport/transportlayer.h" // For TransportLayer::State
 #endif
 
 #ifndef DATACHANNEL_LOG
 #define DATACHANNEL_LOG(args)
 #endif
 
 #ifndef EALREADY
 #define EALREADY  WSAEALREADY
@@ -45,16 +43,18 @@ extern "C" {
   struct sctp_rcvinfo;
 }
 
 namespace mozilla {
 
 class DataChannelConnection;
 class DataChannel;
 class DataChannelOnMessageAvailable;
+class MediaPacket;
+class MediaTransportHandler;
 
 // For sending outgoing messages.
 // This class only holds a reference to the data and the info structure but does
 // not copy it.
 class OutgoingMsg
 {
 public:
   OutgoingMsg(struct sctp_sendv_spa &info, const uint8_t *data,
@@ -140,17 +140,18 @@ public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(DataChannelConnection::DataConnectionListener)
     virtual ~DataConnectionListener() = default;
 
     // Called when a new DataChannel has been opened by the other side.
     virtual void NotifyDataChannel(already_AddRefed<DataChannel> channel) = 0;
   };
 
   DataChannelConnection(DataConnectionListener *listener,
-                        nsIEventTarget *aTarget);
+                        nsIEventTarget *aTarget,
+                        MediaTransportHandler* aTransportHandler);
 
   bool Init(unsigned short aPort, uint16_t aNumStreams, bool aMaxMessageSizeSet,
             uint64_t aMaxMessageSize);
 
   void Destroy(); // So we can spawn refs tied to runnables in shutdown
   // Finish Destroy on STS to avoid SCTP race condition with ABORT from far end
   void DestroyOnSTS(struct socket *aMasterSocket,
                     struct socket *aSocket);
@@ -163,21 +164,21 @@ public:
   // These block; they require something to decide on listener/connector
   // (though you can do simultaneous Connect()).  Do not call these from
   // the main thread!
   bool Listen(unsigned short port);
   bool Connect(const char *addr, unsigned short port);
 #endif
 
 #ifdef SCTP_DTLS_SUPPORTED
-  // Connect using a TransportFlow (DTLS) channel
-  void SetEvenOdd();
-  bool ConnectViaTransportFlow(TransportFlow *aFlow, uint16_t localport, uint16_t remoteport);
-  void CompleteConnect(TransportLayer *layer, TransportLayer::State state);
-  void SetSignals();
+  bool ConnectToTransport(const std::string& aTransportId, bool aClient, uint16_t localport, uint16_t remoteport);
+  void TransportStateChange(const std::string& aTransportId,
+                            TransportLayer::State aState);
+  void CompleteConnect();
+  void SetSignals(const std::string& aTransportId, bool aClient);
 #endif
 
   typedef enum {
     RELIABLE=0,
     PARTIAL_RELIABLE_REXMIT = 1,
     PARTIAL_RELIABLE_TIMED = 2
   } Type;
 
@@ -242,17 +243,17 @@ protected:
   WeakPtr<DataConnectionListener> mListener;
 
 private:
   friend class DataChannelConnectRunnable;
 
 #ifdef SCTP_DTLS_SUPPORTED
   static void DTLSConnectThread(void *data);
   int SendPacket(nsAutoPtr<MediaPacket> packet);
-  void SctpDtlsInput(TransportLayer *layer, MediaPacket& packet);
+  void SctpDtlsInput(const std::string& aTransportId, MediaPacket& packet);
   static int SctpDtlsOutput(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df);
 #endif
   DataChannel* FindChannelByStream(uint16_t stream);
   uint16_t FindFreeStream();
   bool RequestMoreStreams(int32_t aNeeded = 16);
   uint32_t UpdateCurrentStreamIndex();
   uint32_t GetCurrentStreamIndex();
   int SendControlMessage(const uint8_t *data, uint32_t len, uint16_t stream);
@@ -305,19 +306,16 @@ private:
     bool on = false;
     if (mSTS) {
       mSTS->IsOnCurrentThread(&on);
     }
     return on;
   }
 #endif
 
-  // Exists solely for proxying release of the TransportFlow to the STS thread
-  static void ReleaseTransportFlow(const RefPtr<TransportFlow>& aFlow) {}
-
   bool mSendInterleaved;
   bool mPpidFragmentation;
   bool mMaxMessageSizeSet;
   uint64_t mMaxMessageSize;
 
   // Data:
   // NOTE: while this array will auto-expand, increases in the number of
   // channels available from the stack must be negotiated!
@@ -333,18 +331,18 @@ private:
   // Streams pending reset
   AutoTArray<uint16_t,4> mStreamsResetting;
 
   struct socket *mMasterSocket; // accessed from STS thread
   struct socket *mSocket; // cloned from mMasterSocket on successful Connect on STS thread
   uint16_t mState; // Protected with mLock
 
 #ifdef SCTP_DTLS_SUPPORTED
-  RefPtr<TransportFlow> mTransportFlow;
-  TransportLayerDtls* mDtls;
+  std::string mTransportId;
+  RefPtr<MediaTransportHandler> mTransportHandler;
   nsCOMPtr<nsIEventTarget> mSTS;
 #endif
   uint16_t mLocalPort; // Accessed from connect thread
   uint16_t mRemotePort;
 
   nsCOMPtr<nsIThread> mInternalIOThread;
   uint8_t mPendingType;
   nsCString mRecvBuffer;
--- a/netwerk/sctp/datachannel/moz.build
+++ b/netwerk/sctp/datachannel/moz.build
@@ -15,16 +15,17 @@ SOURCES += [
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/media/mtransport',
+    '/media/webrtc',
     '/netwerk/sctp/src',
 ]
 
 DEFINES['SCTP_DEBUG'] = 1
 
 if CONFIG['OS_TARGET'] == 'WINNT':
     DEFINES['__Userspace_os_Windows'] = 1
 else: