Move MID/JsepTransport mappings into a new manager object.

This is part of the work to make Bundle handling understandable,
so that we can get it to work right.

Bug: webrtc:12837
Change-Id: I77f046b4bac2d9709460b3b956a2edc3df0cdaac
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/221745
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34261}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 8a4955c462..a1feb98 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -41,8 +41,6 @@
   visibility = [ "*" ]
   defines = []
   sources = [
-    "bundle_manager.cc",
-    "bundle_manager.h",
     "channel.cc",
     "channel.h",
     "channel_interface.h",
@@ -58,6 +56,8 @@
     "ice_transport.h",
     "jsep_transport.cc",
     "jsep_transport.h",
+    "jsep_transport_collection.cc",
+    "jsep_transport_collection.h",
     "jsep_transport_controller.cc",
     "jsep_transport_controller.h",
     "media_session.cc",
diff --git a/pc/bundle_manager.cc b/pc/bundle_manager.cc
deleted file mode 100644
index 0228635..0000000
--- a/pc/bundle_manager.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Copyright 2021 The WebRTC Project Authors. All rights reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree. An additional intellectual property rights grant can be found
- *  in the file PATENTS.  All contributing project authors may
- *  be found in the AUTHORS file in the root of the source tree.
- */
-
-#include "pc/bundle_manager.h"
-
-namespace webrtc {
-
-void BundleManager::Update(const cricket::SessionDescription* description) {
-  bundle_groups_.clear();
-  for (const cricket::ContentGroup* new_bundle_group :
-       description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
-    bundle_groups_.push_back(
-        std::make_unique<cricket::ContentGroup>(*new_bundle_group));
-  }
-}
-
-void BundleManager::DeleteMid(const cricket::ContentGroup* bundle_group,
-                              const std::string& mid) {
-  // Remove the rejected content from the |bundle_group|.
-  // The const pointer arg is used to identify the group, we verify
-  // it before we use it to make a modification.
-  auto bundle_group_it = std::find_if(
-      bundle_groups_.begin(), bundle_groups_.end(),
-      [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
-        return bundle_group == group.get();
-      });
-  RTC_DCHECK(bundle_group_it != bundle_groups_.end());
-  (*bundle_group_it)->RemoveContentName(mid);
-}
-
-void BundleManager::DeleteGroup(const cricket::ContentGroup* bundle_group) {
-  // Delete the BUNDLE group.
-  auto bundle_group_it = std::find_if(
-      bundle_groups_.begin(), bundle_groups_.end(),
-      [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
-        return bundle_group == group.get();
-      });
-  RTC_DCHECK(bundle_group_it != bundle_groups_.end());
-  bundle_groups_.erase(bundle_group_it);
-}
-
-}  // namespace webrtc
diff --git a/pc/bundle_manager.h b/pc/bundle_manager.h
deleted file mode 100644
index 2e2aa77..0000000
--- a/pc/bundle_manager.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- *  Copyright 2021 The WebRTC Project Authors. All rights reserved.
- *
- *  Use of this source code is governed by a BSD-style license
- *  that can be found in the LICENSE file in the root of the source
- *  tree. An additional intellectual property rights grant can be found
- *  in the file PATENTS.  All contributing project authors may
- *  be found in the AUTHORS file in the root of the source tree.
- */
-
-#ifndef PC_BUNDLE_MANAGER_H_
-#define PC_BUNDLE_MANAGER_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "pc/session_description.h"
-
-namespace webrtc {
-// This class manages information about RFC 8843 BUNDLE bundles
-// in SDP descriptions.
-
-// This is a work-in-progress. Planned steps:
-// 1) Move all Bundle-related data structures from JsepTransport
-//    into this class.
-// 2) Move all Bundle-related functions into this class.
-// 3) Move remaining Bundle-related logic into this class.
-//    Make data members private.
-// 4) Refine interface to have comprehensible semantics.
-// 5) Add unit tests.
-// 6) Change the logic to do what's right.
-class BundleManager {
- public:
-  const std::vector<std::unique_ptr<cricket::ContentGroup>>& bundle_groups()
-      const {
-    return bundle_groups_;
-  }
-  // Update the groups description. This completely replaces the group
-  // description with the one from the SessionDescription.
-  void Update(const cricket::SessionDescription* description);
-  // Delete a MID from the group that contains it.
-  void DeleteMid(const cricket::ContentGroup* bundle_group,
-                 const std::string& mid);
-  // Delete a group.
-  void DeleteGroup(const cricket::ContentGroup* bundle_group);
-
- private:
-  std::vector<std::unique_ptr<cricket::ContentGroup>> bundle_groups_;
-};
-
-}  // namespace webrtc
-
-#endif  // PC_BUNDLE_MANAGER_H_
diff --git a/pc/jsep_transport_collection.cc b/pc/jsep_transport_collection.cc
new file mode 100644
index 0000000..42d374c
--- /dev/null
+++ b/pc/jsep_transport_collection.cc
@@ -0,0 +1,182 @@
+/*
+ *  Copyright 2021 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/jsep_transport_collection.h"
+
+#include <map>
+
+namespace webrtc {
+
+void BundleManager::Update(const cricket::SessionDescription* description) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  bundle_groups_.clear();
+  for (const cricket::ContentGroup* new_bundle_group :
+       description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)) {
+    bundle_groups_.push_back(
+        std::make_unique<cricket::ContentGroup>(*new_bundle_group));
+  }
+}
+
+void BundleManager::DeleteMid(const cricket::ContentGroup* bundle_group,
+                              const std::string& mid) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  // Remove the rejected content from the |bundle_group|.
+  // The const pointer arg is used to identify the group, we verify
+  // it before we use it to make a modification.
+  auto bundle_group_it = std::find_if(
+      bundle_groups_.begin(), bundle_groups_.end(),
+      [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
+        return bundle_group == group.get();
+      });
+  RTC_DCHECK(bundle_group_it != bundle_groups_.end());
+  (*bundle_group_it)->RemoveContentName(mid);
+}
+
+void BundleManager::DeleteGroup(const cricket::ContentGroup* bundle_group) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  // Delete the BUNDLE group.
+  auto bundle_group_it = std::find_if(
+      bundle_groups_.begin(), bundle_groups_.end(),
+      [bundle_group](std::unique_ptr<cricket::ContentGroup>& group) {
+        return bundle_group == group.get();
+      });
+  RTC_DCHECK(bundle_group_it != bundle_groups_.end());
+  bundle_groups_.erase(bundle_group_it);
+}
+
+void JsepTransportCollection::RegisterTransport(
+    const std::string& mid,
+    std::unique_ptr<cricket::JsepTransport> transport) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  jsep_transports_by_name_[mid] = std::move(transport);
+}
+
+std::vector<cricket::JsepTransport*> JsepTransportCollection::Transports() {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  std::vector<cricket::JsepTransport*> result;
+  for (auto& kv : jsep_transports_by_name_) {
+    result.push_back(kv.second.get());
+  }
+  return result;
+}
+
+void JsepTransportCollection::DestroyAllTransports() {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  for (const auto& jsep_transport : jsep_transports_by_name_) {
+    map_change_callback_(jsep_transport.first, nullptr);
+  }
+  jsep_transports_by_name_.clear();
+}
+
+const cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
+    const std::string& transport_name) const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  auto it = jsep_transports_by_name_.find(transport_name);
+  return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
+}
+
+cricket::JsepTransport* JsepTransportCollection::GetTransportByName(
+    const std::string& transport_name) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  auto it = jsep_transports_by_name_.find(transport_name);
+  return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
+}
+
+cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
+    const std::string& mid) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  auto it = mid_to_transport_.find(mid);
+  return it == mid_to_transport_.end() ? nullptr : it->second;
+}
+
+const cricket::JsepTransport* JsepTransportCollection::GetTransportForMid(
+    const std::string& mid) const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  auto it = mid_to_transport_.find(mid);
+  return it == mid_to_transport_.end() ? nullptr : it->second;
+}
+
+bool JsepTransportCollection::SetTransportForMid(
+    const std::string& mid,
+    cricket::JsepTransport* jsep_transport) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  RTC_DCHECK(jsep_transport);
+
+  auto it = mid_to_transport_.find(mid);
+  if (it != mid_to_transport_.end() && it->second == jsep_transport)
+    return true;
+
+  pending_mids_.push_back(mid);
+
+  if (it == mid_to_transport_.end()) {
+    mid_to_transport_.insert(std::make_pair(mid, jsep_transport));
+  } else {
+    it->second = jsep_transport;
+  }
+
+  return map_change_callback_(mid, jsep_transport);
+}
+
+void JsepTransportCollection::RemoveTransportForMid(const std::string& mid) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  bool ret = map_change_callback_(mid, nullptr);
+  // Calling OnTransportChanged with nullptr should always succeed, since it is
+  // only expected to fail when adding media to a transport (not removing).
+  RTC_DCHECK(ret);
+
+  mid_to_transport_.erase(mid);
+}
+
+void JsepTransportCollection::RollbackTransports() {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  for (auto&& mid : pending_mids_) {
+    RemoveTransportForMid(mid);
+  }
+  for (auto&& mid : pending_mids_) {
+    MaybeDestroyJsepTransport(mid);
+  }
+  pending_mids_.clear();
+}
+
+void JsepTransportCollection::CommitTransports() {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  pending_mids_.clear();
+}
+
+bool JsepTransportCollection::TransportInUse(
+    cricket::JsepTransport* jsep_transport) const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  for (const auto& kv : mid_to_transport_) {
+    if (kv.second == jsep_transport) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void JsepTransportCollection::MaybeDestroyJsepTransport(
+    const std::string& mid) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  auto it = jsep_transports_by_name_.find(mid);
+  if (it == jsep_transports_by_name_.end()) {
+    return;
+  }
+
+  // Don't destroy the JsepTransport if there are still media sections referring
+  // to it.
+  if (TransportInUse(it->second.get())) {
+    return;
+  }
+
+  jsep_transports_by_name_.erase(mid);
+  state_change_callback_();
+}
+
+}  // namespace webrtc
diff --git a/pc/jsep_transport_collection.h b/pc/jsep_transport_collection.h
new file mode 100644
index 0000000..3078654
--- /dev/null
+++ b/pc/jsep_transport_collection.h
@@ -0,0 +1,125 @@
+/*
+ *  Copyright 2021 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_JSEP_TRANSPORT_COLLECTION_H_
+#define PC_JSEP_TRANSPORT_COLLECTION_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "pc/jsep_transport.h"
+#include "pc/session_description.h"
+
+namespace webrtc {
+
+// This class manages information about RFC 8843 BUNDLE bundles
+// in SDP descriptions.
+
+// This is a work-in-progress. Planned steps:
+// 1) Move all Bundle-related data structures from JsepTransport
+//    into this class.
+// 2) Move all Bundle-related functions into this class.
+// 3) Move remaining Bundle-related logic into this class.
+//    Make data members private.
+// 4) Refine interface to have comprehensible semantics.
+// 5) Add unit tests.
+// 6) Change the logic to do what's right.
+class BundleManager {
+ public:
+  BundleManager() {
+    // Allow constructor to be called on a different thread.
+    sequence_checker_.Detach();
+  }
+  const std::vector<std::unique_ptr<cricket::ContentGroup>>& bundle_groups()
+      const {
+    RTC_DCHECK_RUN_ON(&sequence_checker_);
+    return bundle_groups_;
+  }
+  // Update the groups description. This completely replaces the group
+  // description with the one from the SessionDescription.
+  void Update(const cricket::SessionDescription* description);
+  // Delete a MID from the group that contains it.
+  void DeleteMid(const cricket::ContentGroup* bundle_group,
+                 const std::string& mid);
+  // Delete a group.
+  void DeleteGroup(const cricket::ContentGroup* bundle_group);
+
+ private:
+  RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
+  std::vector<std::unique_ptr<cricket::ContentGroup>> bundle_groups_
+      RTC_GUARDED_BY(sequence_checker_);
+};
+
+// This class keeps the mapping of MIDs to transports.
+// It is pulled out here because a lot of the code that deals with
+// bundles end up modifying this map, and the two need to be consistent;
+// the managers may merge.
+class JsepTransportCollection {
+ public:
+  JsepTransportCollection(std::function<bool(const std::string& mid,
+                                             cricket::JsepTransport* transport)>
+                              map_change_callback,
+                          std::function<void()> state_change_callback)
+      : map_change_callback_(map_change_callback),
+        state_change_callback_(state_change_callback) {
+    // Allow constructor to be called on a different thread.
+    sequence_checker_.Detach();
+  }
+
+  void RegisterTransport(const std::string& mid,
+                         std::unique_ptr<cricket::JsepTransport> transport);
+  std::vector<cricket::JsepTransport*> Transports();
+  void DestroyAllTransports();
+  // Lookup a JsepTransport by the MID that was used to register it.
+  cricket::JsepTransport* GetTransportByName(const std::string& mid);
+  const cricket::JsepTransport* GetTransportByName(
+      const std::string& mid) const;
+  // Lookup a JsepTransport by any MID that refers to it.
+  cricket::JsepTransport* GetTransportForMid(const std::string& mid);
+  const cricket::JsepTransport* GetTransportForMid(
+      const std::string& mid) const;
+  bool SetTransportForMid(const std::string& mid,
+                          cricket::JsepTransport* jsep_transport);
+  void RemoveTransportForMid(const std::string& mid);
+  // Roll back pending mid-to-transport mappings.
+  void RollbackTransports();
+  // Commit pending mid-transport mappings (rollback is no longer possible).
+  void CommitTransports();
+  // Returns true if any mid currently maps to this transport.
+  bool TransportInUse(cricket::JsepTransport* jsep_transport) const;
+  // Destroy a transport if it's no longer in use.
+  void MaybeDestroyJsepTransport(const std::string& mid);
+
+ private:
+  RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
+  // This member owns the JSEP transports.
+  std::map<std::string, std::unique_ptr<cricket::JsepTransport>>
+      jsep_transports_by_name_ RTC_GUARDED_BY(sequence_checker_);
+
+  // This keeps track of the mapping between media section
+  // (BaseChannel/SctpTransport) and the JsepTransport underneath.
+  std::map<std::string, cricket::JsepTransport*> mid_to_transport_
+      RTC_GUARDED_BY(sequence_checker_);
+  // Keep track of mids that have been mapped to transports. Used for rollback.
+  std::vector<std::string> pending_mids_ RTC_GUARDED_BY(sequence_checker_);
+  // Callback used to inform subscribers of altered transports.
+  const std::function<bool(const std::string& mid,
+                           cricket::JsepTransport* transport)>
+      map_change_callback_;
+  // Callback used to inform subscribers of possibly altered state.
+  const std::function<void()> state_change_callback_;
+};
+
+}  // namespace webrtc
+
+#endif  // PC_JSEP_TRANSPORT_COLLECTION_H_
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index 413c940..f193f02 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -13,6 +13,7 @@
 #include <stddef.h>
 
 #include <algorithm>
+#include <functional>
 #include <memory>
 #include <utility>
 
@@ -59,6 +60,14 @@
     : network_thread_(network_thread),
       port_allocator_(port_allocator),
       async_dns_resolver_factory_(async_dns_resolver_factory),
+      transports_(
+          [this](const std::string& mid, cricket::JsepTransport* transport) {
+            return OnTransportChanged(mid, transport);
+          },
+          [this]() {
+            RTC_DCHECK_RUN_ON(network_thread_);
+            UpdateAggregateStates_n();
+          }),
       config_(config),
       active_reset_srtp_params_(config.active_reset_srtp_params) {
   // The |transport_observer| is assumed to be non-null.
@@ -179,8 +188,8 @@
 
 void JsepTransportController::SetNeedsIceRestartFlag() {
   RTC_DCHECK_RUN_ON(network_thread_);
-  for (auto& kv : jsep_transports_by_name_) {
-    kv.second->SetNeedsIceRestartFlag();
+  for (auto& transport : transports_.Transports()) {
+    transport->SetNeedsIceRestartFlag();
   }
 }
 
@@ -233,8 +242,8 @@
   // Set certificate for JsepTransport, which verifies it matches the
   // fingerprint in SDP, and DTLS transport.
   // Fallback from DTLS to SDES is not supported.
-  for (auto& kv : jsep_transports_by_name_) {
-    kv.second->SetLocalCertificate(certificate_);
+  for (auto& transport : transports_.Transports()) {
+    transport->SetLocalCertificate(certificate_);
   }
   for (auto& dtls : GetDtlsTransports()) {
     bool set_cert_success = dtls->SetLocalCertificate(certificate_);
@@ -374,8 +383,8 @@
       << "Updating the active_reset_srtp_params for JsepTransportController: "
       << active_reset_srtp_params;
   active_reset_srtp_params_ = active_reset_srtp_params;
-  for (auto& kv : jsep_transports_by_name_) {
-    kv.second->SetActiveResetSrtpParams(active_reset_srtp_params);
+  for (auto& transport : transports_.Transports()) {
+    transport->SetActiveResetSrtpParams(active_reset_srtp_params);
   }
 }
 
@@ -385,13 +394,7 @@
     return;
   }
   RTC_DCHECK_RUN_ON(network_thread_);
-  for (auto&& mid : pending_mids_) {
-    RemoveTransportForMid(mid);
-  }
-  for (auto&& mid : pending_mids_) {
-    MaybeDestroyJsepTransport(mid);
-  }
-  pending_mids_.clear();
+  transports_.RollbackTransports();
 }
 
 rtc::scoped_refptr<webrtc::IceTransportInterface>
@@ -523,9 +526,7 @@
 JsepTransportController::GetDtlsTransports() {
   RTC_DCHECK_RUN_ON(network_thread_);
   std::vector<cricket::DtlsTransportInternal*> dtls_transports;
-  for (auto it = jsep_transports_by_name_.begin();
-       it != jsep_transports_by_name_.end(); ++it) {
-    auto jsep_transport = it->second.get();
+  for (auto jsep_transport : transports_.Transports()) {
     RTC_DCHECK(jsep_transport);
     if (jsep_transport->rtp_dtls_transport()) {
       dtls_transports.push_back(jsep_transport->rtp_dtls_transport());
@@ -661,7 +662,7 @@
     }
   }
   if (type == SdpType::kAnswer) {
-    pending_mids_.clear();
+    transports_.CommitTransports();
   }
   return RTCError::OK();
 }
@@ -839,7 +840,7 @@
     // Rejecting a BUNDLE group's first mid means we are rejecting the entire
     // group.
     for (const auto& content_name : bundle_group->content_names()) {
-      RemoveTransportForMid(content_name);
+      transports_.RemoveTransportForMid(content_name);
       // We are about to delete this BUNDLE group, erase all mappings to it.
       it = established_bundle_groups_by_mid.find(content_name);
       RTC_DCHECK(it != established_bundle_groups_by_mid.end());
@@ -848,7 +849,7 @@
     // Delete the BUNDLE group.
     bundles_.DeleteGroup(bundle_group);
   } else {
-    RemoveTransportForMid(content_info.name);
+    transports_.RemoveTransportForMid(content_info.name);
     if (bundle_group) {
       // Remove the rejected content from the |bundle_group|.
       bundles_.DeleteMid(bundle_group, content_info.name);
@@ -868,7 +869,7 @@
   // If the content is bundled, let the
   // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
   // then destroy the cricket::JsepTransport.
-  if (SetTransportForMid(content_info.name, jsep_transport)) {
+  if (transports_.SetTransportForMid(content_info.name, jsep_transport)) {
     // TODO(bugs.webrtc.org/9719) For media transport this is far from ideal,
     // because it means that we first create media transport and start
     // connecting it, and then we destroy it. We will need to address it before
@@ -879,41 +880,6 @@
   return false;
 }
 
-bool JsepTransportController::SetTransportForMid(
-    const std::string& mid,
-    cricket::JsepTransport* jsep_transport) {
-  TRACE_EVENT0("webrtc", "JsepTransportController::SetTransportForMid");
-  RTC_DCHECK_RUN_ON(network_thread_);
-  RTC_DCHECK(jsep_transport);
-
-  auto it = mid_to_transport_.find(mid);
-  if (it != mid_to_transport_.end() && it->second == jsep_transport)
-    return true;
-
-  pending_mids_.push_back(mid);
-
-  if (it == mid_to_transport_.end()) {
-    mid_to_transport_.insert(std::make_pair(mid, jsep_transport));
-  } else {
-    it->second = jsep_transport;
-  }
-
-  return config_.transport_observer->OnTransportChanged(
-      mid, jsep_transport->rtp_transport(), jsep_transport->RtpDtlsTransport(),
-      jsep_transport->data_channel_transport());
-}
-
-void JsepTransportController::RemoveTransportForMid(const std::string& mid) {
-  RTC_DCHECK_RUN_ON(network_thread_);
-  bool ret = config_.transport_observer->OnTransportChanged(mid, nullptr,
-                                                            nullptr, nullptr);
-  // Calling OnTransportChanged with nullptr should always succeed, since it is
-  // only expected to fail when adding media to a transport (not removing).
-  RTC_DCHECK(ret);
-
-  mid_to_transport_.erase(mid);
-}
-
 cricket::JsepTransportDescription
 JsepTransportController::CreateJsepTransportDescription(
     const cricket::ContentInfo& content_info,
@@ -1024,26 +990,22 @@
 
 const cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
     const std::string& mid) const {
-  auto it = mid_to_transport_.find(mid);
-  return it == mid_to_transport_.end() ? nullptr : it->second;
+  return transports_.GetTransportForMid(mid);
 }
 
 cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
     const std::string& mid) {
-  auto it = mid_to_transport_.find(mid);
-  return it == mid_to_transport_.end() ? nullptr : it->second;
+  return transports_.GetTransportForMid(mid);
 }
 
 const cricket::JsepTransport* JsepTransportController::GetJsepTransportByName(
     const std::string& transport_name) const {
-  auto it = jsep_transports_by_name_.find(transport_name);
-  return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
+  return transports_.GetTransportByName(transport_name);
 }
 
 cricket::JsepTransport* JsepTransportController::GetJsepTransportByName(
     const std::string& transport_name) {
-  auto it = jsep_transports_by_name_.find(transport_name);
-  return (it == jsep_transports_by_name_.end()) ? nullptr : it->second.get();
+  return transports_.GetTransportByName(transport_name);
 }
 
 RTCError JsepTransportController::MaybeCreateJsepTransport(
@@ -1116,9 +1078,9 @@
 
   jsep_transport->SignalRtcpMuxActive.connect(
       this, &JsepTransportController::UpdateAggregateStates_n);
-  SetTransportForMid(content_info.name, jsep_transport.get());
+  transports_.SetTransportForMid(content_info.name, jsep_transport.get());
 
-  jsep_transports_by_name_[content_info.name] = std::move(jsep_transport);
+  transports_.RegisterTransport(content_info.name, std::move(jsep_transport));
   UpdateAggregateStates_n();
   return RTCError::OK();
 }
@@ -1133,23 +1095,15 @@
 
   // Don't destroy the JsepTransport if there are still media sections referring
   // to it.
-  for (const auto& kv : mid_to_transport_) {
-    if (kv.second == jsep_transport) {
-      return;
-    }
+  if (transports_.TransportInUse(jsep_transport)) {
+    return;
   }
-
-  jsep_transports_by_name_.erase(mid);
+  transports_.MaybeDestroyJsepTransport(mid);
   UpdateAggregateStates_n();
 }
 
 void JsepTransportController::DestroyAllJsepTransports_n() {
-  for (const auto& jsep_transport : jsep_transports_by_name_) {
-    config_.transport_observer->OnTransportChanged(jsep_transport.first,
-                                                   nullptr, nullptr, nullptr);
-  }
-
-  jsep_transports_by_name_.clear();
+  transports_.DestroyAllTransports();
 }
 
 void JsepTransportController::SetIceRole_n(cricket::IceRole ice_role) {
@@ -1467,4 +1421,21 @@
   config_.on_dtls_handshake_error_(error);
 }
 
+bool JsepTransportController::OnTransportChanged(
+    const std::string& mid,
+    cricket::JsepTransport* jsep_transport) {
+  if (config_.transport_observer) {
+    if (jsep_transport) {
+      return config_.transport_observer->OnTransportChanged(
+          mid, jsep_transport->rtp_transport(),
+          jsep_transport->RtpDtlsTransport(),
+          jsep_transport->data_channel_transport());
+    } else {
+      return config_.transport_observer->OnTransportChanged(mid, nullptr,
+                                                            nullptr, nullptr);
+    }
+  }
+  return false;
+}
+
 }  // namespace webrtc
diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
index 2d912e5..a947075 100644
--- a/pc/jsep_transport_controller.h
+++ b/pc/jsep_transport_controller.h
@@ -44,11 +44,11 @@
 #include "p2p/base/port_allocator.h"
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
-#include "pc/bundle_manager.h"
 #include "pc/channel.h"
 #include "pc/dtls_srtp_transport.h"
 #include "pc/dtls_transport.h"
 #include "pc/jsep_transport.h"
+#include "pc/jsep_transport_collection.h"
 #include "pc/rtp_transport.h"
 #include "pc/rtp_transport_internal.h"
 #include "pc/sctp_transport.h"
@@ -336,10 +336,6 @@
                             const cricket::ContentGroup& bundle_group)
       RTC_RUN_ON(network_thread_);
 
-  bool SetTransportForMid(const std::string& mid,
-                          cricket::JsepTransport* jsep_transport);
-  void RemoveTransportForMid(const std::string& mid);
-
   cricket::JsepTransportDescription CreateJsepTransportDescription(
       const cricket::ContentInfo& content_info,
       const cricket::TransportInfo& transport_info,
@@ -453,18 +449,14 @@
 
   void OnDtlsHandshakeError(rtc::SSLHandshakeError error);
 
+  bool OnTransportChanged(const std::string& mid,
+                          cricket::JsepTransport* transport);
+
   rtc::Thread* const network_thread_ = nullptr;
   cricket::PortAllocator* const port_allocator_ = nullptr;
   AsyncDnsResolverFactoryInterface* const async_dns_resolver_factory_ = nullptr;
 
-  std::map<std::string, std::unique_ptr<cricket::JsepTransport>>
-      jsep_transports_by_name_ RTC_GUARDED_BY(network_thread_);
-  // This keeps track of the mapping between media section
-  // (BaseChannel/SctpTransport) and the JsepTransport underneath.
-  std::map<std::string, cricket::JsepTransport*> mid_to_transport_
-      RTC_GUARDED_BY(network_thread_);
-  // Keep track of mids that have been mapped to transports. Used for rollback.
-  std::vector<std::string> pending_mids_ RTC_GUARDED_BY(network_thread_);
+  JsepTransportCollection transports_ RTC_GUARDED_BY(network_thread_);
   // Aggregate states for Transports.
   // standardized_ice_connection_state_ is intended to replace
   // ice_connection_state, see bugs.webrtc.org/9308