Import Cobalt 23.master.0.308671
diff --git a/.codespellignorelines b/.codespellignorelines
index 0079aa8..0c7fa5f 100644
--- a/.codespellignorelines
+++ b/.codespellignorelines
@@ -7,3 +7,4 @@
                                              texture_size.height(),
                                              GrMipMapped::kNo, texture_info));
 Onces represent initializations that should only ever happen once per process,
+    <Resource Language="TE" />
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index c319c72..529dd2f 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -60,8 +60,6 @@
   data_deps = [
     "//cobalt/dom:licenses",
     "//cobalt/network:copy_ssl_certificates",
-    "//cobalt/speech:speech_testdata",
-    "//cobalt/webdriver:copy_webdriver_data",
     "//third_party/icu:icudata",
   ]
   if (cobalt_font_package == "empty") {
@@ -77,6 +75,8 @@
       "//cobalt/debug/backend/content:copy_backend_web_files",
       "//cobalt/debug/console/content:copy_console_web_files",
       "//cobalt/debug/remote/content:copy_remote_web_files",
+      "//cobalt/speech:speech_testdata",
+      "//cobalt/webdriver:copy_webdriver_data",
       "//third_party/devtools:build_release_devtools",
       "//third_party/devtools:copy_devtools_modules",
       "//third_party/devtools:copy_inspector_images",
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html
index 3354881..4114648 100644
--- a/cobalt/demos/content/watchdog-demo/index.html
+++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -62,7 +62,7 @@
           } else if (watchdogFunction == 'ping') {
             ret = h5vcc.crashLog.ping('test-name', `test-ping`);
           } else if (watchdogFunction == 'getWatchdogViolations') {
-            ret = h5vcc.crashLog.getWatchdogViolations(true);
+            ret = h5vcc.crashLog.getWatchdogViolations();
           } else if (watchdogFunction == 'getPersistentSettingWatchdogCrash') {
             ret = h5vcc.crashLog.getPersistentSettingWatchdogCrash();
           } else if (watchdogFunction == 'setPersistentSettingWatchdogCrashTrue') {
diff --git a/cobalt/evergreen_tests/evergreen_tests.py b/cobalt/evergreen_tests/evergreen_tests.py
index b0e2385..74eafb0 100644
--- a/cobalt/evergreen_tests/evergreen_tests.py
+++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -28,7 +28,6 @@
 from starboard.tools.paths import REPOSITORY_ROOT
 
 _DEFAULT_PLATFORM_UNDER_TEST = 'linux'
-_DEFAULT_TEST_TYPE = 'functional'
 
 
 def _Exec(cmd, env=None):
@@ -58,10 +57,6 @@
       '--platform_under_test',
       default=_DEFAULT_PLATFORM_UNDER_TEST,
       help='The platform to run the tests on (e.g., linux or raspi).')
-  arg_parser.add_argument(
-      '--test_type',
-      default=_DEFAULT_TEST_TYPE,
-      help='The type of tests to run: functional or performance.')
   authentication_method = arg_parser.add_mutually_exclusive_group()
   authentication_method.add_argument(
       '--public-key-auth',
@@ -123,9 +118,6 @@
     command.append('-a')
     command.append('password')
 
-  command.append('-t')
-  command.append(args.test_type)
-
   command.append(args.platform_under_test)
 
   return _Exec(command, env)
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index f9304e9..d61dca0 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -180,9 +180,9 @@
   return false;
 }
 
-std::string H5vccCrashLog::GetWatchdogViolations(bool current) {
+std::string H5vccCrashLog::GetWatchdogViolations() {
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
-  if (watchdog) return watchdog->GetWatchdogViolations(current);
+  if (watchdog) return watchdog->GetWatchdogViolations();
   return "";
 }
 
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index c39f4bd..5b1557b 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -42,7 +42,7 @@
 
   bool Ping(const std::string& name, const std::string& ping_info);
 
-  std::string GetWatchdogViolations(bool current);
+  std::string GetWatchdogViolations();
 
   bool GetPersistentSettingWatchdogCrash();
 
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index d1e9e1a..5c91aa0 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -33,10 +33,12 @@
   //   name, Watchdog client to register.
   //   description, information on the Watchdog client.
   //   monitor_state, application state up to which the client is monitored.
+  //     Inclusive.
   //   time_interval, maximum number of microseconds allowed between pings
   //     before triggering a Watchdog violation.
   //   time_wait, number of microseconds to initially wait before Watchdog
-  //     violations can be triggered.
+  //     violations can be triggered. Reapplies after client resumes from idle
+  //     state due to application state changes.
   //   replace, behavior with previously registered Watchdog clients of the
   //     same name.
   boolean register(DOMString name, DOMString description,
@@ -52,11 +54,9 @@
   // metadata.
   boolean ping(DOMString name, DOMString ping_info);
 
-  // Returns a json string containing the Watchdog violations. Current boolean
-  // determines whether the current file representing ongoing violations or the
-  // previous file containing violations from previous app starts and since the
-  // last call (up to a limit) is returned.
-  DOMString getWatchdogViolations(boolean current);
+  // Returns a json string containing the Watchdog violations since the last
+  // call. Clears internal cache of Watchdog violations to prevent duplicates.
+  DOMString getWatchdogViolations();
 
   // Gets a persistent Watchdog setting that determines whether or not a
   // Watchdog violation will trigger a crash.
diff --git a/cobalt/media/base/playback_statistics.cc b/cobalt/media/base/playback_statistics.cc
index ffb51f8..babd725 100644
--- a/cobalt/media/base/playback_statistics.cc
+++ b/cobalt/media/base/playback_statistics.cc
@@ -33,6 +33,7 @@
 volatile SbAtomic32 s_av1_played = 0;
 volatile SbAtomic32 s_h264_played = 0;
 volatile SbAtomic32 s_hevc_played = 0;
+volatile SbAtomic32 s_vp8_played = 0;
 volatile SbAtomic32 s_vp9_played = 0;
 volatile SbAtomic32 s_min_video_width = 999999;
 volatile SbAtomic32 s_min_video_height = 999999;
@@ -153,6 +154,8 @@
       SbAtomicBarrier_Increment(&s_h264_played, 1);
     } else if (video_config.codec() == VideoCodec::kHEVC) {
       SbAtomicBarrier_Increment(&s_hevc_played, 1);
+    } else if (video_config.codec() == VideoCodec::kVP8) {
+      SbAtomicBarrier_Increment(&s_vp8_played, 1);
     } else if (video_config.codec() == VideoCodec::kVP9) {
       SbAtomicBarrier_Increment(&s_vp9_played, 1);
     } else {
@@ -222,7 +225,7 @@
   return starboard::FormatString(
       "current_codec: %s, drm: %s, width: %d, height: %d"
       ", active_players (max): %d (%d), av1: ~%" PRId64 ", h264: ~%" PRId64
-      ", hevc: ~%" PRId64 ", vp9: ~%" PRId64
+      ", hevc: ~%" PRId64 ", vp8: ~%" PRId64 ", vp9: ~%" PRId64
       ", min_width: %d, min_height: %d, max_width: %d, max_height: %d"
       ", last_working_codec: %s, seek_time: %s"
       ", first_audio_time: ~%" PRId64 ", first_video_time: ~%" PRId64
@@ -234,6 +237,7 @@
       RoundValue(SbAtomicNoBarrier_Load(&s_av1_played)),
       RoundValue(SbAtomicNoBarrier_Load(&s_h264_played)),
       RoundValue(SbAtomicNoBarrier_Load(&s_hevc_played)),
+      RoundValue(SbAtomicNoBarrier_Load(&s_vp8_played)),
       RoundValue(SbAtomicNoBarrier_Load(&s_vp9_played)),
       SbAtomicNoBarrier_Load(&s_min_video_width),
       SbAtomicNoBarrier_Load(&s_min_video_height),
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index b17e8bd..b296507 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -52,6 +52,10 @@
     $ ccache --max-size=20G
     ```
 
+1.  Install GN, which we use for our build system code. There are a few ways to
+    get the binary, follow the instructions for whichever way you prefer
+    [here](https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/#getting-a-binary).
+
 1.  Clone the Cobalt code repository. The following `git` command creates a
     `cobalt` directory that contains the repository:
 
diff --git a/cobalt/site/docs/reference/starboard/modules/14/media.md b/cobalt/site/docs/reference/starboard/modules/14/media.md
index 45f6400..f7ccacd 100644
--- a/cobalt/site/docs/reference/starboard/modules/14/media.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/media.md
@@ -30,6 +30,9 @@
 *   `kSbMediaAudioCodecEac3`
 *   `kSbMediaAudioCodecOpus`
 *   `kSbMediaAudioCodecVorbis`
+*   `kSbMediaAudioCodecMp3`
+*   `kSbMediaAudioCodecFlac`
+*   `kSbMediaAudioCodecPcm`
 
 ### SbMediaAudioCodingType ###
 
diff --git a/cobalt/site/docs/reference/starboard/modules/14/system.md b/cobalt/site/docs/reference/starboard/modules/14/system.md
index 793670f..b4800a6 100644
--- a/cobalt/site/docs/reference/starboard/modules/14/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/system.md
@@ -26,22 +26,6 @@
     only if) a system has this capability will SbSystemGetTotalGPUMemory() and
     SbSystemGetUsedGPUMemory() be valid to call.
 
-### SbSystemConnectionType ###
-
-Enumeration of network connection types.
-
-#### Values ####
-
-*   `kSbSystemConnectionTypeWired`
-
-    The system is on a wired connection.
-*   `kSbSystemConnectionTypeWireless`
-
-    The system is on a wireless connection.
-*   `kSbSystemConnectionTypeUnknown`
-
-    The system connection type is unknown.
-
 ### SbSystemDeviceType ###
 
 Enumeration of device types.
@@ -272,16 +256,6 @@
 void SbSystemClearLastError()
 ```
 
-### SbSystemGetConnectionType ###
-
-Returns the device's current network connection type.
-
-#### Declaration ####
-
-```
-SbSystemConnectionType SbSystemGetConnectionType()
-```
-
 ### SbSystemGetDeviceType ###
 
 Returns the type of the device.
diff --git a/cobalt/site/docs/reference/starboard/modules/system.md b/cobalt/site/docs/reference/starboard/modules/system.md
index 793670f..b4800a6 100644
--- a/cobalt/site/docs/reference/starboard/modules/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/system.md
@@ -26,22 +26,6 @@
     only if) a system has this capability will SbSystemGetTotalGPUMemory() and
     SbSystemGetUsedGPUMemory() be valid to call.
 
-### SbSystemConnectionType ###
-
-Enumeration of network connection types.
-
-#### Values ####
-
-*   `kSbSystemConnectionTypeWired`
-
-    The system is on a wired connection.
-*   `kSbSystemConnectionTypeWireless`
-
-    The system is on a wireless connection.
-*   `kSbSystemConnectionTypeUnknown`
-
-    The system connection type is unknown.
-
 ### SbSystemDeviceType ###
 
 Enumeration of device types.
@@ -272,16 +256,6 @@
 void SbSystemClearLastError()
 ```
 
-### SbSystemGetConnectionType ###
-
-Returns the device's current network connection type.
-
-#### Declaration ####
-
-```
-SbSystemConnectionType SbSystemGetConnectionType()
-```
-
 ### SbSystemGetDeviceType ###
 
 Returns the type of the device.
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index 49349d1..3b6a888 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -34,7 +34,7 @@
 namespace {
 
 // The Watchdog violations json file names.
-const char kWatchdogCurrentViolationsJson[] = "watchdog.json";
+const char kWatchdogViolationsJson[] = "watchdog.json";
 const char kWatchdogPreviousViolationsJson[] = "watchdog_old.json";
 // The default number of microseconds between each monitor loop.
 const int64_t kWatchdogSmallestTimeInterval = 1000000;
@@ -104,21 +104,19 @@
   SbThreadJoin(watchdog_thread_, nullptr);
 }
 
-std::string Watchdog::GetWatchdogFilePaths(bool current) {
-  // Gets the current Watchdog violations file path or the previous Watchdog
-  // violations file path.
+std::string Watchdog::GetWatchdogFilePath(bool current) {
+  // Gets the Watchdog violations file path or the previous Watchdog violations
+  // file path with lazy initialization.
   if (watchdog_file_ == "") {
     // Sets Watchdog violations file paths.
     std::vector<char> cache_dir(kSbFileMaxPath + 1, 0);
     SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(),
                     kSbFileMaxPath);
     watchdog_file_ = std::string(cache_dir.data()) + kSbFileSepString +
-                     std::string(kWatchdogCurrentViolationsJson);
-    SB_LOG(INFO) << "Current Watchdog violations file path: " << watchdog_file_;
+                     std::string(kWatchdogViolationsJson);
+    SB_LOG(INFO) << "Watchdog violations file path: " << watchdog_file_;
     watchdog_old_file_ = std::string(cache_dir.data()) + kSbFileSepString +
                          std::string(kWatchdogPreviousViolationsJson);
-    SB_LOG(INFO) << "Previous Watchdog violations file path: "
-                 << watchdog_old_file_;
     PreservePreviousWatchdogViolations();
   }
   if (current) return watchdog_file_;
@@ -126,8 +124,8 @@
 }
 
 void Watchdog::PreservePreviousWatchdogViolations() {
-  // Copies the previous Watchdog violations file containing violations since
-  // last app start, if it exists, to preserve it.
+  // Copies the previous Watchdog violations file containing violations before
+  // app start, if it exists, to preserve it.
   starboard::ScopedFile read_file(watchdog_file_.c_str(),
                                   kSbFileOpenOnly | kSbFileRead);
   if (read_file.IsValid()) {
@@ -137,7 +135,6 @@
     starboard::ScopedFile write_file(watchdog_old_file_.c_str(),
                                      kSbFileCreateAlways | kSbFileWrite);
     write_file.WriteAll(&watchdog_content[0], kFileSize);
-    starboard::SbFileDeleteRecursive(watchdog_file_.c_str(), true);
   }
 }
 
@@ -159,66 +156,72 @@
 
     int64_t current_time = SbTimeToPosix(SbTimeGetNow());
     SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
-    std::string serialized_watchdog_index = "";
+    std::string serialized_client_map = "";
 
-    // Iterates through Watchdog index to monitor all registered clients.
+    // Iterates through client map to monitor all registered clients.
     bool new_watchdog_violation = false;
-    for (auto& it : static_cast<Watchdog*>(context)->watchdog_index_) {
-      // Ignores and resets clients in idle states.
-      if (static_cast<Watchdog*>(context)->state_ > it.second->monitor_state) {
-        it.second->time_registered_monotonic_microseconds =
-            current_monotonic_time;
-        it.second->time_last_pinged_microseconds = current_monotonic_time;
+    for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
+      Client* client = it.second.get();
+      // Ignores and resets clients in idle states, clients whose monitor_state
+      // is below the current application state. Resets time_wait_microseconds
+      // and time_interval_microseconds deltas.
+      if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) {
+        client->time_registered_monotonic_microseconds = current_monotonic_time;
+        client->time_last_pinged_microseconds = current_monotonic_time;
         continue;
       }
 
       SbTimeMonotonic time_delta =
-          current_monotonic_time - it.second->time_last_pinged_microseconds;
+          current_monotonic_time - client->time_last_pinged_microseconds;
       SbTimeMonotonic time_wait =
           current_monotonic_time -
-          it.second->time_registered_monotonic_microseconds;
+          client->time_registered_monotonic_microseconds;
 
       // Watchdog violation
-      if (time_delta > it.second->time_interval_microseconds &&
-          time_wait > it.second->time_wait_microseconds) {
+      if (time_delta > client->time_interval_microseconds &&
+          time_wait > client->time_wait_microseconds) {
         // Reset time last pinged.
-        it.second->time_last_pinged_microseconds = current_monotonic_time;
-        // Get serialized Watchdog index.
-        if (serialized_watchdog_index == "") {
-          serialized_watchdog_index =
-              static_cast<Watchdog*>(context)->GetSerializedWatchdogIndex();
+        client->time_last_pinged_microseconds = current_monotonic_time;
+        // Get serialized client map.
+        if (serialized_client_map == "") {
+          serialized_client_map =
+              static_cast<Watchdog*>(context)->GetSerializedClientMap();
         }
 
         // Updates Watchdog violations.
         auto iter = (static_cast<Watchdog*>(context)->watchdog_violations_)
-                        .find(it.second->name);
+                        .find(client->name);
         bool already_violated =
             iter !=
             (static_cast<Watchdog*>(context)->watchdog_violations_).end();
 
         if (already_violated) {
           // Prevents excessive Watchdog violation updates.
-          if (iter->second->violation_count <= kWatchdogMaxViolations)
+          Violation* violation = iter->second.get();
+          if (violation->violation_count <= kWatchdogMaxViolations)
             new_watchdog_violation = true;
-          iter->second->ping_infos = it.second->ping_infos;
-          iter->second->violation_time_microseconds = current_time;
-          iter->second->violation_delta_microseconds = time_delta;
-          iter->second->violation_count++;
-          iter->second->serialized_watchdog_index = serialized_watchdog_index;
+          violation->ping_infos = client->ping_infos;
+          violation->violation_time_microseconds = current_time;
+          violation->violation_delta_microseconds = time_delta;
+          violation->violation_count++;
+          violation->serialized_client_map = serialized_client_map;
         } else {
           new_watchdog_violation = true;
           std::unique_ptr<Violation> violation(new Violation);
-          *violation = *(it.second);
+          *violation = *client;
           violation->violation_time_microseconds = current_time;
           violation->violation_delta_microseconds = time_delta;
           violation->violation_count = 1;
-          violation->serialized_watchdog_index = serialized_watchdog_index;
+          violation->serialized_client_map = serialized_client_map;
           (static_cast<Watchdog*>(context)->watchdog_violations_)
               .emplace(violation->name, std::move(violation));
         }
       }
     }
-    if (new_watchdog_violation) SerializeWatchdogViolations(context);
+    if (new_watchdog_violation) {
+      SerializeWatchdogViolations(context);
+      MaybeTriggerCrash(context);
+    }
 
     SB_CHECK(SbMutexRelease(&(static_cast<Watchdog*>(context))->mutex_));
     SbThreadSleep(static_cast<Watchdog*>(context)->smallest_time_interval_);
@@ -226,71 +229,79 @@
   return nullptr;
 }
 
-std::string Watchdog::GetSerializedWatchdogIndex() {
-  // Gets the current list of registered clients from the Watchdog index and
+std::string Watchdog::GetSerializedClientMap() {
+  // Gets the current list of registered clients from the client map and
   // returns it as a serialized json string.
-  std::string serialized_watchdog_index = "[";
+  std::string serialized_client_map = "[";
   std::string comma = "";
-  for (auto& it : watchdog_index_) {
-    serialized_watchdog_index += (comma + "\"" + it.first + "\"");
+  for (auto& it : client_map_) {
+    serialized_client_map += (comma + "\"" + it.first + "\"");
     comma = ", ";
   }
-  serialized_watchdog_index += "]";
-  return serialized_watchdog_index;
+  serialized_client_map += "]";
+  return serialized_client_map;
 }
 
 void Watchdog::SerializeWatchdogViolations(void* context) {
-  // Writes current Watchdog violations to persistent storage as a json file.
-  std::string watchdog_json = "{\n  \"watchdog_violations\": [\n";
+  // Writes Watchdog violations to persistent storage as a partial json file.
+  std::string watchdog_json = "";
   std::string comma = "";
   for (auto& it : static_cast<Watchdog*>(context)->watchdog_violations_) {
+    Violation* violation = it.second.get();
     std::string ping_infos = "[";
     std::string inner_comma = "";
-    while (it.second->ping_infos.size() > 0) {
-      ping_infos += (inner_comma + "\"" + it.second->ping_infos.front() + "\"");
-      it.second->ping_infos.pop();
+    while (violation->ping_infos.size() > 0) {
+      ping_infos += (inner_comma + "\"" + violation->ping_infos.front() + "\"");
+      violation->ping_infos.pop();
       inner_comma = ", ";
     }
     ping_infos += "]";
 
     std::ostringstream ss;
     ss << comma << "    {\n"
-       << "      \"name\": \"" << it.second->name << "\",\n"
-       << "      \"description\": \"" << it.second->description << "\",\n"
+       << "      \"name\": \"" << violation->name << "\",\n"
+       << "      \"description\": \"" << violation->description << "\",\n"
        << "      \"ping_infos\": " << ping_infos << ",\n"
        << "      \"monitor_state\": \""
-       << std::string(GetApplicationStateString(it.second->monitor_state))
+       << std::string(GetApplicationStateString(violation->monitor_state))
        << "\",\n"
        << "      \"time_interval_microseconds\": "
-       << it.second->time_interval_microseconds << ",\n"
+       << violation->time_interval_microseconds << ",\n"
        << "      \"time_wait_microseconds\": "
-       << it.second->time_wait_microseconds << ",\n"
+       << violation->time_wait_microseconds << ",\n"
        << "      \"time_registered_microseconds\": "
-       << it.second->time_registered_microseconds << ",\n"
+       << violation->time_registered_microseconds << ",\n"
        << "      \"violation_time_microseconds\": "
-       << it.second->violation_time_microseconds << ",\n"
+       << violation->violation_time_microseconds << ",\n"
        << "      \"violation_delta_microseconds\": "
-       << it.second->violation_delta_microseconds << ",\n"
-       << "      \"violation_count\": " << it.second->violation_count << ",\n"
-       << "      \"watchdog_index\": " << it.second->serialized_watchdog_index
-       << "\n"
+       << violation->violation_delta_microseconds << ",\n"
+       << "      \"violation_count\": " << violation->violation_count << ",\n"
+       << "      \"client_map\": " << violation->serialized_client_map << "\n"
        << "    }";
     watchdog_json += ss.str();
     comma = ",\n";
   }
-  watchdog_json += "\n  ]\n}";
 
-  SB_LOG(INFO) << "Writing Watchdog violations to: "
-               << static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true);
-  SB_LOG(INFO) << watchdog_json;
+  // Appends previous Watchdog violations.
+  starboard::ScopedFile read_file(
+      (static_cast<Watchdog*>(context)->GetWatchdogFilePath(false)).c_str(),
+      kSbFileOpenOnly | kSbFileRead);
+  if (read_file.IsValid()) {
+    int64_t kFileSize = read_file.GetSize();
+    std::string prev_watchdog_json(kFileSize + 1, '\0');
+    read_file.ReadAll(&prev_watchdog_json[0], kFileSize);
+    prev_watchdog_json.erase(prev_watchdog_json.find('\0'));
+    watchdog_json += ",\n";
+    watchdog_json += prev_watchdog_json;
+  }
+
+  SB_LOG(INFO) << "Writing Watchdog violations:\n" << watchdog_json;
 
   starboard::ScopedFile watchdog_file(
-      (static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true)).c_str(),
+      (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(),
       kSbFileCreateAlways | kSbFileWrite);
   watchdog_file.WriteAll(watchdog_json.c_str(),
                          static_cast<int>(watchdog_json.size()));
-
-  MaybeTriggerCrash(context);
 }
 
 void Watchdog::MaybeTriggerCrash(void* context) {
@@ -317,8 +328,8 @@
 
   // If replace is PING or ALL, handles already registered cases.
   if (replace != NONE) {
-    auto it = watchdog_index_.find(name);
-    bool already_registered = it != watchdog_index_.end();
+    auto it = client_map_.find(name);
+    bool already_registered = it != client_map_.end();
 
     if (already_registered) {
       if (replace == PING) {
@@ -344,7 +355,7 @@
       client->time_registered_monotonic_microseconds;
 
   // Registers.
-  auto result = watchdog_index_.emplace(name, std::move(client));
+  auto result = client_map_.emplace(name, std::move(client));
   // Checks for new smallest_time_interval_.
   smallest_time_interval_ = std::min(smallest_time_interval_, time_interval);
 
@@ -364,10 +375,10 @@
 
   if (lock) SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
   // Unregisters.
-  auto result = watchdog_index_.erase(name);
+  auto result = client_map_.erase(name);
   // Sets new smallest_time_interval_.
   smallest_time_interval_ = kWatchdogSmallestTimeInterval;
-  for (auto& it : watchdog_index_) {
+  for (auto& it : client_map_) {
     smallest_time_interval_ = std::min(smallest_time_interval_,
                                        it.second->time_interval_microseconds);
   }
@@ -388,8 +399,8 @@
   if (is_stub_) return true;
 
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
-  auto it = watchdog_index_.find(name);
-  bool client_exists = it != watchdog_index_.end();
+  auto it = client_map_.find(name);
+  bool client_exists = it != client_map_.end();
 
   if (client_exists) {
     // Updates last ping.
@@ -408,29 +419,36 @@
   return client_exists;
 }
 
-std::string Watchdog::GetWatchdogViolations(bool current) {
-  // Gets the current Watchdog violations file representing ongoing violations
-  // or gets the previous Watchdog violations file containing violations from
-  // previous app starts and since the last call (up to a limit).
+std::string Watchdog::GetWatchdogViolations() {
+  // Gets a json string containing the Watchdog violations since the last
+  // call (up to a limit).
 
   // Watchdog stub
   if (is_stub_) return "";
 
+  std::string watchdog_json = "";
   SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired);
-  starboard::ScopedFile read_file(GetWatchdogFilePaths(current).c_str(),
+  starboard::ScopedFile read_file(GetWatchdogFilePath().c_str(),
                                   kSbFileOpenOnly | kSbFileRead);
   if (read_file.IsValid()) {
     int64_t kFileSize = read_file.GetSize();
     std::string watchdog_content(kFileSize + 1, '\0');
     read_file.ReadAll(&watchdog_content[0], kFileSize);
-    SB_CHECK(SbMutexRelease(&mutex_));
-    SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_content;
-    return watchdog_content;
+    watchdog_content.erase(watchdog_content.find('\0'));
+    watchdog_json = "{\n  \"watchdog_violations\": [\n";
+    watchdog_json += watchdog_content;
+    watchdog_json += "\n  ]\n}";
+
+    // Removes all Watchdog violations.
+    watchdog_violations_.clear();
+    starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
+    starboard::SbFileDeleteRecursive(GetWatchdogFilePath(false).c_str(), true);
+    SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_json;
   } else {
-    SB_CHECK(SbMutexRelease(&mutex_));
     SB_LOG(INFO) << "No Watchdog Violations.";
-    return "";
   }
+  SB_CHECK(SbMutexRelease(&mutex_));
+  return watchdog_json;
 }
 
 bool Watchdog::GetPersistentSettingWatchdogCrash() {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index d95f1ca..ed14db0 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -39,7 +39,12 @@
   std::queue<std::string> ping_infos;
   // Application state to continue monitoring client up to.
   base::ApplicationState monitor_state;
+  // Maximum number of microseconds allowed between pings before triggering a
+  // Watchdog violation.
   int64_t time_interval_microseconds;
+  // Number of microseconds to initially wait before Watchdog violations can be
+  // triggered. Reapplies after client resumes from idle state due to
+  // application state changes.
   int64_t time_wait_microseconds;
   int64_t time_registered_microseconds;                    // since epoch
   SbTimeMonotonic time_registered_monotonic_microseconds;  // since (relative)
@@ -52,16 +57,21 @@
   std::string description;
   // List of strings optionally provided with each Ping.
   std::queue<std::string> ping_infos;
-  // Application state to continue monitoring client up to.
+  // Application state to continue monitoring client up to. Inclusive.
   base::ApplicationState monitor_state;
+  // Maximum number of microseconds allowed between pings before triggering a
+  // Watchdog violation.
   int64_t time_interval_microseconds;
+  // Number of microseconds to initially wait before Watchdog violations can be
+  // triggered. Reapplies after client resumes from idle state due to
+  // application state changes.
   int64_t time_wait_microseconds;
   int64_t time_registered_microseconds;  // since epoch
   int64_t violation_time_microseconds;   // since epoch
   int64_t violation_delta_microseconds;  // over time_interval
   int64_t violation_count;
-  // Watchdog index as a serialized json string
-  std::string serialized_watchdog_index;
+  // Client map as a serialized json string
+  std::string serialized_client_map;
 
   void operator=(const Client& c) {
     name = c.name;
@@ -96,7 +106,7 @@
   bool Unregister(const std::string& name, bool lock = true);
   bool Ping(const std::string& name);
   bool Ping(const std::string& name, const std::string& info);
-  std::string GetWatchdogViolations(bool current = false);
+  std::string GetWatchdogViolations();
   bool GetPersistentSettingWatchdogCrash();
   void SetPersistentSettingWatchdogCrash(bool can_trigger_crash);
 
@@ -106,22 +116,21 @@
 #endif  // defined(_DEBUG)
 
  private:
-  std::string GetWatchdogFilePaths(bool current);
+  std::string GetWatchdogFilePath(bool current = true);
   void PreservePreviousWatchdogViolations();
   static void* Monitor(void* context);
-  std::string GetSerializedWatchdogIndex();
+  std::string GetSerializedClientMap();
   static void SerializeWatchdogViolations(void* context);
   static void MaybeTriggerCrash(void* context);
 
-  // Current Watchdog violations file path.
+  // Watchdog violations file paths.
   std::string watchdog_file_;
-  // Previous Watchdog violations file path.
   std::string watchdog_old_file_;
   // Creates a lock which ensures that each loop of monitor is atomic in that
   // modifications to is_monitoring_, state_, smallest_time_interval_, and most
-  // importantly to the dictionaries containing Watchdog clients,
-  // watchdog_index_ and watchdog_violations_, only occur in between loops of
-  // monitor. API functions like Register(), Unregister(), Ping(), and
+  // importantly to the dictionaries containing Watchdog clients, client_map_
+  // and watchdog_violations_, only occur in between loops of monitor. API
+  // functions like Register(), Unregister(), Ping(), and
   // GetWatchdogViolations() will be called by various threads and interact
   // with these class variables.
   SbMutex mutex_;
@@ -134,7 +143,7 @@
   // Tracks application state.
   base::ApplicationState state_ = base::kApplicationStateStarted;
   // Dictionary of registered Watchdog clients.
-  std::unordered_map<std::string, std::unique_ptr<Client>> watchdog_index_;
+  std::unordered_map<std::string, std::unique_ptr<Client>> client_map_;
   // Dictionary of Watchdog violations.
   std::unordered_map<std::string, std::unique_ptr<Violation>>
       watchdog_violations_;
diff --git a/docker/windows/base/build/Dockerfile b/docker/windows/base/build/Dockerfile
index 658ec30..ec4ebaa 100644
--- a/docker/windows/base/build/Dockerfile
+++ b/docker/windows/base/build/Dockerfile
@@ -29,15 +29,10 @@
 # of the execution, i.e. the full invocation string.
 COPY ./list_python_processes.py /list_python_processes.py
 
-# Helper file to ensure python2 is invoked correctly from command line.
-COPY ./python2.bat /python2/python2.bat
-
 # Install deps via chocolatey.
 RUN iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));`
     mkdir C:\choco-cache;`
-    # Install Python 3 before Python 2, so the default on the path is python2.
     choco install -y -c C:\choco-cache python3 -ia '/quiet InstallAllUsers=1 PrependPath=1 TargetDir="C:\Python3"';`
-    choco install -y -c C:\choco-cache python2 --params '/InstallDir:C:\python2';`
     choco install -y -c C:\choco-cache winflexbison3 --params '/InstallDir:C:\bison';`
     choco install -y -c C:\choco-cache ninja;`
     choco install -y -c C:\choco-cache nodejs-lts;`
@@ -47,16 +42,10 @@
     Remove-Item -Force -Recurse $env:TEMP\*;`
     C:\fast-win-rmdir.cmd C:\choco-cache;`
     # Create version specific copy of each python executable.
-    Copy-Item C:\Python3\python.exe C:\Python3\python3.exe;`
-    Copy-Item C:\python2\python.exe C:\python2\python2.exe
+    Copy-Item C:\Python3\python.exe C:\Python3\python3.exe
 
-# Install python2 packages via PIP.
-# Additionally do the same for python3, and set various configurations.
+# Install python3 packages via PIP and set various configurations.
 RUN mkdir C:\pip-cache;`
-    python2 -m pip install pypiwin32 six --cache-dir C:\pip-cache;`
-    C:\fast-win-rmdir.cmd C:\pip-cache;`
-    # Install python3 packages via PIP.
-    mkdir C:\pip-cache;`
     python3 -m pip install six --cache-dir C:\pip-cache;`
     C:\fast-win-rmdir.cmd C:\pip-cache;`
     # Configure git global settings.
diff --git a/docker/windows/base/build/python2.bat b/docker/windows/base/build/python2.bat
deleted file mode 100644
index 9e2101e..0000000
--- a/docker/windows/base/build/python2.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off

-setlocal

-c:\python2\python.exe %*

diff --git a/docker/windows/win32/unittest/Dockerfile b/docker/windows/win32/unittest/Dockerfile
deleted file mode 100644
index 780c58c..0000000
--- a/docker/windows/win32/unittest/Dockerfile
+++ /dev/null
@@ -1,40 +0,0 @@
-# escape=`
-
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-FROM mcr.microsoft.com/windows:1809
-
-# Restore the default Windows shell for correct batch processing.
-SHELL ["cmd", "/S", "/C"]
-
-RUN powershell -ExecutionPolicy unrestricted -Command `
-    iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
-
-# Needed to extract the app launcher scripts for running tests.
-RUN choco install -f -y unzip
-
-# Install python2
-RUN choco install -f -y python2 --params '/InstallDir:C:\python2'
-RUN pip install pypiwin32
-RUN setx path "%path%;C:\Python2"
-RUN setx python2 "C:\Python2\python.exe"
-ADD ./python2.bat /python2/python2.bat
-
-# Install visual C runtime redistributables
-ADD https://aka.ms/vs/16/release/vc_redist.x64.exe C:/vcredist_x64.exe
-RUN C:/vcredist_x64.exe /install /passive /norestart /log out.txt
-
-ADD ./unittest.cmd /unittest.cmd
-
-CMD [ "/unittest.cmd", "${PLATFORM}", "${CONFIG}", "${TEST}"]
diff --git a/docker/windows/win32/unittest/python2.bat b/docker/windows/win32/unittest/python2.bat
deleted file mode 100644
index 9e2101e..0000000
--- a/docker/windows/win32/unittest/python2.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off

-setlocal

-c:\python2\python.exe %*

diff --git a/docker/windows/win32/unittest/unittest.cmd b/docker/windows/win32/unittest/unittest.cmd
deleted file mode 100644
index 36b1426..0000000
--- a/docker/windows/win32/unittest/unittest.cmd
+++ /dev/null
@@ -1,7 +0,0 @@
-if "%1" == "" set "PLATFORM=win-win32"
-if "%2" == "" set "CONFIG=devel"
-if "%3" == "" set "TEST=eztime_test"
-
-cd c:\code\out\%PLATFORM%_%CONFIG%\
-unzip app_launcher.zip -d c:\app_launcher_out\
-%python2% c:\app_launcher_out\starboard\tools\testing\test_runner.py --run --platform %PLATFORM% --config %CONFIG% -o c:\code\out\%PLATFORM%_%CONFIG%
diff --git a/glimp/egl/display.cc b/glimp/egl/display.cc
index 3747d9c..8cfac3c 100644
--- a/glimp/egl/display.cc
+++ b/glimp/egl/display.cc
@@ -27,12 +27,6 @@
 namespace glimp {
 namespace egl {
 
-// sce::Gnm::submitDone() is expected to be called once every 5 secs:
-// https://ps4.siedev.net/resources/documents/SDK/9.500/Programming-Startup_Guide/0006.html#__document_toc_00000034
-// sce::Gnm::submitDone() gets called regularly in the rasterizer thread and
-// should be called from the main thread when the app is in suspended state.
-// kSubmitDoneDelay is set to 1/60sec (same scheduling frequency as
-// the app in foreground)
 const SbTime kSubmitDoneDelay = kSbTimeSecond / 60;
 
 // Don't repeat the submitDone callback during suspension
diff --git a/glimp/egl/display_impl.h b/glimp/egl/display_impl.h
index caf3c3a..71ac0d1 100644
--- a/glimp/egl/display_impl.h
+++ b/glimp/egl/display_impl.h
@@ -53,8 +53,7 @@
   // Creates and returns a new DisplayImpl object.
   // To be implemented by each implementing platform.
   static nb::scoped_ptr<DisplayImpl> Create(EGLNativeDisplayType display_id);
-  // This method is declared here and defined in ps4/egl/display_impl_ps4.cc
-  // and ps5/egl/display_impl_ps5.cc respectively.
+  // Submit done call.
   static void CallSubmitDone();
 
   // Returns the EGL major and minor versions, if they are not NULL.
diff --git a/starboard/android/apk/app/CMakeLists.txt b/starboard/android/apk/app/CMakeLists.txt
index b3f36b7..d5d598f 100644
--- a/starboard/android/apk/app/CMakeLists.txt
+++ b/starboard/android/apk/app/CMakeLists.txt
@@ -78,7 +78,7 @@
     DEPENDS coat_lib
     COMMAND ${CMAKE_COMMAND} -E create_symlink
             ${COBALT_CONTENT_DIR}
-            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../cobalt_content
+            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../../../cobalt_content
 )
 
 # We need a target (not a file) for the phony native dependency below.
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle
index 568d689..2712f25 100644
--- a/starboard/android/apk/app/build.gradle
+++ b/starboard/android/apk/app/build.gradle
@@ -42,8 +42,8 @@
 println "TARGET: ${cobaltTarget}"
 
 android {
-    compileSdkVersion 'android-30'
-    buildToolsVersion '30.0.0'
+    compileSdkVersion 'android-31'
+    buildToolsVersion '31.0.0'
     ndkVersion NDK_VERSION
 
     compileOptions {
@@ -72,8 +72,8 @@
     }
     defaultConfig {
         applicationId "dev.cobalt.coat"
-        minSdkVersion 21
-        targetSdkVersion 30
+        minSdkVersion 24
+        targetSdkVersion 31
         versionCode 1
         versionName "${buildId}"
         manifestPlaceholders = [applicationName: "CoAT: ${cobaltTarget}"]
@@ -140,16 +140,16 @@
         }
         // Add the directories symlinked by the CMake "cobalt_content" custom command.
         debug {
-            assets.srcDir "${buildDir}/intermediates/cmake/debug/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
         }
         devel {
-            assets.srcDir "${buildDir}/intermediates/cmake/devel/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
         }
         qa {
-            assets.srcDir "${buildDir}/intermediates/cmake/qa/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
         }
         release {
-            assets.srcDir "${buildDir}/intermediates/cmake/release/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
         }
     }
     externalNativeBuild {
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml
index 78b3400..42d8bc0 100644
--- a/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -42,6 +42,7 @@
     android:label="${applicationName}">
 
     <activity android:name="dev.cobalt.app.MainActivity"
+      android:exported="true"
       android:launchMode="singleTask"
       android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode"
       android:screenOrientation="sensorLandscape"
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 902ca00..f9ceeee 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -58,9 +58,7 @@
 import java.util.Calendar;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Locale;
-import java.util.Set;
 import java.util.TimeZone;
 
 /** Implementation of the required JNI methods called by the Starboard C++ code. */
@@ -111,10 +109,6 @@
   private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles");
   private final long timeNanosecondsPerMicrosecond = 1000;
 
-  private Set<Integer> supportedHdrTypesSet = new HashSet<Integer>();
-  private long supportedHdrTypesSetUpdatedAt = 0;
-  private final long supportedHdrTypesCacheTtlMs = 1000;
-
   public StarboardBridge(
       Context appContext,
       Holder<Activity> activityHolder,
@@ -747,53 +741,22 @@
     }
   }
 
-  long supportedHdrTypesSetUpdatedAtNs;
-
-  private void refreshHdrTypesCacheIfNecessary() {
-    if (System.currentTimeMillis() - supportedHdrTypesSetUpdatedAt < supportedHdrTypesCacheTtlMs) {
-      // Cache is up to date.
-      return;
-    }
-    supportedHdrTypesSet.clear();
-    supportedHdrTypesSetUpdatedAt = System.currentTimeMillis();
-
+  /** Return supported hdr types. */
+  @RequiresApi(24)
+  @SuppressWarnings("unused")
+  @UsedByNative
+  public int[] getSupportedHdrTypes() {
     Display defaultDisplay = DisplayUtil.getDefaultDisplay();
     if (defaultDisplay == null) {
-      return;
+      return null;
     }
 
     Display.HdrCapabilities hdrCapabilities = defaultDisplay.getHdrCapabilities();
     if (hdrCapabilities == null) {
-      return;
+      return null;
     }
 
-    int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes();
-    if (supportedHdrTypes == null) {
-      return;
-    }
-
-    for (int supportedType : supportedHdrTypes) {
-      supportedHdrTypesSet.add(supportedType);
-    }
-  }
-
-  /**
-   * Check if hdrType is supported by the current default display. See
-   * https://developer.android.com/reference/android/view/Display.HdrCapabilities.html for valid
-   * values.
-   */
-  @RequiresApi(24)
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public boolean isHdrTypeSupported(int hdrType) {
-    if (android.os.Build.VERSION.SDK_INT < 24) {
-      return false;
-    }
-
-    synchronized (this) {
-      refreshHdrTypesCacheIfNecessary();
-      return supportedHdrTypesSet.contains(hdrType);
-    }
+    return hdrCapabilities.getSupportedHdrTypes();
   }
 
   /** Return the CobaltMediaSession. */
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 69c2e78..4c11399 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -386,4 +386,22 @@
     }
     return audioTrack.getUnderrunCount();
   }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
+  private int getStartThresholdInFrames() {
+    if (Build.VERSION.SDK_INT >= 31) {
+      return getStartThresholdInFramesV31();
+    }
+    return 0;
+  }
+
+  @RequiresApi(31)
+  private int getStartThresholdInFramesV31() {
+    if (audioTrack == null) {
+      Log.e(TAG, "Unable to call getStartThresholdInFrames() with NULL audio track.");
+      return 0;
+    }
+    return audioTrack.getStartThresholdInFrames();
+  }
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 9012df2..c5c66cd 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -24,6 +24,7 @@
 import android.media.MediaCodec;
 import android.media.MediaCodec.CryptoInfo;
 import android.media.MediaCodec.CryptoInfo.Pattern;
+import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.VideoCapabilities;
 import android.media.MediaCrypto;
@@ -525,22 +526,24 @@
   public static MediaCodecBridge createAudioMediaCodecBridge(
       long nativeMediaCodecBridge,
       String mime,
+      String decoderName,
       int sampleRate,
       int channelCount,
       MediaCrypto crypto,
       @Nullable byte[] configurationData) {
+    if (decoderName.equals("")) {
+      Log.e(TAG, "Invalid decoder name.");
+      return null;
+    }
     MediaCodec mediaCodec = null;
     try {
-      String decoderName =
-          MediaCodecUtil.findAudioDecoder(mime, 0, false /* mustSupportTunnelMode */);
-      if (decoderName.equals("")) {
-        Log.e(TAG, String.format("Failed to find decoder: %s", mime));
-        return null;
-      }
       Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName));
       mediaCodec = MediaCodec.createByCodecName(decoderName);
     } catch (Exception e) {
-      Log.e(TAG, String.format("Failed to create MediaCodec: %s, ", mime), e);
+      Log.e(
+          TAG,
+          String.format("Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName),
+          e);
       return null;
     }
     if (mediaCodec == null) {
@@ -586,9 +589,7 @@
   public static void createVideoMediaCodecBridge(
       long nativeMediaCodecBridge,
       String mime,
-      boolean mustSupportSecure,
-      boolean mustSupportSoftwareCodec,
-      boolean forceImprovedSupportCheck,
+      String decoderName,
       int width,
       int height,
       int fps,
@@ -600,54 +601,21 @@
     MediaCodec mediaCodec = null;
     outCreateMediaCodecBridgeResult.mMediaCodecBridge = null;
 
-    boolean mustSupportHdr = android.os.Build.VERSION.SDK_INT >= 24 && colorInfo != null;
-    boolean mustSupportTunneled = tunnelModeAudioSessionId != -1;
-    // On first pass, try to find a decoder with HDR if the color info is non-null.
-    MediaCodecUtil.FindVideoDecoderResult findVideoDecoderResult =
-        MediaCodecUtil.findVideoDecoder(
-            mime,
-            mustSupportSecure,
-            mustSupportHdr,
-            mustSupportSoftwareCodec,
-            mustSupportTunneled,
-            forceImprovedSupportCheck,
-            -1 /* decoderCacheTtlMs */,
-            0 /* frameWidth */,
-            0 /* frameHeight */,
-            0 /* bitrate */,
-            0 /* fps */);
-    if (findVideoDecoderResult.name.equals("") && mustSupportHdr) {
-      // On second pass, forget HDR.
-      findVideoDecoderResult =
-          MediaCodecUtil.findVideoDecoder(
-              mime,
-              mustSupportSecure,
-              false /* mustSupportHdr */,
-              mustSupportSoftwareCodec,
-              mustSupportTunneled,
-              forceImprovedSupportCheck,
-              -1 /* decoderCacheTtlMs */,
-              0 /* frameWidth */,
-              0 /* frameHeight */,
-              0 /* bitrate */,
-              0 /* fps */);
+    if (decoderName.equals("")) {
+      String message = "Invalid decoder name.";
+      Log.e(TAG, message);
+      outCreateMediaCodecBridgeResult.mErrorMessage = message;
+      return;
     }
+
     try {
-      String decoderName = findVideoDecoderResult.name;
-      if (decoderName.equals("") || findVideoDecoderResult.videoCapabilities == null) {
-        String message =
-            String.format(
-                "Failed to find decoder: %s, mustSupportSecure: %s", mime, mustSupportSecure);
-        Log.e(TAG, message);
-        outCreateMediaCodecBridgeResult.mErrorMessage = message;
-        return;
-      }
       Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName));
       mediaCodec = MediaCodec.createByCodecName(decoderName);
     } catch (Exception e) {
       String message =
           String.format(
-              "Failed to create MediaCodec: %s, mustSupportSecure: %s", mime, mustSupportSecure);
+              "Failed to create MediaCodec: %s, mustSupportSecure: %s," + " DecoderName: %s",
+              mime, crypto != null, decoderName);
       Log.e(TAG, message, e);
       outCreateMediaCodecBridgeResult.mErrorMessage = message;
       return;
@@ -656,6 +624,23 @@
       outCreateMediaCodecBridgeResult.mErrorMessage = "mediaCodec is null";
       return;
     }
+
+    MediaCodecInfo codecInfo = mediaCodec.getCodecInfo();
+    if (codecInfo == null) {
+      outCreateMediaCodecBridgeResult.mErrorMessage = "codecInfo is null";
+      return;
+    }
+    CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(mime);
+    if (codecCapabilities == null) {
+      outCreateMediaCodecBridgeResult.mErrorMessage = "codecCapabilities is null";
+      return;
+    }
+    VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities();
+    if (videoCapabilities == null) {
+      outCreateMediaCodecBridgeResult.mErrorMessage = "videoCapabilities is null";
+      return;
+    }
+
     MediaCodecBridge bridge =
         new MediaCodecBridge(
             nativeMediaCodecBridge,
@@ -664,13 +649,10 @@
             true,
             BitrateAdjustmentTypes.NO_ADJUSTMENT,
             tunnelModeAudioSessionId);
-    MediaFormat mediaFormat =
-        createVideoDecoderFormat(mime, width, height, findVideoDecoderResult.videoCapabilities);
+    MediaFormat mediaFormat = createVideoDecoderFormat(mime, width, height, videoCapabilities);
 
     boolean shouldConfigureHdr =
-        android.os.Build.VERSION.SDK_INT >= 24
-            && colorInfo != null
-            && MediaCodecUtil.isHdrCapableVideoDecoder(mime, findVideoDecoderResult);
+        colorInfo != null && MediaCodecUtil.isHdrCapableVideoDecoder(mime, codecCapabilities);
     if (shouldConfigureHdr) {
       Log.d(TAG, "Setting HDR info.");
       mediaFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
@@ -688,7 +670,6 @@
       Log.d(TAG, "Enabled tunnel mode playback on audio session " + tunnelModeAudioSessionId);
     }
 
-    VideoCapabilities videoCapabilities = findVideoDecoderResult.videoCapabilities;
     int maxWidth = videoCapabilities.getSupportedWidths().getUpper();
     int maxHeight = videoCapabilities.getSupportedHeights().getUpper();
     if (fps > 0) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index bd1461e..2d023be 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -31,6 +31,8 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -41,8 +43,6 @@
   // A high priority allow list of brands/model that should always attempt to
   // play vp9.
   private static final Map<String, Set<String>> vp9AllowList = new HashMap<>();
-  // An allow list of software codec names that can be used.
-  private static final Set<String> softwareCodecAllowList = new HashSet<>();
   // Whether we should report vp9 codecs as supported or not.  Will be set
   // based on whether vp9AllowList contains our brand/model.  If this is set
   // to true, then videoCodecDenyList will be ignored.
@@ -51,23 +51,6 @@
   private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
   private static final String AV1_MIME_TYPE = "video/av01";
 
-  /**
-   * A simple "struct" to bundle up the results from findVideoDecoder, as its clients may require
-   * the max supported width and height in addition to just the decoder name.
-   */
-  public static final class FindVideoDecoderResult {
-    public String name;
-    public VideoCapabilities videoCapabilities;
-    public CodecCapabilities codecCapabilities;
-
-    public FindVideoDecoderResult(
-        String name, VideoCapabilities videoCapabilities, CodecCapabilities codecCapabilities) {
-      this.name = name;
-      this.videoCapabilities = videoCapabilities;
-      this.codecCapabilities = codecCapabilities;
-    }
-  }
-
   static {
     if (Build.VERSION.SDK_INT >= 24 && Build.BRAND.equals("google")) {
       videoCodecDenyList.add("OMX.Nvidia.vp9.decode");
@@ -377,61 +360,108 @@
     isVp9AllowListed =
         vp9AllowList.containsKey(Build.BRAND)
             && vp9AllowList.get(Build.BRAND).contains(Build.MODEL);
-
-    softwareCodecAllowList.add("OMX.google.h264.decoder");
   }
 
   private MediaCodecUtil() {}
 
-  /**
-   * Returns whether a given combination of (frame width x frame height) frames at bitrate and fps
-   * has a decoder with mime type.
-   *
-   * <p>Setting any of the int parameters to 0 indicates that they shouldn't be considered.
-   */
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public static boolean hasVideoDecoderFor(
-      String mimeType,
-      boolean mustSupportSecure,
-      boolean mustSupportHdr,
-      boolean mustSupportTunnelMode,
-      boolean forceImprovedSupportCheck,
-      int decoderCacheTtlMs,
-      int frameWidth,
-      int frameHeight,
-      int bitrate,
-      int fps) {
-    FindVideoDecoderResult findVideoDecoderResult =
-        findVideoDecoder(
-            mimeType,
-            mustSupportSecure,
-            mustSupportHdr,
-            false /* mustSupportSoftwareCodec */,
-            mustSupportTunnelMode,
-            forceImprovedSupportCheck,
-            decoderCacheTtlMs,
-            frameWidth,
-            frameHeight,
-            bitrate,
-            fps);
-    return !findVideoDecoderResult.name.isEmpty();
+  /** A wrapper class of codec capability infos. */
+  public static class CodecCapabilityInfo {
+    CodecCapabilityInfo(MediaCodecInfo codecInfo, String mimeType) {
+      this.codecInfo = codecInfo;
+      this.mimeType = mimeType;
+      this.decoderName = codecInfo.getName();
+      this.codecCapabilities = codecInfo.getCapabilitiesForType(mimeType);
+      this.audioCapabilities = this.codecCapabilities.getAudioCapabilities();
+      this.videoCapabilities = this.codecCapabilities.getVideoCapabilities();
+    }
+
+    public MediaCodecInfo codecInfo;
+    public String mimeType;
+    public String decoderName;
+    public CodecCapabilities codecCapabilities;
+    public AudioCapabilities audioCapabilities;
+    public VideoCapabilities videoCapabilities;
+
+    public boolean isSecureRequired() {
+      // MediaCodecList is supposed to feed us names of decoders that do NOT end in ".secure".  We
+      // are then supposed to check if FEATURE_SecurePlayback is supported, and if it is and we
+      // want a secure codec, we append ".secure" ourselves, and then pass that to
+      // MediaCodec.createDecoderByName.  Some devices, do not follow this spec, and show us
+      // decoders that end in ".secure".  Empirically, FEATURE_SecurePlayback has still been
+      // correct when this happens.
+      if (this.decoderName.endsWith(SECURE_DECODER_SUFFIX)) {
+        // If a decoder name ends with ".secure", then we don't want to use it for clear content.
+        return true;
+      }
+      return this.codecCapabilities.isFeatureRequired(
+          MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
+    }
+
+    public boolean isSecureSupported() {
+      return this.codecCapabilities.isFeatureSupported(
+          MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
+    }
+
+    public boolean isTunnelModeRequired() {
+      return this.codecCapabilities.isFeatureRequired(
+          MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
+    }
+
+    public boolean isTunnelModeSupported() {
+      return this.codecCapabilities.isFeatureSupported(
+          MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
+    }
+
+    public boolean isSoftware() {
+      return isSoftwareDecoder(this.codecInfo);
+    }
+
+    public boolean isHdrCapable() {
+      return isHdrCapableVideoDecoder(this.mimeType, this.codecCapabilities);
+    }
   }
 
-  /**
-   * Returns whether an audio decoder that supports mimeType at bitrate. Setting bitrate to 0
-   * indicates that it should not be considered.
-   */
+  /** Returns an array of CodecCapabilityInfo for all available decoder. */
   @SuppressWarnings("unused")
   @UsedByNative
-  public static boolean hasAudioDecoderFor(
-      String mimeType, int bitrate, boolean mustSupportTunnelMode) {
-    return !findAudioDecoder(mimeType, bitrate, mustSupportTunnelMode).equals("");
+  public static CodecCapabilityInfo[] getAllCodecCapabilityInfos() {
+    List<CodecCapabilityInfo> codecCapabilityInfos = new ArrayList<>();
+
+    for (MediaCodecInfo codecInfo : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
+      // We don't use encoder.
+      if (codecInfo.isEncoder()) {
+        continue;
+      }
+
+      // Filter blacklisted video decoders.
+      String name = codecInfo.getName();
+      if (!isVp9AllowListed && videoCodecDenyList.contains(name)) {
+        Log.v(TAG, String.format("Rejecting %s, reason: codec is on deny list", name));
+        continue;
+      }
+      if (name.endsWith(SECURE_DECODER_SUFFIX)) {
+        // If we want a secure decoder, then make sure the version without ".secure" isn't
+        // denylisted.
+        String nameWithoutSecureSuffix =
+            name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length());
+        if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) {
+          String format = "Rejecting %s, reason: offpsec denylisted secure decoder";
+          Log.v(TAG, String.format(format, name));
+          continue;
+        }
+      }
+
+      for (String mimeType : codecInfo.getSupportedTypes()) {
+        codecCapabilityInfos.add(new CodecCapabilityInfo(codecInfo, mimeType));
+      }
+    }
+    CodecCapabilityInfo[] array = new CodecCapabilityInfo[codecCapabilityInfos.size()];
+    return codecCapabilityInfos.toArray(array);
   }
 
-  /** Determine whether findVideoDecoderResult is capable of playing HDR. */
+  /** Determine whether codecCapabilities is capable of playing HDR. */
   public static boolean isHdrCapableVideoDecoder(
-      String mimeType, FindVideoDecoderResult findVideoDecoderResult) {
+      String mimeType, CodecCapabilities codecCapabilities) {
     // VP9Profile* values were not added until API level 24.  See
     // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html.
     if (Build.VERSION.SDK_INT < 24) {
@@ -443,7 +473,6 @@
       return false;
     }
 
-    CodecCapabilities codecCapabilities = findVideoDecoderResult.codecCapabilities;
     if (codecCapabilities == null) {
       return false;
     }
@@ -467,14 +496,34 @@
     return false;
   }
 
+  /** Return true if and only if info belongs to a software decoder. */
+  public static boolean isSoftwareDecoder(MediaCodecInfo codecInfo) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+      return !codecInfo.isHardwareAccelerated();
+    }
+    String name = codecInfo.getName().toLowerCase(Locale.ROOT);
+    // This is taken from libstagefright/OMXCodec.cpp for pre codec2.
+    if (name.startsWith("omx.google.")) {
+      return true;
+    }
+
+    // Codec2 names sw decoders this way.
+    // See hardware/google/av/codec2/vndk/C2Store.cpp.
+    if (name.startsWith("c2.google.") || name.startsWith("c2.android.")) {
+      return true;
+    }
+
+    return false;
+  }
+
   /**
-   * The same as hasVideoDecoderFor, returns a FindVideoDecoderResult constructed from the video
-   * decoder if it is found, or an empty instance otherwise.
+   * The same as hasVideoDecoderFor, returns the name of the video decoder if it is found, or ""
+   * otherwise.
    *
    * <p>NOTE: This code path is called repeatedly by the player to determine the decoding
    * capabilities of the device. To ensure speedy playback the code below should be kept performant.
    */
-  public static FindVideoDecoderResult findVideoDecoder(
+  public static String findVideoDecoder(
       String mimeType,
       boolean mustSupportSecure,
       boolean mustSupportHdr,
@@ -516,15 +565,16 @@
     for (VideoDecoderCache.CachedDecoder decoder :
         VideoDecoderCache.getCachedDecoders(mimeType, decoderCacheTtlMs)) {
       String name = decoder.info.getName();
-      if (mustSupportSoftwareCodec && !softwareCodecAllowList.contains(name)) {
-        Log.v(TAG, "Rejecting " + name + ", reason: require software codec");
-        continue;
-      }
       if (!isVp9AllowListed && videoCodecDenyList.contains(name)) {
         Log.v(TAG, "Rejecting " + name + ", reason: codec is on deny list");
         continue;
       }
 
+      if (mustSupportSoftwareCodec && !isSoftwareDecoder(decoder.info)) {
+        Log.v(TAG, "Rejecting " + name + ", reason: require software codec");
+        continue;
+      }
+
       // MediaCodecList is supposed to feed us names of decoders that do NOT end in ".secure".  We
       // are then supposed to check if FEATURE_SecurePlayback is supported, and if it is and we
       // want a secure codec, we append ".secure" ourselves, and then pass that to
@@ -664,20 +714,19 @@
         }
       }
 
+      if (mustSupportHdr && !isHdrCapableVideoDecoder(mimeType, decoder.codecCapabilities)) {
+        Log.v(TAG, "Rejecting " + name + ", reason: codec does not support HDR");
+        continue;
+      }
+
       String resultName =
           (mustSupportSecure && !name.endsWith(SECURE_DECODER_SUFFIX))
               ? (name + SECURE_DECODER_SUFFIX)
               : name;
-      FindVideoDecoderResult findVideoDecoderResult =
-          new FindVideoDecoderResult(resultName, videoCapabilities, decoder.codecCapabilities);
-      if (mustSupportHdr && !isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult)) {
-        Log.v(TAG, "Rejecting " + name + ", reason: codec does not support HDR");
-        continue;
-      }
       Log.v(TAG, "Found suitable decoder, " + name);
-      return findVideoDecoderResult;
+      return resultName;
     }
-    return new FindVideoDecoderResult("", null, null);
+    return "";
   }
 
   /**
@@ -841,10 +890,8 @@
                     && !name.endsWith(SECURE_DECODER_SUFFIX))
                 ? (name + SECURE_DECODER_SUFFIX)
                 : name;
-        FindVideoDecoderResult findVideoDecoderResult =
-            new FindVideoDecoderResult(resultName, videoCapabilities, codecCapabilities);
         boolean isHdrCapable =
-            isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), findVideoDecoderResult);
+            isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), codecCapabilities);
         if (videoCapabilities != null) {
           String frameRateAndResolutionString =
               getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable);
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index b712376..1d3428e 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -170,14 +170,22 @@
    * @return true if the container and the crypto scheme is supported, or false otherwise.
    */
   @UsedByNative
-  static boolean isWidevineCryptoSchemeSupported(boolean usesCbcs) {
-    if (Build.VERSION.SDK_INT < 24 && usesCbcs) {
-      Log.e(TAG, "Encryption scheme 'cbcs' is not supported on this platform.");
-      return false;
-    }
+  static boolean isWidevineCryptoSchemeSupported() {
     return MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID);
   }
 
+  /**
+   * Check whether `cbcs` scheme is supported.
+   *
+   * @return true if the `cbcs` encryption is supported, or false otherwise.
+   */
+  @UsedByNative
+  static boolean isCbcsSchemeSupported() {
+    // While 'cbcs' scheme was originally implemented in N, there was a bug (in the
+    // DRM code) which means that it didn't really work properly until N-MR1).
+    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1;
+  }
+
   /** Destroy the MediaDrmBridge object. */
   @UsedByNative
   void destroy() {
diff --git a/starboard/android/apk/build.gradle b/starboard/android/apk/build.gradle
index 302652a..3db6ad6 100644
--- a/starboard/android/apk/build.gradle
+++ b/starboard/android/apk/build.gradle
@@ -20,7 +20,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath 'com.android.tools.build:gradle:7.0.2'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties b/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
index 186b715..29e4134 100644
--- a/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
+++ b/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn
index 599bd7d..5efe78e 100644
--- a/starboard/android/shared/BUILD.gn
+++ b/starboard/android/shared/BUILD.gn
@@ -334,6 +334,8 @@
     "log_is_tty.cc",
     "log_raw.cc",
     "main.cc",
+    "media_capabilities_cache.cc",
+    "media_capabilities_cache.h",
     "media_codec_bridge.cc",
     "media_codec_bridge.h",
     "media_common.h",
@@ -446,6 +448,18 @@
       "player_components_factory.cc",
     ]
   }
+
+  if (sb_is_evergreen_compatible) {
+    public_deps += [ "//starboard/elf_loader:evergreen_config" ]
+
+    if (!sb_evergreen_compatible_enable_lite) {
+      public_deps += [ "//starboard/loader_app:pending_restart" ]
+    }
+  }
+
+  if (sb_evergreen_compatible_use_libunwind) {
+    deps += [ "//third_party/llvm-project/libunwind:unwind_starboard" ]
+  }
 }
 
 static_library("starboard_base_symbolize") {
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index 80656e2..92ca9e4 100644
--- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -131,16 +131,30 @@
       wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
     }
 
-    if (wait_timeout) {
-      SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
-    }
+    // Get start threshold before release the audio sink.
+    int start_threshold = audio_sink_->GetStartThresholdInFrames();
 
+    // |min_required_frames_| is shared between two threads. Release audio sink
+    // to end audio sink thread before access |min_required_frames_| on this
+    // thread.
     delete audio_sink_;
     audio_sink_ = nullptr;
 
-    // Call |received_cb_| after audio sink thread is ended.
-    // |min_required_frames_| is shared between two threads.
-    if (!destroying_.load() && !wait_timeout) {
+    if (wait_timeout) {
+      SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
+      // Overwrite |min_required_frames_| if failed to get a stable result.
+      min_required_frames_ = max_required_frames_;
+    }
+
+    if (start_threshold > min_required_frames_) {
+      SB_LOG(INFO) << "Audio sink min required frames is overwritten from "
+                   << min_required_frames_ << " to audio track start threshold "
+                   << start_threshold << ".";
+      // Overwrite |min_required_frames_| to match |start_threshold|.
+      min_required_frames_ = start_threshold;
+    }
+
+    if (!destroying_.load()) {
       task.received_cb(task.number_of_channels, task.sample_type,
                        task.sample_rate, min_required_frames_);
     }
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index 45d90b0..d055a29 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -389,6 +389,10 @@
   return bridge_.GetUnderrunCount();
 }
 
+int AudioTrackAudioSink::GetStartThresholdInFrames() {
+  return bridge_.GetStartThresholdInFrames();
+}
+
 // static
 int AudioTrackAudioSinkType::GetMinBufferSizeInFrames(
     int channels,
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h
index 035b093..38c7014 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -125,6 +125,7 @@
 
   void SetVolume(double volume) override;
   int GetUnderrunCount();
+  int GetStartThresholdInFrames();
 
  private:
   static void* ThreadEntryPoint(void* context);
diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc
index aad3562..629a96e 100644
--- a/starboard/android/shared/audio_track_bridge.cc
+++ b/starboard/android/shared/audio_track_bridge.cc
@@ -296,6 +296,15 @@
                                    "()I");
 }
 
+int AudioTrackBridge::GetStartThresholdInFrames(
+    JniEnvExt* env /*= JniEnvExt::Get()*/) {
+  SB_DCHECK(env);
+  SB_DCHECK(is_valid());
+
+  return env->CallIntMethodOrAbort(j_audio_track_bridge_,
+                                   "getStartThresholdInFrames", "()I");
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/audio_track_bridge.h b/starboard/android/shared/audio_track_bridge.h
index 9ddb4f1..1844151 100644
--- a/starboard/android/shared/audio_track_bridge.h
+++ b/starboard/android/shared/audio_track_bridge.h
@@ -83,6 +83,7 @@
                             JniEnvExt* env = JniEnvExt::Get());
   bool GetAndResetHasAudioDeviceChanged(JniEnvExt* env = JniEnvExt::Get());
   int GetUnderrunCount(JniEnvExt* env = JniEnvExt::Get());
+  int GetStartThresholdInFrames(JniEnvExt* env = JniEnvExt::Get());
 
  private:
   int max_samples_per_write_;
diff --git a/starboard/android/shared/download_sdk.sh b/starboard/android/shared/download_sdk.sh
index d968f8b..e05cef4 100755
--- a/starboard/android/shared/download_sdk.sh
+++ b/starboard/android/shared/download_sdk.sh
@@ -44,14 +44,14 @@
 
 # Update the installation
 ${SDK_MANAGER_TOOL} --sdk_root=${ANDROID_SDK_ROOT} \
-    "build-tools;30.0.0" \
+    "build-tools;31.0.0" \
     "cmake;3.10.2.4988404" \
     "cmdline-tools;1.0" \
     "extras;android;m2repository" \
     "extras;google;m2repository" \
     "ndk;21.1.6352462" \
     "patcher;v4" \
-    "platforms;android-30" \
+    "platforms;android-31" \
     "platform-tools"
 
 echo "Android SDK updated."
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
new file mode 100644
index 0000000..53360ea
--- /dev/null
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -0,0 +1,531 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/media_capabilities_cache.h"
+
+#include <utility>
+
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/common/log.h"
+#include "starboard/once.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+// https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10
+const jint HDR_TYPE_DOLBY_VISION = 1;
+const jint HDR_TYPE_HDR10 = 2;
+const jint HDR_TYPE_HLG = 3;
+const jint HDR_TYPE_HDR10_PLUS = 4;
+
+const char SECURE_DECODER_SUFFIX[] = ".secure";
+
+bool EndsWith(const std::string& str, const std::string& suffix) {
+  if (str.size() < suffix.size()) {
+    return false;
+  }
+  return strcmp(str.c_str() + (str.size() - suffix.size()), suffix.c_str()) ==
+         0;
+}
+
+Range ConvertJavaRangeToRange(JniEnvExt* env, jobject j_range) {
+  jobject j_upper_comparable = env->CallObjectMethodOrAbort(
+      j_range, "getUpper", "()Ljava/lang/Comparable;");
+  jint j_upper_int =
+      env->CallIntMethodOrAbort(j_upper_comparable, "intValue", "()I");
+
+  jobject j_lower_comparable = env->CallObjectMethodOrAbort(
+      j_range, "getLower", "()Ljava/lang/Comparable;");
+  jint j_lower_int =
+      env->CallIntMethodOrAbort(j_lower_comparable, "intValue", "()I");
+  return Range(j_lower_int, j_upper_int);
+}
+
+void ConvertStringToLowerCase(std::string* str) {
+  for (int i = 0; i < str->length(); i++) {
+    (*str)[i] = std::tolower((*str)[i]);
+  }
+}
+
+bool GetIsWidevineSupported() {
+  return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
+             "dev/cobalt/media/MediaDrmBridge",
+             "isWidevineCryptoSchemeSupported", "()Z") == JNI_TRUE;
+}
+
+bool GetIsCbcsSupported() {
+  return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
+             "dev/cobalt/media/MediaDrmBridge", "isCbcsSchemeSupported",
+             "()Z") == JNI_TRUE;
+}
+
+std::set<SbMediaTransferId> GetSupportedHdrTypes() {
+  std::set<SbMediaTransferId> supported_transfer_ids;
+
+  JniEnvExt* env = JniEnvExt::Get();
+  jintArray j_supported_hdr_types = static_cast<jintArray>(
+      env->CallStarboardObjectMethodOrAbort("getSupportedHdrTypes", "()[I"));
+  jsize length = env->GetArrayLength(j_supported_hdr_types);
+  jint* numbers = env->GetIntArrayElements(j_supported_hdr_types, 0);
+  for (int i = 0; i < length; i++) {
+    switch (numbers[i]) {
+      case HDR_TYPE_DOLBY_VISION:
+        continue;
+      case HDR_TYPE_HDR10:
+        supported_transfer_ids.insert(kSbMediaTransferIdSmpteSt2084);
+        continue;
+      case HDR_TYPE_HLG:
+        supported_transfer_ids.insert(kSbMediaTransferIdAribStdB67);
+        continue;
+      case HDR_TYPE_HDR10_PLUS:
+        continue;
+    }
+  }
+  env->ReleaseIntArrayElements(j_supported_hdr_types, numbers, 0);
+
+  return supported_transfer_ids;
+}
+
+bool GetIsPassthroughSupported(SbMediaAudioCodec codec) {
+  SbMediaAudioCodingType coding_type;
+  switch (codec) {
+    case kSbMediaAudioCodecAc3:
+      coding_type = kSbMediaAudioCodingTypeAc3;
+      break;
+    case kSbMediaAudioCodecEac3:
+      coding_type = kSbMediaAudioCodingTypeDolbyDigitalPlus;
+      break;
+    default:
+      return false;
+  }
+  int encoding = GetAudioFormatSampleType(coding_type);
+  JniEnvExt* env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+  return env->CallBooleanMethodOrAbort(j_audio_output_manager.Get(),
+                                       "hasPassthroughSupportFor", "(I)Z",
+                                       encoding) == JNI_TRUE;
+}
+
+int GetMaxAudioOutputChannels() {
+  JniEnvExt* env = JniEnvExt::Get();
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+  return static_cast<int>(env->CallIntMethodOrAbort(
+      j_audio_output_manager.Get(), "getMaxChannels", "()I"));
+}
+
+}  // namespace
+
+CodecCapability::CodecCapability(JniEnvExt* env, jobject j_codec_info) {
+  SB_DCHECK(env);
+  SB_DCHECK(j_codec_info);
+
+  name_ = env->GetStringStandardUTFOrAbort(
+      env->GetStringFieldOrAbort(j_codec_info, "decoderName"));
+
+  is_secure_required_ =
+      env->CallBooleanMethodOrAbort(j_codec_info, "isSecureRequired", "()Z") ==
+      JNI_TRUE;
+  is_secure_supported_ =
+      env->CallBooleanMethodOrAbort(j_codec_info, "isSecureSupported", "()Z") ==
+      JNI_TRUE;
+  is_tunnel_mode_required_ =
+      env->CallBooleanMethodOrAbort(j_codec_info, "isTunnelModeRequired",
+                                    "()Z") == JNI_TRUE;
+  is_tunnel_mode_supported_ =
+      env->CallBooleanMethodOrAbort(j_codec_info, "isTunnelModeSupported",
+                                    "()Z") == JNI_TRUE;
+}
+
+AudioCodecCapability::AudioCodecCapability(JniEnvExt* env,
+                                           jobject j_codec_info,
+                                           jobject j_audio_capabilities)
+    : CodecCapability(env, j_codec_info) {
+  SB_DCHECK(env);
+  SB_DCHECK(j_codec_info);
+  SB_DCHECK(j_audio_capabilities);
+
+  jobject j_bitrate_range = env->CallObjectMethodOrAbort(
+      j_audio_capabilities, "getBitrateRange", "()Landroid/util/Range;");
+  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range);
+
+  // Overwrite the lower bound to 0.
+  supported_bitrates_.minimum = 0;
+}
+
+bool AudioCodecCapability::IsBitrateSupported(int bitrate) const {
+  return supported_bitrates_.Contains(bitrate);
+}
+
+VideoCodecCapability::VideoCodecCapability(JniEnvExt* env,
+                                           jobject j_codec_info,
+                                           jobject j_video_capabilities)
+    : CodecCapability(env, j_codec_info) {
+  SB_DCHECK(env);
+  SB_DCHECK(j_codec_info);
+  SB_DCHECK(j_video_capabilities);
+
+  is_software_decoder_ = env->CallBooleanMethodOrAbort(
+                             j_codec_info, "isSoftware", "()Z") == JNI_TRUE;
+
+  is_hdr_capable_ = env->CallBooleanMethodOrAbort(j_codec_info, "isHdrCapable",
+                                                  "()Z") == JNI_TRUE;
+
+  j_video_capabilities_ = env->ConvertLocalRefToGlobalRef(j_video_capabilities);
+
+  jobject j_width_range = env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getSupportedWidths", "()Landroid/util/Range;");
+  supported_widths_ = ConvertJavaRangeToRange(env, j_width_range);
+
+  jobject j_height_range = env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getSupportedHeights", "()Landroid/util/Range;");
+  supported_heights_ = ConvertJavaRangeToRange(env, j_height_range);
+
+  jobject j_bitrate_range = env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getBitrateRange", "()Landroid/util/Range;");
+  supported_bitrates_ = ConvertJavaRangeToRange(env, j_bitrate_range);
+
+  jobject j_frame_rate_range = env->CallObjectMethodOrAbort(
+      j_video_capabilities_, "getSupportedFrameRates",
+      "()Landroid/util/Range;");
+  supported_frame_rates_ = ConvertJavaRangeToRange(env, j_frame_rate_range);
+}
+
+VideoCodecCapability::~VideoCodecCapability() {
+  JniEnvExt* env = JniEnvExt::Get();
+  env->DeleteGlobalRef(j_video_capabilities_);
+}
+
+bool VideoCodecCapability::IsBitrateSupported(int bitrate) const {
+  return supported_bitrates_.Contains(bitrate);
+}
+
+bool VideoCodecCapability::AreResolutionAndRateSupported(
+    bool force_improved_support_check,
+    int frame_width,
+    int frame_height,
+    int fps) {
+  if (force_improved_support_check) {
+    if (frame_width != 0 && frame_height != 0 && fps != 0) {
+      return JniEnvExt::Get()->CallBooleanMethodOrAbort(
+                 j_video_capabilities_, "areSizeAndRateSupported", "(IID)Z",
+                 frame_width, frame_height,
+                 static_cast<jdouble>(fps)) == JNI_TRUE;
+    } else if (frame_width != 0 && frame_height != 0) {
+      return JniEnvExt::Get()->CallBooleanMethodOrAbort(
+                 j_video_capabilities_, "isSizeSupported", "(II)Z", frame_width,
+                 frame_height) == JNI_TRUE;
+    }
+  }
+  if (frame_width != 0 && !supported_widths_.Contains(frame_width)) {
+    return false;
+  }
+  if (frame_height != 0 && !supported_heights_.Contains(frame_height)) {
+    return false;
+  }
+  if (fps != 0 && !supported_frame_rates_.Contains(fps)) {
+    return false;
+  }
+  return true;
+}
+
+// static
+SB_ONCE_INITIALIZE_FUNCTION(MediaCapabilitiesCache,
+                            MediaCapabilitiesCache::GetInstance);
+
+bool MediaCapabilitiesCache::IsWidevineSupported() {
+  if (!is_enabled_) {
+    return GetIsWidevineSupported();
+  }
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+  return is_widevine_supported_;
+}
+
+bool MediaCapabilitiesCache::IsCbcsSchemeSupported() {
+  if (!is_enabled_) {
+    return GetIsCbcsSupported();
+  }
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+  return is_cbcs_supported_;
+}
+
+bool MediaCapabilitiesCache::IsHDRTransferCharacteristicsSupported(
+    SbMediaTransferId transfer_id) {
+  if (!is_enabled_) {
+    std::set<SbMediaTransferId> supported_transfer_ids = GetSupportedHdrTypes();
+    return supported_transfer_ids.find(transfer_id) !=
+           supported_transfer_ids.end();
+  }
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+  return supported_transfer_ids_.find(transfer_id) !=
+         supported_transfer_ids_.end();
+}
+
+bool MediaCapabilitiesCache::IsPassthroughSupported(SbMediaAudioCodec codec) {
+  if (!is_enabled_) {
+    return GetIsPassthroughSupported(codec);
+  }
+  // IsPassthroughSupported() caches the results of previous quiries, and does
+  // not rely on LazyInitialize(), which is different from other functions.
+  ScopedLock scoped_lock(mutex_);
+  auto iter = passthrough_supportabilities_.find(codec);
+  if (iter != passthrough_supportabilities_.end()) {
+    return iter->second;
+  }
+  bool supported = GetIsPassthroughSupported(codec);
+  passthrough_supportabilities_[codec] = supported;
+  return supported;
+}
+
+int MediaCapabilitiesCache::GetMaxAudioOutputChannels() {
+  if (!is_enabled_) {
+    return ::starboard::android::shared::GetMaxAudioOutputChannels();
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+  return max_audio_output_channels_;
+}
+
+bool MediaCapabilitiesCache::HasAudioDecoderFor(const std::string& mime_type,
+                                                int bitrate,
+                                                bool must_support_tunnel_mode) {
+  return !FindAudioDecoder(mime_type, bitrate, must_support_tunnel_mode)
+              .empty();
+}
+
+bool MediaCapabilitiesCache::HasVideoDecoderFor(
+    const std::string& mime_type,
+    bool must_support_secure,
+    bool must_support_hdr,
+    bool must_support_tunnel_mode,
+    bool force_improved_support_check,
+    int frame_width,
+    int frame_height,
+    int bitrate,
+    int fps) {
+  return !FindVideoDecoder(mime_type, must_support_secure, must_support_hdr,
+                           false, must_support_tunnel_mode,
+                           force_improved_support_check, frame_width,
+                           frame_height, bitrate, fps)
+              .empty();
+}
+
+std::string MediaCapabilitiesCache::FindAudioDecoder(
+    const std::string& mime_type,
+    int bitrate,
+    bool must_support_tunnel_mode) {
+  if (!is_enabled_) {
+    JniEnvExt* env = JniEnvExt::Get();
+    ScopedLocalJavaRef<jstring> j_mime(
+        env->NewStringStandardUTFOrAbort(mime_type.c_str()));
+    jobject j_decoder_name = env->CallStaticObjectMethodOrAbort(
+        "dev/cobalt/media/MediaCodecUtil", "findAudioDecoder",
+        "(Ljava/lang/String;IZ)Ljava/lang/String;", j_mime.Get(), bitrate,
+        must_support_tunnel_mode);
+    return env->GetStringStandardUTFOrAbort(
+        static_cast<jstring>(j_decoder_name));
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+
+  for (auto& audio_capability : audio_codec_capabilities_map_[mime_type]) {
+    // Reject if tunnel mode is required but codec doesn't support it.
+    if (must_support_tunnel_mode &&
+        !audio_capability->is_tunnel_mode_supported()) {
+      continue;
+    }
+    // Reject if bitrate is not supported.
+    if (!audio_capability->IsBitrateSupported(bitrate)) {
+      continue;
+    }
+    return audio_capability->name();
+  }
+
+  return "";
+}
+
+std::string MediaCapabilitiesCache::FindVideoDecoder(
+    const std::string& mime_type,
+    bool must_support_secure,
+    bool must_support_hdr,
+    bool require_software_codec,
+    bool must_support_tunnel_mode,
+    bool force_improved_support_check,
+    int frame_width,
+    int frame_height,
+    int bitrate,
+    int fps) {
+  if (!is_enabled_) {
+    JniEnvExt* env = JniEnvExt::Get();
+    ScopedLocalJavaRef<jstring> j_mime(
+        env->NewStringStandardUTFOrAbort(mime_type.c_str()));
+    jobject j_decoder_name = env->CallStaticObjectMethodOrAbort(
+        "dev/cobalt/media/MediaCodecUtil", "findVideoDecoder",
+        "(Ljava/lang/String;ZZZZZIIIII)Ljava/lang/String;", j_mime.Get(),
+        must_support_secure, must_support_hdr,
+        false, /* mustSupportSoftwareCodec */
+        must_support_tunnel_mode, force_improved_support_check,
+        -1, /* decoderCacheTtlMs */
+        frame_width, frame_height, bitrate, fps);
+    return env->GetStringStandardUTFOrAbort(
+        static_cast<jstring>(j_decoder_name));
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  LazyInitialize_Locked();
+
+  for (auto& video_capability : video_codec_capabilities_map_[mime_type]) {
+    // Reject if secure decoder is required but codec doesn't support it.
+    if (must_support_secure && !video_capability->is_secure_supported()) {
+      continue;
+    }
+    // Reject if non secure decoder is required but codec doesn't support it.
+    if (!must_support_secure && video_capability->is_secure_required()) {
+      continue;
+    }
+    // Reject if tunnel mode is required but codec doesn't support it.
+    if (must_support_tunnel_mode &&
+        !video_capability->is_tunnel_mode_supported()) {
+      continue;
+    }
+    // Reject if non tunnel mode is required but codec doesn't support it.
+    if (!must_support_tunnel_mode &&
+        video_capability->is_tunnel_mode_required()) {
+      continue;
+    }
+    // Reject if software codec is required but codec is not.
+    if (require_software_codec && !video_capability->is_software_decoder()) {
+      continue;
+    }
+    // Reject if hdr is required but codec doesn't support it.
+    if (must_support_hdr && !video_capability->is_hdr_capable()) {
+      continue;
+    }
+
+    bool enable_improved_support_check =
+        force_improved_support_check ||
+        (frame_width > 3840 || frame_height > 2160);
+    // Reject if resolution or frame rate is not supported.
+    if (!video_capability->AreResolutionAndRateSupported(
+            enable_improved_support_check, frame_width, frame_height, fps)) {
+      continue;
+    }
+
+    // Reject if bitrate is not supported.
+    if (bitrate != 0 && !video_capability->IsBitrateSupported(bitrate)) {
+      continue;
+    }
+
+    // Append ".secure" for secure decoder if not represents.
+    if (must_support_secure &&
+        !EndsWith(video_capability->name(), SECURE_DECODER_SUFFIX)) {
+      return video_capability->name() + SECURE_DECODER_SUFFIX;
+    }
+    return video_capability->name();
+  }
+
+  return "";
+}
+
+void MediaCapabilitiesCache::ClearCache() {
+  ScopedLock scoped_lock(mutex_);
+  is_initialized_ = false;
+  is_widevine_supported_ = false;
+  is_cbcs_supported_ = false;
+  supported_transfer_ids_.clear();
+  passthrough_supportabilities_.clear();
+  audio_codec_capabilities_map_.clear();
+  video_codec_capabilities_map_.clear();
+  max_audio_output_channels_ = -1;
+}
+
+void MediaCapabilitiesCache::LazyInitialize_Locked() {
+  mutex_.DCheckAcquired();
+
+  if (is_initialized_) {
+    return;
+  }
+
+  is_widevine_supported_ = GetIsWidevineSupported();
+  is_cbcs_supported_ = GetIsCbcsSupported();
+  supported_transfer_ids_ = GetSupportedHdrTypes();
+  max_audio_output_channels_ =
+      ::starboard::android::shared::GetMaxAudioOutputChannels();
+
+  LoadCodecInfos_Locked();
+
+  is_initialized_ = true;
+}
+
+void MediaCapabilitiesCache::LoadCodecInfos_Locked() {
+  SB_DCHECK(audio_codec_capabilities_map_.empty());
+  SB_DCHECK(video_codec_capabilities_map_.empty());
+  mutex_.DCheckAcquired();
+
+  JniEnvExt* env = JniEnvExt::Get();
+  jobjectArray j_codec_infos =
+      static_cast<jobjectArray>(env->CallStaticObjectMethodOrAbort(
+          "dev/cobalt/media/MediaCodecUtil", "getAllCodecCapabilityInfos",
+          "()[Ldev/cobalt/media/MediaCodecUtil$CodecCapabilityInfo;"));
+  jsize length = env->GetArrayLength(j_codec_infos);
+  // Note: Codec infos are sorted by the framework such that the best
+  // decoders come first.
+  // This order is maintained in the cache.
+  for (int i = 0; i < length; i++) {
+    jobject j_codec_info = env->GetObjectArrayElementOrAbort(j_codec_infos, i);
+
+    std::string mime_type = env->GetStringStandardUTFOrAbort(
+        env->GetStringFieldOrAbort(j_codec_info, "mimeType"));
+
+    // Convert the mime type to lower case.
+    ConvertStringToLowerCase(&mime_type);
+
+    jobject j_audio_capabilities = env->GetObjectFieldOrAbort(
+        j_codec_info, "audioCapabilities",
+        "Landroid/media/MediaCodecInfo$AudioCapabilities;");
+    if (j_audio_capabilities) {
+      // Found an audio decoder.
+      std::unique_ptr<AudioCodecCapability> audio_codec_capabilities(
+          new AudioCodecCapability(env, j_codec_info, j_audio_capabilities));
+      audio_codec_capabilities_map_[mime_type].push_back(
+          std::move(audio_codec_capabilities));
+      continue;
+    }
+    jobject j_video_capabilities = env->GetObjectFieldOrAbort(
+        j_codec_info, "videoCapabilities",
+        "Landroid/media/MediaCodecInfo$VideoCapabilities;");
+    if (j_video_capabilities) {
+      // Found a video decoder.
+      std::unique_ptr<VideoCodecCapability> video_codec_capabilities(
+          new VideoCodecCapability(env, j_codec_info, j_video_capabilities));
+      video_codec_capabilities_map_[mime_type].push_back(
+          std::move(video_codec_capabilities));
+    }
+  }
+}
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h
new file mode 100644
index 0000000..46da91a
--- /dev/null
+++ b/starboard/android/shared/media_capabilities_cache.h
@@ -0,0 +1,197 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_
+#define STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_
+
+#include <jni.h>
+#include <atomic>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/common/mutex.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// TODO: encapsulate a common Range class.
+struct Range {
+  Range() : minimum(0), maximum(0) {}
+  Range(int min, int max) : minimum(min), maximum(max) {}
+  int minimum;
+  int maximum;
+
+  bool Contains(int val) const { return val >= minimum && val <= maximum; }
+};
+
+class CodecCapability {
+ public:
+  CodecCapability(JniEnvExt* env, jobject j_codec_info);
+  virtual ~CodecCapability() {}
+
+  const std::string& name() const { return name_; }
+  bool is_secure_required() const { return is_secure_required_; }
+  bool is_secure_supported() const { return is_secure_supported_; }
+  bool is_tunnel_mode_required() const { return is_tunnel_mode_required_; }
+  bool is_tunnel_mode_supported() const { return is_tunnel_mode_supported_; }
+
+ private:
+  CodecCapability(const CodecCapability&) = delete;
+  CodecCapability& operator=(const CodecCapability&) = delete;
+
+  std::string name_;
+  bool is_secure_required_;
+  bool is_secure_supported_;
+  bool is_tunnel_mode_required_;
+  bool is_tunnel_mode_supported_;
+};
+
+class AudioCodecCapability : public CodecCapability {
+ public:
+  AudioCodecCapability(JniEnvExt* env,
+                       jobject j_codec_info,
+                       jobject j_audio_capabilities);
+  ~AudioCodecCapability() override {}
+
+  bool IsBitrateSupported(int bitrate) const;
+
+ private:
+  AudioCodecCapability(const AudioCodecCapability&) = delete;
+  AudioCodecCapability& operator=(const AudioCodecCapability&) = delete;
+
+  Range supported_bitrates_;
+};
+
+class VideoCodecCapability : public CodecCapability {
+ public:
+  VideoCodecCapability(JniEnvExt* env,
+                       jobject j_codec_info,
+                       jobject j_video_capabilities);
+  ~VideoCodecCapability() override;
+
+  bool is_software_decoder() const { return is_software_decoder_; }
+  bool is_hdr_capable() const { return is_hdr_capable_; }
+
+  bool IsBitrateSupported(int bitrate) const;
+  // VideoCodecCapability caches java object MediaCodecInfo.VideoCapabilities.
+  // If improved support check is used,
+  // VideoCapabilities.areSizeAndRateSupported() or
+  // VideoCapabilities.isSizeSupported() will be used to check the
+  // supportability.
+  bool AreResolutionAndRateSupported(bool force_improved_support_check,
+                                     int frame_width,
+                                     int frame_height,
+                                     int fps);
+
+ private:
+  VideoCodecCapability(const VideoCodecCapability&) = delete;
+  VideoCodecCapability& operator=(const VideoCodecCapability&) = delete;
+
+  bool is_software_decoder_;
+  bool is_hdr_capable_;
+  jobject j_video_capabilities_;
+  Range supported_widths_;
+  Range supported_heights_;
+  Range supported_bitrates_;
+  Range supported_frame_rates_;
+};
+
+class MediaCapabilitiesCache {
+ public:
+  static MediaCapabilitiesCache* GetInstance();
+
+  bool IsWidevineSupported();
+  bool IsCbcsSchemeSupported();
+
+  bool IsHDRTransferCharacteristicsSupported(SbMediaTransferId transfer_id);
+
+  bool IsPassthroughSupported(SbMediaAudioCodec codec);
+
+  int GetMaxAudioOutputChannels();
+
+  bool HasAudioDecoderFor(const std::string& mime_type,
+                          int bitrate,
+                          bool must_support_tunnel_mode);
+
+  bool HasVideoDecoderFor(const std::string& mime_type,
+                          bool must_support_secure,
+                          bool must_support_hdr,
+                          bool must_support_tunnel_mode,
+                          bool force_improved_support_check,
+                          int frame_width,
+                          int frame_height,
+                          int bitrate,
+                          int fps);
+
+  std::string FindAudioDecoder(const std::string& mime_type,
+                               int bitrate,
+                               bool must_support_tunnel_mode);
+
+  std::string FindVideoDecoder(const std::string& mime_type,
+                               bool must_support_secure,
+                               bool must_support_hdr,
+                               bool require_software_codec,
+                               bool must_support_tunnel_mode,
+                               bool force_improved_support_check,
+                               int frame_width,
+                               int frame_height,
+                               int bitrate,
+                               int fps);
+
+  bool IsEnabled() const { return is_enabled_; }
+  void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
+  void ClearCache();
+
+ private:
+  MediaCapabilitiesCache() {}
+  ~MediaCapabilitiesCache() {}
+
+  MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete;
+  MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete;
+
+  void LazyInitialize_Locked();
+  void LoadCodecInfos_Locked();
+
+  Mutex mutex_;
+
+  std::set<SbMediaTransferId> supported_transfer_ids_;
+  std::map<SbMediaAudioCodec, bool> passthrough_supportabilities_;
+
+  typedef std::vector<std::unique_ptr<AudioCodecCapability>>
+      AudioCodecCapabilities;
+  typedef std::vector<std::unique_ptr<VideoCodecCapability>>
+      VideoCodecCapabilities;
+
+  std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_;
+  std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_;
+
+  std::atomic_bool is_enabled_{false};
+  bool is_initialized_ = false;
+  bool is_widevine_supported_ = false;
+  bool is_cbcs_supported_ = false;
+  int max_audio_output_channels_ = -1;
+};
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+#endif  // STARBOARD_ANDROID_SHARED_MEDIA_CAPABILITIES_CACHE_H_
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc
index d663f08..2e2cd1a 100644
--- a/starboard/android/shared/media_codec_bridge.cc
+++ b/starboard/android/shared/media_codec_bridge.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/android/shared/media_codec_bridge.h"
 
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/common/string.h"
 
 namespace starboard {
@@ -163,8 +164,20 @@
   const char* mime =
       SupportedAudioCodecToMimeType(audio_codec, &is_passthrough);
   if (!mime) {
+    SB_LOG(ERROR) << "Unsupported codec " << audio_codec << ".";
     return scoped_ptr<MediaCodecBridge>(NULL);
   }
+
+  std::string decoder_name =
+      MediaCapabilitiesCache::GetInstance()->FindAudioDecoder(
+          mime, 0, /* bitrate */
+          false /* must_support_tunnel_mode */);
+
+  if (decoder_name.empty()) {
+    SB_LOG(ERROR) << "Failed to find decoder for " << audio_codec << ".";
+    return scoped_ptr<MediaCodecBridge>(NULL);
+  }
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jbyteArray> configuration_data;
   if (audio_codec == kSbMediaAudioCodecOpus &&
@@ -174,18 +187,21 @@
         audio_sample_info.audio_specific_config_size));
   }
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+  ScopedLocalJavaRef<jstring> j_decoder_name(
+      env->NewStringStandardUTFOrAbort(decoder_name.c_str()));
   scoped_ptr<MediaCodecBridge> native_media_codec_bridge(
       new MediaCodecBridge(handler));
   jobject j_media_codec_bridge = env->CallStaticObjectMethodOrAbort(
       "dev/cobalt/media/MediaCodecBridge", "createAudioMediaCodecBridge",
-      "(JLjava/lang/String;IILandroid/media/MediaCrypto;[B)Ldev/cobalt/media/"
-      "MediaCodecBridge;",
+      "(JLjava/lang/String;Ljava/lang/String;IILandroid/media/MediaCrypto;"
+      "[B)Ldev/cobalt/media/MediaCodecBridge;",
       reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(),
-      audio_sample_info.samples_per_second,
+      j_decoder_name.Get(), audio_sample_info.samples_per_second,
       audio_sample_info.number_of_channels, j_media_crypto,
       configuration_data.Get());
 
   if (!j_media_codec_bridge) {
+    SB_LOG(ERROR) << "Failed to create codec bridge for " << audio_codec << ".";
     return scoped_ptr<MediaCodecBridge>(NULL);
   }
 
@@ -216,8 +232,48 @@
     *error_message = FormatString("Unsupported mime for codec %d", video_codec);
     return scoped_ptr<MediaCodecBridge>(NULL);
   }
+
+  const bool must_support_secure = !!j_media_crypto;
+  const bool must_support_hdr = color_metadata;
+  const bool must_support_tunnel_mode = tunnel_mode_audio_session_id != -1;
+  // On first pass, try to find a decoder with HDR if the color info is
+  // non-null.
+  std::string decoder_name =
+      MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
+          mime, must_support_secure,    /* must_support_secure */
+          must_support_hdr,             /* must_support_hdr */
+          require_software_codec,       /* is_software_codec */
+          must_support_tunnel_mode,     /* must_support_tunnel_mode */
+          force_improved_support_check, /* force_improved_support_check */
+          0,                            /* frame_width */
+          0,                            /* frame_height */
+          0,                            /* bitrate */
+          0 /* fps */);
+  if (decoder_name.empty() && color_metadata) {
+    // On second pass, forget HDR.
+    decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
+        mime, must_support_secure,    /* must_support_secure */
+        false,                        /* must_support_hdr */
+        require_software_codec,       /* is_software_codec */
+        must_support_tunnel_mode,     /* must_support_tunnel_mode */
+        force_improved_support_check, /* force_improved_support_check */
+        0,                            /* frame_width */
+        0,                            /* frame_height */
+        0,                            /* bitrate */
+        0 /* fps */);
+  }
+
+  if (decoder_name.empty()) {
+    *error_message =
+        FormatString("Failed to find decoder: %s, mustSupportSecure: %d.", mime,
+                     !!j_media_crypto);
+    return scoped_ptr<MediaCodecBridge>(NULL);
+  }
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+  ScopedLocalJavaRef<jstring> j_decoder_name(
+      env->NewStringStandardUTFOrAbort(decoder_name.c_str()));
 
   ScopedLocalJavaRef<jobject> j_color_info(nullptr);
   if (color_metadata) {
@@ -257,16 +313,16 @@
       new MediaCodecBridge(handler));
   env->CallStaticVoidMethodOrAbort(
       "dev/cobalt/media/MediaCodecBridge", "createVideoMediaCodecBridge",
-      "(JLjava/lang/String;ZZZIIILandroid/view/Surface;"
+      "(JLjava/lang/String;Ljava/lang/String;IIILandroid/view/Surface;"
       "Landroid/media/MediaCrypto;"
       "Ldev/cobalt/media/MediaCodecBridge$ColorInfo;"
       "I"
       "Ldev/cobalt/media/MediaCodecBridge$CreateMediaCodecBridgeResult;)"
       "V",
       reinterpret_cast<jlong>(native_media_codec_bridge.get()), j_mime.Get(),
-      !!j_media_crypto, require_software_codec, force_improved_support_check,
-      width, height, fps, j_surface, j_media_crypto, j_color_info.Get(),
-      tunnel_mode_audio_session_id, j_create_media_codec_bridge_result.Get());
+      j_decoder_name.Get(), width, height, fps, j_surface, j_media_crypto,
+      j_color_info.Get(), tunnel_mode_audio_session_id,
+      j_create_media_codec_bridge_result.Get());
 
   jobject j_media_codec_bridge = env->CallObjectMethodOrAbort(
       j_create_media_codec_bridge_result.Get(), "mediaCodecBridge",
diff --git a/starboard/android/shared/media_get_audio_configuration.cc b/starboard/android/shared/media_get_audio_configuration.cc
index ef59d08..bd70438 100644
--- a/starboard/android/shared/media_get_audio_configuration.cc
+++ b/starboard/android/shared/media_get_audio_configuration.cc
@@ -14,11 +14,9 @@
 
 #include "starboard/media.h"
 
-#include "starboard/android/shared/jni_env_ext.h"
-#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_capabilities_cache.h"
 
-using starboard::android::shared::JniEnvExt;
-using starboard::android::shared::ScopedLocalJavaRef;
+using starboard::android::shared::MediaCapabilitiesCache;
 
 bool SbMediaGetAudioConfiguration(
     int output_index,
@@ -32,12 +30,8 @@
   out_configuration->latency = 0;
   out_configuration->coding_type = kSbMediaAudioCodingTypePcm;
 
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jobject> j_audio_output_manager(
-      env->CallStarboardObjectMethodOrAbort(
-          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
-  int channels = static_cast<int>(env->CallIntMethodOrAbort(
-      j_audio_output_manager.Get(), "getMaxChannels", "()I"));
+  int channels =
+      MediaCapabilitiesCache::GetInstance()->GetMaxAudioOutputChannels();
   if (channels < 2) {
     SB_DLOG(WARNING)
         << "The supported channels from output device is smaller than 2. "
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc
index d9efe42..46b73e5 100644
--- a/starboard/android/shared/media_is_audio_supported.cc
+++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -15,20 +15,19 @@
 #include "starboard/shared/starboard/media/media_support_internal.h"
 
 #include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/android/shared/media_common.h"
 #include "starboard/audio_sink.h"
 #include "starboard/configuration.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
-#include "starboard/shared/starboard/media/mime_type.h"
 
-using starboard::android::shared::JniEnvExt;
-using starboard::android::shared::ScopedLocalJavaRef;
+using starboard::android::shared::MediaCapabilitiesCache;
 using starboard::android::shared::SupportedAudioCodecToMimeType;
 using starboard::shared::starboard::media::MimeType;
 
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int64_t bitrate) {
   if (bitrate >= kSbMediaMaxAudioBitrateInBitsPerSecond) {
     return false;
@@ -41,28 +40,41 @@
     return false;
   }
 
-  MimeType mime_type(content_type);
-  if (strlen(content_type) > 0) {
+  bool enable_tunnel_mode = false;
+  bool enable_audio_passthrough = true;
+  if (mime_type) {
+    if (!mime_type->is_valid()) {
+      return false;
+    }
     // Allows for disabling the use of the AudioDeviceCallback API to detect
     // when audio peripherals are connected. Enabled by default.
     // (https://developer.android.com/reference/android/media/AudioDeviceCallback)
-    mime_type.RegisterBoolParameter("enableaudiodevicecallback");
+    if (!mime_type->ValidateBoolParameter("enableaudiodevicecallback")) {
+      return false;
+    }
+
     // Allows for enabling tunneled playback. Disabled by default.
     // (https://source.android.com/devices/tv/multimedia-tunneling)
-    mime_type.RegisterBoolParameter("tunnelmode");
+    if (!mime_type->ValidateBoolParameter("tunnelmode")) {
+      return false;
+    }
+    enable_tunnel_mode = mime_type->GetParamBoolValue("tunnelmode", false);
+
     // Enables audio passthrough if the codec supports it.
-    mime_type.RegisterBoolParameter("audiopassthrough");
+    if (!mime_type->ValidateBoolParameter("audiopassthrough")) {
+      return false;
+    }
+    enable_audio_passthrough =
+        mime_type->GetParamBoolValue("audiopassthrough", true);
+
     // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for
     // non-tunneled playbacks with PCM audio. Enabled by default.
     // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE)
-    mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
-
-    if (!mime_type.is_valid()) {
+    if (!mime_type->ValidateBoolParameter("enablepcmcontenttypemovie")) {
       return false;
     }
   }
 
-  bool enable_tunnel_mode = mime_type.GetParamBoolValue("tunnelmode", false);
   if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported(
                                 kSbMediaAudioSampleTypeInt16Deprecated)) {
     SB_LOG(WARNING)
@@ -77,13 +89,10 @@
     return true;
   }
 
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
-  auto media_codec_supported =
-      env->CallStaticBooleanMethodOrAbort(
-          "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor",
-          "(Ljava/lang/String;IZ)Z", j_mime.Get(), static_cast<jint>(bitrate),
-          enable_tunnel_mode) == JNI_TRUE;
+  bool media_codec_supported =
+      MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor(
+          mime, bitrate, enable_tunnel_mode);
+
   if (!media_codec_supported) {
     return false;
   }
@@ -92,29 +101,12 @@
     return true;
   }
 
-  if (!mime_type.GetParamBoolValue("audiopassthrough", true)) {
+  if (!enable_audio_passthrough) {
     SB_LOG(INFO) << "Passthrough codec is rejected because passthrough is "
                     "disabled through mime param.";
     return false;
   }
 
-  SbMediaAudioCodingType coding_type;
-  switch (audio_codec) {
-    case kSbMediaAudioCodecAc3:
-      coding_type = kSbMediaAudioCodingTypeAc3;
-      break;
-    case kSbMediaAudioCodecEac3:
-      coding_type = kSbMediaAudioCodingTypeDolbyDigitalPlus;
-      break;
-    default:
-      return false;
-  }
-  int encoding =
-      ::starboard::android::shared::GetAudioFormatSampleType(coding_type);
-  ScopedLocalJavaRef<jobject> j_audio_output_manager(
-      env->CallStarboardObjectMethodOrAbort(
-          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
-  return env->CallBooleanMethodOrAbort(j_audio_output_manager.Get(),
-                                       "hasPassthroughSupportFor", "(I)Z",
-                                       encoding) == JNI_TRUE;
+  return MediaCapabilitiesCache::GetInstance()->IsPassthroughSupported(
+      audio_codec);
 }
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc
index f6cfe75..fcd6806 100644
--- a/starboard/android/shared/media_is_supported.cc
+++ b/starboard/android/shared/media_is_supported.cc
@@ -16,7 +16,7 @@
 
 #include "starboard/shared/starboard/media/media_support_internal.h"
 
-#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/android/shared/media_common.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/media/mime_type.h"
@@ -26,15 +26,15 @@
                         SbMediaAudioCodec audio_codec,
                         const char* key_system) {
   using starboard::android::shared::IsWidevineL1;
-  using starboard::android::shared::JniEnvExt;
+  using starboard::android::shared::MediaCapabilitiesCache;
   using starboard::shared::starboard::media::MimeType;
 
   // It is possible that the |key_system| comes with extra attributes, like
   // `com.widevine.alpha; encryptionscheme="cenc"`. We prepend "key_system/"
   // to it, so it can be parsed by MimeType.
   MimeType mime_type(std::string("key_system/") + key_system);
-  mime_type.RegisterStringParameter("encryptionscheme", "cenc|cbcs|cbcs-1-9");
-  if (!mime_type.is_valid()) {
+  if (!mime_type.is_valid() || !mime_type.ValidateStringParameter(
+                                   "encryptionscheme", "cenc|cbcs|cbcs-1-9")) {
     return false;
   }
 
@@ -51,11 +51,16 @@
   if (!IsWidevineL1(key_system_type)) {
     return false;
   }
+
+  if (!MediaCapabilitiesCache::GetInstance()->IsWidevineSupported()) {
+    return false;
+  }
+
   std::string encryption_scheme =
       mime_type.GetParamStringValue("encryptionscheme", "");
-  bool uses_cbcs =
-      encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9";
-  return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
-             "dev/cobalt/media/MediaDrmBridge",
-             "isWidevineCryptoSchemeSupported", "(Z)Z", uses_cbcs) == JNI_TRUE;
+  if (encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9") {
+    return MediaCapabilitiesCache::GetInstance()->IsCbcsSchemeSupported();
+  }
+
+  return true;
 }
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc
index ff0acdf..1f5c851 100644
--- a/starboard/android/shared/media_is_video_supported.cc
+++ b/starboard/android/shared/media_is_video_supported.cc
@@ -14,47 +14,19 @@
 
 #include "starboard/shared/starboard/media/media_support_internal.h"
 
-#include "starboard/android/shared/jni_env_ext.h"
-#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/android/shared/media_common.h"
 #include "starboard/configuration.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/media/media_util.h"
-#include "starboard/shared/starboard/media/mime_type.h"
 
-using starboard::android::shared::JniEnvExt;
-using starboard::android::shared::ScopedLocalJavaRef;
+using starboard::android::shared::MediaCapabilitiesCache;
 using starboard::android::shared::SupportedVideoCodecToMimeType;
 using starboard::shared::starboard::media::IsSDRVideo;
 using starboard::shared::starboard::media::MimeType;
 
-namespace {
-
-// https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10
-const jint HDR_TYPE_DOLBY_VISION = 1;
-const jint HDR_TYPE_HDR10 = 2;
-const jint HDR_TYPE_HLG = 3;
-
-bool IsHDRTransferCharacteristicsSupported(SbMediaTransferId transfer_id) {
-  jint hdr_type;
-  if (transfer_id == kSbMediaTransferIdSmpteSt2084) {
-    hdr_type = HDR_TYPE_HDR10;
-  } else if (transfer_id == kSbMediaTransferIdAribStdB67) {
-    hdr_type = HDR_TYPE_HLG;
-  } else {
-    // No other transfer functions are supported, see
-    // https://source.android.com/devices/tech/display/hdr.
-    return false;
-  }
-
-  return JniEnvExt::Get()->CallStarboardBooleanMethodOrAbort(
-             "isHdrTypeSupported", "(I)Z", hdr_type) == JNI_TRUE;
-}
-
-}  // namespace
-
 bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int profile,
                              int level,
                              int bit_depth,
@@ -68,36 +40,48 @@
                              bool decode_to_texture_required) {
   const bool must_support_hdr =
       !IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id);
-  if (must_support_hdr && !IsHDRTransferCharacteristicsSupported(transfer_id)) {
+  if (must_support_hdr &&
+      !MediaCapabilitiesCache::GetInstance()
+           ->IsHDRTransferCharacteristicsSupported(transfer_id)) {
     return false;
   }
   // While not necessarily true, for now we assume that all Android devices
   // can play decode-to-texture video just as well as normal video.
 
-  // Check extended parameters for correctness and return false if any invalid
-  // invalid params are found.
-  MimeType mime_type(content_type);
-  if (strlen(content_type) > 0) {
-    // Allows for enabling tunneled playback. Disabled by default.
-    // https://source.android.com/devices/tv/multimedia-tunneling
-    mime_type.RegisterBoolParameter("tunnelmode");
-    // Override endianness on HDR Info header. Defaults to little.
-    mime_type.RegisterStringParameter("hdrinfoendianness", "big|little");
-    // Forces the use of specific Android APIs (isSizeSupported() and
-    // areSizeAndRateSupported()) to determine format support.
-    mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
-    // Override the default decoder cache TTL to the specified value.
-    // The cache will be disabled if the value is non-positive.
-    // TODO(b/227356434): RegisterIntParameter requires API review.
-    // mime_type.RegisterIntParameter("decoder_cache_ttl_ms");
-
-    if (!mime_type.is_valid()) {
+  bool must_support_tunnel_mode = false;
+  bool force_improved_support_check = true;
+  int decoder_cache_ttl_ms = -1;
+  if (mime_type) {
+    if (!mime_type->is_valid()) {
       return false;
     }
+
+    // Allows for enabling tunneled playback. Disabled by default.
+    // https://source.android.com/devices/tv/multimedia-tunneling
+    if (!mime_type->ValidateBoolParameter("tunnelmode")) {
+      return false;
+    }
+    must_support_tunnel_mode =
+        mime_type->GetParamBoolValue("tunnelmode", false);
+
+    // Override endianness on HDR Info header. Defaults to little.
+    if (!mime_type->ValidateStringParameter("hdrinfoendianness",
+                                            "big|little")) {
+      return false;
+    }
+
+    // Forces the use of specific Android APIs (isSizeSupported() and
+    // areSizeAndRateSupported()) to determine format support.
+    if (!mime_type->ValidateBoolParameter("forceimprovedsupportcheck")) {
+      return false;
+    }
+    force_improved_support_check =
+        mime_type->GetParamBoolValue("forceimprovedsupportcheck", true);
+
+    decoder_cache_ttl_ms =
+        mime_type->GetParamIntValue("decoder_cache_ttl_ms", -1);
   }
 
-  bool must_support_tunnel_mode =
-      mime_type.GetParamBoolValue("tunnelmode", false);
   if (must_support_tunnel_mode && decode_to_texture_required) {
     SB_LOG(WARNING) << "Tunnel mode is rejected because output mode decode to "
                        "texture is required but not supported.";
@@ -108,24 +92,14 @@
   if (!mime) {
     return false;
   }
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
 
   // We assume that if a device supports a format for clear playback, it will
   // also support it for encrypted playback. However, some devices require
   // tunneled playback to be encrypted, so we must align the tunnel mode
   // requirement with the secure playback requirement.
   const bool require_secure_playback = must_support_tunnel_mode;
-  const bool force_improved_support_check =
-      mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
-  const int decoder_cache_ttl_ms =
-      mime_type.GetParamIntValue("decoder_cache_ttl_ms", -1);
 
-  return env->CallStaticBooleanMethodOrAbort(
-             "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-             "(Ljava/lang/String;ZZZZIIIII)Z", j_mime.Get(),
-             require_secure_playback, must_support_hdr,
-             must_support_tunnel_mode, force_improved_support_check,
-             decoder_cache_ttl_ms, frame_width, frame_height,
-             static_cast<jint>(bitrate), fps) == JNI_TRUE;
+  return MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
+      mime, require_secure_playback, must_support_hdr, must_support_tunnel_mode,
+      force_improved_support_check, frame_width, frame_height, bitrate, fps);
 }
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index 3b683fb..6d26909 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -24,6 +24,7 @@
 #include "starboard/android/shared/drm_system.h"
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/android/shared/media_common.h"
 #include "starboard/android/shared/video_decoder.h"
 #include "starboard/atomic.h"
@@ -206,25 +207,26 @@
                                                          error_message);
     }
 
-    MimeType audio_mime_type(creation_parameters.audio_mime());
+    bool enable_audio_device_callback = true;
 
     if (strlen(creation_parameters.audio_mime()) > 0) {
-      audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback");
-      audio_mime_type.RegisterBoolParameter("audiopassthrough");
-      if (!audio_mime_type.is_valid()) {
+      MimeType audio_mime_type(creation_parameters.audio_mime());
+      if (!audio_mime_type.is_valid() ||
+          !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") ||
+          !audio_mime_type.ValidateBoolParameter("audiopassthrough")) {
         return scoped_ptr<PlayerComponents>();
       }
-    }
 
-    bool enable_audio_device_callback =
-        audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
-    SB_LOG(INFO) << "AudioDeviceCallback is "
-                 << (enable_audio_device_callback ? "enabled." : "disabled.");
+      enable_audio_device_callback =
+          audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
+      SB_LOG(INFO) << "AudioDeviceCallback is "
+                   << (enable_audio_device_callback ? "enabled." : "disabled.");
 
-    if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) {
-      SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: "
-                      "false. Passthrough is disabled.";
-      return scoped_ptr<PlayerComponents>();
+      if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) {
+        SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: "
+                        "false. Passthrough is disabled.";
+        return scoped_ptr<PlayerComponents>();
+      }
     }
 
     SB_LOG(INFO) << "Creating passthrough components.";
@@ -244,15 +246,20 @@
       constexpr int kTunnelModeAudioSessionId = -1;
       constexpr bool kForceSecurePipelineUnderTunnelMode = false;
 
-      MimeType video_mime_type(creation_parameters.video_mime());
-      video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
-      if (!video_mime_type.is_valid()) {
-        return scoped_ptr<PlayerComponents>();
+      bool force_improved_support_check = true;
+
+      if (strlen(creation_parameters.video_mime()) > 0) {
+        MimeType video_mime_type(creation_parameters.video_mime());
+        if (!video_mime_type.is_valid() ||
+            !video_mime_type.ValidateBoolParameter(
+                "forceimprovedsupportcheck")) {
+          return scoped_ptr<PlayerComponents>();
+        }
+        force_improved_support_check = video_mime_type.GetParamBoolValue(
+            "forceimprovedsupportcheck", true);
+        SB_LOG_IF(INFO, !force_improved_support_check)
+            << "Improved support check is disabled for queries under 4K.";
       }
-      const bool force_improved_support_check =
-          video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
-      SB_LOG_IF(INFO, !force_improved_support_check)
-          << "Improved support check is disabled for queries under 4K.";
 
       scoped_ptr<VideoDecoder> video_decoder =
           CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId,
@@ -292,13 +299,11 @@
             ? creation_parameters.audio_mime()
             : "";
     MimeType audio_mime_type(audio_mime);
-    if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone &&
-        strlen(creation_parameters.audio_mime()) > 0) {
-      audio_mime_type.RegisterBoolParameter("tunnelmode");
-      audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback");
-      audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
-
-      if (!audio_mime_type.is_valid()) {
+    if (strlen(audio_mime) > 0) {
+      if (!audio_mime_type.is_valid() ||
+          !audio_mime_type.ValidateBoolParameter("tunnelmode") ||
+          !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") ||
+          !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) {
         *error_message =
             "Invalid audio MIME: '" + std::string(audio_mime) + "'";
         return false;
@@ -310,12 +315,10 @@
             ? creation_parameters.video_mime()
             : "";
     MimeType video_mime_type(video_mime);
-    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone &&
-        strlen(creation_parameters.video_mime()) > 0) {
-      video_mime_type.RegisterBoolParameter("tunnelmode");
-      video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
-
-      if (!video_mime_type.is_valid()) {
+    if (strlen(video_mime) > 0) {
+      if (!video_mime_type.is_valid() ||
+          !video_mime_type.ValidateBoolParameter("tunnelmode") ||
+          !video_mime_type.ValidateBoolParameter("forceimprovedsupportcheck")) {
         *error_message =
             "Invalid video MIME: '" + std::string(video_mime) + "'";
         return false;
@@ -496,18 +499,18 @@
       bool force_secure_pipeline_under_tunnel_mode,
       bool force_improved_support_check,
       std::string* error_message) {
-    // Use mime param to determine endianness of HDR metadata. If param is
-    // missing or invalid it defaults to Little Endian.
-    MimeType video_mime_type(creation_parameters.video_mime());
-
+    bool force_big_endian_hdr_metadata = false;
     if (strlen(creation_parameters.video_mime()) > 0) {
-      video_mime_type.RegisterStringParameter("hdrinfoendianness",
+      // Use mime param to determine endianness of HDR metadata. If param is
+      // missing or invalid it defaults to Little Endian.
+      MimeType video_mime_type(creation_parameters.video_mime());
+      video_mime_type.ValidateStringParameter("hdrinfoendianness",
                                               "big|little");
+      const std::string& hdr_info_endianness =
+          video_mime_type.GetParamStringValue("hdrinfoendianness",
+                                              /*default=*/"little");
+      force_big_endian_hdr_metadata = hdr_info_endianness == "big";
     }
-    const std::string& hdr_info_endianness =
-        video_mime_type.GetParamStringValue("hdrinfoendianness",
-                                            /*default=*/"little");
-    bool force_big_endian_hdr_metadata = hdr_info_endianness == "big";
 
     scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder(
         creation_parameters.video_codec(),
@@ -563,29 +566,24 @@
                    << creation_parameters.video_codec() << " is not supported.";
       return false;
     }
-    JniEnvExt* env = JniEnvExt::Get();
-    ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
     DrmSystem* drm_system_ptr =
         static_cast<DrmSystem*>(creation_parameters.drm_system());
     jobject j_media_crypto =
         drm_system_ptr ? drm_system_ptr->GetMediaCrypto() : NULL;
 
     bool is_encrypted = !!j_media_crypto;
-    if (env->CallStaticBooleanMethodOrAbort(
-            "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-            "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), is_encrypted, false,
-            true, force_improved_support_check, 0, 0, 0, 0) == JNI_TRUE) {
+    if (MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
+            mime, is_encrypted, false, true, force_improved_support_check, 0, 0,
+            0, 0)) {
       return true;
     }
 
     if (kForceSecurePipelineInTunnelModeWhenRequired && !is_encrypted) {
       const bool kIsEncrypted = true;
       auto support_tunnel_mode_under_secure_pipeline =
-          env->CallStaticBooleanMethodOrAbort(
-              "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
-              "(Ljava/lang/String;ZZZZIIII)Z", j_mime.Get(), kIsEncrypted,
-              false, true, force_improved_support_check, 0, 0, 0,
-              0) == JNI_TRUE;
+          MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
+              mime, kIsEncrypted, false, true, force_improved_support_check, 0,
+              0, 0, 0);
       if (support_tunnel_mode_under_secure_pipeline) {
         *force_secure_pipeline_under_tunnel_mode = true;
         return true;
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn
index 4a9f9a6..07974b0 100644
--- a/starboard/elf_loader/BUILD.gn
+++ b/starboard/elf_loader/BUILD.gn
@@ -108,9 +108,6 @@
     "//starboard",
   ]
 
-  # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions.
-  deps += cobalt_platform_dependencies
-
   if (!sb_is_evergreen_compatible) {
     deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
   }
@@ -173,9 +170,6 @@
       ":elf_loader",
     ]
 
-    # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions.
-    deps += cobalt_platform_dependencies
-
     data_deps = [ ":copy_elf_loader_testdata" ]
   }
 }
diff --git a/starboard/evergreen/testing/performance_tests/baseline_update_test.sh b/starboard/evergreen/testing/performance_tests/baseline_update_test.sh
deleted file mode 100644
index de3a2f6..0000000
--- a/starboard/evergreen/testing/performance_tests/baseline_update_test.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Unset the previous test's name and runner function.
-unset TEST_NAME
-unset TEST_FILE
-unset -f run_test
-
-TEST_NAME="BaselineUpdatePerformance"
-
-function run_test() {
-  source $(dirname "$0")/performance_tests/run_update_trial.sh
-
-  for i in {1..3}; do
-    run_update_trial "$i";
-    result=$?
-    if [[ "${result}" -ne 0 ]]; then
-      return 1
-    fi
-  done
-
-  return 0
-}
diff --git a/starboard/evergreen/testing/performance_tests/compression_update_test.sh b/starboard/evergreen/testing/performance_tests/compression_update_test.sh
deleted file mode 100644
index 4b1d4ef..0000000
--- a/starboard/evergreen/testing/performance_tests/compression_update_test.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Unset the previous test's name and runner function.
-unset TEST_NAME
-unset TEST_FILE
-unset -f run_test
-
-TEST_NAME="CompressionUpdatePerformance"
-
-function run_test() {
-  source $(dirname "$0")/performance_tests/run_update_trial.sh
-
-  for i in {1..3}; do
-    run_update_trial "$i" "--compress_update" "--loader_use_compression";
-    result=$?
-    if [[ "${result}" -ne 0 ]]; then
-      return 1
-    fi
-  done
-
-  return 0
-}
diff --git a/starboard/evergreen/testing/performance_tests/run_update_trial.sh b/starboard/evergreen/testing/performance_tests/run_update_trial.sh
deleted file mode 100644
index 7188c4a..0000000
--- a/starboard/evergreen/testing/performance_tests/run_update_trial.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Runs an update cycle to generate performance logs.
-#
-# The size of the update is reported by this function directly, while memory
-# usage and shared library load time are logged by the loader app process.
-#
-# Args:
-#   Trial number, extra arguments for the loader app run to install an update,
-#   extra arguments for the loader app run to load the update.
-function run_update_trial() {
-  clear_storage
-
-  log "info" "Running trial ${1}"
-  # Run the loader app once until an update has been installed.
-  cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.0.log" "PingSender::SendPingComplete" "--loader_track_memory=100 ${2}"
-
-  run_command "ls -l \"${STORAGE_DIR}/installation_1/lib\""
-
-  # And run the loader app once more until it loads the installed update. The
-  # memory tracker is configured with a shorter period to capture more data
-  # during loading of the shared library.
-  cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.1.log" "Loading took" "--loader_track_memory=10 ${3}"
-
-  if grep -Eq "content/app/cobalt/lib/" "${LOG_PATH}/${TEST_NAME}.1.log"; then
-    log "error" "The system image was loaded instead of the update, which must have failed"
-    return 1
-  fi
-
-  return 0
-}
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh
index 2349c2e..efb9d0b 100755
--- a/starboard/evergreen/testing/run_all_tests.sh
+++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,8 +21,7 @@
 DIR="$(dirname "${0}")"
 
 AUTH_METHOD="public-key"
-TEST_TYPE="functional"
-while getopts "d:a:t:" o; do
+while getopts "d:a:" o; do
     case "${o}" in
         d)
             DEVICE_ID=${OPTARG}
@@ -30,9 +29,6 @@
         a)
             AUTH_METHOD=${OPTARG}
             ;;
-        t)
-            TEST_TYPE=${OPTARG}
-            ;;
     esac
 done
 shift $((OPTIND-1))
@@ -45,14 +41,8 @@
 source $DIR/setup.sh
 
 # Find all of the test files within the 'test' subdirectory.
-if [[ "${TEST_TYPE}" == "functional" ]]; then
-  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
-elif [[ "${TEST_TYPE}" == "performance" ]]; then
-  TESTS=($(eval "find ${DIR}/performance_tests -maxdepth 1 -name '*_test.sh'"))
-else
-  echo "Only functional and performance tests are supported"
-  exit 1
-fi
+TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
+
 COUNT=0
 RETRIED=()
 FAILED=()
diff --git a/starboard/linux/shared/media_is_audio_supported.cc b/starboard/linux/shared/media_is_audio_supported.cc
index 1780684..bedb0c7 100644
--- a/starboard/linux/shared/media_is_audio_supported.cc
+++ b/starboard/linux/shared/media_is_audio_supported.cc
@@ -19,14 +19,11 @@
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
 
-bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
-                             int64_t bitrate) {
-  if (!content_type) {
-    SB_LOG(WARNING) << "|content_type| cannot be nullptr.";
-    return false;
-  }
+using ::starboard::shared::starboard::media::MimeType;
 
+bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
+                             const MimeType* mime_type,
+                             int64_t bitrate) {
   if (audio_codec == kSbMediaAudioCodecAac) {
     return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
   }
@@ -42,5 +39,14 @@
     }
   }
 
+  if (audio_codec == kSbMediaAudioCodecVorbis) {
+    return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
+  }
+#if SB_API_VERSION >= 14
+  if (audio_codec == kSbMediaAudioCodecMp3) {
+    return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
+  }
+#endif  // SB_API_VERSION >= 14
+
   return false;
 }
diff --git a/starboard/linux/shared/media_is_video_supported.cc b/starboard/linux/shared/media_is_video_supported.cc
index 98ccacd..e932cb1 100644
--- a/starboard/linux/shared/media_is_video_supported.cc
+++ b/starboard/linux/shared/media_is_video_supported.cc
@@ -22,11 +22,12 @@
 #include "starboard/shared/libde265/de265_library_loader.h"
 #include "starboard/shared/starboard/media/media_util.h"
 
-using starboard::shared::de265::is_de265_supported;
-using starboard::shared::starboard::media::IsSDRVideo;
+using ::starboard::shared::de265::is_de265_supported;
+using ::starboard::shared::starboard::media::IsSDRVideo;
+using ::starboard::shared::starboard::media::MimeType;
 
 bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int profile,
                              int level,
                              int bit_depth,
@@ -38,11 +39,6 @@
                              int64_t bitrate,
                              int fps,
                              bool decode_to_texture_required) {
-  if (!content_type) {
-    SB_LOG(WARNING) << "|content_type| cannot be nullptr.";
-    return false;
-  }
-
   if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) {
     if (bit_depth != 10 && bit_depth != 12) {
       return false;
@@ -67,6 +63,7 @@
   return (video_codec == kSbMediaVideoCodecAv1 ||
           video_codec == kSbMediaVideoCodecH264 ||
           (video_codec == kSbMediaVideoCodecH265 && is_de265_supported()) ||
+          (video_codec == kSbMediaVideoCodecVp8) ||
           (video_codec == kSbMediaVideoCodecVp9)) &&
          frame_width <= 1920 && frame_height <= 1080 &&
          bitrate <= kSbMediaMaxVideoBitrateInBitsPerSecond && fps <= 60;
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index 951827f..3ef98fd 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -47,7 +47,6 @@
       "//cobalt/content/fonts:copy_font_data",
       "//starboard/elf_loader",
     ]
-    deps += cobalt_platform_dependencies
   }
 }
 
@@ -68,7 +67,6 @@
         "//cobalt/content/fonts:copy_font_data",
         "//starboard/elf_loader:elf_loader_sys",
       ]
-      deps += cobalt_platform_dependencies
     }
   }
 }
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index beea8ac..af93b99 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -74,6 +74,10 @@
   result = SbMediaCanPlayMimeAndKeySystem(
       "audio/webm; codecs=\"opus\"; channels=2", "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+  // Two codecs
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 }
 
 TEST(SbMediaCanPlayMimeAndKeySystem, Invalid) {
diff --git a/starboard/nplb/media_set_audio_write_duration_test.cc b/starboard/nplb/media_set_audio_write_duration_test.cc
index 237fe58..84ff9b2 100644
--- a/starboard/nplb/media_set_audio_write_duration_test.cc
+++ b/starboard/nplb/media_set_audio_write_duration_test.cc
@@ -31,7 +31,7 @@
 namespace {
 
 using ::starboard::testing::FakeGraphicsContextProvider;
-using shared::starboard::player::video_dmp::VideoDmpReader;
+using ::shared::starboard::player::video_dmp::VideoDmpReader;
 using ::testing::ValuesIn;
 
 const SbTime kDuration = kSbTimeSecond / 2;
@@ -261,8 +261,7 @@
 
     const SbMediaAudioSampleInfo* audio_sample_info =
         &dmp_reader.audio_sample_info();
-    if (SbMediaIsAudioSupported(dmp_reader.audio_codec(),
-                                "",  // content_type
+    if (SbMediaIsAudioSupported(dmp_reader.audio_codec(), nullptr,
                                 dmp_reader.audio_bitrate())) {
       test_params.push_back(filename);
     }
diff --git a/starboard/raspi/shared/media_is_video_supported.cc b/starboard/raspi/shared/media_is_video_supported.cc
index 704faea..16a7088 100644
--- a/starboard/raspi/shared/media_is_video_supported.cc
+++ b/starboard/raspi/shared/media_is_video_supported.cc
@@ -19,8 +19,11 @@
 #include "starboard/media.h"
 #include "starboard/shared/starboard/media/media_util.h"
 
+using ::starboard::shared::starboard::media::IsSDRVideo;
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int profile,
                              int level,
                              int bit_depth,
@@ -32,8 +35,6 @@
                              int64_t bitrate,
                              int fps,
                              bool decode_to_texture_required) {
-  using starboard::shared::starboard::media::IsSDRVideo;
-
   if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) {
     return false;
   }
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
index 34d66fd..50cb9bd 100644
--- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -46,6 +46,12 @@
       return kSbHasAc3Audio ? AV_CODEC_ID_EAC3 : AV_CODEC_ID_NONE;
     case kSbMediaAudioCodecOpus:
       return AV_CODEC_ID_OPUS;
+    case kSbMediaAudioCodecVorbis:
+      return AV_CODEC_ID_VORBIS;
+#if SB_API_VERSION >= 14
+    case kSbMediaAudioCodecMp3:
+      return AV_CODEC_ID_MP3;
+#endif  // SB_API_VERSION >= 14
     default:
       return AV_CODEC_ID_NONE;
   }
@@ -272,7 +278,8 @@
   codec_context_->extradata = NULL;
   codec_context_->extradata_size = 0;
 
-  if (codec_context_->codec_id == AV_CODEC_ID_OPUS &&
+  if ((codec_context_->codec_id == AV_CODEC_ID_OPUS ||
+       codec_context_->codec_id == AV_CODEC_ID_VORBIS) &&
       audio_sample_info_.audio_specific_config_size > 0) {
     // AV_INPUT_BUFFER_PADDING_SIZE is not defined in ancient avcodec.h.  Use a
     // large enough padding here explicitly.
diff --git a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
index 1a6678e..d8605a6 100644
--- a/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_video_decoder_impl.cc
@@ -74,6 +74,22 @@
 }
 #endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
 
+AVCodecID GetFfmpegCodecIdByMediaCodec(SbMediaVideoCodec video_codec) {
+  // Note: although SbMediaVideoCodec values exist for them, MPEG2VIDEO, THEORA,
+  // and VC1 are not included here since we do not officially support them.
+  // Also, note that VP9 and HEVC use different decoders (not FFmpeg).
+  switch (video_codec) {
+    case kSbMediaVideoCodecH264:
+      return AV_CODEC_ID_H264;
+    case kSbMediaVideoCodecVp8:
+      return AV_CODEC_ID_VP8;
+    default:
+      SB_DLOG(WARNING) << "FFmpeg decoder does not support SbMediaVideoCodec "
+                       << video_codec;
+  }
+  return AV_CODEC_ID_NONE;
+}
+
 const bool g_registered =
     FFMPEGDispatch::RegisterSpecialization(FFMPEG,
                                            LIBAVCODEC_VERSION_MAJOR,
@@ -320,7 +336,7 @@
   }
 
   codec_context_->codec_type = AVMEDIA_TYPE_VIDEO;
-  codec_context_->codec_id = AV_CODEC_ID_H264;
+  codec_context_->codec_id = GetFfmpegCodecIdByMediaCodec(video_codec_);
   codec_context_->profile = FF_PROFILE_UNKNOWN;
   codec_context_->coded_width = 0;
   codec_context_->coded_height = 0;
diff --git a/starboard/shared/starboard/media/BUILD.gn b/starboard/shared/starboard/media/BUILD.gn
index 1e7355b..28ba606 100644
--- a/starboard/shared/starboard/media/BUILD.gn
+++ b/starboard/shared/starboard/media/BUILD.gn
@@ -17,10 +17,20 @@
   sources = [
     "//starboard/shared/starboard/media/avc_util.cc",
     "//starboard/shared/starboard/media/avc_util.h",
+    "//starboard/shared/starboard/media/bitrate_supportability_cache.cc",
+    "//starboard/shared/starboard/media/bitrate_supportability_cache.h",
     "//starboard/shared/starboard/media/codec_util.cc",
     "//starboard/shared/starboard/media/codec_util.h",
+    "//starboard/shared/starboard/media/key_system_supportability_cache.cc",
+    "//starboard/shared/starboard/media/key_system_supportability_cache.h",
     "//starboard/shared/starboard/media/media_util.cc",
     "//starboard/shared/starboard/media/media_util.h",
+    "//starboard/shared/starboard/media/mime_supportability_cache.cc",
+    "//starboard/shared/starboard/media/mime_supportability_cache.h",
+    "//starboard/shared/starboard/media/mime_util.cc",
+    "//starboard/shared/starboard/media/mime_util.h",
+    "//starboard/shared/starboard/media/parsed_mime_info.cc",
+    "//starboard/shared/starboard/media/parsed_mime_info.h",
     "//starboard/shared/starboard/media/video_capabilities.cc",
     "//starboard/shared/starboard/media/video_capabilities.h",
     "//starboard/shared/starboard/media/vp9_util.cc",
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.cc b/starboard/shared/starboard/media/bitrate_supportability_cache.cc
new file mode 100644
index 0000000..01c2917
--- /dev/null
+++ b/starboard/shared/starboard/media/bitrate_supportability_cache.cc
@@ -0,0 +1,150 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/bitrate_supportability_cache.h"
+
+#include <map>
+
+#include "starboard/common/log.h"
+#include "starboard/common/mutex.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/once.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+template <typename T>
+class BitrateSupportabilityContainer {
+ public:
+  Supportability GetSupportability(T codec, int bitrate) {
+    // Reject invalid parameters.
+    if (bitrate < 0) {
+      return kSupportabilityNotSupported;
+    }
+    // Bitrate 0 is always supported.
+    if (bitrate == 0) {
+      return kSupportabilitySupported;
+    }
+
+    ScopedLock scoped_lock(mutex_);
+    auto iter = supported_bitrate_ranges_.find(codec);
+    if (iter == supported_bitrate_ranges_.end()) {
+      return kSupportabilityUnknown;
+    }
+    const Range& range = iter->second;
+    if (bitrate < range.minimum || bitrate > range.maximum) {
+      return kSupportabilityNotSupported;
+    }
+    return kSupportabilitySupported;
+  }
+
+  void SetSupportedBitrate(T codec, int min, int max) {
+    SB_DCHECK(min >= 0 && max >= min);
+
+    ScopedLock scoped_lock(mutex_);
+    supported_bitrate_ranges_[codec] = Range(min, max);
+  }
+  void ClearContainer() {
+    ScopedLock scoped_lock(mutex_);
+    supported_bitrate_ranges_.clear();
+  }
+
+ private:
+  struct Range {
+    Range() : minimum(0), maximum(0) {}
+    Range(int min, int max) : minimum(min), maximum(max) {}
+    int minimum;
+    int maximum;
+  };
+
+  Mutex mutex_;
+  std::map<T, Range> supported_bitrate_ranges_;
+};
+
+template <typename T>
+SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityContainer<T>, GetContainer);
+
+}  // namespace
+
+// static
+SB_ONCE_INITIALIZE_FUNCTION(BitrateSupportabilityCache,
+                            BitrateSupportabilityCache::GetInstance);
+
+Supportability BitrateSupportabilityCache::GetBitrateSupportability(
+    const ParsedMimeInfo& mime_info) {
+  SB_DCHECK(mime_info.is_valid());
+
+  if (!is_enabled_) {
+    return kSupportabilityUnknown;
+  }
+
+  Supportability audio_supportability = kSupportabilitySupported;
+  if (mime_info.has_audio_info()) {
+    audio_supportability = GetContainer<SbMediaAudioCodec>()->GetSupportability(
+        mime_info.audio_info().codec, mime_info.audio_info().bitrate);
+  }
+
+  Supportability video_supportability = kSupportabilitySupported;
+  if (mime_info.has_video_info()) {
+    video_supportability = GetContainer<SbMediaVideoCodec>()->GetSupportability(
+        mime_info.video_info().codec, mime_info.video_info().bitrate);
+  }
+
+  if (audio_supportability == kSupportabilityNotSupported ||
+      video_supportability == kSupportabilityNotSupported) {
+    return kSupportabilityNotSupported;
+  }
+  if (audio_supportability == kSupportabilityUnknown ||
+      video_supportability == kSupportabilityUnknown) {
+    return kSupportabilityUnknown;
+  }
+  return kSupportabilitySupported;
+}
+
+void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaAudioCodec codec,
+                                                     int min,
+                                                     int max) {
+  SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range.";
+
+  if (!is_enabled_) {
+    return;
+  }
+  GetContainer<SbMediaAudioCodec>()->SetSupportedBitrate(codec, min, max);
+}
+
+void BitrateSupportabilityCache::SetSupportedBitrate(SbMediaVideoCodec codec,
+                                                     int min,
+                                                     int max) {
+  SB_DCHECK(min >= 0 && min <= max) << "Invalid bitrate range.";
+
+  if (!is_enabled_) {
+    return;
+  }
+  GetContainer<SbMediaVideoCodec>()->SetSupportedBitrate(codec, min, max);
+}
+
+void BitrateSupportabilityCache::ClearCache() {
+  GetContainer<SbMediaAudioCodec>()->ClearContainer();
+  GetContainer<SbMediaVideoCodec>()->ClearContainer();
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/bitrate_supportability_cache.h b/starboard/shared/starboard/media/bitrate_supportability_cache.h
new file mode 100644
index 0000000..fc1f553
--- /dev/null
+++ b/starboard/shared/starboard/media/bitrate_supportability_cache.h
@@ -0,0 +1,66 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_
+
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/mime_supportability_cache.h"
+#include "starboard/shared/starboard/media/parsed_mime_info.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+// TODO: add unit tests for BitrateSupportabilityCache
+class BitrateSupportabilityCache {
+ public:
+  static BitrateSupportabilityCache* GetInstance();
+
+  // When cache is not enabled, GetBitrateSupportability() will always return
+  // kSupportabilityUnknown, and SetSupportedBitrate() will do nothing.
+  bool IsEnabled() const { return is_enabled_; }
+  void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
+
+  // Get bitrate supportability.
+  Supportability GetBitrateSupportability(const ParsedMimeInfo& mime_info);
+  // Set supported bitrate range for the |codec|. Note that if supported bitrate
+  // range is not set, MimeSupportabilityCache::GetMimeSupportability() will
+  // always return kSupportabilityUnknown.
+  void SetSupportedBitrate(SbMediaAudioCodec codec, int min, int max);
+  void SetSupportedBitrate(SbMediaVideoCodec codec, int min, int max);
+
+  // Clear all cached supported bitrate ranges.
+  void ClearCache();
+
+ private:
+  // Class can only be instanced via the singleton
+  BitrateSupportabilityCache() {}
+  ~BitrateSupportabilityCache() {}
+
+  BitrateSupportabilityCache(const BitrateSupportabilityCache&) = delete;
+  BitrateSupportabilityCache& operator=(const BitrateSupportabilityCache&) =
+      delete;
+
+  std::atomic_bool is_enabled_{false};
+};
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_BITRATE_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.cc b/starboard/shared/starboard/media/key_system_supportability_cache.cc
new file mode 100644
index 0000000..6ee6532
--- /dev/null
+++ b/starboard/shared/starboard/media/key_system_supportability_cache.cc
@@ -0,0 +1,169 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/key_system_supportability_cache.h"
+
+#include <cstring>
+#include <map>
+#include <string>
+
+#include "starboard/common/log.h"
+#include "starboard/common/mutex.h"
+#include "starboard/media.h"
+#include "starboard/once.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+template <typename T>
+class KeySystemSupportabilityContainer {
+ public:
+  Supportability GetKeySystemSupportability(T codec, const char* key_system) {
+    SB_DCHECK(key_system);
+    SB_DCHECK(strlen(key_system) > 0);
+
+    ScopedLock scoped_lock(mutex_);
+    auto map_iter = key_system_supportabilities_.find(codec);
+    if (map_iter == key_system_supportabilities_.end()) {
+      return kSupportabilityUnknown;
+    }
+    KeySystemToSupportabilityMap& map = map_iter->second;
+    auto supportability_iter = map.find(std::string(key_system));
+    if (supportability_iter == map.end()) {
+      return kSupportabilityUnknown;
+    }
+    return supportability_iter->second;
+  }
+
+  void CacheKeySystemSupportability(T codec,
+                                    const char* key_system,
+                                    Supportability supportability) {
+    SB_DCHECK(key_system);
+    SB_DCHECK(strlen(key_system) > 0);
+    SB_DCHECK(supportability != kSupportabilityUnknown);
+
+    ScopedLock scoped_lock(mutex_);
+    key_system_supportabilities_[codec][key_system] = supportability;
+  }
+
+  void ClearContainer() {
+    ScopedLock scoped_lock(mutex_);
+    key_system_supportabilities_.clear();
+  }
+
+ private:
+  typedef std::map<std::string, Supportability> KeySystemToSupportabilityMap;
+
+  Mutex mutex_;
+  std::map<T, KeySystemToSupportabilityMap> key_system_supportabilities_;
+};
+
+template <typename T>
+SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityContainer<T>, GetContainer);
+
+}  // namespace
+
+// static
+SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityCache,
+                            KeySystemSupportabilityCache::GetInstance);
+
+Supportability KeySystemSupportabilityCache::GetKeySystemSupportability(
+    SbMediaAudioCodec codec,
+    const char* key_system) {
+  SB_DCHECK(key_system);
+
+  // Empty key system is always supported.
+  if (strlen(key_system) == 0) {
+    return kSupportabilitySupported;
+  }
+
+  if (!is_enabled_) {
+    return kSupportabilityUnknown;
+  }
+
+  return GetContainer<SbMediaAudioCodec>()->GetKeySystemSupportability(
+      codec, key_system);
+}
+
+Supportability KeySystemSupportabilityCache::GetKeySystemSupportability(
+    SbMediaVideoCodec codec,
+    const char* key_system) {
+  SB_DCHECK(key_system);
+
+  // Empty key system is always supported.
+  if (strlen(key_system) == 0) {
+    return kSupportabilitySupported;
+  }
+
+  if (!is_enabled_) {
+    return kSupportabilityUnknown;
+  }
+
+  return GetContainer<SbMediaVideoCodec>()->GetKeySystemSupportability(
+      codec, key_system);
+}
+
+void KeySystemSupportabilityCache::CacheKeySystemSupportability(
+    SbMediaAudioCodec codec,
+    const char* key_system,
+    Supportability supportability) {
+  SB_DCHECK(key_system);
+  SB_DCHECK(supportability != kSupportabilityUnknown);
+
+  if (!is_enabled_) {
+    return;
+  }
+
+  if (strlen(key_system) == 0) {
+    SB_LOG(WARNING) << "Rejected empty key system as it's always supported.";
+  }
+
+  GetContainer<SbMediaAudioCodec>()->CacheKeySystemSupportability(
+      codec, key_system, supportability);
+}
+
+void KeySystemSupportabilityCache::CacheKeySystemSupportability(
+    SbMediaVideoCodec codec,
+    const char* key_system,
+    Supportability supportability) {
+  SB_DCHECK(key_system);
+  SB_DCHECK(strlen(key_system) > 0);
+  SB_DCHECK(supportability != kSupportabilityUnknown);
+
+  if (!is_enabled_) {
+    return;
+  }
+
+  if (strlen(key_system) == 0) {
+    SB_LOG(WARNING) << "Rejected empty key system as it's always supported.";
+    return;
+  }
+
+  GetContainer<SbMediaVideoCodec>()->CacheKeySystemSupportability(
+      codec, key_system, supportability);
+}
+
+void KeySystemSupportabilityCache::ClearCache() {
+  GetContainer<SbMediaAudioCodec>()->ClearContainer();
+  GetContainer<SbMediaVideoCodec>()->ClearContainer();
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.h b/starboard/shared/starboard/media/key_system_supportability_cache.h
new file mode 100644
index 0000000..a1f2f68
--- /dev/null
+++ b/starboard/shared/starboard/media/key_system_supportability_cache.h
@@ -0,0 +1,70 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_
+
+#include <atomic>
+
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/mime_supportability_cache.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+// TODO: add unit tests for KeySystemSupportabilityCache
+class KeySystemSupportabilityCache {
+ public:
+  static KeySystemSupportabilityCache* GetInstance();
+
+  // When cache is not enabled, GetKeySystemSupportability() will always return
+  // kSupportabilityUnknown, and CacheKeySystemSupportability() will do nothing.
+  bool IsEnabled() const { return is_enabled_; }
+  void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
+
+  // Get & cache key system supportability.
+  Supportability GetKeySystemSupportability(SbMediaAudioCodec codec,
+                                            const char* key_system);
+  Supportability GetKeySystemSupportability(SbMediaVideoCodec codec,
+                                            const char* key_system);
+  void CacheKeySystemSupportability(SbMediaAudioCodec codec,
+                                    const char* key_system,
+                                    Supportability supportability);
+  void CacheKeySystemSupportability(SbMediaVideoCodec codec,
+                                    const char* key_system,
+                                    Supportability supportability);
+
+  // Clear all cached supportabilities.
+  void ClearCache();
+
+ private:
+  // Class can only be instanced via the singleton
+  KeySystemSupportabilityCache() {}
+  ~KeySystemSupportabilityCache() {}
+
+  KeySystemSupportabilityCache(const KeySystemSupportabilityCache&) = delete;
+  KeySystemSupportabilityCache& operator=(const KeySystemSupportabilityCache&) =
+      delete;
+
+  std::atomic_bool is_enabled_{false};
+};
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
index b3c5723..8e5bae0 100644
--- a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
+++ b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -12,12 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/media.h"
-
 #include "starboard/common/log.h"
-#include "starboard/common/string.h"
-#include "starboard/shared/starboard/media/media_util.h"
-#include "starboard/shared/starboard/media/mime_type.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/media/mime_util.h"
 
 SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime,
                                                   const char* key_system) {
@@ -31,11 +28,6 @@
     return kSbMediaSupportTypeNotSupported;
   }
 
-  starboard::shared::starboard::media::MimeType mime_type(mime);
-  if (!mime_type.is_valid()) {
-    SB_DLOG(WARNING) << mime << " is not a valid mime type";
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  return CanPlayMimeAndKeySystem(mime_type, key_system);
+  return starboard::shared::starboard::media::CanPlayMimeAndKeySystem(
+      mime, key_system);
 }
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc
index 6d2c701..e4689ca 100644
--- a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc
+++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc
@@ -18,8 +18,10 @@
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int64_t bitrate) {
   if (audio_codec == kSbMediaAudioCodecAac) {
     return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc
index fc72934..83dbd14 100644
--- a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc
+++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc
@@ -18,8 +18,10 @@
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int64_t bitrate) {
   return audio_codec == kSbMediaAudioCodecAac &&
          bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_support_internal.h b/starboard/shared/starboard/media/media_support_internal.h
index bf5fe78..6fcc832 100644
--- a/starboard/shared/starboard/media/media_support_internal.h
+++ b/starboard/shared/starboard/media/media_support_internal.h
@@ -18,6 +18,7 @@
 #include "starboard/configuration.h"
 #include "starboard/media.h"
 #include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/mime_type.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -31,9 +32,9 @@
 // the platform to decode any supported input formats.
 //
 // |video_codec|: The |SbMediaVideoCodec| being checked for platform
-//   compatibility.
+//                compatibility.
 // |audio_codec|: The |SbMediaAudioCodec| being checked for platform
-//   compatibility.
+//                compatibility.
 // |key_system|: The key system being checked for platform compatibility.
 SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec,
                                   SbMediaAudioCodec audio_codec,
@@ -46,9 +47,8 @@
 // function returns |false|.
 //
 // |video_codec|: The video codec used in the media content.
-// |content_type|: The full content type passed to the corresponding dom
-//                 interface if there is any.  Otherwise it will be set to "".
-//                 It should never to set to NULL.
+// |mime_type|: The parsed mime type passed to the corresponding interface.
+//              Note that |mime_type| can be NULL.
 // |profile|: The profile in the context of |video_codec|.  It should be set to
 //            -1 when it is unknown or not applicable.
 // |level|: The level in the context of |video_codec|.  It should be set to -1
@@ -75,32 +75,33 @@
 //        it indicates that the fps shouldn't be considered.
 // |decode_to_texture_required|: Whether or not the resulting video frames can
 //                               be decoded and used as textures by the GPU.
-bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
-                             int profile,
-                             int level,
-                             int bit_depth,
-                             SbMediaPrimaryId primary_id,
-                             SbMediaTransferId transfer_id,
-                             SbMediaMatrixId matrix_id,
-                             int frame_width,
-                             int frame_height,
-                             int64_t bitrate,
-                             int fps,
-                             bool decode_to_texture_required);
+bool SbMediaIsVideoSupported(
+    SbMediaVideoCodec video_codec,
+    const starboard::shared::starboard::media::MimeType* mime_type,
+    int profile,
+    int level,
+    int bit_depth,
+    SbMediaPrimaryId primary_id,
+    SbMediaTransferId transfer_id,
+    SbMediaMatrixId matrix_id,
+    int frame_width,
+    int frame_height,
+    int64_t bitrate,
+    int fps,
+    bool decode_to_texture_required);
 
 // Indicates whether this platform supports |audio_codec| at |bitrate|.
 // If |audio_codec| is not supported under any condition, this function
 // returns |false|.
 //
 // |audio_codec|: The media's audio codec (|SbMediaAudioCodec|).
-// |content_type|: The full content type passed to the corresponding dom
-//                 interface if there is any.  Otherwise it will be set to "".
-//                 It should never to set to NULL.
+// |mime_type|: The parsed mime type passed to the corresponding interface.
+//              Note that |mime_type| can be NULL.
 // |bitrate|: The media's bitrate.
-bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
-                             int64_t bitrate);
+bool SbMediaIsAudioSupported(
+    SbMediaAudioCodec audio_codec,
+    const starboard::shared::starboard::media::MimeType* mime_type,
+    int64_t bitrate);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/starboard/shared/starboard/media/media_util.cc b/starboard/shared/starboard/media/media_util.cc
index a5a135c..58bb7bf 100644
--- a/starboard/shared/starboard/media/media_util.cc
+++ b/starboard/shared/starboard/media/media_util.cc
@@ -21,9 +21,7 @@
 #include "starboard/common/media.h"
 #include "starboard/common/string.h"
 #include "starboard/log.h"
-#include "starboard/memory.h"
 #include "starboard/shared/starboard/media/codec_util.h"
-#include "starboard/shared/starboard/media/media_support_internal.h"
 #include "starboard/shared/starboard/media/mime_type.h"
 
 namespace starboard {
@@ -36,165 +34,6 @@
 const int64_t kDefaultBitRate = 0;
 const int64_t kDefaultAudioChannels = 2;
 
-bool IsSupportedAudioCodec(const MimeType& mime_type,
-                           const std::string& codec,
-                           const char* key_system) {
-  SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str());
-  if (audio_codec == kSbMediaAudioCodecNone) {
-    return false;
-  }
-
-  // TODO: allow platform-specific rejection of a combination of codec &
-  // number of channels, by passing channels to SbMediaAudioIsSupported and /
-  // or SbMediaIsSupported.
-
-  if (strlen(key_system) != 0) {
-    if (!SbMediaIsSupported(kSbMediaVideoCodecNone, audio_codec, key_system)) {
-      return false;
-    }
-  }
-
-  int channels = mime_type.GetParamIntValue("channels", kDefaultAudioChannels);
-  if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm, channels)) {
-    return false;
-  }
-
-  int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate);
-
-  if (!SbMediaIsAudioSupported(audio_codec,
-                               mime_type.raw_content_type().c_str(), bitrate)) {
-    return false;
-  }
-
-  switch (audio_codec) {
-    case kSbMediaAudioCodecNone:
-      SB_NOTREACHED();
-      return false;
-    case kSbMediaAudioCodecAac:
-      return mime_type.subtype() == "mp4";
-    case kSbMediaAudioCodecAc3:
-      if (!kSbHasAc3Audio) {
-        SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To "
-                        << "enable it, set kSbHasAc3Audio to |true|.";
-        return false;
-      }
-      return mime_type.subtype() == "mp4";
-    case kSbMediaAudioCodecEac3:
-      if (!kSbHasAc3Audio) {
-        SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To "
-                        << "enable it, set kSbHasAc3Audio to |true|.";
-        return false;
-      }
-      return mime_type.subtype() == "mp4";
-    case kSbMediaAudioCodecOpus:
-    case kSbMediaAudioCodecVorbis:
-      return mime_type.subtype() == "webm";
-#if SB_API_VERSION >= 14
-    case kSbMediaAudioCodecMp3:
-      return mime_type.subtype() == "mpeg" || mime_type.subtype() == "mp3" ||
-             mime_type.subtype() == "mp4";
-    case kSbMediaAudioCodecPcm:
-      return mime_type.subtype() == "wav" || mime_type.subtype() == "wave" ||
-             mime_type.subtype() == "x-wav" ||
-             mime_type.subtype() == "x-pn-wav";
-    case kSbMediaAudioCodecFlac:
-      return mime_type.subtype() == "ogg";
-#endif  // SB_API_VERSION >= 14
-  }
-
-  SB_NOTREACHED();
-  return false;
-}
-
-bool IsSupportedVideoCodec(const MimeType& mime_type,
-                           const std::string& codec,
-                           const char* key_system,
-                           bool decode_to_texture_required) {
-  SbMediaVideoCodec video_codec;
-  int profile = -1;
-  int level = -1;
-  int bit_depth = 8;
-  SbMediaPrimaryId primary_id = kSbMediaPrimaryIdUnspecified;
-  SbMediaTransferId transfer_id = kSbMediaTransferIdUnspecified;
-  SbMediaMatrixId matrix_id = kSbMediaMatrixIdUnspecified;
-
-  if (!ParseVideoCodec(codec.c_str(), &video_codec, &profile, &level,
-                       &bit_depth, &primary_id, &transfer_id, &matrix_id)) {
-    return false;
-  }
-  SB_DCHECK(video_codec != kSbMediaVideoCodecNone);
-
-  if (strlen(key_system) != 0) {
-    if (!SbMediaIsSupported(video_codec, kSbMediaAudioCodecNone, key_system)) {
-      return false;
-    }
-  }
-
-  std::string eotf = mime_type.GetParamStringValue("eotf", "");
-  if (!eotf.empty()) {
-    SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf);
-    // If the eotf is not known, reject immediately - without checking with
-    // the platform.
-    if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) {
-      return false;
-    }
-    if (transfer_id != kSbMediaTransferIdUnspecified &&
-        transfer_id != transfer_id_from_eotf) {
-      SB_LOG_IF(WARNING, transfer_id != kSbMediaTransferIdUnspecified)
-          << "transfer_id " << transfer_id << " set by the codec string \""
-          << codec << "\" will be overwritten by the eotf attribute " << eotf;
-    }
-    transfer_id = transfer_id_from_eotf;
-  }
-
-  std::string cryptoblockformat =
-      mime_type.GetParamStringValue("cryptoblockformat", "");
-  if (!cryptoblockformat.empty()) {
-    if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") {
-      return false;
-    }
-  }
-
-  int width = mime_type.GetParamIntValue("width", 0);
-  int height = mime_type.GetParamIntValue("height", 0);
-  int fps = mime_type.GetParamIntValue("framerate", 0);
-
-  int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate);
-
-  if (width < 0 || height < 0 || fps < 0 || bitrate < 0) {
-    return false;
-  }
-
-  if (!SbMediaIsVideoSupported(
-          video_codec, mime_type.raw_content_type().c_str(), profile, level,
-          bit_depth, primary_id, transfer_id, matrix_id, width, height, bitrate,
-          fps, decode_to_texture_required)) {
-    return false;
-  }
-
-  switch (video_codec) {
-    case kSbMediaVideoCodecNone:
-      SB_NOTREACHED();
-      return false;
-    case kSbMediaVideoCodecH264:
-    case kSbMediaVideoCodecH265:
-      return mime_type.subtype() == "mp4";
-    case kSbMediaVideoCodecMpeg2:
-    case kSbMediaVideoCodecTheora:
-      return false;  // No associated container in YT.
-    case kSbMediaVideoCodecVc1:
-    case kSbMediaVideoCodecAv1:
-      return mime_type.subtype() == "mp4";
-    case kSbMediaVideoCodecVp8:
-      return mime_type.subtype() == "webm";
-    case kSbMediaVideoCodecVp9:
-      return mime_type.subtype() == "mp4" || mime_type.subtype() == "webm";
-  }
-
-  SB_NOTREACHED();
-  return false;
-}
-
 }  // namespace
 
 AudioSampleInfo::AudioSampleInfo() {
@@ -254,24 +93,6 @@
   return *this;
 }
 
-bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) {
-  int count = SbMediaGetAudioOutputCount();
-
-  for (int output_index = 0; output_index < count; ++output_index) {
-    SbMediaAudioConfiguration configuration;
-    if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
-      continue;
-    }
-
-    if (configuration.coding_type == coding_type &&
-        configuration.number_of_channels >= channels) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
 bool IsSDRVideo(int bit_depth,
                 SbMediaPrimaryId primary_id,
                 SbMediaTransferId transfer_id,
@@ -346,17 +167,6 @@
   return bit_depth == 8;
 }
 
-SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) {
-  if (transfer_id == "bt709") {
-    return kSbMediaTransferIdBt709;
-  } else if (transfer_id == "smpte2084") {
-    return kSbMediaTransferIdSmpteSt2084;
-  } else if (transfer_id == "arib-std-b67") {
-    return kSbMediaTransferIdAribStdB67;
-  }
-  return kSbMediaTransferIdUnknown;
-}
-
 int GetBytesPerSample(SbMediaAudioSampleType sample_type) {
   switch (sample_type) {
     case kSbMediaAudioSampleTypeInt16Deprecated:
@@ -369,84 +179,6 @@
   return 4;
 }
 
-SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type,
-                                           const char* key_system) {
-  SB_DCHECK(mime_type.is_valid());
-
-  if (mime_type.type() != "audio" && mime_type.type() != "video") {
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  auto codecs = mime_type.GetCodecs();
-
-  // Pre-filter for |key_system|.
-  if (strlen(key_system) != 0) {
-    if (!SbMediaIsSupported(kSbMediaVideoCodecNone, kSbMediaAudioCodecNone,
-                            key_system)) {
-      return kSbMediaSupportTypeNotSupported;
-    }
-  }
-
-  bool decode_to_texture_required = false;
-  std::string decode_to_texture_value =
-      mime_type.GetParamStringValue("decode-to-texture", "false");
-  if (decode_to_texture_value == "true") {
-    decode_to_texture_required = true;
-  } else if (decode_to_texture_value != "false") {
-    // If an invalid value (e.g. not "true" or "false") is passed in for
-    // decode-to-texture, trivially reject.
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  if (codecs.size() == 0) {
-    // This happens when the H5 player is either querying for progressive
-    // playback support, or probing for generic mp4 support without specific
-    // codecs.  We only support "audio/mp4" and "video/mp4" for these cases.
-    if ((mime_type.type() == "audio" || mime_type.type() == "video") &&
-        mime_type.subtype() == "mp4") {
-      return kSbMediaSupportTypeMaybe;
-    }
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  if (codecs.size() > 2) {
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  bool has_audio_codec = false;
-  bool has_video_codec = false;
-  for (const auto& codec : codecs) {
-    if (IsSupportedAudioCodec(mime_type, codec, key_system)) {
-      if (has_audio_codec) {
-        // We don't support two audio codecs in one stream.
-        return kSbMediaSupportTypeNotSupported;
-      }
-      has_audio_codec = true;
-      continue;
-    }
-    if (IsSupportedVideoCodec(mime_type, codec, key_system,
-                              decode_to_texture_required)) {
-      if (mime_type.type() != "video") {
-        // Video can only be contained in "video/*", while audio can be
-        // contained in both "audio/*" and "video/*".
-        return kSbMediaSupportTypeNotSupported;
-      }
-      if (has_video_codec) {
-        // We don't support two video codecs in one stream.
-        return kSbMediaSupportTypeNotSupported;
-      }
-      has_video_codec = true;
-      continue;
-    }
-    return kSbMediaSupportTypeNotSupported;
-  }
-
-  if (has_audio_codec || has_video_codec) {
-    return kSbMediaSupportTypeProbably;
-  }
-  return kSbMediaSupportTypeNotSupported;
-}
-
 std::string GetStringRepresentation(const uint8_t* data, const int size) {
   std::string result;
 
diff --git a/starboard/shared/starboard/media/media_util.h b/starboard/shared/starboard/media/media_util.h
index aef3cb8..8a97986 100644
--- a/starboard/shared/starboard/media/media_util.h
+++ b/starboard/shared/starboard/media/media_util.h
@@ -21,7 +21,6 @@
 
 #include "starboard/media.h"
 #include "starboard/shared/internal_only.h"
-#include "starboard/shared/starboard/media/mime_type.h"
 
 namespace starboard {
 namespace shared {
@@ -48,49 +47,14 @@
   std::string max_video_capabilities_storage;
 };
 
-bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels);
-
 bool IsSDRVideo(int bit_depth,
                 SbMediaPrimaryId primary_id,
                 SbMediaTransferId transfer_id,
                 SbMediaMatrixId matrix_id);
 bool IsSDRVideo(const char* mime);
 
-// Turns |eotf| into value of SbMediaTransferId.  If |eotf| isn't recognized the
-// function returns kSbMediaTransferIdReserved0.
-// This function supports all eotfs required by YouTube TV HTML5 Technical
-// Requirements (2018).
-SbMediaTransferId GetTransferIdFromString(const std::string& eotf);
-
 int GetBytesPerSample(SbMediaAudioSampleType sample_type);
 
-// Calls to canPlayType() and isTypeSupported() are redirected to this function.
-// Following are some example inputs:
-//   canPlayType(video/mp4)
-//   canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2")
-//   canPlayType(video/webm)
-//   isTypeSupported(video/webm; codecs="vp9")
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000)
-//   isTypeSupported(audio/mp4; codecs="mp4a.40.2")
-//   isTypeSupported(audio/webm; codecs="vorbis")
-//   isTypeSupported(video/webm; codecs="vp9")
-//   isTypeSupported(video/webm; codecs="vp9")
-//   isTypeSupported(audio/webm; codecs="opus")
-//   isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2)
-//   isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false)
-//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid)
-SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type,
-                                           const char* key_system);
-
 std::string GetStringRepresentation(const uint8_t* data, const int size);
 std::string GetMixedRepresentation(const uint8_t* data,
                                    const int size,
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.cc b/starboard/shared/starboard/media/mime_supportability_cache.cc
new file mode 100644
index 0000000..34024f3
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_supportability_cache.cc
@@ -0,0 +1,227 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/mime_supportability_cache.h"
+
+#include <queue>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#include "starboard/common/log.h"
+#include "starboard/common/media.h"
+#include "starboard/common/mutex.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/once.h"
+#include "starboard/shared/starboard/media/mime_type.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+const size_t kDefaultCacheMaxSize = 2000;
+
+class MimeSupportabilityContainer {
+ public:
+  struct Entry {
+    ParsedMimeInfo mime_info;
+    Supportability supportability = kSupportabilityUnknown;
+
+    explicit Entry(const std::string& mime_string) : mime_info(mime_string) {}
+  };
+
+  // GetParsedMimeAndSupportability() will first try to find a cached Entry for
+  // the mime string. If no cached entry, a new Entry will be created with
+  // parsed mime information and supportability kSupportabilityUnknown.
+  // Ideally, we should decouple mime parsing and cache functionality, but
+  // considering that the cache is only for internal use, to avoid repeated
+  // lookups, we do parsing in this function.
+  const Entry& GetParsedMimeAndSupportability(const std::string& mime_string) {
+    ScopedLock scoped_lock(mutex_);
+    auto entry_iter = entries_.find(mime_string);
+    if (entry_iter != entries_.end()) {
+      return entry_iter->second;
+    }
+
+    // We can't find anything from the cache. Parse mime string and cache
+    // parsed MimeType and ParsedMimeInfo.
+    auto insert_result = entries_.insert({mime_string, Entry(mime_string)});
+
+    fifo_queue_.push(insert_result.first);
+    while (fifo_queue_.size() > max_size_) {
+      entries_.erase(fifo_queue_.front());
+      fifo_queue_.pop();
+    }
+    SB_DCHECK(entries_.size() == fifo_queue_.size());
+
+    return insert_result.first->second;
+  }
+
+  // CacheSupportability() will find the target entry and update the
+  // supportability. If there's no existing entry, it will parse the mime
+  // string and create one.
+  void CacheSupportability(const std::string& mime_string,
+                           Supportability supportability) {
+    SB_DCHECK(!mime_string.empty());
+    SB_DCHECK(supportability != kSupportabilityUnknown);
+
+    {
+      ScopedLock scoped_lock(mutex_);
+      auto entry_iter = entries_.find(mime_string);
+      if (entry_iter != entries_.end()) {
+        entry_iter->second.supportability = supportability;
+        return;
+      }
+    }
+
+    // Parse the mime string and create an entry.
+    GetParsedMimeAndSupportability(mime_string);
+    // Update the supportability again.
+    CacheSupportability(mime_string, supportability);
+  }
+
+  // ClearCachedSupportabilities() will reset all cached |supportability|, but
+  // will not remove parsed mime infos.
+  void ClearCachedSupportabilities() {
+    ScopedLock scoped_lock(mutex_);
+    for (auto& iter : entries_) {
+      iter.second.supportability = kSupportabilityUnknown;
+    }
+  }
+
+  void SetCacheMaxSize(int size) { max_size_ = size; }
+
+  void DumpCache() {
+    ScopedLock scoped_lock(mutex_);
+    std::stringstream ss;
+    ss << "\n========Dumping MimeInfoCache========";
+    for (const auto& entry_iter : entries_) {
+      const ParsedMimeInfo& mime_info = entry_iter.second.mime_info;
+      ss << "\nMime: " << entry_iter.first;
+      ss << "\n  ParsedMimeInfo:";
+      ss << "\n    MimeType : " << mime_info.mime_type().ToString();
+      if (mime_info.is_valid()) {
+        if (mime_info.has_audio_info()) {
+          const ParsedMimeInfo::AudioCodecInfo& audio_info =
+              mime_info.audio_info();
+          ss << "\n    Audio Codec : "
+             << GetMediaAudioCodecName(audio_info.codec);
+          ss << "\n    Channels : " << audio_info.channels;
+        }
+        if (mime_info.has_video_info()) {
+          const ParsedMimeInfo::VideoCodecInfo& video_info =
+              mime_info.video_info();
+          ss << "\n    Video Codec : "
+             << GetMediaVideoCodecName(video_info.codec);
+          ss << "\n    Profile : " << video_info.profile;
+          ss << "\n    Level : " << video_info.level;
+          ss << "\n    BitDepth : " << video_info.bit_depth;
+          ss << "\n    PrimaryId : "
+             << GetMediaPrimaryIdName(video_info.primary_id);
+          ss << "\n    TransferId : "
+             << GetMediaTransferIdName(video_info.transfer_id);
+          ss << "\n    MatrixId : "
+             << GetMediaMatrixIdName(video_info.matrix_id);
+          ss << "\n    Width : " << video_info.frame_width;
+          ss << "\n    Height : " << video_info.frame_height;
+          ss << "\n    Fps : " << video_info.fps;
+          ss << "\n    DecodeToTexture : "
+             << (video_info.decode_to_texture_required ? "true" : "false");
+        }
+      } else {
+        ss << "\n    Mime info is not valid";
+      }
+
+      ss << "\n  Supportability: ";
+      switch (entry_iter.second.supportability) {
+        case kSupportabilityUnknown:
+          ss << "Unknown";
+          break;
+        case kSupportabilitySupported:
+          ss << "Supported";
+          break;
+        case kSupportabilityNotSupported:
+          ss << "NotSupported";
+          break;
+      }
+    }
+    ss << "\n========End of Dumping========";
+
+    SB_DLOG(INFO) << ss.str();
+  }
+
+ private:
+  typedef std::unordered_map<std::string, Entry> Entries;
+
+  Mutex mutex_;
+  Entries entries_;
+  std::queue<Entries::iterator> fifo_queue_;
+  std::atomic_int max_size_{kDefaultCacheMaxSize};
+};
+
+SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityContainer, GetContainer);
+
+}  // namespace
+
+// static
+SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityCache,
+                            MimeSupportabilityCache::GetInstance);
+
+void MimeSupportabilityCache::SetCacheMaxSize(size_t size) {
+  GetContainer()->SetCacheMaxSize(size);
+}
+
+Supportability MimeSupportabilityCache::GetMimeSupportability(
+    const std::string& mime,
+    ParsedMimeInfo* mime_info) {
+  // Get cached parsed mime infos and supportability. If no cache is found,
+  // MimeSupportabilityContainer will parse the mime string, and return a parsed
+  // MimeType and its parsed audio/video information.
+  const MimeSupportabilityContainer::Entry& entry =
+      GetContainer()->GetParsedMimeAndSupportability(mime);
+
+  if (mime_info) {
+    // Return cached ParsedMimeInfo.
+    *mime_info = entry.mime_info;
+  }
+
+  return is_enabled_ ? entry.supportability : kSupportabilityUnknown;
+}
+
+void MimeSupportabilityCache::CacheMimeSupportability(
+    const std::string& mime,
+    Supportability supportability) {
+  if (!is_enabled_) {
+    return;
+  }
+  if (supportability == kSupportabilityUnknown) {
+    SB_LOG(WARNING) << "Rejected unknown supportability.";
+    return;
+  }
+
+  GetContainer()->CacheSupportability(mime, supportability);
+}
+
+void MimeSupportabilityCache::ClearCachedMimeSupportabilities() {
+  GetContainer()->ClearCachedSupportabilities();
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.h b/starboard/shared/starboard/media/mime_supportability_cache.h
new file mode 100644
index 0000000..7eb3d4f
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_supportability_cache.h
@@ -0,0 +1,78 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_
+
+#include <atomic>
+#include <string>
+
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/parsed_mime_info.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+typedef enum Supportability {
+  kSupportabilityUnknown,
+  kSupportabilitySupported,
+  kSupportabilityNotSupported,
+} Supportability;
+
+// TODO: add unit tests for MimeSupportabilityCache
+class MimeSupportabilityCache {
+ public:
+  static MimeSupportabilityCache* GetInstance();
+
+  // When cache is not enabled, GetMimeSupportability() will always return
+  // kSupportabilityUnknown, and CacheMimeSupportability() will do nothing,
+  // but GetMimeSupportability() will still return parsed ParsedMimeInfo.
+  bool IsEnabled() const { return is_enabled_; }
+  void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
+
+  void SetCacheMaxSize(size_t size);
+
+  // Get cached mime supportability. The parsed mime information would be
+  // returned via |mime_info| if it is not NULL.
+  Supportability GetMimeSupportability(const std::string& mime,
+                                       ParsedMimeInfo* mime_info);
+  // Cache mime supportability. If there's no cached parsed mime info and
+  // supportability for the mime, the function will parse the mime first and
+  // then update its supportability.
+  void CacheMimeSupportability(const std::string& mime,
+                               Supportability supportability);
+
+  // Clear all cached supportabilities. Note that it will not remove cached
+  // parsed mime infos.
+  void ClearCachedMimeSupportabilities();
+
+ private:
+  // Class can only be instanced via the singleton
+  MimeSupportabilityCache() {}
+  ~MimeSupportabilityCache() {}
+
+  MimeSupportabilityCache(const MimeSupportabilityCache&) = delete;
+  MimeSupportabilityCache& operator=(const MimeSupportabilityCache&) = delete;
+
+  std::atomic_bool is_enabled_{false};
+};
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/mime_type.cc b/starboard/shared/starboard/media/mime_type.cc
index 035de69..4954cee 100644
--- a/starboard/shared/starboard/media/mime_type.cc
+++ b/starboard/shared/starboard/media/mime_type.cc
@@ -18,6 +18,7 @@
 #include <iosfwd>
 #include <locale>
 #include <numeric>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -33,26 +34,44 @@
 
 typedef std::vector<std::string> Strings;
 
-MimeType::ParamType GetParamTypeByValue(const std::string& value) {
+void ParseParamTypeAndValue(const std::string& name,
+                            const std::string& value,
+                            MimeType::Param* param) {
+  SB_DCHECK(param);
+
+  param->name = name;
+  if (value.size() >= 2 && value[0] == '\"' && value.back() == '\"') {
+    param->type = MimeType::kParamTypeString;
+    param->string_value = value.substr(1, value.size() - 2);
+    return;
+  }
+
+  param->string_value = value;
+
   int count;
   int i;
   if (SbStringScanF(value.c_str(), "%d%n", &i, &count) == 1 &&
       count == value.size()) {
-    return MimeType::kParamTypeInteger;
+    param->type = MimeType::kParamTypeInteger;
+    param->int_value = i;
+    return;
   }
   float f;
   std::stringstream buffer(value);
   buffer.imbue(std::locale::classic());
   buffer >> f;
   if (!buffer.fail() && buffer.rdbuf()->in_avail() == 0) {
-    return MimeType::kParamTypeFloat;
+    param->type = MimeType::kParamTypeFloat;
+    param->float_value = f;
+    return;
   }
-
   if (value == "true" || value == "false") {
-    return MimeType::kParamTypeBoolean;
+    param->type = MimeType::kParamTypeBoolean;
+    param->bool_value = value == "true";
+    return;
   }
 
-  return MimeType::kParamTypeString;
+  param->type = MimeType::kParamTypeString;
 }
 
 bool ContainsSpace(const std::string& str) {
@@ -61,7 +80,6 @@
       return true;
     }
   }
-
   return false;
 }
 
@@ -94,28 +112,29 @@
   return result;
 }
 
-const char* ParamTypeToString(MimeType::ParamType param_type) {
-  switch (param_type) {
-    case MimeType::kParamTypeInteger:
-      return "Integer";
-    case MimeType::kParamTypeFloat:
-      return "Float";
-    case MimeType::kParamTypeString:
-      return "String";
-    case MimeType::kParamTypeBoolean:
-      return "Boolean";
-    default:
-      SB_NOTREACHED();
-      return "Unknown";
-  }
-}
-
 }  // namespace
 
 const int MimeType::kInvalidParamIndex = -1;
 
-MimeType::MimeType(const std::string& content_type)
-    : raw_content_type_(content_type), is_valid_(false) {
+// static
+bool MimeType::ParseParamString(const std::string& param_string, Param* param) {
+  std::vector<std::string> name_and_value = SplitAndTrim(param_string, '=');
+  // The parameter must be on the format 'name=value' and neither |name| nor
+  // |value| can be empty. |value| must also not contain '|' and ';'.
+  if (name_and_value.size() != 2 || name_and_value[0].empty() ||
+      name_and_value[1].empty() ||
+      name_and_value[1].find('|') != std::string::npos ||
+      name_and_value[1].find(';') != std::string::npos) {
+    return false;
+  }
+
+  if (param) {
+    ParseParamTypeAndValue(name_and_value[0], name_and_value[1], param);
+  }
+  return true;
+}
+
+MimeType::MimeType(const std::string& content_type) {
   Strings components = SplitAndTrim(content_type, ';');
 
   if (components.empty()) {
@@ -135,38 +154,23 @@
   }
   type_ = type_and_container[0];
   subtype_ = type_and_container[1];
+
   components.erase(components.begin());
 
   // 2. Verify the parameters have valid formats, we want to be strict here.
-  bool has_codecs = false;
   for (Strings::iterator iter = components.begin(); iter != components.end();
        ++iter) {
-    std::vector<std::string> name_and_value = SplitAndTrim(*iter, '=');
-    // The parameter must be on the format 'name=value' and neither |name| nor
-    // |value| can be empty. |value| must also not contain '|'.
-    if (name_and_value.size() != 2 || name_and_value[0].empty() ||
-        name_and_value[1].empty() ||
-        name_and_value[1].find('|') != std::string::npos) {
+    Param param;
+    if (!ParseParamString(*iter, &param)) {
       return;
     }
-    Param param;
-    if (name_and_value[1].size() > 2 && name_and_value[1][0] == '\"' &&
-        *name_and_value[1].rbegin() == '\"') {
-      param.type = kParamTypeString;
-      param.value = name_and_value[1].substr(1, name_and_value[1].size() - 2);
-    } else {
-      param.type = GetParamTypeByValue(name_and_value[1]);
-      param.value = name_and_value[1];
-    }
-    param.name = name_and_value[0];
+    // There can only be no more than one codecs parameter and it has to be
+    // the first parameter if it is present.
     if (param.name == "codecs") {
-      // There can only be no more than one codecs parameter and it has to be
-      // the first parameter if it is present.
-      if (!params_.empty() || has_codecs) {
+      if (!params_.empty()) {
         return;
-      } else {
-        has_codecs = true;
       }
+      codecs_ = SplitAndTrim(param.string_value, ',');
     }
     params_.push_back(param);
   }
@@ -174,84 +178,65 @@
   is_valid_ = true;
 }
 
-const std::vector<std::string>& MimeType::GetCodecs() const {
-  if (!codecs_.empty()) {
-    return codecs_;
-  }
-  int codecs_index = GetParamIndexByName("codecs");
-  if (codecs_index != 0) {
-    return codecs_;
-  }
-  codecs_ = SplitAndTrim(params_[0].value, ',');
-  return codecs_;
-}
-
 int MimeType::GetParamCount() const {
-  SB_DCHECK(is_valid());
-
   return static_cast<int>(params_.size());
 }
 
 MimeType::ParamType MimeType::GetParamType(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
   return params_[index].type;
 }
 
 const std::string& MimeType::GetParamName(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
   return params_[index].name;
 }
 
+int MimeType::GetParamIndexByName(const char* name) const {
+  for (size_t i = 0; i < params_.size(); ++i) {
+    if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) {
+      return static_cast<int>(i);
+    }
+  }
+  return kInvalidParamIndex;
+}
+
 int MimeType::GetParamIntValue(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
-  if (GetParamType(index) != kParamTypeInteger) {
-    return 0;
+  if (params_[index].type == kParamTypeInteger) {
+    return params_[index].int_value;
   }
-
-  int i;
-  SbStringScanF(params_[index].value.c_str(), "%d", &i);
-  return i;
+  return 0;
 }
 
 float MimeType::GetParamFloatValue(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
-  if (GetParamType(index) != kParamTypeInteger &&
-      GetParamType(index) != kParamTypeFloat) {
-    return 0.0f;
+  if (params_[index].type == kParamTypeInteger) {
+    return params_[index].int_value;
   }
-
-  float f;
-  std::stringstream buffer(params_[index].value.c_str());
-  buffer.imbue(std::locale::classic());
-  buffer >> f;
-
-  return f;
+  if (params_[index].type == kParamTypeFloat) {
+    return params_[index].float_value;
+  }
+  return 0.0f;
 }
 
 const std::string& MimeType::GetParamStringValue(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
-  return params_[index].value;
+  return params_[index].string_value;
 }
 
 bool MimeType::GetParamBoolValue(int index) const {
-  SB_DCHECK(is_valid());
   SB_DCHECK(index < GetParamCount());
 
-  if (GetParamType(index) != kParamTypeBoolean) {
-    return false;
+  if (params_[index].type == kParamTypeBoolean) {
+    return params_[index].bool_value;
   }
-
-  return params_[index].value == "true";
+  return false;
 }
 
 int MimeType::GetParamIntValue(const char* name, int default_value) const {
@@ -289,23 +274,44 @@
   return default_value;
 }
 
-bool MimeType::RegisterBoolParameter(const char* name) {
-  return RegisterParameter(name, kParamTypeBoolean);
-}
-
-bool MimeType::RegisterStringParameter(const char* name,
-                                       const std::string& pattern /* = "" */) {
-  if (!RegisterParameter(name, kParamTypeString)) {
+bool MimeType::ValidateIntParameter(const char* name) const {
+  if (!is_valid()) {
     return false;
   }
 
-  int param_index = GetParamIndexByName(name);
-  if (param_index == kInvalidParamIndex || pattern.empty()) {
+  int index = GetParamIndexByName(name);
+  if (index == kInvalidParamIndex) {
+    return true;
+  }
+  return GetParamType(index) == kParamTypeInteger;
+}
+
+bool MimeType::ValidateFloatParameter(const char* name) const {
+  if (!is_valid()) {
+    return false;
+  }
+
+  int index = GetParamIndexByName(name);
+  if (index == kInvalidParamIndex) {
+    return true;
+  }
+  ParamType type = GetParamType(index);
+  return type == kParamTypeInteger || type == kParamTypeFloat;
+}
+
+bool MimeType::ValidateStringParameter(const char* name,
+                                       const std::string& pattern) const {
+  if (!is_valid()) {
+    return false;
+  }
+
+  int index = GetParamIndexByName(name);
+  if (pattern.empty() || index == kInvalidParamIndex) {
     return true;
   }
 
-  // Compare the parameter value with the provided pattern.
-  const std::string& param_value = GetParamStringValue(param_index);
+  const std::string& param_value = params_[index].string_value;
+
   bool matches = false;
   size_t match_start = 0;
   while (!matches) {
@@ -324,27 +330,10 @@
         (match_end >= pattern.length() || pattern[match_end] == '|');
     match_start = match_end + 1;
   }
-
-  if (matches) {
-    return true;
-  }
-
-  SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value
-               << "' does not match the supplied pattern: '" << pattern << "'";
-  is_valid_ = false;
-  return false;
+  return matches;
 }
 
-int MimeType::GetParamIndexByName(const char* name) const {
-  for (size_t i = 0; i < params_.size(); ++i) {
-    if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) {
-      return static_cast<int>(i);
-    }
-  }
-  return kInvalidParamIndex;
-}
-
-bool MimeType::RegisterParameter(const char* name, ParamType param_type) {
+bool MimeType::ValidateBoolParameter(const char* name) const {
   if (!is_valid()) {
     return false;
   }
@@ -353,25 +342,56 @@
   if (index == kInvalidParamIndex) {
     return true;
   }
+  ParamType type = GetParamType(index);
+  return type == kParamTypeBoolean;
+}
 
-  const std::string& param_value = GetParamStringValue(index);
-  ParamType parsed_type = GetParamType(index);
-
-  // Check that the parameter can be returned as the requested type.
-  // Allowed conversions:
-  // Any Type -> String, Int -> Float
-  bool convertible =
-      param_type == parsed_type || param_type == kParamTypeString ||
-      (param_type == kParamTypeFloat && parsed_type == kParamTypeInteger);
-  if (!convertible) {
-    SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value
-                 << "' can't be converted to " << ParamTypeToString(param_type);
-    is_valid_ = false;
-    return false;
+std::string MimeType::ToString() const {
+  if (!is_valid()) {
+    return "{ InvalidMimeType }; ";
   }
-
-  // All validations succeeded.
-  return true;
+  std::stringstream ss;
+  ss << "{ type: " << type();
+  ss << ", subtype: " << subtype();
+  ss << ", codecs: ";
+  if (codecs_.empty()) {
+    ss << "null";
+  } else {
+    ss << codecs_[0];
+    for (size_t i = 1; i < codecs_.size(); i++) {
+      ss << "|" << codecs_[i];
+    }
+  }
+  ss << ", params: ";
+  if (params_.empty()) {
+    ss << "null";
+  } else {
+    ss << "{ ";
+    for (size_t i = 0; i < params_.size(); i++) {
+      const Param& param = params_[i];
+      if (i != 0) {
+        ss << ",";
+      }
+      ss << param.name << "=";
+      switch (param.type) {
+        case kParamTypeInteger:
+          ss << "(int)" << param.int_value;
+          break;
+        case kParamTypeFloat:
+          ss << "(float)" << param.float_value;
+          break;
+        case kParamTypeString:
+          ss << "(string)" << param.string_value;
+          break;
+        case kParamTypeBoolean:
+          ss << "(bool)" << (param.bool_value ? "true" : "false");
+          break;
+      }
+    }
+    ss << " }";
+  }
+  ss << " }";
+  return ss.str();
 }
 
 }  // namespace media
diff --git a/starboard/shared/starboard/media/mime_type.h b/starboard/shared/starboard/media/mime_type.h
index a05a301..5390ed0 100644
--- a/starboard/shared/starboard/media/mime_type.h
+++ b/starboard/shared/starboard/media/mime_type.h
@@ -53,21 +53,36 @@
     kParamTypeBoolean,
   };
 
+  struct Param {
+    ParamType type;
+    std::string name;
+    std::string string_value;
+    union {
+      int int_value;
+      float float_value;
+      bool bool_value;
+    };
+  };
+
   static const int kInvalidParamIndex;
 
+  // Expose the function as a helper function to parse a mime attribute.
+  static bool ParseParamString(const std::string& param_string, Param* param);
+
   explicit MimeType(const std::string& content_type);
 
-  const std::string& raw_content_type() const { return raw_content_type_; }
   bool is_valid() const { return is_valid_; }
 
   const std::string& type() const { return type_; }
   const std::string& subtype() const { return subtype_; }
-
-  const std::vector<std::string>& GetCodecs() const;
+  const std::vector<std::string>& GetCodecs() const { return codecs_; }
 
   int GetParamCount() const;
   ParamType GetParamType(int index) const;
   const std::string& GetParamName(int index) const;
+  // GetParamIndexByName() will return |kInvalidParamIndex| if the param name is
+  // not found.
+  int GetParamIndexByName(const char* name) const;
 
   int GetParamIntValue(int index) const;
   float GetParamFloatValue(int index) const;
@@ -81,42 +96,29 @@
       const std::string& default_value) const;
   bool GetParamBoolValue(const char* name, bool default_value) const;
 
-  // Pre-register a mime parameter of type boolean.
-  // Returns true if the mime type is valid and the value passes validation.
-  // If the parameter validation fails this MimeType will be marked invalid.
-  // NOTE: The function returns true for missing parameters.
-  bool RegisterBoolParameter(const char* name);
-
-  // Pre-register a mime parameter of type string.
-  // Returns true if the mime type is valid and the value passes validation.
+  // Validate functions will return true if the param contains a valid value or
+  // if param name is not found.
+  bool ValidateIntParameter(const char* name) const;
+  bool ValidateFloatParameter(const char* name) const;
   // Allows passing a pattern on the format "value_1|...|value_n"
   // where the parameter value must match one of the values in the pattern in
   // order to be considered valid.
-  // If the parameter validation fails this MimeType will be marked invalid.
-  // NOTE: The function returns true for missing parameters.
-  bool RegisterStringParameter(const char* name,
-                               const std::string& pattern = "");
+  bool ValidateStringParameter(const char* name,
+                               const std::string& pattern = "") const;
+  bool ValidateBoolParameter(const char* name) const;
+
+  std::string ToString() const;
 
  private:
-  struct Param {
-    ParamType type;
-    std::string name;
-    std::string value;
-  };
-
   // Use std::vector as the number of components are usually small and we'd like
   // to keep the order of components.
   typedef std::vector<Param> Params;
 
-  int GetParamIndexByName(const char* name) const;
-  bool RegisterParameter(const char* name, ParamType type);
-
-  const std::string raw_content_type_;
-  bool is_valid_;
+  bool is_valid_ = false;
   std::string type_;
   std::string subtype_;
+  std::vector<std::string> codecs_;
   Params params_;
-  mutable std::vector<std::string> codecs_;
 };
 
 }  // namespace media
diff --git a/starboard/shared/starboard/media/mime_type_test.cc b/starboard/shared/starboard/media/mime_type_test.cc
index 854444d..52705f3 100644
--- a/starboard/shared/starboard/media/mime_type_test.cc
+++ b/starboard/shared/starboard/media/mime_type_test.cc
@@ -22,19 +22,6 @@
 namespace media {
 namespace {
 
-TEST(MimeTypeTest, RawContentType) {
-  {
-    const char kContentTypeWithSpace[] = " video/mp4; name0=123; name1=123.4 ";
-    MimeType mime_type(kContentTypeWithSpace);
-    EXPECT_EQ(mime_type.raw_content_type(), kContentTypeWithSpace);
-  }
-  {
-    const char kInvalidContentType[] = "video /mp4";
-    MimeType mime_type(kInvalidContentType);
-    EXPECT_EQ(mime_type.raw_content_type(), kInvalidContentType);
-  }
-}
-
 TEST(MimeTypeTest, EmptyString) {
   MimeType mime_type("");
   EXPECT_FALSE(mime_type.is_valid());
@@ -343,69 +330,116 @@
   EXPECT_FALSE(mime_type.GetParamBoolValue("float", false));
 }
 
-TEST(MimeTypeTest, RegisterAndValidateParamsWithPatterns) {
+TEST(MimeTypeTest, ParseInvalidParamString) {
+  EXPECT_FALSE(MimeType::ParseParamString("", nullptr));
+  EXPECT_FALSE(MimeType::ParseParamString("invalid", nullptr));
+  EXPECT_FALSE(MimeType::ParseParamString("val=0;", nullptr));
+  EXPECT_FALSE(MimeType::ParseParamString("val=a|b", nullptr));
+  EXPECT_FALSE(MimeType::ParseParamString("val=", nullptr));
+}
+
+TEST(MimeTypeTest, ParseValidParamString) {
+  MimeType::Param result;
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=0", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeInteger);
+  EXPECT_EQ(result.int_value, 0);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=1", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeInteger);
+  EXPECT_EQ(result.int_value, 1);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=-1", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeInteger);
+  EXPECT_EQ(result.int_value, -1);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=0.0", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeFloat);
+  EXPECT_EQ(result.float_value, 0.0f);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=1.0", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeFloat);
+  EXPECT_EQ(result.float_value, 1.0f);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=-1.0", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeFloat);
+  EXPECT_EQ(result.float_value, -1.0f);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=true", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeBoolean);
+  EXPECT_EQ(result.bool_value, true);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=false", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeBoolean);
+  EXPECT_EQ(result.bool_value, false);
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=\"\"", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeString);
+  EXPECT_EQ(result.string_value, "");
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=\"abc\"", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeString);
+  EXPECT_EQ(result.string_value, "abc");
+
+  EXPECT_TRUE(MimeType::ParseParamString("val=abc", &result));
+  EXPECT_EQ(result.name, "val");
+  EXPECT_EQ(result.type, MimeType::kParamTypeString);
+  EXPECT_EQ(result.string_value, "abc");
+}
+
+TEST(MimeTypeTest, ValidateParamsWithPatterns) {
   MimeType mime_type("video/mp4; string=yes");
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes|no"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|yes|no"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|no|yes"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "noyes|yes"));
-  EXPECT_TRUE(mime_type.is_valid());
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes|no"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|yes|no"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|no|yes"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "noyes|yes"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no"));
 }
 
-TEST(MimeTypeTest, RegisterAndValidateParamsWithShortPatterns) {
+TEST(MimeTypeTest, ValidateParamsWithShortPatterns) {
   MimeType mime_type("video/mp4; string=y");
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y|n"));
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string", "n|y"));
-  EXPECT_TRUE(mime_type.is_valid());
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y|n"));
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", "n|y"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "n"));
 }
 
-TEST(MimeTypeTest, RegisterAndValidateParamsWithPartialMatches) {
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "yesno|no"));
-    EXPECT_FALSE(mime_type.is_valid());
-  }
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "noyes|no"));
-    EXPECT_FALSE(mime_type.is_valid());
-  }
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|yesno"));
-    EXPECT_FALSE(mime_type.is_valid());
-  }
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|noyes"));
-    EXPECT_FALSE(mime_type.is_valid());
-  }
+TEST(MimeTypeTest, ValidateParamsWithPartialMatches) {
+  MimeType mime_type("video/mp4; string=yes");
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "yesno|no"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "noyes|no"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|yesno"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|noyes"));
 }
 
-TEST(MimeTypeTest, MissingParamReturnsTrueOnRegistration) {
+TEST(MimeTypeTest, ValidateMissingParam) {
   MimeType mime_type("video/mp4");
-  EXPECT_TRUE(mime_type.RegisterStringParameter("string"));
-  EXPECT_TRUE(mime_type.is_valid());
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string"));
   EXPECT_EQ(mime_type.GetParamStringValue("string", "default"), "default");
 }
 
-TEST(MimeTypeTest, RegisterAndValidateParamsWithEmptyishPattern) {
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "|"));
-  }
-  {
-    MimeType mime_type("video/mp4; string=yes");
-    EXPECT_FALSE(mime_type.RegisterStringParameter("string", "||"));
-  }
+TEST(MimeTypeTest, ValidateParamsWithEmptyishPattern) {
+  MimeType mime_type("video/mp4; string=yes");
+  EXPECT_TRUE(mime_type.ValidateStringParameter("string", ""));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "|"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string", "||"));
 }
 
-TEST(MimeTypeTest, CannotRegisterParamWithInvalidMimeType) {
+TEST(MimeTypeTest, ValidateParamWithInvalidMimeType) {
   MimeType mime_type("video/mp4; string=");
   ASSERT_FALSE(mime_type.is_valid());
-  EXPECT_FALSE(mime_type.RegisterStringParameter("string"));
+  EXPECT_FALSE(mime_type.ValidateStringParameter("string"));
 }
 
 }  // namespace
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc
new file mode 100644
index 0000000..518338f
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_util.cc
@@ -0,0 +1,425 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/mime_util.h"
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "starboard/common/log.h"
+#include "starboard/common/media.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/media/bitrate_supportability_cache.h"
+#include "starboard/shared/starboard/media/key_system_supportability_cache.h"
+#include "starboard/shared/starboard/media/media_support_internal.h"
+#include "starboard/shared/starboard/media/mime_supportability_cache.h"
+#include "starboard/shared/starboard/media/mime_type.h"
+#include "starboard/shared/starboard/media/parsed_mime_info.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+// RemoveAttributeFromMime() will return a new mime string with the specified
+// attribute removed. If |attribute_string| is not null, the removed attribute
+// string will be returned via |attribute_string|. Following are some examples:
+//   mime: "video/webm; codecs=\"vp9\"; bitrate=300000"
+//   attribute_name: "bitrate"
+//   return: "video/webm; codecs=\"vp9\""
+//   attribute_string: "bitrate=300000"
+//
+//   mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709"
+//   attribute_name: "bitrate"
+//   return: "video/webm; codecs=\"vp9\"; eotf=bt709"
+//   attribute_string: "bitrate=300000"
+//
+//   mime: "bitrate=300000"
+//   attribute_name: "bitrate"
+//   return: ""
+//   attribute_string: "bitrate=300000"
+std::string RemoveAttributeFromMime(const char* mime,
+                                    const char* attribute_name,
+                                    std::string* attribute_string) {
+  size_t name_length = strlen(attribute_name);
+  if (name_length == 0) {
+    return mime;
+  }
+
+  std::string mime_without_attribute;
+  const char* start_pos = strstr(mime, attribute_name);
+  while (start_pos) {
+    if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) &&
+        (start_pos[name_length] &&
+         (start_pos[name_length] == '=' || isspace(start_pos[name_length])))) {
+      break;
+    }
+    start_pos += name_length;
+    start_pos = strstr(start_pos, attribute_name);
+  }
+
+  if (!start_pos) {
+    // Target attribute is not found.
+    return std::string(mime);
+  }
+  const char* end_pos = strstr(start_pos, ";");
+  if (end_pos) {
+    // There may be other attribute after target attribute.
+    if (attribute_string) {
+      // Returned |attribute_string| will not have a trailing ';'.
+      attribute_string->assign(start_pos, end_pos - start_pos);
+    }
+
+    end_pos++;
+    // Remove leading spaces.
+    while (*end_pos && isspace(*end_pos)) {
+      end_pos++;
+    }
+    if (*end_pos) {
+      // Append the string after target attribute.
+      mime_without_attribute = std::string(mime, start_pos - mime);
+      mime_without_attribute.append(end_pos);
+    } else {
+      // Target attribute is the last one. Remove trailing spaces.
+      size_t mime_length = start_pos - mime;
+      while (mime_length > 0 && (isspace(mime[mime_length - 1]))) {
+        mime_length--;
+      }
+      mime_without_attribute = std::string(mime, mime_length);
+    }
+  } else {
+    // It can't find a trailing ';'. The target attribute must be the last one.
+    size_t mime_length = start_pos - mime;
+    // Remove trailing spaces.
+    while (mime_length > 0 && (isspace(mime[mime_length - 1]))) {
+      mime_length--;
+    }
+    // Remove the trailing ';'.
+    if (mime_length > 0 && mime[mime_length - 1] == ';') {
+      mime_length--;
+    }
+    mime_without_attribute = std::string(mime, mime_length);
+    if (attribute_string) {
+      *attribute_string = std::string(start_pos);
+    }
+  }
+  return mime_without_attribute;
+}
+
+// Use SbMediaGetAudioConfiguration() to check if the platform can support
+// |channels|.
+bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) {
+  int count = SbMediaGetAudioOutputCount();
+
+  for (int output_index = 0; output_index < count; ++output_index) {
+    SbMediaAudioConfiguration configuration;
+    if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
+      continue;
+    }
+
+    if (configuration.coding_type == coding_type &&
+        configuration.number_of_channels >= channels) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool IsSupportedKeySystem(SbMediaAudioCodec codec, const char* key_system) {
+  SB_DCHECK(key_system);
+  // KeySystemSupportabilityCache() should always return supported for empty
+  // |key_system|, so here it should always be non empty.
+  SB_DCHECK(strlen(key_system) > 0);
+
+  return SbMediaIsSupported(kSbMediaVideoCodecNone, codec, key_system);
+}
+
+bool IsSupportedKeySystem(SbMediaVideoCodec codec, const char* key_system) {
+  SB_DCHECK(key_system);
+  // KeySystemSupportabilityCache() should always return supported for empty
+  // |key_system|, so here it should always be non empty.
+  SB_DCHECK(strlen(key_system) > 0);
+
+  return SbMediaIsSupported(codec, kSbMediaAudioCodecNone, key_system);
+}
+
+bool IsSupportedAudioCodec(const ParsedMimeInfo& mime_info) {
+  SB_DCHECK(mime_info.is_valid());
+  SB_DCHECK(mime_info.mime_type().is_valid());
+  SB_DCHECK(mime_info.has_audio_info());
+
+  const MimeType& mime_type = mime_info.mime_type();
+  const ParsedMimeInfo::AudioCodecInfo& audio_info = mime_info.audio_info();
+
+  switch (audio_info.codec) {
+    case kSbMediaAudioCodecNone:
+      SB_NOTREACHED();
+      return false;
+    case kSbMediaAudioCodecAac:
+    case kSbMediaAudioCodecAc3:
+    case kSbMediaAudioCodecEac3:
+      if (mime_type.subtype() != "mp4") {
+        return false;
+      }
+      break;
+    case kSbMediaAudioCodecOpus:
+    case kSbMediaAudioCodecVorbis:
+      if (mime_type.subtype() != "webm") {
+        return false;
+      }
+      break;
+#if SB_API_VERSION >= 14
+    case kSbMediaAudioCodecMp3:
+    case kSbMediaAudioCodecFlac:
+    case kSbMediaAudioCodecPcm:
+      return false;
+#endif  // SB_API_VERSION >= 14
+  }
+
+  if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm,
+                              audio_info.channels)) {
+    return false;
+  }
+
+  return SbMediaIsAudioSupported(audio_info.codec, &mime_type,
+                                 audio_info.bitrate);
+}
+
+bool IsSupportedVideoCodec(const ParsedMimeInfo& mime_info) {
+  SB_DCHECK(mime_info.is_valid());
+  SB_DCHECK(mime_info.mime_type().is_valid());
+  SB_DCHECK(mime_info.has_video_info());
+
+  const MimeType& mime_type = mime_info.mime_type();
+  const ParsedMimeInfo::VideoCodecInfo& video_info = mime_info.video_info();
+
+  switch (video_info.codec) {
+    case kSbMediaVideoCodecNone:
+      SB_NOTREACHED();
+      return false;
+    case kSbMediaVideoCodecH264:
+    case kSbMediaVideoCodecH265:
+      if (mime_type.subtype() != "mp4") {
+        return false;
+      }
+      break;
+    case kSbMediaVideoCodecMpeg2:
+    case kSbMediaVideoCodecTheora:
+      return false;  // No associated container in YT.
+    case kSbMediaVideoCodecVc1:
+    case kSbMediaVideoCodecAv1:
+      if (mime_type.subtype() != "mp4") {
+        return false;
+      }
+      break;
+    case kSbMediaVideoCodecVp8:
+      if (mime_type.subtype() != "webm") {
+        return false;
+      }
+      break;
+    case kSbMediaVideoCodecVp9:
+      if (mime_type.subtype() != "mp4" && mime_type.subtype() != "webm") {
+        return false;
+      }
+      break;
+  }
+
+  std::string cryptoblockformat =
+      mime_type.GetParamStringValue("cryptoblockformat", "");
+  if (!cryptoblockformat.empty()) {
+    if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") {
+      return false;
+    }
+  }
+
+  return SbMediaIsVideoSupported(
+      video_info.codec, &mime_type, video_info.profile, video_info.level,
+      video_info.bit_depth, video_info.primary_id, video_info.transfer_id,
+      video_info.matrix_id, video_info.frame_width, video_info.frame_height,
+      video_info.bitrate, video_info.fps,
+      video_info.decode_to_texture_required);
+}
+
+bool ValidateAndParseBitrate(const std::string& bitrate_string, int* bitrate) {
+  SB_DCHECK(!bitrate_string.empty());
+
+  MimeType::Param param;
+  if (!MimeType::ParseParamString(bitrate_string, &param)) {
+    return false;
+  }
+  if (param.type != MimeType::kParamTypeInteger) {
+    return false;
+  }
+  if (bitrate) {
+    *bitrate = param.int_value;
+  }
+  return true;
+}
+
+}  // namespace
+
+SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime,
+                                           const char* key_system) {
+  SB_DCHECK(mime);
+  SB_DCHECK(key_system);
+
+  // Remove bitrate from mime string and read bitrate if presents.
+  std::string bitrate_string;
+  std::string mime_without_bitrate =
+      RemoveAttributeFromMime(mime, "bitrate", &bitrate_string);
+  int bitrate = 0;
+  if (!bitrate_string.empty()) {
+    if (!ValidateAndParseBitrate(bitrate_string, &bitrate)) {
+      return kSbMediaSupportTypeNotSupported;
+    }
+  }
+
+  if (bitrate < 0) {
+    // Reject invalid bitrate.
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  // Get cached parsed mime infos and supportability. If it is not found in the
+  // cache, MimeSupportabilityCache would parse the mime string and return a
+  // ParsedMimeInfo.
+  ParsedMimeInfo mime_info;
+  Supportability mime_supportability =
+      MimeSupportabilityCache::GetInstance()->GetMimeSupportability(
+          mime_without_bitrate, &mime_info);
+  // Overwrite the bitrate.
+  mime_info.SetBitrate(bitrate);
+
+  if (mime_info.disable_cache()) {
+    // Disable all caches if required.
+    mime_supportability = kSupportabilityUnknown;
+    MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false);
+    KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false);
+    BitrateSupportabilityCache::GetInstance()->SetCacheEnabled(false);
+  }
+
+  // Reject mime if cached result is not supported.
+  if (mime_supportability == kSupportabilityNotSupported) {
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  // Reject mime if parsed mime info is invalid.
+  if (!mime_info.is_valid()) {
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  const MimeType& mime_type = mime_info.mime_type();
+  const std::vector<std::string>& codecs = mime_type.GetCodecs();
+
+  // Quick check for mp4 format.
+  if (codecs.size() == 0) {
+    // This happens when the H5 player is either querying for progressive
+    // playback support, or probing for generic mp4 support without specific
+    // codecs.
+    if (mime_type.subtype() == "mp4") {
+      return kSbMediaSupportTypeMaybe;
+    } else {
+      return kSbMediaSupportTypeNotSupported;
+    }
+  }
+
+  // Reject mime if it doesn't have any valid codec info.
+  if (!mime_info.has_audio_info() && !mime_info.has_video_info()) {
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  // Get cached key system supportability. Note that we check if audio or video
+  // codec supports key system separately.
+  if (mime_info.has_audio_info()) {
+    Supportability key_system_supportability =
+        KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability(
+            mime_info.audio_info().codec, key_system);
+    if (key_system_supportability == kSupportabilityUnknown) {
+      key_system_supportability =
+          IsSupportedKeySystem(mime_info.audio_info().codec, key_system)
+              ? kSupportabilitySupported
+              : kSupportabilityNotSupported;
+      KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability(
+          mime_info.audio_info().codec, key_system, key_system_supportability);
+    }
+    // Reject mime if audio codec doesn't support the key system.
+    if (key_system_supportability == kSupportabilityNotSupported) {
+      return kSbMediaSupportTypeNotSupported;
+    }
+  }
+  if (mime_info.has_video_info()) {
+    Supportability key_system_supportability =
+        KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability(
+            mime_info.video_info().codec, key_system);
+    if (key_system_supportability == kSupportabilityUnknown) {
+      key_system_supportability =
+          IsSupportedKeySystem(mime_info.video_info().codec, key_system)
+              ? kSupportabilitySupported
+              : kSupportabilityNotSupported;
+      KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability(
+          mime_info.video_info().codec, key_system, key_system_supportability);
+    }
+    // Reject mime if video codec doesn't the key system.
+    if (key_system_supportability == kSupportabilityNotSupported) {
+      return kSbMediaSupportTypeNotSupported;
+    }
+  }
+
+  // Get cached bitrate supportability.
+  Supportability bitrate_supportability =
+      BitrateSupportabilityCache::GetInstance()->GetBitrateSupportability(
+          mime_info);
+
+  // Reject mime if bitrate is not supported.
+  if (bitrate_supportability == kSupportabilityNotSupported) {
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  // Return supported if mime and bitrate are all supported.
+  if (mime_supportability == kSupportabilitySupported &&
+      bitrate_supportability == kSupportabilitySupported) {
+    return kSbMediaSupportTypeProbably;
+  }
+
+  // At this point, either mime or bitrate supportability must be unknown.
+  // Call platform functions to check if they are supported.
+  SB_DCHECK(mime_supportability == kSupportabilityUnknown ||
+            bitrate_supportability == kSupportabilityUnknown);
+  if (mime_info.has_audio_info() && !IsSupportedAudioCodec(mime_info)) {
+    mime_supportability = kSupportabilityNotSupported;
+  } else if (mime_info.has_video_info() && !IsSupportedVideoCodec(mime_info)) {
+    mime_supportability = kSupportabilityNotSupported;
+  } else {
+    mime_supportability = kSupportabilitySupported;
+  }
+
+  // Cache mime supportability when bitrate supportability is known.
+  if (bitrate_supportability == kSupportabilitySupported) {
+    MimeSupportabilityCache::GetInstance()->CacheMimeSupportability(
+        mime_without_bitrate, mime_supportability);
+  }
+
+  SB_DCHECK(mime_supportability != kSupportabilityUnknown);
+  return mime_supportability == kSupportabilitySupported
+             ? kSbMediaSupportTypeProbably
+             : kSbMediaSupportTypeNotSupported;
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_util.h b/starboard/shared/starboard/media/mime_util.h
new file mode 100644
index 0000000..e79dac1
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_util.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_
+
+#include <string>
+
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+// Calls to canPlayType() and isTypeSupported() are redirected to this function.
+// Following are some example inputs:
+//   canPlayType(video/mp4)
+//   canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2")
+//   canPlayType(video/webm)
+//   isTypeSupported(video/webm; codecs="vp9")
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000)
+//   isTypeSupported(audio/mp4; codecs="mp4a.40.2")
+//   isTypeSupported(audio/webm; codecs="vorbis")
+//   isTypeSupported(video/webm; codecs="vp9")
+//   isTypeSupported(video/webm; codecs="vp9")
+//   isTypeSupported(audio/webm; codecs="opus")
+//   isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2)
+//   isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false)
+//   isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid)
+SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime,
+                                           const char* key_system);
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc
new file mode 100644
index 0000000..8894289
--- /dev/null
+++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -0,0 +1,178 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/parsed_mime_info.h"
+
+#include <string>
+
+#include "starboard/common/log.h"
+#include "starboard/common/media.h"
+#include "starboard/shared/starboard/media/codec_util.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+namespace {
+
+const int64_t kDefaultAudioChannels = 2;
+
+// Turns |eotf| into value of SbMediaTransferId.  If |eotf| isn't recognized the
+// function returns kSbMediaTransferIdUnknown.
+// This function supports all eotfs required by YouTube TV HTML5 Technical
+// Requirements.
+SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) {
+  if (transfer_id == "bt709") {
+    return kSbMediaTransferIdBt709;
+  } else if (transfer_id == "smpte2084") {
+    return kSbMediaTransferIdSmpteSt2084;
+  } else if (transfer_id == "arib-std-b67") {
+    return kSbMediaTransferIdAribStdB67;
+  }
+  return kSbMediaTransferIdUnknown;
+}
+
+}  // namespace
+
+ParsedMimeInfo::ParsedMimeInfo(const std::string& mime_string)
+    : mime_type_(mime_string) {
+  ParseMimeInfo();
+}
+
+void ParsedMimeInfo::SetBitrate(int bitrate) {
+  audio_info_.bitrate = bitrate;
+  video_info_.bitrate = bitrate;
+}
+
+void ParsedMimeInfo::ParseMimeInfo() {
+  if (!mime_type_.is_valid()) {
+    is_valid_ = false;
+    return;
+  }
+
+  // Read "disablecache".
+  if (!mime_type_.ValidateBoolParameter("disablecache")) {
+    is_valid_ = false;
+    return;
+  }
+  disable_cache_ = mime_type_.GetParamBoolValue("disablecache", false);
+
+  // We only support audio or video type.
+  if (mime_type_.type() != "audio" && mime_type_.type() != "video") {
+    is_valid_ = false;
+    return;
+  }
+
+  auto codecs = mime_type_.GetCodecs();
+  // We only support up to one audio codec and one video codec.
+  if (codecs.size() > 2) {
+    is_valid_ = false;
+    return;
+  }
+
+  for (const auto& codec : codecs) {
+    if (!has_audio_info() && ParseAudioInfo(codec)) {
+      continue;
+    }
+    if (!has_video_info() && ParseVideoInfo(codec)) {
+      continue;
+    }
+    // It either has an invalid codec or has two codecs of same type.
+    ResetCodecInfos();
+    is_valid_ = false;
+    return;
+  }
+}
+
+bool ParsedMimeInfo::ParseAudioInfo(const std::string& codec) {
+  SB_DCHECK(mime_type_.is_valid());
+  SB_DCHECK(!has_audio_info());
+
+  SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str());
+  if (audio_codec == kSbMediaAudioCodecNone) {
+    return false;
+  }
+  if (!mime_type_.ValidateIntParameter("channels") ||
+      !mime_type_.ValidateIntParameter("bitrate")) {
+    return false;
+  }
+  audio_info_.codec = audio_codec;
+  audio_info_.channels =
+      mime_type_.GetParamIntValue("channels", kDefaultAudioChannels);
+  audio_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0);
+
+  return audio_info_.channels >= 0 && audio_info_.bitrate >= 0;
+}
+
+bool ParsedMimeInfo::ParseVideoInfo(const std::string& codec) {
+  SB_DCHECK(mime_type_.is_valid());
+  SB_DCHECK(!has_video_info());
+
+  if (!ParseVideoCodec(codec.c_str(), &video_info_.codec, &video_info_.profile,
+                       &video_info_.level, &video_info_.bit_depth,
+                       &video_info_.primary_id, &video_info_.transfer_id,
+                       &video_info_.matrix_id)) {
+    return false;
+  }
+
+  if (video_info_.codec == kSbMediaVideoCodecNone) {
+    return false;
+  }
+
+  std::string eotf = mime_type_.GetParamStringValue("eotf", "");
+  if (!eotf.empty()) {
+    SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf);
+    if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) {
+      // The eotf is an unknown value, mark the codec info as invalid.
+      SB_LOG(WARNING) << "Unknown eotf " << eotf << ".";
+      return false;
+    }
+    SB_LOG_IF(WARNING,
+              video_info_.transfer_id != kSbMediaTransferIdUnspecified &&
+                  video_info_.transfer_id != transfer_id_from_eotf)
+        << "transfer_id " << video_info_.transfer_id
+        << " set by the codec string \"" << video_info_.codec
+        << "\" will be overwritten by the eotf attribute " << eotf;
+    video_info_.transfer_id = transfer_id_from_eotf;
+  }
+
+  if (!mime_type_.ValidateIntParameter("width") ||
+      !mime_type_.ValidateIntParameter("height") ||
+      !mime_type_.ValidateIntParameter("framerate") ||
+      !mime_type_.ValidateIntParameter("bitrate") ||
+      !mime_type_.ValidateBoolParameter("decode-to-texture")) {
+    return false;
+  }
+
+  video_info_.frame_width = mime_type_.GetParamIntValue("width", 0);
+  video_info_.frame_height = mime_type_.GetParamIntValue("height", 0);
+  video_info_.fps = mime_type_.GetParamIntValue("framerate", 0);
+  video_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0);
+  video_info_.decode_to_texture_required =
+      mime_type_.GetParamBoolValue("decode-to-texture", false);
+
+  return video_info_.frame_width >= 0 && video_info_.frame_height >= 0 &&
+         video_info_.fps >= 0 && video_info_.bitrate >= 0;
+}
+
+void ParsedMimeInfo::ResetCodecInfos() {
+  audio_info_.codec = kSbMediaAudioCodecNone;
+  video_info_.codec = kSbMediaVideoCodecNone;
+}
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/parsed_mime_info.h b/starboard/shared/starboard/media/parsed_mime_info.h
new file mode 100644
index 0000000..5be03a5
--- /dev/null
+++ b/starboard/shared/starboard/media/parsed_mime_info.h
@@ -0,0 +1,106 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_
+#define STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_
+
+#include <string>
+
+#include "starboard/common/log.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/media/mime_type.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+
+// TODO: add unit tests for ParsedMimeInfo
+class ParsedMimeInfo {
+ public:
+  struct AudioCodecInfo {
+    SbMediaAudioCodec codec = kSbMediaAudioCodecNone;
+    int channels;
+    int bitrate;
+  };
+
+  struct VideoCodecInfo {
+    SbMediaVideoCodec codec = kSbMediaVideoCodecNone;
+    int profile;
+    int level;
+    int bit_depth;
+    SbMediaPrimaryId primary_id;
+    SbMediaTransferId transfer_id;
+    SbMediaMatrixId matrix_id;
+    int frame_width;
+    int frame_height;
+    int fps;
+    int bitrate;
+    bool decode_to_texture_required;
+  };
+
+  ParsedMimeInfo() : mime_type_("") {}
+  explicit ParsedMimeInfo(const std::string& mime_string);
+
+  const MimeType& mime_type() const { return mime_type_; }
+
+  bool is_valid() const { return is_valid_; }
+
+  // A switch in the mime string to disable caches.
+  bool disable_cache() const { return disable_cache_; }
+
+  bool has_audio_info() const {
+    return audio_info_.codec != kSbMediaAudioCodecNone;
+  }
+  // Extra information for audio codec. Note that audio_info() can only be
+  // used when has_audio_info() returns true.
+  const AudioCodecInfo& audio_info() const {
+    SB_DCHECK(has_audio_info());
+    return audio_info_;
+  }
+
+  bool has_video_info() const {
+    return video_info_.codec != kSbMediaVideoCodecNone;
+  }
+  // Extra information for video codec. Note that video_info() can only be
+  // used when has_video_info() returns true.
+  const VideoCodecInfo& video_info() const {
+    SB_DCHECK(has_video_info());
+    return video_info_;
+  }
+
+  // Allow to overwrite the bitrate.
+  void SetBitrate(int bitrate);
+
+ private:
+  void ParseMimeInfo();
+  bool ParseAudioInfo(const std::string& codec);
+  bool ParseVideoInfo(const std::string& codec);
+
+  void ResetCodecInfos();
+
+  MimeType mime_type_;
+  bool is_valid_ = true;
+  bool disable_cache_ = false;
+  AudioCodecInfo audio_info_;
+  VideoCodecInfo video_info_;
+};
+
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc
index 9bfdb7e..d87f71d 100644
--- a/starboard/shared/starboard/player/filter/testing/test_util.cc
+++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -18,6 +18,7 @@
 #include "starboard/common/log.h"
 #include "starboard/directory.h"
 #include "starboard/shared/starboard/media/media_support_internal.h"
+#include "starboard/shared/starboard/media/mime_type.h"
 #include "starboard/shared/starboard/player/filter/player_components.h"
 #include "starboard/shared/starboard/player/filter/stub_player_components_factory.h"
 #include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
@@ -33,6 +34,7 @@
 namespace testing {
 namespace {
 
+using ::starboard::shared::starboard::media::MimeType;
 using ::testing::AssertionFailure;
 using ::testing::AssertionResult;
 using ::testing::AssertionSuccess;
@@ -155,12 +157,11 @@
     }
 
     // Filter files of unsupported codec.
-    if (!SbMediaIsAudioSupported(
-            audio_file_info.audio_codec,
-            GetContentTypeFromAudioCodec(audio_file_info.audio_codec,
-                                         extra_mime_attributes)
-                .c_str(),
-            audio_file_info.bitrate)) {
+    const std::string audio_mime = GetContentTypeFromAudioCodec(
+        audio_file_info.audio_codec, extra_mime_attributes);
+    const MimeType audio_mime_type(audio_mime.c_str());
+    if (!SbMediaIsAudioSupported(audio_file_info.audio_codec, &audio_mime_type,
+                                 audio_file_info.bitrate)) {
       continue;
     }
 
@@ -203,13 +204,15 @@
       const auto& video_sample_info =
           dmp_reader.GetPlayerSampleInfo(kSbMediaTypeVideo, 0)
               .video_sample_info;
-
+      const std::string video_mime = dmp_reader.video_mime_type();
+      const MimeType video_mime_type(video_mime.c_str());
       if (SbMediaIsVideoSupported(
-              dmp_reader.video_codec(), dmp_reader.video_mime_type().c_str(),
-              -1, -1, 8, kSbMediaPrimaryIdUnspecified,
-              kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified,
-              video_sample_info.frame_width, video_sample_info.frame_height,
-              dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) {
+              dmp_reader.video_codec(),
+              video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8,
+              kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified,
+              kSbMediaMatrixIdUnspecified, video_sample_info.frame_width,
+              video_sample_info.frame_height, dmp_reader.video_bitrate(),
+              dmp_reader.video_fps(), false)) {
         test_params.push_back(std::make_tuple(filename, output_mode));
       }
     }
diff --git a/starboard/shared/starboard/player/player_create.cc b/starboard/shared/starboard/player/player_create.cc
index c2e0a07..16bc57f 100644
--- a/starboard/shared/starboard/player/player_create.cc
+++ b/starboard/shared/starboard/player/player_create.cc
@@ -30,12 +30,13 @@
 #include "starboard/shared/starboard/player/video_dmp_writer.h"
 #endif  // SB_PLAYER_ENABLE_VIDEO_DUMPER
 
-using starboard::shared::media_session::
+using ::starboard::shared::media_session::
     UpdateActiveSessionPlatformPlaybackState;
-using starboard::shared::media_session::kPlaying;
-using starboard::shared::starboard::player::filter::
+using ::starboard::shared::media_session::kPlaying;
+using ::starboard::shared::starboard::media::MimeType;
+using ::starboard::shared::starboard::player::filter::
     FilterBasedPlayerWorkerHandler;
-using starboard::shared::starboard::player::PlayerWorker;
+using ::starboard::shared::starboard::player::PlayerWorker;
 
 SbPlayer SbPlayerCreate(SbWindow window,
                         const SbPlayerCreationParam* creation_param,
@@ -135,16 +136,19 @@
   }
 
   const int64_t kDefaultBitRate = 0;
-  if (audio_codec != kSbMediaAudioCodecNone &&
-      !SbMediaIsAudioSupported(audio_codec, audio_mime, kDefaultBitRate)) {
-    SB_LOG(ERROR) << "Unsupported audio codec "
-                  << starboard::GetMediaAudioCodecName(audio_codec) << ".";
-    player_error_func(
-        kSbPlayerInvalid, context, kSbPlayerErrorDecode,
-        starboard::FormatString("Unsupported audio codec: %s",
-                                starboard::GetMediaAudioCodecName(audio_codec))
-            .c_str());
-    return kSbPlayerInvalid;
+  if (audio_codec != kSbMediaAudioCodecNone) {
+    const MimeType audio_mime_type(audio_mime);
+    if (!SbMediaIsAudioSupported(audio_codec, &audio_mime_type,
+                                 kDefaultBitRate)) {
+      SB_LOG(ERROR) << "Unsupported audio codec "
+                    << starboard::GetMediaAudioCodecName(audio_codec) << ".";
+      player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode,
+                        starboard::FormatString(
+                            "Unsupported audio codec: %s",
+                            starboard::GetMediaAudioCodecName(audio_codec))
+                            .c_str());
+      return kSbPlayerInvalid;
+    }
   }
 
   const int kDefaultProfile = -1;
@@ -153,22 +157,24 @@
   const int kDefaultFrameWidth = 0;
   const int kDefaultFrameHeight = 0;
   const int kDefaultFrameRate = 0;
-  if (video_codec != kSbMediaVideoCodecNone &&
-      !SbMediaIsVideoSupported(
-          video_codec, video_mime, kDefaultProfile, kDefaultLevel,
-          kDefaultColorDepth, kSbMediaPrimaryIdUnspecified,
-          kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified,
-          kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate,
-          kDefaultFrameRate,
-          output_mode == kSbPlayerOutputModeDecodeToTexture)) {
-    SB_LOG(ERROR) << "Unsupported video codec "
-                  << starboard::GetMediaVideoCodecName(video_codec) << ".";
-    player_error_func(
-        kSbPlayerInvalid, context, kSbPlayerErrorDecode,
-        starboard::FormatString("Unsupported video codec: %s",
-                                starboard::GetMediaVideoCodecName(video_codec))
-            .c_str());
-    return kSbPlayerInvalid;
+  if (video_codec != kSbMediaVideoCodecNone) {
+    const MimeType video_mime_type(video_mime);
+    if (!SbMediaIsVideoSupported(
+            video_codec, &video_mime_type, kDefaultProfile, kDefaultLevel,
+            kDefaultColorDepth, kSbMediaPrimaryIdUnspecified,
+            kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified,
+            kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate,
+            kDefaultFrameRate,
+            output_mode == kSbPlayerOutputModeDecodeToTexture)) {
+      SB_LOG(ERROR) << "Unsupported video codec "
+                    << starboard::GetMediaVideoCodecName(video_codec) << ".";
+      player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode,
+                        starboard::FormatString(
+                            "Unsupported video codec: %s",
+                            starboard::GetMediaVideoCodecName(video_codec))
+                            .c_str());
+      return kSbPlayerInvalid;
+    }
   }
 
   if (audio_codec != kSbMediaAudioCodecNone && !audio_sample_info) {
diff --git a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc b/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc
deleted file mode 100644
index c841163..0000000
--- a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/shared/starboard/thread_local_storage_external_access_hack_public.h"
-
-#include "starboard/shared/starboard/thread_local_storage_internal.h"
-
-void StarboardSharedTLSKeyManagerInitializeTLSForThread() {
-  starboard::shared::TLSKeyManager::Get()->InitializeTLSForThread();
-}
-
-void StarboardSharedTLSKeyManagerShutdownTLSForThread() {
-  starboard::shared::TLSKeyManager::Get()->ShutdownTLSForThread();
-}
diff --git a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h b/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h
deleted file mode 100644
index 003869b..0000000
--- a/starboard/shared/starboard/thread_local_storage_external_access_hack_public.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_
-#define STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_
-
-// This file defines "backdoor" functions that allow external libraries access
-// to internal functions.  In particular, this is currently being used by Cobalt
-// on some non-Starboard platforms to gain access to thread local storage thread
-// initialization and shutdown functions since some platforms are only "kinda"
-// using Starboard right now, via glimp.  Since most of non-Starboard Cobalt
-// creates threads through pthreads, not Starboard, we need to call these
-// functions on newly created pthread-created threads.
-
-// TODO: This file should ABSOLUTELY be removed as soon as all platforms are
-//       fully up and running on Starboard.
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void StarboardSharedTLSKeyManagerInitializeTLSForThread();
-void StarboardSharedTLSKeyManagerShutdownTLSForThread();
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif  // STARBOARD_SHARED_STARBOARD_THREAD_LOCAL_STORAGE_EXTERNAL_ACCESS_HACK_PUBLIC_H_
diff --git a/starboard/shared/starboard/thread_local_storage_internal.cc b/starboard/shared/starboard/thread_local_storage_internal.cc
index 02a1763..55bfb03 100644
--- a/starboard/shared/starboard/thread_local_storage_internal.cc
+++ b/starboard/shared/starboard/thread_local_storage_internal.cc
@@ -284,54 +284,6 @@
   return data_->key_table_[key->index].values[current_thread_id];
 }
 
-void TLSKeyManager::InitializeTLSForThread() {
-  int current_thread_id = GetCurrentThreadId();
-
-  ScopedLock lock(mutex_);
-
-  const size_t table_size = data_->key_table_.size();
-  for (int i = 0; i < table_size; ++i) {
-    KeyRecord* key_record = &data_->key_table_[i];
-    if (key_record->valid) {
-      key_record->values[current_thread_id] = NULL;
-    }
-  }
-}
-
-void TLSKeyManager::ShutdownTLSForThread() {
-  int current_thread_id = GetCurrentThreadId();
-
-  ScopedLock lock(mutex_);
-
-  // Apply the destructors multiple times (4 is the minimum value
-  // according to the specifications).  This is necessary if one of
-  // the destructors adds new values to the key map.
-  for (int d = 0; d < 4; ++d) {
-    // Move the map into a new temporary map so that we can iterate
-    // through that while the original s_tls_thread_keys may have more
-    // values added to it via destructor calls.
-    const size_t table_size = data_->key_table_.size();
-
-    for (int i = 0; i < table_size; ++i) {
-      KeyRecord* key_record = &data_->key_table_[i];
-      if (key_record->valid) {
-        void* value = key_record->values[current_thread_id];
-        key_record->values[current_thread_id] = NULL;
-        SbThreadLocalDestructor destructor = key_record->destructor;
-
-        if (value && destructor) {
-          mutex_.Release();
-          destructor(value);
-          mutex_.Acquire();
-        }
-      }
-    }
-  }
-
-  data_->thread_id_map_.Erase(SbThreadGetId());
-  data_->available_thread_ids_.push_back(current_thread_id);
-}
-
 bool TLSKeyManager::IsKeyActive(SbThreadLocalKey key) {
   return data_->key_table_[key->index].valid;
 }
diff --git a/starboard/shared/starboard/thread_local_storage_internal.h b/starboard/shared/starboard/thread_local_storage_internal.h
index ffa225a..13e988a 100644
--- a/starboard/shared/starboard/thread_local_storage_internal.h
+++ b/starboard/shared/starboard/thread_local_storage_internal.h
@@ -46,12 +46,6 @@
   // Returns the thread local value for the given key.
   void* GetLocalValue(SbThreadLocalKey key);
 
-  // Called whenever a thread is created.
-  void InitializeTLSForThread();
-
-  // Called whenever a thread is destroyed.
-  void ShutdownTLSForThread();
-
  private:
   // We add 1 to kSbMaxThreads here to account for the main thread.
   static const int kMaxThreads = kSbMaxThreads + 1;
diff --git a/starboard/shared/stub/media_is_audio_supported.cc b/starboard/shared/stub/media_is_audio_supported.cc
index d9c70c5..853eca0 100644
--- a/starboard/shared/stub/media_is_audio_supported.cc
+++ b/starboard/shared/stub/media_is_audio_supported.cc
@@ -16,8 +16,10 @@
 
 #include "starboard/media.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int64_t bitrate) {
   return false;
 }
diff --git a/starboard/shared/stub/media_is_video_supported.cc b/starboard/shared/stub/media_is_video_supported.cc
index e16ee53..1049791 100644
--- a/starboard/shared/stub/media_is_video_supported.cc
+++ b/starboard/shared/stub/media_is_video_supported.cc
@@ -16,8 +16,10 @@
 
 #include "starboard/media.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int profile,
                              int level,
                              int bit_depth,
diff --git a/starboard/shared/win32/media_is_audio_supported.cc b/starboard/shared/win32/media_is_audio_supported.cc
index b1c99f3..f838ac3 100644
--- a/starboard/shared/win32/media_is_audio_supported.cc
+++ b/starboard/shared/win32/media_is_audio_supported.cc
@@ -18,8 +18,10 @@
 #include "starboard/configuration_constants.h"
 #include "starboard/media.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int64_t bitrate) {
   if (audio_codec != kSbMediaAudioCodecAac &&
       audio_codec != kSbMediaAudioCodecOpus) {
diff --git a/starboard/shared/win32/media_is_video_supported.cc b/starboard/shared/win32/media_is_video_supported.cc
index 4b49b55..b7af5ce 100644
--- a/starboard/shared/win32/media_is_video_supported.cc
+++ b/starboard/shared/win32/media_is_video_supported.cc
@@ -22,16 +22,14 @@
 #include "starboard/configuration_constants.h"
 #include "starboard/shared/starboard/media/media_util.h"
 
+using ::starboard::shared::starboard::media::MimeType;
+
 namespace {
 
 #if SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION || \
     defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT)
 // Cache the VP9 support status since the check may be expensive.
-enum Vp9Support {
-  kVp9SupportUnknown,
-  kVp9SupportYes,
-  kVp9SupportNo
-};
+enum Vp9Support { kVp9SupportUnknown, kVp9SupportYes, kVp9SupportNo };
 Vp9Support s_vp9_support = kVp9SupportUnknown;
 
 // Check for VP9 support. Since this is used by a starboard function, it
@@ -76,7 +74,7 @@
   return s_vp9_support == kVp9SupportYes;
 }
 #else   // SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION ||
-        // defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT)
+// defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT)
 bool IsVp9Supported() {
   return false;
 }
@@ -86,7 +84,7 @@
 }  // namespace
 
 bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
-                             const char* content_type,
+                             const MimeType* mime_type,
                              int profile,
                              int level,
                              int bit_depth,
@@ -105,18 +103,18 @@
   int max_height = 1080;
 
   if (video_codec == kSbMediaVideoCodecVp9) {
-    // Vp9 supports 8k only in whitelisted platforms, up to 4k in the others.
+// Vp9 supports 8k only in whitelisted platforms, up to 4k in the others.
 #ifdef ENABLE_VP9_8K_SUPPORT
     max_width = 7680;
     max_height = 4320;
-#else  // ENABLE_VP9_8K_SUPPORT
+#else   // ENABLE_VP9_8K_SUPPORT
     max_width = 3840;
     max_height = 2160;
 #endif  // ENABLE_VP9_8K_SUPPORT
   } else if (video_codec == kSbMediaVideoCodecH264) {
-    // Not all devices can support 4k H264; some (e.g. xb1) may crash in
-    // the decoder if provided too high of a resolution. Therefore
-    // platforms must explicitly opt-in to support 4k H264.
+// Not all devices can support 4k H264; some (e.g. xb1) may crash in
+// the decoder if provided too high of a resolution. Therefore
+// platforms must explicitly opt-in to support 4k H264.
 #ifdef ENABLE_H264_4K_SUPPORT
     max_width = 3840;
     max_height = 2160;
diff --git a/third_party/crashpad/handler/BUILD.gn b/third_party/crashpad/handler/BUILD.gn
index 9442c21..10582a8 100644
--- a/third_party/crashpad/handler/BUILD.gn
+++ b/third_party/crashpad/handler/BUILD.gn
@@ -170,6 +170,7 @@
 
   crashpad_executable("crashpad_handler") {
     if (crashpad_is_in_starboard) {
+      install_target = !crashpad_is_android
       check_includes = false
       data_deps = [ "//third_party/icu:icudata" ]
     }
diff --git a/third_party/libvpx/.gitignore b/third_party/libvpx/.gitignore
index 5f26835..aeb6d58 100644
--- a/third_party/libvpx/.gitignore
+++ b/third_party/libvpx/.gitignore
@@ -1,5 +1,6 @@
 *.S
 *.a
+!/platforms/*/libvpx.a
 *.asm.s
 *.d
 *.gcda
diff --git a/third_party/opus/.gitignore b/third_party/opus/.gitignore
index eccd0a2..8cf3ed1 100644
--- a/third_party/opus/.gitignore
+++ b/third_party/opus/.gitignore
@@ -9,6 +9,7 @@
 compile
 config.guess
 config.h
+!/*/config.h
 config.h.in
 config.log
 config.status
@@ -67,6 +68,7 @@
 doc/man
 package_version
 version.h
+!/*/version.h
 celt/Debug
 celt/Release
 celt/x64