Import Cobalt 23.lts.1.308713
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/bindings/contexts.py b/cobalt/bindings/contexts.py
index 9285877..fb03640 100644
--- a/cobalt/bindings/contexts.py
+++ b/cobalt/bindings/contexts.py
@@ -244,7 +244,8 @@
             not idl_type.is_callback_interface), 'Callback types not supported.'
     element_cobalt_type = self.idl_type_to_cobalt_type(
         self.resolve_typedef(result_idl_type))
-    result = '::cobalt::script::Promise< %s >' % element_cobalt_type
+    result = 'std::unique_ptr<::cobalt::script::Promise< %s* > >' % (
+        element_cobalt_type)
     return result
 
   def idl_union_type_to_cobalt(self, idl_type):
diff --git a/cobalt/bindings/v8c/templates/interface.cc.template b/cobalt/bindings/v8c/templates/interface.cc.template
index a11e229..65f703c 100644
--- a/cobalt/bindings/v8c/templates/interface.cc.template
+++ b/cobalt/bindings/v8c/templates/interface.cc.template
@@ -40,6 +40,7 @@
 #include "cobalt/script/v8c/entry_scope.h"
 #include "cobalt/script/v8c/helpers.h"
 #include "cobalt/script/v8c/native_promise.h"
+#include "cobalt/script/v8c/script_promise.h"
 #include "cobalt/script/v8c/type_traits.h"
 #include "cobalt/script/v8c/v8c_typed_arrays.h"
 #include "cobalt/script/v8c/v8c_data_view.h"
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.js b/cobalt/black_box_tests/testdata/service_worker_test.js
index 44ffc55..5b9e117 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test.js
+++ b/cobalt/black_box_tests/testdata/service_worker_test.js
@@ -53,10 +53,10 @@
     self.clients.matchAll(options).then(function (clients) {
       console.log('(Expected) self.clients.matchAll():', clients.length, clients);
       for (var i = 0; i < clients.length; i++) {
-        console.log('Client with url', clients[i].url);
-        console.log('Client with frameType', clients[i].frameType);
-        console.log('Client with id', clients[i].id);
-        console.log('Client with type', clients[i].type);
+        console.log('Client with url', clients[i].url,
+          'frameType', clients[i].frameType,
+          'id', clients[i].id,
+          'type', clients[i].type);
       }
     }, function (error) {
       console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
@@ -106,10 +106,10 @@
   console.log('(Expected) self.clients.matchAll():', clients.length, clients);
   // Note: This will return 0 clients if none are controlled so far.
   for (var i = 0; i < clients.length; i++) {
-    console.log('Client with url', clients[i].url);
-    console.log('Client with frameType', clients[i].frameType);
-    console.log('Client with id', clients[i].id);
-    console.log('Client with type', clients[i].type);
+    console.log('Client with url', clients[i].url,
+      'frameType', clients[i].frameType,
+      'id', clients[i].id,
+      'type', clients[i].type);
   }
 }, function (error) {
   console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
@@ -123,18 +123,18 @@
 self.clients.matchAll(options).then(function (clients) {
   console.log('(Expected) self.clients.matchAll():', clients.length, clients);
   for (var i = 0; i < clients.length; i++) {
-    console.log('Client with url', clients[i].url);
-    console.log('Client with frameType', clients[i].frameType);
-    console.log('Client with id', clients[i].id);
-    console.log('Client with type', clients[i].type);
+    console.log('Client with url', clients[i].url,
+      'frameType', clients[i].frameType,
+      'id', clients[i].id,
+      'type', clients[i].type);
 
     console.log('self.clients.get()');
     self.clients.get(clients[i].id).then(function (client) {
       console.log('(Expected) self.clients.get():', client);
-      console.log('Client with url', client.url);
-      console.log('Client with frameType', client.frameType);
-      console.log('Client with id', client.id);
-      console.log('Client with type', client.type);
+      console.log('Client with url', client.url,
+        'frameType', client.frameType,
+        'id', client.id,
+        'type', client.type);
     }, function (error) {
       console.log(`(Unexpected) self.clients.get(): ${error}`, error);
     });
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
index cc0fcde..21cb0d7 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
+++ b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
@@ -27,37 +27,47 @@
   });
 }
 
+function delay_promise(delay) {
+  return new Promise(function (resolve) {
+    setTimeout(resolve.bind(null), delay)
+  });
+}
+
 self.oninstall = function (e) {
   console.log('oninstall event received', e);
+  e.waitUntil(delay_promise(500).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n')));
 }
+
 self.onactivate = function (e) {
   console.log('onactivate event received', e);
 
   // Claim should pass here, since the state is activating.
   console.log('self.clients.claim()');
-  self.clients.claim().then(function (result) {
+  e.waitUntil(self.clients.claim().then(function (result) {
     console.log('(Expected) self.clients.claim():', result);
 
     var options = {
       includeUncontrolled: false, type: 'window'
     };
-    console.log('self.clients.matchAll(options)');
-    self.clients.matchAll(options).then(function (clients) {
-      console.log('(Expected) self.clients.matchAll():', clients.length, clients);
-      for (var i = 0; i < clients.length; i++) {
-        console.log('Client with url', clients[i].url);
-        console.log('Client with frameType', clients[i].frameType);
-        console.log('Client with id', clients[i].id);
-        console.log('Client with type', clients[i].type);
-        clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`);
-      }
-    }, function (error) {
-      console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
-    });
+    e.waitUntil(delay_promise(1000).then(function () {
+      console.log('self.clients.matchAll(options)');
+      e.waitUntil(self.clients.matchAll(options).then(function (clients) {
+        console.log('(Expected) self.clients.matchAll():', clients.length, clients);
+        for (var i = 0; i < clients.length; i++) {
+          console.log('Client with url', clients[i].url,
+            'frameType', clients[i].frameType,
+            'id', clients[i].id,
+            'type', clients[i].type);
+          clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`);
+        }
+      }, function (error) {
+        console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
+      }));
+    }));
 
   }, function (error) {
     console.log(`(Unexpected) self.clients.claim(): ${error}`, error);
-  });
+  }));
 
 }
 console.log('self.registration', self.registration);
@@ -73,10 +83,10 @@
 self.clients.matchAll(options).then(function (clients) {
   console.log('(Expected) self.clients.matchAll():', clients.length, clients);
   for (var i = 0; i < clients.length; i++) {
-    console.log('Client with url', clients[i].url);
-    console.log('Client with frameType', clients[i].frameType);
-    console.log('Client with id', clients[i].id);
-    console.log('Client with type', clients[i].type);
+    console.log('Client with url', clients[i].url,
+      'frameType', clients[i].frameType,
+      'id', clients[i].id,
+      'type', clients[i].type);
   }
 }, function (error) {
   console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 3816d70..e3ca3a6 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -158,6 +158,7 @@
     "//cobalt/browser/memory_settings:browser_memory_settings",
     "//cobalt/browser/memory_tracker:memory_tracker_tool",
     "//cobalt/build:cobalt_build_id",
+    "//cobalt/cache",
     "//cobalt/configuration",
     "//cobalt/css_parser",
     "//cobalt/cssom",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index d710cae..5fa7318 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -61,6 +61,7 @@
 #include "cobalt/browser/switches.h"
 #include "cobalt/browser/user_agent_platform_info.h"
 #include "cobalt/browser/user_agent_string.h"
+#include "cobalt/cache/cache.h"
 #include "cobalt/configuration/configuration.h"
 #include "cobalt/extension/crash_handler.h"
 #include "cobalt/extension/installation_manager.h"
@@ -648,6 +649,9 @@
       watchdog::Watchdog::CreateInstance(persistent_settings_.get());
   DCHECK(watchdog);
 
+  cobalt::cache::Cache::GetInstance()->set_persistent_settings(
+      persistent_settings_.get());
+
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   base::Optional<cssom::ViewportSize> requested_viewport_size =
       GetRequestedViewportSize(command_line);
@@ -661,6 +665,7 @@
   // Create the main components of our browser.
   BrowserModule::Options options(web_options);
   network_module_options.preferred_language = language;
+  network_module_options.persistent_settings = persistent_settings_.get();
   options.persistent_settings = persistent_settings_.get();
   options.command_line_auto_mem_settings =
       memory_settings::GetSettings(*command_line);
@@ -860,7 +865,7 @@
 #if SB_IS(EVERGREEN)
       updater_module_.get(),
 #endif
-      options, persistent_settings_.get()));
+      options));
 
   UpdateUserAgent();
 
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index 8b7b1d4..79dd071 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -219,16 +219,15 @@
 
 }  // namespace
 
-BrowserModule::BrowserModule(
-    const GURL& url, base::ApplicationState initial_application_state,
-    base::EventDispatcher* event_dispatcher,
-    account::AccountManager* account_manager,
-    network::NetworkModule* network_module,
+BrowserModule::BrowserModule(const GURL& url,
+                             base::ApplicationState initial_application_state,
+                             base::EventDispatcher* event_dispatcher,
+                             account::AccountManager* account_manager,
+                             network::NetworkModule* network_module,
 #if SB_IS(EVERGREEN)
-    updater::UpdaterModule* updater_module,
+                             updater::UpdaterModule* updater_module,
 #endif
-    const Options& options,
-    persistent_storage::PersistentSettings* persistent_settings)
+                             const Options& options)
     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       ALLOW_THIS_IN_INITIALIZER_LIST(
           weak_this_(weak_ptr_factory_.GetWeakPtr())),
@@ -298,8 +297,7 @@
       next_timeline_id_(1),
       current_splash_screen_timeline_id_(-1),
       current_main_web_module_timeline_id_(-1),
-      service_worker_registry_(network_module),
-      persistent_settings_(persistent_settings) {
+      service_worker_registry_(network_module) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()");
 
   // Apply platform memory setting adjustments and defaults.
@@ -2075,7 +2073,7 @@
       dom_settings->window()->navigator()->user_agent_data();
   h5vcc_settings.global_environment =
       dom_settings->context()->global_environment();
-  h5vcc_settings.persistent_settings = persistent_settings_;
+  h5vcc_settings.persistent_settings = options_.persistent_settings;
 
   auto* h5vcc_object = new h5vcc::H5vcc(h5vcc_settings);
   if (!web_module_created_callback_.is_null()) {
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 0706039..a12551e 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -131,8 +131,7 @@
 #if SB_IS(EVERGREEN)
                 updater::UpdaterModule* updater_module,
 #endif
-                const Options& options,
-                persistent_storage::PersistentSettings* persistent_settings);
+                const Options& options);
   ~BrowserModule();
 
   std::string GetUserAgent() { return network_module_->GetUserAgent(); }
@@ -724,8 +723,6 @@
 
   // Manages the Service Workers.
   ServiceWorkerRegistry service_worker_registry_;
-
-  persistent_storage::PersistentSettings* persistent_settings_;
 };
 
 }  // namespace browser
diff --git a/cobalt/build/ninja/README b/cobalt/build/ninja/README
deleted file mode 100644
index fe0f765..0000000
--- a/cobalt/build/ninja/README
+++ /dev/null
@@ -1,7 +0,0 @@
-ninja-win.exe built with MSVC 2012
-from
-https://github.com/REDACTED/ninja/commit/1287257fe343c4b6b0f760308a7bec52d86b0edf
-
-ninja-linux built with gcc
-from
-https://github.com/REDACTED/ninja/commit/613d89893e56acc8c3670a66a33fcd2caf7d9050
diff --git a/cobalt/build/ninja/ninja-linux b/cobalt/build/ninja/ninja-linux
deleted file mode 100755
index 1f30022..0000000
--- a/cobalt/build/ninja/ninja-linux
+++ /dev/null
Binary files differ
diff --git a/cobalt/build/ninja/ninja-linux32.armv7l b/cobalt/build/ninja/ninja-linux32.armv7l
deleted file mode 100644
index cb4c3ec..0000000
--- a/cobalt/build/ninja/ninja-linux32.armv7l
+++ /dev/null
Binary files differ
diff --git a/cobalt/build/ninja/ninja-win.exe b/cobalt/build/ninja/ninja-win.exe
deleted file mode 100755
index 4a68328..0000000
--- a/cobalt/build/ninja/ninja-win.exe
+++ /dev/null
Binary files differ
diff --git a/cobalt/cache/BUILD.gn b/cobalt/cache/BUILD.gn
index 7fe8ddd..edc1daf 100644
--- a/cobalt/cache/BUILD.gn
+++ b/cobalt/cache/BUILD.gn
@@ -22,6 +22,7 @@
     "//base",
     "//cobalt/base",
     "//cobalt/configuration",
+    "//cobalt/persistent_storage:persistent_settings",
     "//net",
     "//starboard:starboard_headers_only",
   ]
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc
index 0600895..e2c568d 100644
--- a/cobalt/cache/cache.cc
+++ b/cobalt/cache/cache.cc
@@ -24,6 +24,8 @@
 #include "base/optional.h"
 #include "cobalt/configuration/configuration.h"
 #include "cobalt/extension/javascript_cache.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
+#include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/system.h"
 
@@ -86,14 +88,6 @@
   return nullptr;
 }
 
-bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size) {
-  return cobalt::configuration::Configuration::GetInstance()
-             ->CobaltCanStoreCompiledJavascript() &&
-         data_size > 0u &&
-         data_size >= GetMinSizeToCacheInBytes(resource_type) &&
-         data_size <= GetMaxCacheStorageInBytes(resource_type);
-}
-
 }  // namespace
 
 namespace cobalt {
@@ -158,6 +152,19 @@
   return data;
 }
 
+void Cache::set_enabled(bool enabled) { enabled_ = enabled; }
+
+void Cache::set_persistent_settings(
+    persistent_storage::PersistentSettings* persistent_settings) {
+  persistent_settings_ = persistent_settings;
+
+  // Guaranteed to be called before any calls to Retrieve()
+  // since set_persistent_settings() is called from the Application()
+  // constructor before the NetworkModule is initialized.
+  set_enabled(persistent_settings_->GetPersistentSettingAsBool(
+      disk_cache::kCacheEnabledPersistentSettingsKey, true));
+}
+
 MemoryCappedDirectory* Cache::GetMemoryCappedDirectory(
     disk_cache::ResourceType resource_type) {
   base::AutoLock auto_lock(lock_);
@@ -221,5 +228,15 @@
   }
 }
 
+bool Cache::CanCache(disk_cache::ResourceType resource_type,
+                     uint32_t data_size) {
+  return enabled_ &&
+         cobalt::configuration::Configuration::GetInstance()
+             ->CobaltCanStoreCompiledJavascript() &&
+         data_size > 0u &&
+         data_size >= GetMinSizeToCacheInBytes(resource_type) &&
+         data_size <= GetMaxCacheStorageInBytes(resource_type);
+}
+
 }  // namespace cache
 }  // namespace cobalt
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h
index 790da73..4f9e4fe 100644
--- a/cobalt/cache/cache.h
+++ b/cobalt/cache/cache.h
@@ -27,6 +27,7 @@
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "cobalt/cache/memory_capped_directory.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/resource_type.h"
 
 namespace base {
@@ -45,6 +46,11 @@
       disk_cache::ResourceType resource_type, uint32_t key,
       std::function<std::unique_ptr<std::vector<uint8_t>>()> generate);
 
+  void set_enabled(bool enabled);
+
+  void set_persistent_settings(
+      persistent_storage::PersistentSettings* persistent_settings);
+
  private:
   friend struct base::DefaultSingletonTraits<Cache>;
   Cache() {}
@@ -56,6 +62,7 @@
   void Notify(disk_cache::ResourceType resource_type, uint32_t key);
   void TryStore(disk_cache::ResourceType resource_type, uint32_t key,
                 const std::vector<uint8_t>& data);
+  bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size);
 
   mutable base::Lock lock_;
   // The following map is only used when the JavaScript cache extension is
@@ -65,6 +72,9 @@
   std::map<disk_cache::ResourceType,
            std::map<uint32_t, std::vector<base::WaitableEvent*>>>
       pending_;
+  bool enabled_;
+
+  persistent_storage::PersistentSettings* persistent_settings_;
 
   DISALLOW_COPY_AND_ASSIGN(Cache);
 };  // class Cache
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 74eafb0..00d23ae 100644
--- a/cobalt/evergreen_tests/evergreen_tests.py
+++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -31,6 +31,7 @@
 
 
 def _Exec(cmd, env=None):
+  """Executes a command in a subprocess and returns the result."""
   try:
     msg = 'Executing:\n    ' + ' '.join(cmd)
     logging.info(msg)
@@ -46,31 +47,8 @@
     return 1
 
 
-def main():
-  arg_parser = argparse.ArgumentParser()
-  arg_parser.add_argument(
-      '--no-can_mount_tmpfs',
-      dest='can_mount_tmpfs',
-      action='store_false',
-      help='A temporary filesystem cannot be mounted on the target device.')
-  arg_parser.add_argument(
-      '--platform_under_test',
-      default=_DEFAULT_PLATFORM_UNDER_TEST,
-      help='The platform to run the tests on (e.g., linux or raspi).')
-  authentication_method = arg_parser.add_mutually_exclusive_group()
-  authentication_method.add_argument(
-      '--public-key-auth',
-      help='Public key authentication should be used with the remote device.',
-      action='store_true')
-  authentication_method.add_argument(
-      '--password-auth',
-      help='Password authentication should be used with the remote device.',
-      action='store_true')
-  command_line.AddLauncherArguments(arg_parser)
-  args = arg_parser.parse_args()
-
-  log_level.InitializeLogging(args)
-
+def _RunTests(arg_parser, args, use_compressed_system_image):
+  """Runs an instance of the Evergreen tests for the provided configuration."""
   launcher_params = command_line.CreateLauncherParams(arg_parser)
 
   # Creating an instance of the Evergreen abstract launcher implementation
@@ -86,7 +64,8 @@
       loader_platform=launcher_params.loader_platform,
       loader_config=launcher_params.loader_config,
       loader_target='loader_app',
-      loader_out_directory=launcher_params.loader_out_directory)
+      loader_out_directory=launcher_params.loader_out_directory,
+      use_compressed_library=use_compressed_system_image)
 
   # The automated tests use the |OUT| environment variable as the path to a
   # known directory structure containing the desired binaries. This path is
@@ -118,10 +97,52 @@
     command.append('-a')
     command.append('password')
 
+  if use_compressed_system_image:
+    command.append('-c')
+
   command.append(args.platform_under_test)
 
   return _Exec(command, env)
 
 
+def main():
+  arg_parser = argparse.ArgumentParser()
+  arg_parser.add_argument(
+      '--no-can_mount_tmpfs',
+      dest='can_mount_tmpfs',
+      action='store_false',
+      help='A temporary filesystem cannot be mounted on the target device.')
+  arg_parser.add_argument(
+      '--platform_under_test',
+      default=_DEFAULT_PLATFORM_UNDER_TEST,
+      help='The platform to run the tests on (e.g., linux or raspi).')
+  authentication_method = arg_parser.add_mutually_exclusive_group()
+  authentication_method.add_argument(
+      '--public-key-auth',
+      help='Public key authentication should be used with the remote device.',
+      action='store_true')
+  authentication_method.add_argument(
+      '--password-auth',
+      help='Password authentication should be used with the remote device.',
+      action='store_true')
+  arg_parser.add_argument(
+      '--no-rerun_using_compressed_system_image',
+      dest='rerun_using_compressed_system_image',
+      action='store_false',
+      help='Do not run a second instance of the tests with a compressed system '
+      'image.')
+  command_line.AddLauncherArguments(arg_parser)
+  args = arg_parser.parse_args()
+
+  log_level.InitializeLogging(args)
+
+  uncompressed_system_image_result = _RunTests(arg_parser, args, False)
+  if args.rerun_using_compressed_system_image:
+    compressed_system_image_result = _RunTests(arg_parser, args, True)
+    return uncompressed_system_image_result or compressed_system_image_result
+
+  return uncompressed_system_image_result
+
+
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index 246c396..9c5a145 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -76,6 +76,7 @@
     "//cobalt/base",
     "//cobalt/browser:browser_switches",
     "//cobalt/build:cobalt_build_id",
+    "//cobalt/cache",
     "//cobalt/configuration",
     "//cobalt/dom",
     "//cobalt/media",
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/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index e9a9229..dbb6b3b 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -19,10 +19,14 @@
 
 #include "base/files/file_util.h"
 #include "base/values.h"
+#include "cobalt/cache/cache.h"
 #include "cobalt/h5vcc/h5vcc_storage.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/storage/storage_manager.h"
+#include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "net/disk_cache/cobalt/resource_type.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_transaction_factory.h"
 
 #include "starboard/common/file.h"
 #include "starboard/common/string.h"
@@ -31,6 +35,7 @@
 namespace h5vcc {
 
 namespace {
+
 const char kTestFileName[] = "cache_test_file.json";
 
 const uint32 kWriteBufferSize = 1024 * 1024;
@@ -69,7 +74,16 @@
     network::NetworkModule* network_module,
     persistent_storage::PersistentSettings* persistent_settings)
     : network_module_(network_module),
-      persistent_settings_(persistent_settings) {}
+      persistent_settings_(persistent_settings) {
+  http_cache_ = nullptr;
+  if (network_module == nullptr) {
+    return;
+  }
+  auto url_request_context = network_module_->url_request_context();
+  if (url_request_context->using_http_cache()) {
+    http_cache_ = url_request_context->http_transaction_factory()->GetCache();
+  }
+}
 
 void H5vccStorage::ClearCookies() {
   net::CookieStore* cookie_store =
@@ -334,5 +348,29 @@
   return quota;
 }
 
+void H5vccStorage::EnableCache() {
+  persistent_settings_->SetPersistentSetting(
+      disk_cache::kCacheEnabledPersistentSettingsKey,
+      std::make_unique<base::Value>(true));
+
+  cobalt::cache::Cache::GetInstance()->set_enabled(true);
+
+  if (http_cache_) {
+    http_cache_->set_mode(net::HttpCache::Mode::NORMAL);
+  }
+}
+
+void H5vccStorage::DisableCache() {
+  persistent_settings_->SetPersistentSetting(
+      disk_cache::kCacheEnabledPersistentSettingsKey,
+      std::make_unique<base::Value>(false));
+
+  cobalt::cache::Cache::GetInstance()->set_enabled(false);
+
+  if (http_cache_) {
+    http_cache_->set_mode(net::HttpCache::Mode::DISABLE);
+  }
+}
+
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_storage.h b/cobalt/h5vcc/h5vcc_storage.h
index 7f6368b..ff96351 100644
--- a/cobalt/h5vcc/h5vcc_storage.h
+++ b/cobalt/h5vcc/h5vcc_storage.h
@@ -26,6 +26,7 @@
 #include "cobalt/network/network_module.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/script/wrappable.h"
+#include "net/http/http_cache.h"
 
 namespace cobalt {
 namespace h5vcc {
@@ -56,6 +57,10 @@
   H5vccStorageSetQuotaResponse SetQuota(
       H5vccStorageResourceTypeQuotaBytesDictionary quota);
 
+  void EnableCache();
+
+  void DisableCache();
+
   DEFINE_WRAPPABLE_TYPE(H5vccStorage);
 
  private:
@@ -63,6 +68,8 @@
 
   persistent_storage::PersistentSettings* persistent_settings_;
 
+  net::HttpCache* http_cache_;
+
   DISALLOW_COPY_AND_ASSIGN(H5vccStorage);
 };
 
diff --git a/cobalt/h5vcc/h5vcc_storage.idl b/cobalt/h5vcc/h5vcc_storage.idl
index 3a45910..93a6d37 100644
--- a/cobalt/h5vcc/h5vcc_storage.idl
+++ b/cobalt/h5vcc/h5vcc_storage.idl
@@ -24,4 +24,7 @@
 
   H5vccStorageResourceTypeQuotaBytesDictionary getQuota();
   H5vccStorageSetQuotaResponse setQuota(H5vccStorageResourceTypeQuotaBytesDictionary quota);
+
+  void enableCache();
+  void disableCache();
 };
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index a68d213..53821e9 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -51,6 +51,7 @@
     "//cobalt/build:cobalt_build_id",
     "//cobalt/configuration",
     "//cobalt/network_bridge",
+    "//cobalt/persistent_storage:persistent_settings",
     "//cobalt/storage",
     "//starboard/common",
     "//third_party/protobuf:protobuf_lite",
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc
index 4eae9a8..f192961 100644
--- a/cobalt/network/network_module.cc
+++ b/cobalt/network/network_module.cc
@@ -178,7 +178,8 @@
 #endif
   url_request_context_.reset(
       new URLRequestContext(storage_manager_, options_.custom_proxy, net_log,
-                            options_.ignore_certificate_errors, task_runner()));
+                            options_.ignore_certificate_errors, task_runner(),
+                            options_.persistent_settings));
   network_delegate_.reset(
       new NetworkDelegate(options_.cookie_policy, options_.https_requirement));
   url_request_context_->set_http_user_agent_settings(
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h
index be655f9..ee04620 100644
--- a/cobalt/network/network_module.h
+++ b/cobalt/network/network_module.h
@@ -28,6 +28,7 @@
 #include "cobalt/network/network_delegate.h"
 #include "cobalt/network/url_request_context.h"
 #include "cobalt/network/url_request_context_getter.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/base/static_cookie_policy.h"
 #include "url/gurl.h"
 #if defined(DIAL_SERVER)
@@ -60,13 +61,15 @@
           ignore_certificate_errors(false),
           https_requirement(network::kHTTPSRequired),
           preferred_language("en-US"),
-          max_network_delay(0) {}
+          max_network_delay(0),
+          persistent_settings(nullptr) {}
     net::StaticCookiePolicy::Type cookie_policy;
     bool ignore_certificate_errors;
     HTTPSRequirement https_requirement;
     std::string preferred_language;
     std::string custom_proxy;
     SbTime max_network_delay;
+    persistent_storage::PersistentSettings* persistent_settings;
   };
 
   // Simple constructor intended to be used only by tests.
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc
index 020410c..34cc482 100644
--- a/cobalt/network/url_request_context.cc
+++ b/cobalt/network/url_request_context.cc
@@ -26,12 +26,14 @@
 #include "cobalt/network/persistent_cookie_store.h"
 #include "cobalt/network/proxy_config_service.h"
 #include "cobalt/network/switches.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/cert/cert_net_fetcher.h"
 #include "net/cert/cert_verifier.h"
 #include "net/cert/cert_verify_proc.h"
 #include "net/cert/ct_policy_enforcer.h"
 #include "net/cert/do_nothing_ct_verifier.h"
 #include "net/cert_net/cert_net_fetcher_impl.h"
+#include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "net/dns/host_cache.h"
 #include "net/http/http_auth_handler_factory.h"
 #include "net/http/http_cache.h"
@@ -68,7 +70,8 @@
 URLRequestContext::URLRequestContext(
     storage::StorageManager* storage_manager, const std::string& custom_proxy,
     net::NetLog* net_log, bool ignore_certificate_errors,
-    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
+    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+    persistent_storage::PersistentSettings* persistent_settings)
     : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this))
 #if defined(ENABLE_DEBUGGER)
       ,
@@ -176,6 +179,8 @@
         std::unique_ptr<net::HttpNetworkLayer>(
             new net::HttpNetworkLayer(storage_.http_network_session())));
   } else {
+    using_http_cache_ = true;
+
     // TODO: Set max size of cache in Starboard.
     const int cache_size_mb = 24;
     auto http_cache = std::make_unique<net::HttpCache>(
@@ -185,6 +190,15 @@
             base::FilePath(std::string(path.data())),
             /* max_bytes */ 1024 * 1024 * cache_size_mb),
         true);
+    if (persistent_settings != nullptr) {
+      auto cache_enabled = persistent_settings->GetPersistentSettingAsBool(
+          disk_cache::kCacheEnabledPersistentSettingsKey, true);
+
+      if (!cache_enabled) {
+        http_cache->set_mode(net::HttpCache::Mode::DISABLE);
+      }
+    }
+
     storage_.set_http_transaction_factory(std::move(http_cache));
   }
 
@@ -214,6 +228,8 @@
   storage_.http_network_session()->SetEnableQuic(enable_quic);
 }
 
+bool URLRequestContext::using_http_cache() { return using_http_cache_; }
+
 #if defined(ENABLE_DEBUGGER)
 void URLRequestContext::OnQuicToggle(const std::string& message) {
   DCHECK(storage_.http_network_session());
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h
index 074fcdb..8f673c3 100644
--- a/cobalt/network/url_request_context.h
+++ b/cobalt/network/url_request_context.h
@@ -20,6 +20,7 @@
 #include "base/basictypes.h"
 #include "base/macros.h"
 #include "base/threading/thread_checker.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/cookies/cookie_monster.h"
 #include "net/log/net_log.h"
 #include "net/url_request/url_request_context.h"
@@ -42,19 +43,24 @@
   URLRequestContext(
       storage::StorageManager* storage_manager, const std::string& custom_proxy,
       net::NetLog* net_log, bool ignore_certificate_errors,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner);
+      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+      persistent_storage::PersistentSettings* persistent_settings);
   ~URLRequestContext() override;
 
   void SetProxy(const std::string& custom_proxy_rules);
 
   void SetEnableQuic(bool enable_quic);
 
+  bool using_http_cache();
+
  private:
   THREAD_CHECKER(thread_checker_);
   net::URLRequestContextStorage storage_;
   scoped_refptr<net::CookieMonster::PersistentCookieStore>
       persistent_cookie_store_;
 
+  bool using_http_cache_;
+
 #if defined(ENABLE_DEBUGGER)
   // Command handler object for toggling the input fuzzer on/off.
   debug::console::ConsoleCommandManager::CommandHandler
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc
index d420c0e..ffebc2a 100644
--- a/cobalt/persistent_storage/persistent_settings.cc
+++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "cobalt/persistent_storage/persistent_settings.h"
+
 #include <utility>
 #include <vector>
 
 #include "base/values.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration_constants.h"
diff --git a/cobalt/script/promise.h b/cobalt/script/promise.h
index 6fa4b54..d9e1c17 100644
--- a/cobalt/script/promise.h
+++ b/cobalt/script/promise.h
@@ -15,6 +15,9 @@
 #ifndef COBALT_SCRIPT_PROMISE_H_
 #define COBALT_SCRIPT_PROMISE_H_
 
+#include <memory>
+
+#include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "cobalt/script/exception_message.h"
 #include "cobalt/script/script_exception.h"
@@ -42,21 +45,19 @@
  public:
   // Call the |resolve| function that was passed as an argument to the Promise's
   // executor function supplying |result| as its argument.
-  virtual void Resolve(const T& result) const { NOTREACHED(); }
+  virtual void Resolve(const T& result) const = 0;
 
   // Call the |reject| function passed as an argument to the Promise's executor
   // function.
-  virtual void Reject() const { NOTREACHED(); }
-  virtual void Reject(SimpleExceptionType exception) const { NOTREACHED(); }
-  virtual void Reject(const scoped_refptr<ScriptException>& result) const {
-    NOTREACHED();
-  }
+  virtual void Reject() const = 0;
+  virtual void Reject(SimpleExceptionType exception) const = 0;
+  virtual void Reject(const scoped_refptr<ScriptException>& result) const = 0;
 
   // Returns the value of the [[PromiseState]] field.
-  virtual PromiseState State() const {
-    NOTREACHED();
-    return PromiseState::kRejected;
-  }
+  virtual PromiseState State() const = 0;
+
+  virtual void AddStateChangeCallback(
+      std::unique_ptr<base::OnceCallback<void()>> callback) = 0;
 
   virtual ~Promise() {}
 };
diff --git a/cobalt/script/v8c/conversion_helpers.h b/cobalt/script/v8c/conversion_helpers.h
index 85aa597..81bf303 100644
--- a/cobalt/script/v8c/conversion_helpers.h
+++ b/cobalt/script/v8c/conversion_helpers.h
@@ -16,7 +16,6 @@
 #define COBALT_SCRIPT_V8C_CONVERSION_HELPERS_H_
 
 #include <cmath>
-
 #include <limits>
 #include <string>
 #include <utility>
@@ -29,6 +28,7 @@
 #include "cobalt/base/compiler.h"
 #include "cobalt/base/enable_if.h"
 #include "cobalt/base/token.h"
+#include "cobalt/script/promise.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/v8c/algorithm_helpers.h"
 #include "cobalt/script/v8c/helpers.h"
@@ -713,11 +713,7 @@
 template <typename T>
 void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value,
                  int conversion_flags, ExceptionState* exception_state,
-                 script::Promise<T>* out_promise) {
-  // TODO(b/228976500): Implement conversion from JS to native for Promise<T>.
-  // https://webidl.spec.whatwg.org/#es-promise
-  NOTIMPLEMENTED();
-}
+                 script::Promise<T>* out_promise);
 
 // script::Handle<T> -> JSValue
 template <typename T>
diff --git a/cobalt/script/v8c/native_promise.h b/cobalt/script/v8c/native_promise.h
index 85069c7..ab8b323 100644
--- a/cobalt/script/v8c/native_promise.h
+++ b/cobalt/script/v8c/native_promise.h
@@ -15,12 +15,15 @@
 #ifndef COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_
 #define COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_
 
+#include <memory>
+
 #include "base/logging.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
 #include "cobalt/script/v8c/entry_scope.h"
 #include "cobalt/script/v8c/scoped_persistent.h"
+#include "cobalt/script/v8c/script_promise.h"
 #include "cobalt/script/v8c/type_traits.h"
 #include "cobalt/script/v8c/v8c_exception_state.h"
 #include "cobalt/script/v8c/v8c_user_object_holder.h"
@@ -40,10 +43,9 @@
   *out_value = v8::Undefined(isolate);
 }
 
-// Shared functionality for NativePromise<T>. Does not implement the Resolve
-// function, since that needs to be specialized for Promise<T>.
+// Shared functionality for NativePromise<T>.
 template <typename T>
-class NativePromise : public ScopedPersistent<v8::Value>, public Promise<T> {
+class NativePromise : public ScriptPromise<T> {
  public:
   // ScriptValue boilerplate.
   typedef Promise<T> BaseType;
@@ -63,20 +65,20 @@
                                 PromiseResultUndefined, T>::type;
 
   NativePromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver)
-      : isolate_(isolate), ScopedPersistent(isolate, resolver) {
+      : ScriptPromise<T>(isolate, resolver) {
     DCHECK(resolver->IsPromise());
   }
 
   void Resolve(const ResolveType& value) const override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!this->IsEmpty());
-    DCHECK(State() == PromiseState::kPending);
-    EntryScope entry_scope(isolate_);
-    v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    DCHECK(this->State() == PromiseState::kPending);
+    EntryScope entry_scope(this->isolate());
+    v8::Local<v8::Context> context = this->isolate()->GetCurrentContext();
 
     v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver();
     v8::Local<v8::Value> converted_value;
-    ToJSValue(isolate_, value, &converted_value);
+    ToJSValue(this->isolate(), value, &converted_value);
     v8::Maybe<bool> reject_result =
         promise_resolver->Resolve(context, converted_value);
     DCHECK(reject_result.FromJust());
@@ -85,25 +87,26 @@
   void Reject() const override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!this->IsEmpty());
-    DCHECK(State() == PromiseState::kPending);
-    EntryScope entry_scope(isolate_);
-    v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    DCHECK(this->State() == PromiseState::kPending);
+    EntryScope entry_scope(this->isolate());
+    v8::Local<v8::Context> context = this->isolate()->GetCurrentContext();
 
     v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver();
     v8::Maybe<bool> reject_result =
-        promise_resolver->Reject(context, v8::Undefined(isolate_));
+        promise_resolver->Reject(context, v8::Undefined(this->isolate()));
     DCHECK(reject_result.FromJust());
   }
 
   void Reject(SimpleExceptionType exception) const override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!this->IsEmpty());
-    DCHECK(State() == PromiseState::kPending);
-    EntryScope entry_scope(isolate_);
-    v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    DCHECK(this->State() == PromiseState::kPending);
+    EntryScope entry_scope(this->isolate());
+    v8::Local<v8::Context> context = this->isolate()->GetCurrentContext();
 
     v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver();
-    v8::Local<v8::Value> error_result = CreateErrorObject(isolate_, exception);
+    v8::Local<v8::Value> error_result =
+        CreateErrorObject(this->isolate(), exception);
     v8::Maybe<bool> reject_result =
         promise_resolver->Reject(context, error_result);
     DCHECK(reject_result.FromJust());
@@ -112,51 +115,22 @@
   void Reject(const scoped_refptr<ScriptException>& result) const override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!this->IsEmpty());
-    DCHECK(State() == PromiseState::kPending);
-    EntryScope entry_scope(isolate_);
-    v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+    DCHECK(this->State() == PromiseState::kPending);
+    EntryScope entry_scope(this->isolate());
+    v8::Local<v8::Context> context = this->isolate()->GetCurrentContext();
 
     v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver();
     v8::Local<v8::Value> converted_result;
-    ToJSValue(isolate_, result, &converted_result);
+    ToJSValue(this->isolate(), result, &converted_result);
     v8::Maybe<bool> reject_result =
         promise_resolver->Reject(context, converted_result);
     DCHECK(reject_result.FromJust());
   }
 
-  PromiseState State() const override {
-    DCHECK(!this->IsEmpty());
-    EntryScope entry_scope(isolate_);
-
-    v8::Promise::PromiseState v8_promise_state = this->promise()->State();
-    switch (v8_promise_state) {
-      case v8::Promise::kPending:
-        return PromiseState::kPending;
-      case v8::Promise::kFulfilled:
-        return PromiseState::kFulfilled;
-      case v8::Promise::kRejected:
-        return PromiseState::kRejected;
-    }
-    NOTREACHED();
-    return PromiseState::kRejected;
-  }
-
-  v8::Local<v8::Promise> promise() const {
-    DCHECK(!this->IsEmpty());
-    return resolver()->GetPromise();
-  }
-
  private:
-  v8::Isolate* isolate_;
-
   // Thread checker ensures all calls to the Promise are made from the same
   // thread that it is created in.
   THREAD_CHECKER(thread_checker_);
-
-  v8::Local<v8::Promise::Resolver> resolver() const {
-    DCHECK(!this->IsEmpty());
-    return this->Get().Get(isolate_).template As<v8::Promise::Resolver>();
-  }
 };
 
 template <typename T>
diff --git a/cobalt/script/v8c/script_promise.h b/cobalt/script/v8c/script_promise.h
new file mode 100644
index 0000000..93c4561
--- /dev/null
+++ b/cobalt/script/v8c/script_promise.h
@@ -0,0 +1,148 @@
+// 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 COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_
+#define COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/threading/thread_checker.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/v8c/conversion_helpers.h"
+#include "cobalt/script/v8c/entry_scope.h"
+#include "cobalt/script/v8c/scoped_persistent.h"
+#include "cobalt/script/v8c/type_traits.h"
+#include "cobalt/script/v8c/v8c_exception_state.h"
+#include "cobalt/script/v8c/v8c_user_object_holder.h"
+#include "v8/include/v8.h"
+
+namespace cobalt {
+namespace script {
+namespace v8c {
+
+// Shared functionality for ScriptPromise<T>. Does not implement the Resolve
+// function, since that needs to be specialized for Promise<T>.
+template <typename T>
+class ScriptPromise : public ScopedPersistent<v8::Value>, public Promise<T> {
+ public:
+  // ScriptValue boilerplate.
+  typedef Promise<T> BaseType;
+
+  // Handle special case T=void, by swapping the input parameter |T| for
+  // |PromiseResultUndefined|. Combined with how |Promise| handles this
+  // special case, we're left with something like:
+  //
+  //   NativePromise<T>    ->            Promise<T>
+  //                                         ^
+  //                                         | (T=PromiseResultUndefined)
+  //                                        /
+  //   NativePromise<void> -> Promise<void>
+  //
+  using ResolveType =
+      typename std::conditional<std::is_same<T, void>::value,
+                                PromiseResultUndefined, T>::type;
+
+  ScriptPromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver)
+      : isolate_(isolate), ScopedPersistent(isolate, resolver) {
+    DCHECK(resolver->IsPromise());
+  }
+
+  void Resolve(const ResolveType& value) const override { NOTREACHED(); }
+
+  void Reject() const override { NOTREACHED(); }
+
+  void Reject(SimpleExceptionType exception) const override { NOTREACHED(); }
+
+  void Reject(const scoped_refptr<ScriptException>& result) const override {
+    NOTREACHED();
+  }
+
+  PromiseState State() const override {
+    DCHECK(!this->IsEmpty());
+    EntryScope entry_scope(isolate_);
+
+    v8::Promise::PromiseState v8_promise_state = this->promise()->State();
+    switch (v8_promise_state) {
+      case v8::Promise::kPending:
+        return PromiseState::kPending;
+      case v8::Promise::kFulfilled:
+        return PromiseState::kFulfilled;
+      case v8::Promise::kRejected:
+        return PromiseState::kRejected;
+    }
+    NOTREACHED();
+    return PromiseState::kRejected;
+  }
+
+  void AddStateChangeCallback(
+      std::unique_ptr<base::OnceCallback<void()>> callback) override {
+    v8::Local<v8::Context> context = context_.NewLocal(isolate_);
+
+    auto callback_lambda = [](const v8::FunctionCallbackInfo<v8::Value>& info) {
+      auto* callback = static_cast<base::OnceCallback<void()>*>(
+          info.Data().As<v8::External>()->Value());
+      DCHECK(callback);
+      std::move(*callback).Run();
+      delete callback;
+    };
+    v8::Local<v8::Function> function =
+        v8::Function::New(isolate_->GetCurrentContext(), callback_lambda,
+                          v8::External::New(isolate_, callback.release()))
+            .ToLocalChecked();
+    v8::Local<v8::Promise> result_promise;
+    if (!promise()
+             ->Then(context, function, function)
+             .ToLocal(&result_promise)) {
+      DLOG(ERROR) << "Unable to add promise state change callback.";
+      NOTREACHED();
+    }
+  }
+
+  v8::Local<v8::Promise> promise() const {
+    DCHECK(!this->IsEmpty());
+    return resolver()->GetPromise();
+  }
+
+ protected:
+  v8::Local<v8::Promise::Resolver> resolver() const {
+    DCHECK(!this->IsEmpty());
+    return this->Get().Get(isolate_).template As<v8::Promise::Resolver>();
+  }
+
+  v8::Isolate* isolate() const { return isolate_; }
+
+ private:
+  v8::Isolate* isolate_;
+  ScopedPersistent<v8::Context> context_;
+};
+
+// JSValue -> Promise
+template <typename T>
+void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value,
+                 int conversion_flags, ExceptionState* exception_state,
+                 std::unique_ptr<script::Promise<T*>>* out_promise) {
+  if (!value->IsPromise()) {
+    exception_state->SetSimpleException(kNotSupportedType);
+    return;
+  }
+  out_promise->reset(new ScriptPromise<T*>(isolate, value));
+}
+
+}  // namespace v8c
+}  // namespace script
+}  // namespace cobalt
+
+#endif  // COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_
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/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
index 4e5d4ff..1765ad2 100644
--- a/cobalt/worker/extendable_event.h
+++ b/cobalt/worker/extendable_event.h
@@ -15,15 +15,26 @@
 #ifndef COBALT_WORKER_EXTENDABLE_EVENT_H_
 #define COBALT_WORKER_EXTENDABLE_EVENT_H_
 
+#include <memory>
 #include <string>
+#include <utility>
 
+#include "base/bind.h"
 #include "cobalt/base/token.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/v8c/native_promise.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/environment_settings.h"
 #include "cobalt/web/event.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/worker/extendable_event_init.h"
+#include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_jobs.h"
+#include "cobalt/worker/service_worker_object.h"
+#include "cobalt/worker/service_worker_registration_object.h"
 
 namespace cobalt {
 namespace worker {
@@ -35,16 +46,87 @@
   ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict)
       : Event(type, init_dict) {}
 
-  void WaitUntil(script::EnvironmentSettings* settings,
-                 const script::Promise<script::ValueHandle>& promise) {
-    // TODO(b/228976500): Implement WaitUntil().
-    NOTIMPLEMENTED();
+  void WaitUntil(
+      script::EnvironmentSettings* settings,
+      std::unique_ptr<script::Promise<script::ValueHandle*>>& promise,
+      script::ExceptionState* exception_state) {
+    // Algorithm for waitUntil(), to add lifetime promise to event.
+    //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+
+    // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError"
+    //    DOMException.
+    // 2. If event is not active, throw an "InvalidStateError" DOMException.
+    if (!IsActive()) {
+      web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                               exception_state);
+      return;
+    }
+    // 3. Add promise to event’s extend lifetime promises.
+    // 4. Increment event’s pending promises count by one.
+    ++pending_promise_count_;
+    // 5. Upon fulfillment or rejection of promise, queue a microtask to run
+    //    these substeps:
+    std::unique_ptr<base::OnceCallback<void()>> callback(
+        new base::OnceCallback<void()>(std::move(
+            base::BindOnce(&ExtendableEvent::StateChange,
+                           base::Unretained(this), settings, promise.get()))));
+    promise->AddStateChangeCallback(std::move(callback));
+    promise.release();
+  }
+
+  void StateChange(script::EnvironmentSettings* settings,
+                   const script::Promise<script::ValueHandle*>* promise) {
+    // Implement the microtask called upon fulfillment or rejection of a
+    // promise, as part of the algorithm for waitUntil().
+    //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+    DCHECK(promise);
+    has_rejected_promise_ |=
+        promise->State() == script::PromiseState::kRejected;
+    // 5.1. Decrement event’s pending promises count by one.
+    --pending_promise_count_;
+    // 5.2. If event’s pending promises count is 0, then:
+    if (0 == pending_promise_count_) {
+      web::Context* context =
+          base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
+              ->context();
+      ServiceWorkerJobs* jobs = context->service_worker_jobs();
+      DCHECK(jobs);
+      // 5.2.1. Let registration be the current global object's associated
+      //        service worker's containing service worker registration.
+      jobs->message_loop()->task_runner()->PostTask(
+          FROM_HERE,
+          base::BindOnce(&ServiceWorkerJobs::WaitUntilSubSteps,
+                         base::Unretained(jobs),
+                         base::Unretained(
+                             context->GetWindowOrWorkerGlobalScope()
+                                 ->AsServiceWorker()
+                                 ->service_worker_object()
+                                 ->containing_service_worker_registration())));
+    }
+    delete promise;
+  }
+
+  bool IsActive() {
+    // An ExtendableEvent object is said to be active when its timed out flag
+    // is unset and either its pending promises count is greater than zero or
+    // its dispatch flag is set.
+    //   https://w3c.github.io/ServiceWorker/#extendableevent-active
+    return !timed_out_flag_ &&
+           ((pending_promise_count_ > 0) || IsBeingDispatched());
   }
 
   DEFINE_WRAPPABLE_TYPE(ExtendableEvent);
 
  protected:
   ~ExtendableEvent() override {}
+
+ private:
+  // https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises
+  // std::list<script::Promise<script::ValueHandle*>> extend_lifetime_promises_;
+  int pending_promise_count_ = 0;
+  bool has_rejected_promise_ = false;
+  // https://w3c.github.io/ServiceWorker/#extendableevent-timed-out-flag
+  bool timed_out_flag_ = false;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/extendable_event.idl b/cobalt/worker/extendable_event.idl
index 2297cbc..8a12344 100644
--- a/cobalt/worker/extendable_event.idl
+++ b/cobalt/worker/extendable_event.idl
@@ -18,5 +18,5 @@
   Exposed = ServiceWorker,
   Constructor(DOMString type, optional ExtendableEventInit eventInitDict)
 ] interface ExtendableEvent : Event {
-  [CallWith = EnvironmentSettings] void waitUntil(Promise<any> f);
+  [RaisesException, CallWith = EnvironmentSettings] void waitUntil(Promise<any> f);
 };
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index f901854..9ba0101 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -44,6 +44,7 @@
 #include "cobalt/worker/client.h"
 #include "cobalt/worker/client_query_options.h"
 #include "cobalt/worker/client_type.h"
+#include "cobalt/worker/extendable_event.h"
 #include "cobalt/worker/frame_type.h"
 #include "cobalt/worker/service_worker.h"
 #include "cobalt/worker/service_worker_container.h"
@@ -891,7 +892,7 @@
                     // 11.3.1.2. Initialize e’s type attribute to install.
                     // 11.3.1.3. Dispatch e at installingWorker’s global object.
                     installing_worker->worker_global_scope()->DispatchEvent(
-                        new web::Event(base::Tokens::install()));
+                        new ExtendableEvent(base::Tokens::install()));
                     // 11.3.1.4. WaitForAsynchronousExtensions: Run the
                     //           following substeps in parallel:
                     // 11.3.1.4.1. Wait until e is not active.
@@ -1137,7 +1138,7 @@
                     // 11.1.1.2. Initialize e’s type attribute to activate.
                     // 11.1.1.3. Dispatch e at activeWorker’s global object.
                     active_worker->worker_global_scope()->DispatchEvent(
-                        new web::Event(base::Tokens::activate()));
+                        new ExtendableEvent(base::Tokens::activate()));
                     // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in
                     //           parallel, until e is not active.
                   },
@@ -1837,6 +1838,23 @@
           std::move(promise_reference)));
 }
 
+void ServiceWorkerJobs::WaitUntilSubSteps(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::WaitUntilSubSteps()");
+  DCHECK_EQ(message_loop_, base::MessageLoop::current());
+  // Sub steps for WaitUntil.
+  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  // 5.2.2. If registration is unregistered, invoke Try Clear Registration
+  //        with registration.
+  if (scope_to_registration_map_.IsUnregistered(registration)) {
+    TryClearRegistration(registration);
+  }
+  // 5.2.3. If registration is not null, invoke Try Activate with
+  //        registration.
+  if (registration) {
+    TryActivate(registration);
+  }
+}
 void ServiceWorkerJobs::ClientsGetSubSteps(
     web::EnvironmentSettings* settings,
     ServiceWorkerObject* associated_service_worker,
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index 89dd24a..8e09831 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -204,6 +204,10 @@
       const base::WeakPtr<ServiceWorkerObject>& service_worker,
       std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
 
+  // Sub steps for WaitUntil.
+  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration);
+
   // Parallel sub steps (2) for algorithm for Clients.get(id):
   //   https://w3c.github.io/ServiceWorker/#clients-get
   void ClientsGetSubSteps(
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h
index c2b82f7..5a4443b 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.h
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -31,6 +31,8 @@
 
 namespace disk_cache {
 
+const char kCacheEnabledPersistentSettingsKey[] = "cacheEnabled";
+
 // This class implements the Backend interface. An object of this class handles
 // the operations of the cache without writing to disk.
 class NET_EXPORT_PRIVATE CobaltBackendImpl final : public Backend {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index a858ea3..4b05f41 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -29,6 +29,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.widget.FrameLayout;
+import dev.cobalt.media.AudioOutputManager;
 import dev.cobalt.media.MediaCodecUtil;
 import dev.cobalt.media.VideoSurfaceView;
 import dev.cobalt.util.DisplayUtil;
@@ -37,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /** Native activity that has the required JNI methods called by the Starboard implementation. */
 public abstract class CobaltActivity extends NativeActivity {
@@ -130,6 +132,8 @@
     }
 
     DisplayUtil.cacheDefaultDisplay(this);
+    DisplayUtil.addDisplayListener(this);
+    AudioOutputManager.addAudioDeviceListener(this);
 
     getStarboardBridge().onActivityStart(this, keyboardEditor);
     super.onStart();
@@ -248,11 +252,12 @@
         return;
       }
 
-      String customProxy = String.format("--proxy=\"http=http://%s:%d\"", config.first, port);
+      String customProxy =
+          String.format(Locale.US, "--proxy=\"http=http://%s:%d\"", config.first, port);
       Log.i(TAG, "addCustomProxyArgs: " + customProxy);
       args.add(customProxy);
     } catch (NumberFormatException e) {
-      Log.w(TAG, String.format("http.proxyPort: %s is not valid number", config.second), e);
+      Log.w(TAG, "http.proxyPort: %s is not valid number", config.second, e);
     }
   }
 
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
index f131da7..758030d 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
@@ -29,6 +29,7 @@
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.util.Locale;
 
 /** Loads MediaImage artwork, and caches one image. */
 public class ArtworkLoader {
@@ -97,7 +98,7 @@
   private Size parseImageSize(MediaImage image) {
     try {
       String sizeStr = image.sizes.split("\\s+", -1)[0];
-      return Size.parseSize(sizeStr.toLowerCase());
+      return Size.parseSize(sizeStr.toLowerCase(Locale.US));
     } catch (NumberFormatException | NullPointerException e) {
       return new Size(0, 0);
     }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 5a13b01..ebd8967 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Creates and destroys AudioTrackBridge and handles the volume change. */
@@ -103,9 +104,9 @@
                 // AudioFormat.
                 Log.v(
                     TAG,
-                    String.format(
-                        "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.",
-                        info.getProductName(), getDeviceTypeNameV23(info.getType())));
+                    "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.",
+                    info.getProductName(),
+                    getDeviceTypeNameV23(info.getType()));
                 hasAudioDeviceChanged.set(true);
                 break;
               }
@@ -116,9 +117,8 @@
           public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
             Log.v(
                 TAG,
-                String.format(
-                    "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.",
-                    initialDevicesAdded));
+                "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.",
+                initialDevicesAdded);
             if (initialDevicesAdded) {
               handleConnectedDeviceChange(addedDevices);
               return;
@@ -236,7 +236,7 @@
         return "TYPE_WIRED_HEADSET";
       default:
         // This may include constants introduced after API 23.
-        return String.format("TYPE_UNKNOWN (%d)", device_type);
+        return String.format(Locale.US, "TYPE_UNKNOWN (%d)", device_type);
     }
   }
 
@@ -278,7 +278,7 @@
           break;
         default:
           // This may include constants introduced after API 23.
-          encodings_in_string.append(String.format("UNKNOWN (%d)", encodings[i]));
+          encodings_in_string.append(String.format(Locale.US, "UNKNOWN (%d)", encodings[i]));
           break;
       }
       if (i != encodings.length - 1) {
@@ -304,12 +304,11 @@
     for (AudioDeviceInfo info : deviceInfos) {
       Log.i(
           TAG,
-          String.format(
-              "  Audio Device: %s, channels: %s, sample rates: %s, encodings: %s",
-              getDeviceTypeNameV23(info.getType()),
-              Arrays.toString(info.getChannelCounts()),
-              Arrays.toString(info.getSampleRates()),
-              getEncodingNames(info.getEncodings())));
+          "  Audio Device: %s, channels: %s, sample rates: %s, encodings: %s",
+          getDeviceTypeNameV23(info.getType()),
+          Arrays.toString(info.getChannelCounts()),
+          Arrays.toString(info.getSampleRates()),
+          getEncodingNames(info.getEncodings()));
     }
   }
 
@@ -348,10 +347,11 @@
       if (AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE % frameSizeInBytes != 0) {
         Log.w(
             TAG,
-            String.format(
-                "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't"
-                    + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).",
-                sampleSizeInBytes, numberOfChannels, AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE));
+            "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't"
+                + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).",
+            sampleSizeInBytes,
+            numberOfChannels,
+            AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE);
         return -1;
       }
     }
@@ -366,10 +366,10 @@
     if (Build.VERSION.SDK_INT < 23) {
       Log.i(
           TAG,
-          String.format(
-              "Passthrough on encoding %d is rejected on api %d, as passthrough is only"
-                  + " supported on api 23 or later.",
-              encoding, Build.VERSION.SDK_INT));
+          "Passthrough on encoding %d is rejected on api %d, as passthrough is only"
+              + " supported on api 23 or later.",
+          encoding,
+          Build.VERSION.SDK_INT);
       return false;
     }
 
@@ -382,10 +382,9 @@
       if (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
         Log.i(
             TAG,
-            String.format(
-                "Passthrough on encoding %d is disabled because Bluetooth output device is"
-                    + " connected.",
-                encoding));
+            "Passthrough on encoding %d is disabled because Bluetooth output device is"
+                + " connected.",
+            encoding);
         return false;
       }
     }
@@ -396,34 +395,31 @@
     if (hasPassthroughSupportForV23(deviceInfos, encoding)) {
       Log.i(
           TAG,
-          String.format(
-              "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns"
-                  + " true.",
-              encoding));
+          "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns"
+              + " true.",
+          encoding);
     } else {
       if (Build.VERSION.SDK_INT < 29) {
         Log.i(
             TAG,
-            String.format(
-                "Passthrough on encoding %d is rejected, as"
-                    + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.",
-                encoding, Build.VERSION.SDK_INT));
+            "Passthrough on encoding %d is rejected, as"
+                + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.",
+            encoding,
+            Build.VERSION.SDK_INT);
         return false;
       }
       if (hasDirectSurroundPlaybackSupportForV29(encoding, DEFAULT_SURROUND_SAMPLE_RATE)) {
         Log.i(
             TAG,
-            String.format(
-                "Passthrough on encoding %d is supported, as"
-                    + " hasDirectSurroundPlaybackSupportForV29() returns true.",
-                encoding));
+            "Passthrough on encoding %d is supported, as"
+                + " hasDirectSurroundPlaybackSupportForV29() returns true.",
+            encoding);
       } else {
         Log.i(
             TAG,
-            String.format(
-                "Passthrough on encoding %d is not supported, as"
-                    + " hasDirectSurroundPlaybackSupportForV29() returns false.",
-                encoding));
+            "Passthrough on encoding %d is not supported, as"
+                + " hasDirectSurroundPlaybackSupportForV29() returns false.",
+            encoding);
         return false;
       }
     }
@@ -445,10 +441,9 @@
       // HDMI and SPDIF are connected, where the output should fallback to AC3.
       Log.w(
           TAG,
-          String.format(
-              "Passthrough on encoding %d is disabled because creating AudioTrack raises"
-                  + " exception: ",
-              encoding),
+          "Passthrough on encoding %d is disabled because creating AudioTrack raises"
+              + " exception: ",
+          encoding,
           e);
       return false;
     }
@@ -475,31 +470,29 @@
         // an empty array indicates that the device supports arbitrary encodings.
         Log.i(
             TAG,
-            String.format(
-                "Passthrough on encoding %d is supported on %s, because getEncodings() returns"
-                    + " an empty array.",
-                encoding, getDeviceTypeNameV23(type)));
+            "Passthrough on encoding %d is supported on %s, because getEncodings() returns"
+                + " an empty array.",
+            encoding,
+            getDeviceTypeNameV23(type));
         return true;
       }
       for (int i = 0; i < encodings.length; ++i) {
         if (encodings[i] == encoding) {
           Log.i(
               TAG,
-              String.format(
-                  "Passthrough on encoding %d is supported on %s.",
-                  encoding, getDeviceTypeNameV23(type)));
+              "Passthrough on encoding %d is supported on %s.",
+              encoding,
+              getDeviceTypeNameV23(type));
           return true;
         }
       }
       Log.i(
           TAG,
-          String.format(
-              "Passthrough on encoding %d is not supported on %s.",
-              encoding, getDeviceTypeNameV23(type)));
+          "Passthrough on encoding %d is not supported on %s.",
+          encoding,
+          getDeviceTypeNameV23(type));
     }
-    Log.i(
-        TAG,
-        String.format("Passthrough on encoding %d is not supported on any devices.", encoding));
+    Log.i(TAG, "Passthrough on encoding %d is not supported on any devices.", encoding);
     return false;
   }
 
@@ -511,19 +504,15 @@
         && encoding != AudioFormat.ENCODING_E_AC3_JOC) {
       Log.w(
           TAG,
-          String.format(
-              "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.",
-              encoding));
+          "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.",
+          encoding);
       return false;
     }
 
     boolean supported =
         AudioTrack.isDirectPlaybackSupported(
             getPassthroughAudioFormatFor(encoding, sampleRate), getDefaultAudioAttributes());
-    Log.i(
-        TAG,
-        String.format(
-            "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported));
+    Log.i(TAG, "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported);
     return supported;
   }
 
@@ -550,4 +539,31 @@
   private boolean getAndResetHasAudioDeviceChanged() {
     return hasAudioDeviceChanged.getAndSet(false);
   }
+
+  private static AudioDeviceCallback audioDeviceCallback =
+      new AudioDeviceCallback() {
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+          nativeOnAudioDeviceChanged();
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+          nativeOnAudioDeviceChanged();
+        }
+      };
+
+  private static boolean audioDeviceListenerAdded = false;
+
+  public static void addAudioDeviceListener(Context context) {
+    if (audioDeviceListenerAdded) {
+      return;
+    }
+
+    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    audioManager.registerAudioDeviceCallback(audioDeviceCallback, null);
+    audioDeviceListenerAdded = true;
+  }
+
+  private static native void nativeOnAudioDeviceChanged();
 }
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 4c11399..5050078 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
@@ -27,6 +27,7 @@
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Locale;
 
 /**
  * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the
@@ -96,9 +97,12 @@
           audioTrack = null;
           String errorMessage =
               String.format(
+                  Locale.US,
                   "Enable tunnel mode when frame size is unaligned, "
                       + "sampleType: %d, channel: %d, sync header size: %d.",
-                  sampleType, channelCount, AV_SYNC_HEADER_V1_SIZE);
+                  sampleType,
+                  channelCount,
+                  AV_SYNC_HEADER_V1_SIZE);
           Log.e(TAG, errorMessage);
           throw new RuntimeException(errorMessage);
         }
@@ -160,12 +164,11 @@
     }
     Log.i(
         TAG,
-        String.format(
-            "AudioTrack created with buffer size %d (preferred: %d).  The minimum buffer size is"
-                + " %d.",
-            audioTrackBufferSize,
-            preferredBufferSizeInBytes,
-            AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType)));
+        "AudioTrack created with buffer size %d (preferred: %d).  The minimum buffer size is"
+            + " %d.",
+        audioTrackBufferSize,
+        preferredBufferSizeInBytes,
+        AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType));
   }
 
   public Boolean isAudioTrackValid() {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
index a310b15..f36dad3 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -515,9 +515,11 @@
 
     Log.i(
         TAG,
-        String.format(
-            "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms",
-            stateName, positionMs, speed, duration));
+        "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms",
+        stateName,
+        positionMs,
+        speed,
+        duration);
 
     playbackStateBuilder =
         new PlaybackStateCompat.Builder()
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 c5c66cd..eeed7a7 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
@@ -37,6 +37,7 @@
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Locale;
 
 /** A wrapper of the MediaCodec class. */
 @SuppressWarnings("unused")
@@ -141,7 +142,7 @@
         return;
       }
       if (presentationTimeUs <= mLastFrameTimestampUs) {
-        Log.v(TAG, String.format("Invalid output presentation timestamp."));
+        Log.v(TAG, "Invalid output presentation timestamp.");
         return;
       }
 
@@ -537,13 +538,10 @@
     }
     MediaCodec mediaCodec = null;
     try {
-      Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName));
+      Log.i(TAG, "Creating \"%s\" decoder.", decoderName);
       mediaCodec = MediaCodec.createByCodecName(decoderName);
     } catch (Exception e) {
-      Log.e(
-          TAG,
-          String.format("Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName),
-          e);
+      Log.e(TAG, "Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName, e);
       return null;
     }
     if (mediaCodec == null) {
@@ -609,13 +607,16 @@
     }
 
     try {
-      Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName));
+      Log.i(TAG, "Creating \"%s\" decoder.", decoderName);
       mediaCodec = MediaCodec.createByCodecName(decoderName);
     } catch (Exception e) {
       String message =
           String.format(
+              Locale.US,
               "Failed to create MediaCodec: %s, mustSupportSecure: %s," + " DecoderName: %s",
-              mime, crypto != null, decoderName);
+              mime,
+              crypto != null,
+              decoderName);
       Log.e(TAG, message, e);
       outCreateMediaCodecBridgeResult.mErrorMessage = message;
       return;
@@ -1189,8 +1190,10 @@
               + (configurationData == null
                   ? "|configurationData| is null."
                   : String.format(
+                      Locale.US,
                       "Configuration data size (%d) is less than the required size (%d).",
-                      configurationData.length, MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE)));
+                      configurationData.length,
+                      MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE)));
       return false;
     }
     // Both the number of samples to skip from the beginning of the stream and the amount of time
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 2d023be..e2e9b43 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
@@ -436,7 +436,7 @@
       // 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));
+        Log.v(TAG, "Rejecting %s, reason: codec is on deny list", name);
         continue;
       }
       if (name.endsWith(SECURE_DECODER_SUFFIX)) {
@@ -446,7 +446,7 @@
             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));
+          Log.v(TAG, format, name);
           continue;
         }
       }
@@ -537,6 +537,7 @@
       int fps) {
     String decoderInfo =
         String.format(
+            Locale.US,
             "Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth:"
                 + " %d, frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b,"
                 + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b,"
@@ -554,6 +555,7 @@
     Log.v(TAG, decoderInfo);
     String deviceInfo =
         String.format(
+            Locale.US,
             "brand: %s, model: %s, version: %s, API level: %d, isVp9AllowListed: %b",
             Build.BRAND,
             Build.MODEL,
@@ -606,9 +608,13 @@
           || !mustSupportSecure && requiresSecurePlayback) {
         String message =
             String.format(
+                Locale.US,
                 "Rejecting %s, reason: secure decoder requested: %b, "
                     + "codec FEATURE_SecurePlayback supported: %b, required: %b",
-                name, mustSupportSecure, supportsSecurePlayback, requiresSecurePlayback);
+                name,
+                mustSupportSecure,
+                supportsSecurePlayback,
+                requiresSecurePlayback);
         Log.v(TAG, message);
         continue;
       }
@@ -623,9 +629,13 @@
           || !mustSupportTunnelMode && requiresTunneledPlayback) {
         String message =
             String.format(
+                Locale.US,
                 "Rejecting %s, reason: tunneled playback requested: %b, "
                     + "codec FEATURE_TunneledPlayback supported: %b, required: %b",
-                name, mustSupportTunnelMode, supportsTunneledPlayback, requiresTunneledPlayback);
+                name,
+                mustSupportTunnelMode,
+                supportsTunneledPlayback,
+                requiresTunneledPlayback);
         Log.v(TAG, message);
         continue;
       }
@@ -652,31 +662,31 @@
         if (frameWidth != 0 && frameHeight != 0) {
           if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) {
             String format = "Rejecting %s, reason: width %s is not compatible with height %d";
-            Log.v(TAG, String.format(format, name, frameWidth, frameHeight));
+            Log.v(TAG, format, name, frameWidth, frameHeight);
             continue;
           }
         } else if (frameWidth != 0) {
           if (!supportedWidths.contains(frameWidth)) {
             String format = "Rejecting %s, reason: supported widths %s does not contain %d";
-            Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth));
+            Log.v(TAG, format, name, supportedWidths.toString(), frameWidth);
             continue;
           }
         } else if (frameHeight != 0) {
           if (!supportedHeights.contains(frameHeight)) {
             String format = "Rejecting %s, reason: supported heights %s does not contain %d";
-            Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight));
+            Log.v(TAG, format, name, supportedHeights.toString(), frameHeight);
             continue;
           }
         }
       } else {
         if (frameWidth != 0 && !supportedWidths.contains(frameWidth)) {
           String format = "Rejecting %s, reason: supported widths %s does not contain %d";
-          Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth));
+          Log.v(TAG, format, name, supportedWidths.toString(), frameWidth);
           continue;
         }
         if (frameHeight != 0 && !supportedHeights.contains(frameHeight)) {
           String format = "Rejecting %s, reason: supported heights %s does not contain %d";
-          Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight));
+          Log.v(TAG, format, name, supportedHeights.toString(), frameHeight);
           continue;
         }
       }
@@ -684,7 +694,7 @@
       Range<Integer> bitrates = videoCapabilities.getBitrateRange();
       if (bitrate != 0 && !bitrates.contains(bitrate)) {
         String format = "Rejecting %s, reason: bitrate range %s does not contain %d";
-        Log.v(TAG, String.format(format, name, bitrates.toString(), bitrate));
+        Log.v(TAG, format, name, bitrates.toString(), bitrate);
         continue;
       }
 
@@ -694,14 +704,14 @@
           if (frameHeight != 0 && frameWidth != 0) {
             if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) {
               String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-              Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps));
+              Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
               continue;
             }
           } else {
             // At least one of frameHeight or frameWidth is 0
             if (!supportedFrameRates.contains(fps)) {
               String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-              Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps));
+              Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
               continue;
             }
           }
@@ -709,7 +719,7 @@
       } else {
         if (fps != 0 && !supportedFrameRates.contains(fps)) {
           String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-          Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps));
+          Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
           continue;
         }
       }
@@ -855,6 +865,7 @@
     for (ResolutionAndFrameRate resolutionAndFrameRate : supported) {
       frameRateAndResolutionString +=
           String.format(
+              Locale.US,
               "[%d x %d, %.3f fps], ",
               resolutionAndFrameRate.width,
               resolutionAndFrameRate.height,
@@ -878,6 +889,7 @@
         String name = info.getName();
         decoderDumpString +=
             String.format(
+                Locale.US,
                 "name: %s (%s, %s): ",
                 name,
                 supportedType,
@@ -897,6 +909,7 @@
               getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable);
           decoderDumpString +=
               String.format(
+                  Locale.US,
                   "\n\t\t"
                       + "widths: %s, "
                       + "heights: %s, "
@@ -923,6 +936,7 @@
             || isTunneledPlaybackSupported) {
           decoderDumpString +=
               String.format(
+                  Locale.US,
                   "(%s%s%s",
                   isAdaptivePlaybackSupported ? "AdaptivePlayback, " : "",
                   isSecurePlaybackSupported ? "SecurePlayback, " : "",
@@ -938,13 +952,12 @@
     }
     Log.v(
         TAG,
-        String.format(
-            " \n"
-                + "==================================================\n"
-                + "Full list of decoder features: [AdaptivePlayback, SecurePlayback,"
-                + " TunneledPlayback]\n"
-                + "Unsupported features for each codec are not listed\n"
-                + decoderDumpString
-                + "=================================================="));
+        " \n"
+            + "==================================================\n"
+            + "Full list of decoder features: [AdaptivePlayback, SecurePlayback,"
+            + " TunneledPlayback]\n"
+            + "Unsupported features for each codec are not listed\n"
+            + decoderDumpString
+            + "==================================================");
   }
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
index 29a2f80..b96bb87 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -16,6 +16,8 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
 import android.util.DisplayMetrics;
 import android.util.Size;
 import android.util.SizeF;
@@ -139,4 +141,40 @@
   private static DisplayMetrics getDisplayMetrics() {
     return cachedDisplayMetrics;
   }
+
+  private static DisplayListener displayerListener =
+      new DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+          nativeOnDisplayChanged();
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+          nativeOnDisplayChanged();
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+          nativeOnDisplayChanged();
+        }
+      };
+
+  private static boolean displayerListenerAdded = false;
+
+  public static void addDisplayListener(Context context) {
+    if (displayerListenerAdded) {
+      return;
+    }
+
+    DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+    displayManager.registerDisplayListener(displayerListener, null);
+    displayerListenerAdded = true;
+
+    // Call nativeOnDisplayChanged() to reload supported hdr types here after a default
+    // Display created.
+    nativeOnDisplayChanged();
+  }
+
+  private static native void nativeOnDisplayChanged();
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java
index 0a75c79..cca7c02 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java
@@ -15,6 +15,7 @@
 package dev.cobalt.util;
 
 import java.lang.reflect.Method;
+import java.util.Locale;
 
 /**
  * Logging wrapper to allow for better control of Proguard log stripping. Many dependent
@@ -59,9 +60,27 @@
     }
   }
 
-  private static int logWithMethod(Method logMethod, String tag, String msg, Throwable tr) {
+  private static Throwable getThrowableToLog(Object[] args) {
+    if (args == null || args.length == 0) return null;
+    Object lastArg = args[args.length - 1];
+    if (!(lastArg instanceof Throwable)) return null;
+    return (Throwable) lastArg;
+  }
+
+  /** Returns a formatted log message, using the supplied format and arguments. */
+  private static String formatLog(String messageTemplate, Throwable tr, Object... params) {
+    if ((params != null) && ((tr == null && params.length > 0) || params.length > 1)) {
+      messageTemplate = String.format(Locale.US, messageTemplate, params);
+    }
+    return messageTemplate;
+  }
+
+  private static int logWithMethod(
+      Method logMethod, String tag, String messageTemplate, Object... args) {
     try {
       if (logMethod != null) {
+        Throwable tr = getThrowableToLog(args);
+        String msg = formatLog(messageTemplate, tr, args);
         return (int) logMethod.invoke(null, tag, msg, tr);
       }
     } catch (Throwable e) {
@@ -70,47 +89,27 @@
     return 0;
   }
 
-  public static int v(String tag, String msg) {
-    return logWithMethod(logV, tag, msg, null);
+  public static int v(String tag, String messageTemplate, Object... args) {
+    return logWithMethod(logV, tag, messageTemplate, args);
   }
 
-  public static int v(String tag, String msg, Throwable tr) {
-    return logWithMethod(logV, tag, msg, tr);
+  public static int d(String tag, String messageTemplate, Object... args) {
+    return logWithMethod(logD, tag, messageTemplate, args);
   }
 
-  public static int d(String tag, String msg) {
-    return logWithMethod(logD, tag, msg, null);
+  public static int i(String tag, String messageTemplate, Object... args) {
+    return logWithMethod(logI, tag, messageTemplate, args);
   }
 
-  public static int d(String tag, String msg, Throwable tr) {
-    return logWithMethod(logD, tag, msg, tr);
-  }
-
-  public static int i(String tag, String msg) {
-    return logWithMethod(logI, tag, msg, null);
-  }
-
-  public static int i(String tag, String msg, Throwable tr) {
-    return logWithMethod(logI, tag, msg, tr);
-  }
-
-  public static int w(String tag, String msg) {
-    return logWithMethod(logW, tag, msg, null);
-  }
-
-  public static int w(String tag, String msg, Throwable tr) {
-    return logWithMethod(logW, tag, msg, tr);
+  public static int w(String tag, String messageTemplate, Object... args) {
+    return logWithMethod(logW, tag, messageTemplate, args);
   }
 
   public static int w(String tag, Throwable tr) {
     return logWithMethod(logW, tag, "", tr);
   }
 
-  public static int e(String tag, String msg) {
-    return logWithMethod(logE, tag, msg, null);
-  }
-
-  public static int e(String tag, String msg, Throwable tr) {
-    return logWithMethod(logE, tag, msg, tr);
+  public static int e(String tag, String messageTemplate, Object... args) {
+    return logWithMethod(logE, tag, messageTemplate, args);
   }
 }
diff --git a/starboard/android/shared/file_open.cc b/starboard/android/shared/file_open.cc
index 83d924a..0f398a4 100644
--- a/starboard/android/shared/file_open.cc
+++ b/starboard/android/shared/file_open.cc
@@ -30,7 +30,12 @@
 // font file of the same name.
 const std::string kFontsXml("fonts.xml");
 const std::string kSystemFontsDir("/system/fonts/");
+
+#if SB_IS(EVERGREEN_COMPATIBLE)
+const std::string kCobaltFontsDir("/cobalt/assets/app/cobalt/content/fonts/");
+#else
 const std::string kCobaltFontsDir("/cobalt/assets/fonts/");
+#endif
 
 // Returns the fallback for the given asset path, or an empty string if none.
 // NOTE: While Cobalt now provides a mechanism for loading system fonts through
@@ -67,8 +72,8 @@
                   bool* out_created,
                   SbFileError* out_error) {
   if (!IsAndroidAssetPath(path)) {
-    return ::starboard::shared::posix::impl::FileOpen(
-        path, flags, out_created, out_error);
+    return ::starboard::shared::posix::impl::FileOpen(path, flags, out_created,
+                                                      out_error);
   }
 
   // Assets are never created and are always read-only, whether it's actually an
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
index 53360ea..e8090b5 100644
--- a/starboard/android/shared/media_capabilities_cache.cc
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -20,12 +20,17 @@
 #include "starboard/android/shared/media_common.h"
 #include "starboard/common/log.h"
 #include "starboard/once.h"
+#include "starboard/shared/starboard/media/key_system_supportability_cache.h"
+#include "starboard/shared/starboard/media/mime_supportability_cache.h"
 
 namespace starboard {
 namespace android {
 namespace shared {
 namespace {
 
+using ::starboard::shared::starboard::media::KeySystemSupportabilityCache;
+using ::starboard::shared::starboard::media::MimeSupportabilityCache;
+
 // 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;
@@ -79,6 +84,13 @@
   JniEnvExt* env = JniEnvExt::Get();
   jintArray j_supported_hdr_types = static_cast<jintArray>(
       env->CallStarboardObjectMethodOrAbort("getSupportedHdrTypes", "()[I"));
+
+  if (!j_supported_hdr_types) {
+    // Failed to get supported hdr types.
+    SB_LOG(ERROR) << "Failed to load supported hdr types.";
+    return std::set<SbMediaTransferId>();
+  }
+
   jsize length = env->GetArrayLength(j_supported_hdr_types);
   jint* numbers = env->GetIntArrayElements(j_supported_hdr_types, 0);
   for (int i = 0; i < length; i++) {
@@ -461,6 +473,31 @@
   max_audio_output_channels_ = -1;
 }
 
+void MediaCapabilitiesCache::ReloadSupportedHdrTypes() {
+  ScopedLock scoped_lock(mutex_);
+  if (!is_initialized_) {
+    LazyInitialize_Locked();
+    return;
+  }
+  supported_transfer_ids_ = GetSupportedHdrTypes();
+}
+
+void MediaCapabilitiesCache::ReloadAudioOutputChannels() {
+  ScopedLock scoped_lock(mutex_);
+  if (!is_initialized_) {
+    LazyInitialize_Locked();
+    return;
+  }
+  max_audio_output_channels_ =
+      ::starboard::android::shared::GetMaxAudioOutputChannels();
+}
+
+MediaCapabilitiesCache::MediaCapabilitiesCache() {
+  // Enable mime and key system caches.
+  MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true);
+  KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true);
+}
+
 void MediaCapabilitiesCache::LazyInitialize_Locked() {
   mutex_.DCheckAcquired();
 
@@ -526,6 +563,20 @@
   }
 }
 
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() {
+  SB_DLOG(INFO) << "Display device has changed.";
+  MediaCapabilitiesCache::GetInstance()->ReloadSupportedHdrTypes();
+  MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() {
+  SB_DLOG(INFO) << "Audio device has changed.";
+  MediaCapabilitiesCache::GetInstance()->ReloadAudioOutputChannels();
+  MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h
index 46da91a..21d735b 100644
--- a/starboard/android/shared/media_capabilities_cache.h
+++ b/starboard/android/shared/media_capabilities_cache.h
@@ -160,8 +160,11 @@
   void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
   void ClearCache();
 
+  void ReloadSupportedHdrTypes();
+  void ReloadAudioOutputChannels();
+
  private:
-  MediaCapabilitiesCache() {}
+  MediaCapabilitiesCache();
   ~MediaCapabilitiesCache() {}
 
   MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete;
@@ -183,7 +186,7 @@
   std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_;
   std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_;
 
-  std::atomic_bool is_enabled_{false};
+  std::atomic_bool is_enabled_{true};
   bool is_initialized_ = false;
   bool is_widevine_supported_ = false;
   bool is_cbcs_supported_ = false;
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc
index e80c61d..46b73e5 100644
--- a/starboard/android/shared/media_is_audio_supported.cc
+++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -21,16 +21,13 @@
 #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::MediaCapabilitiesCache;
-using starboard::android::shared::ScopedLocalJavaRef;
 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;
@@ -43,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)
@@ -91,7 +101,7 @@
     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;
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc
index feaae31..fcd6806 100644
--- a/starboard/android/shared/media_is_supported.cc
+++ b/starboard/android/shared/media_is_supported.cc
@@ -33,8 +33,8 @@
   // `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;
   }
 
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc
index 12c2f9f..9b8fcf5 100644
--- a/starboard/android/shared/media_is_video_supported.cc
+++ b/starboard/android/shared/media_is_video_supported.cc
@@ -19,7 +19,6 @@
 #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::MediaCapabilitiesCache;
 using starboard::android::shared::SupportedVideoCodecToMimeType;
@@ -27,7 +26,7 @@
 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,
@@ -49,26 +48,48 @@
   // 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) {
+  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
-    mime_type.RegisterBoolParameter("tunnelmode");
+    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.
-    mime_type.RegisterStringParameter("hdrinfoendianness", "big|little");
+    if (!mime_type->ValidateStringParameter("hdrinfoendianness",
+                                            "big|little")) {
+      return false;
+    }
+
     // Forces the use of specific Android APIs (isSizeSupported() and
     // areSizeAndRateSupported()) to determine format support.
-    mime_type.RegisterBoolParameter("forceimprovedsupportcheck");
-
-    if (!mime_type.is_valid()) {
+    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);
+
+    // Disable MediaCapabilitiesCache if "disablecache" option presented.
+    if (!mime_type->ValidateBoolParameter("disablecache")) {
+      return false;
+    }
+    if (mime_type->GetParamBoolValue("disablecache", false)) {
+      MediaCapabilitiesCache::GetInstance()->SetCacheEnabled(false);
+    }
   }
 
-  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.";
@@ -85,8 +106,6 @@
   // 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);
 
   return MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
       mime, require_secure_playback, must_support_hdr, must_support_tunnel_mode,
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index e356269..6d26909 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -207,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.";
@@ -245,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,
@@ -293,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;
@@ -311,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;
@@ -497,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(),
diff --git a/starboard/android/shared/system_get_path.cc b/starboard/android/shared/system_get_path.cc
index 2ca0815..5e4610a 100644
--- a/starboard/android/shared/system_get_path.cc
+++ b/starboard/android/shared/system_get_path.cc
@@ -25,10 +25,35 @@
 #include "starboard/common/string.h"
 #include "starboard/directory.h"
 
+#if SB_IS(EVERGREEN_COMPATIBLE)
+#include "starboard/elf_loader/evergreen_config.h"  // nogncheck
+#endif
+
 using ::starboard::android::shared::g_app_assets_dir;
 using ::starboard::android::shared::g_app_cache_dir;
+using ::starboard::android::shared::g_app_files_dir;
 using ::starboard::android::shared::g_app_lib_dir;
 
+#if SB_IS(EVERGREEN_COMPATIBLE)
+bool GetEvergreenContentPathOverride(char* out_path, int path_size) {
+  const starboard::elf_loader::EvergreenConfig* evergreen_config =
+      starboard::elf_loader::EvergreenConfig::GetInstance();
+  if (!evergreen_config) {
+    return true;
+  }
+  if (evergreen_config->content_path_.empty()) {
+    return true;
+  }
+
+  if (starboard::strlcpy(out_path, evergreen_config->content_path_.c_str(),
+                         path_size) >= path_size) {
+    return false;
+  }
+
+  return true;
+}
+#endif
+
 bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) {
   if (!out_path || !path_size) {
     return false;
@@ -43,9 +68,25 @@
       if (starboard::strlcat(path, g_app_assets_dir, kPathSize) >= kPathSize) {
         return false;
       }
+
+#if SB_IS(EVERGREEN_COMPATIBLE)
+      if (!GetEvergreenContentPathOverride(path, kPathSize)) {
+        return false;
+      }
+#endif
       break;
     }
 
+    case kSbSystemPathStorageDirectory: {
+      if (starboard::strlcpy(path, g_app_files_dir, kPathSize) >= kPathSize) {
+        return false;
+      }
+      if (starboard::strlcat(path, "/storage", kPathSize) >= kPathSize) {
+        return false;
+      }
+      SbDirectoryCreate(path);
+      break;
+    }
     case kSbSystemPathCacheDirectory: {
       if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) {
         return false;
diff --git a/starboard/evergreen/shared/launcher.py b/starboard/evergreen/shared/launcher.py
index c8bf2d5..e6f331d 100644
--- a/starboard/evergreen/shared/launcher.py
+++ b/starboard/evergreen/shared/launcher.py
@@ -75,6 +75,8 @@
       self.loader_out_directory = paths.BuildOutputDirectory(
           self.loader_platform, self.loader_config)
 
+    self.use_compressed_library = kwargs.get('use_compressed_library')
+
     # The relationship of loader platforms and configurations to evergreen
     # platforms and configurations is many-to-many. We need a separate directory
     # for each of them, i.e. linux-x64x11_debug__evergreen-x64_gold.
@@ -92,9 +94,18 @@
 
     # Ensure the path, relative to the content of the ELF Loader, to the
     # Evergreen target and its content are passed as command line switches.
+    library_path_param = '--evergreen_library=app/{}/lib/lib{}'.format(
+        self.target_name, self.target_name)
+    if self.use_compressed_library:
+      if self.target_name != 'cobalt':
+        raise ValueError(
+            '|use_compressed_library| only expected with |target_name| cobalt')
+      library_path_param += '.lz4'
+    else:
+      library_path_param += '.so'
+
     target_command_line_params = [
-        '--evergreen_library=app/{}/lib/lib{}.so'.format(
-            self.target_name, self.target_name),
+        library_path_param,
         '--evergreen_content=app/{}/content'.format(self.target_name)
     ]
 
@@ -171,6 +182,7 @@
       self._StageTargetsAndContentsGyp()
 
   def _StageTargetsAndContentsGnLinux(self):
+    """Stage targets and their contents for GN builds for Linux platforms."""
     content_subdir = os.path.join('usr', 'share', 'cobalt')
 
     # Copy loader content and binaries
@@ -199,13 +211,15 @@
     target_content_dst = os.path.join(target_staging_dir, 'content')
     shutil.copytree(target_content_src, target_content_dst)
 
-    shlib_name = 'lib{}.so'.format(self.target_name)
+    shlib_name = 'lib{}'.format(self.target_name)
+    shlib_name += '.lz4' if self.use_compressed_library else '.so'
     target_binary_src = os.path.join(target_install_path, 'lib', shlib_name)
     target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name)
     os.makedirs(os.path.join(target_staging_dir, 'lib'))
     shutil.copy(target_binary_src, target_binary_dst)
 
   def _StageTargetsAndContentsGnRaspi(self):
+    """Stage targets and their contents for GN builds for Raspi platforms."""
     # TODO(b/218889313): `content` is hardcoded on raspi and must be in the same
     # directory as the binaries.
     if 'raspi' in self.loader_platform:
@@ -251,13 +265,15 @@
     target_content_dst = os.path.join(target_staging_dir, 'content')
     shutil.copytree(target_content_src, target_content_dst)
 
-    shlib_name = 'lib{}.so'.format(self.target_name)
+    shlib_name = 'lib{}'.format(self.target_name)
+    shlib_name += '.lz4' if self.use_compressed_library else '.so'
     target_binary_src = os.path.join(target_install_path, 'lib', shlib_name)
     target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name)
     os.makedirs(os.path.join(target_staging_dir, 'lib'))
     shutil.copy(target_binary_src, target_binary_dst)
 
   def _StageTargetsAndContentsGyp(self):
+    """Stage targets and their contents for GYP builds."""
     # <outpath>/deploy/elf_loader_sandbox
     staging_directory_loader = os.path.join(self.staging_directory, 'deploy',
                                             self.loader_target)
diff --git a/starboard/evergreen/testing/README.md b/starboard/evergreen/testing/README.md
index bbd33fe..e401325 100644
--- a/starboard/evergreen/testing/README.md
+++ b/starboard/evergreen/testing/README.md
@@ -121,9 +121,9 @@
              +-- content    <-- loader content
                   +-- app
                        +-- cobalt
-                            +-- content           <-- cobalt content
+                            +-- content                 <-- cobalt content
                             +-- lib
-                                 +-- libcobalt.so <-- cobalt binary
+                                 +-- libcobalt.{so,lz4} <-- cobalt binary
 ```
 
 Note: This directory structure is the same as what would be generated by
diff --git a/starboard/evergreen/testing/linux/deploy_cobalt.sh b/starboard/evergreen/testing/linux/deploy_cobalt.sh
index 6dc6e59..a461fae 100755
--- a/starboard/evergreen/testing/linux/deploy_cobalt.sh
+++ b/starboard/evergreen/testing/linux/deploy_cobalt.sh
@@ -31,8 +31,8 @@
 
   echo " Checking '${staging_dir}'"
 
-  PATHS=("${staging_dir}/loader_app"                          \
-         "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \
+  PATHS=("${staging_dir}/loader_app"                                                \
+         "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \
          "${staging_dir}/content/app/cobalt/content/")
 
   for file in "${PATHS[@]}"; do
diff --git a/starboard/evergreen/testing/raspi/deploy_cobalt.sh b/starboard/evergreen/testing/raspi/deploy_cobalt.sh
index 8fdbc22..9012f3c 100755
--- a/starboard/evergreen/testing/raspi/deploy_cobalt.sh
+++ b/starboard/evergreen/testing/raspi/deploy_cobalt.sh
@@ -31,8 +31,8 @@
 
   echo " Checking '${staging_dir}'"
 
-  PATHS=("${staging_dir}/loader_app"                          \
-         "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \
+  PATHS=("${staging_dir}/loader_app"                                                \
+         "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \
          "${staging_dir}/content/app/cobalt/content/")
 
   for file in "${PATHS[@]}"; do
@@ -60,7 +60,7 @@
   eval "${SSH} \"mkdir -p /home/pi/coeg/content/app/cobalt/lib\""
 
   echo " Copying cobalt to system image directory"
-  eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt.so pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\""
+  eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION} pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\""
 
   echo " Copying content to system image directory"
   eval "${SCP} \"-r ${staging_dir}/content/app/cobalt/content/ pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/\""
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh
index efb9d0b..91c53fb 100755
--- a/starboard/evergreen/testing/run_all_tests.sh
+++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,7 +21,9 @@
 DIR="$(dirname "${0}")"
 
 AUTH_METHOD="public-key"
-while getopts "d:a:" o; do
+USE_COMPRESSED_SYSTEM_IMAGE="false"
+SYSTEM_IMAGE_EXTENSION=".so"
+while getopts "d:a:c" o; do
     case "${o}" in
         d)
             DEVICE_ID=${OPTARG}
@@ -29,6 +31,10 @@
         a)
             AUTH_METHOD=${OPTARG}
             ;;
+        c)
+            USE_COMPRESSED_SYSTEM_IMAGE="true"
+            SYSTEM_IMAGE_EXTENSION=".lz4"
+            ;;
     esac
 done
 shift $((OPTIND-1))
@@ -40,8 +46,14 @@
 
 source $DIR/setup.sh
 
-# Find all of the test files within the 'test' subdirectory.
-TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
+if [[ "${USE_COMPRESSED_SYSTEM_IMAGE}" == "true" ]]; then
+  # It would be valid to run all test cases using a compressed system image but
+  # is probably excessive. Instead, just the Evergreen Lite case is run to test
+  # that the compressed system image can be successfully loaded.
+  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name 'evergreen_lite_test.sh'"))
+else
+  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
+fi
 
 COUNT=0
 RETRIED=()
@@ -145,7 +157,7 @@
 
 clean_up
 
-log "info" " [==========] Finished."
+log "info" " [==========] Finished testing with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}."
 
 if [[ "${#FAILED[@]}" -eq 0 ]]; then
   exit 0
diff --git a/starboard/evergreen/testing/setup.sh b/starboard/evergreen/testing/setup.sh
index 57bdbc4..393ea6d 100755
--- a/starboard/evergreen/testing/setup.sh
+++ b/starboard/evergreen/testing/setup.sh
@@ -24,7 +24,7 @@
 
 source $DIR/pprint.sh
 
-log "info" " [==========] Preparing Cobalt."
+log "info" " [==========] Preparing to test with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}."
 
 if [[ -z ${1} ]]; then
   log "error" "A platform must be provided"
diff --git a/starboard/linux/shared/media_is_audio_supported.cc b/starboard/linux/shared/media_is_audio_supported.cc
index 30d71e5..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;
   }
diff --git a/starboard/linux/shared/media_is_video_supported.cc b/starboard/linux/shared/media_is_video_supported.cc
index b05bfe4..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;
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/starboard/media/BUILD.gn b/starboard/shared/starboard/media/BUILD.gn
index 1e7355b..7baaf99 100644
--- a/starboard/shared/starboard/media/BUILD.gn
+++ b/starboard/shared/starboard/media/BUILD.gn
@@ -19,8 +19,16 @@
     "//starboard/shared/starboard/media/avc_util.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/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..e9ad962
--- /dev/null
+++ b/starboard/shared/starboard/media/key_system_supportability_cache.h
@@ -0,0 +1,77 @@
+// 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 {
+
+// KeySystemSupportabilityCache caches the supportabilities of the combinations
+// of codec and key system.
+//
+// Note: anytime the platform key system capabilities have changed, please
+// call KeySystemSupportabilityCache::ClearCache() to clear the outdated
+// results.
+//
+// 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..d52761c
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_supportability_cache.cc
@@ -0,0 +1,336 @@
+// 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 <cstring>
+#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 {
+
+// 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;
+}
+
+// Note that if bitrate parsing failed, |bitrate| will be set to -1.
+void StripAndParseBitrate(const char* mime,
+                          std::string* mime_without_bitrate,
+                          int* bitrate) {
+  SB_DCHECK(mime_without_bitrate);
+  SB_DCHECK(bitrate);
+
+  std::string bitrate_string;
+  *mime_without_bitrate =
+      RemoveAttributeFromMime(mime, "bitrate", &bitrate_string);
+
+  if (bitrate_string.empty()) {
+    *bitrate = 0;
+    return;
+  }
+
+  MimeType::Param param;
+  if (!MimeType::ParseParamString(bitrate_string, &param) ||
+      param.type != MimeType::kParamTypeInteger) {
+    *bitrate = -1;
+    return;
+  }
+  SB_DCHECK(param.name == "bitrate");
+  *bitrate = param.int_value;
+}
+
+}  // namespace
+
+// static
+SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityCache,
+                            MimeSupportabilityCache::GetInstance);
+
+Supportability MimeSupportabilityCache::GetMimeSupportability(
+    const char* mime,
+    ParsedMimeInfo* mime_info) {
+  SB_DCHECK(mime);
+  SB_DCHECK(mime_info);
+
+  // Strip the bitrate from mime string and check it separately.
+  std::string mime_without_bitrate;
+  int bitrate;
+  StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate);
+
+  if (bitrate < 0) {
+    // The mime string contains an invalid bitrate attribute. In that case, we
+    // return an invalid ParsedMimeInfo with kSbMediaSupportTypeNotSupported.
+    *mime_info = ParsedMimeInfo(mime);
+    return kSupportabilityNotSupported;
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  Entry& entry = GetEntry_Locked(mime_without_bitrate);
+
+  // Return cached ParsedMimeInfo with real bitrate.
+  *mime_info = entry.mime_info;
+  mime_info->SetBitrate(bitrate);
+
+  if (!mime_info->is_valid()) {
+    // Return kSupportabilityNotSupported if we can't get a valid
+    // ParsedMimeInfo.
+    return kSupportabilityNotSupported;
+  }
+
+  return is_enabled_ ? IsBitrateSupported_Locked(entry, bitrate)
+                     : kSupportabilityUnknown;
+}
+
+void MimeSupportabilityCache::CacheMimeSupportability(
+    const char* mime,
+    Supportability supportability) {
+  SB_DCHECK(mime);
+  SB_DCHECK(supportability != kSupportabilityUnknown);
+
+  if (!is_enabled_) {
+    return;
+  }
+
+  // Strip bitrate as what we do in GetMimeSupportability().
+  std::string mime_without_bitrate;
+  int bitrate;
+  StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate);
+
+  if (bitrate < 0) {
+    // The mime string contains an invalid bitrate attribute.
+    return;
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  Entry& entry = GetEntry_Locked(mime_without_bitrate);
+
+  if (entry.mime_info.is_valid()) {
+    UpdateBitrateSupportability_Locked(&entry, bitrate, supportability);
+  }
+}
+
+void MimeSupportabilityCache::ClearCachedMimeSupportabilities() {
+  ScopedLock scoped_lock(mutex_);
+  for (auto& iter : entries_) {
+    iter.second.max_supported_bitrate = -1;
+    iter.second.min_unsupported_bitrate = INT_MAX;
+  }
+}
+
+void MimeSupportabilityCache::DumpCache() {
+  ScopedLock scoped_lock(mutex_);
+  std::stringstream ss;
+  ss << "\n========Dumping MimeSupportabilityCache========";
+  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  MaxSupportedBitrate: "
+       << entry_iter.second.max_supported_bitrate;
+    ss << "\n  MinUnsupportedBitrate: "
+       << entry_iter.second.min_unsupported_bitrate;
+  }
+  ss << "\n========End of Dumping========";
+
+  SB_DLOG(INFO) << ss.str();
+}
+
+MimeSupportabilityCache::Entry& MimeSupportabilityCache::GetEntry_Locked(
+    const std::string& mime_string) {
+  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 ParsedMimeInfo.
+  auto insert_result = entries_.insert({mime_string, Entry(mime_string)});
+
+  // Keep cached items not exceeding max size.
+  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;
+}
+
+Supportability MimeSupportabilityCache::IsBitrateSupported_Locked(
+    const Entry& entry,
+    int bitrate) const {
+  SB_DCHECK(bitrate >= 0);
+
+  if (bitrate <= entry.max_supported_bitrate) {
+    return kSupportabilitySupported;
+  }
+  if (bitrate >= entry.min_unsupported_bitrate) {
+    return kSupportabilityNotSupported;
+  }
+  return kSupportabilityUnknown;
+}
+
+void MimeSupportabilityCache::UpdateBitrateSupportability_Locked(
+    Entry* entry,
+    int bitrate,
+    Supportability supportability) {
+  SB_DCHECK(entry);
+  SB_DCHECK(bitrate >= 0);
+  SB_DCHECK(supportability != kSupportabilityUnknown);
+
+  if (supportability == kSupportabilitySupported) {
+    SB_DCHECK(bitrate < entry->min_unsupported_bitrate);
+    if (bitrate > entry->max_supported_bitrate) {
+      entry->max_supported_bitrate = bitrate;
+    }
+  } else if (supportability == kSupportabilityNotSupported) {
+    SB_DCHECK(bitrate > entry->max_supported_bitrate);
+    if (bitrate < entry->min_unsupported_bitrate) {
+      entry->min_unsupported_bitrate = bitrate;
+    }
+  }
+}
+
+}  // 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..c1e560b
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_supportability_cache.h
@@ -0,0 +1,128 @@
+// 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 <queue>
+#include <string>
+#include <unordered_map>
+
+#include "starboard/common/mutex.h"
+#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;
+
+// MimeSupportabilityCache caches the supportabilities of raw mime strings.
+// To increase cache hit rate, it strips bitrate from the raw mime string, and
+// stores a supported bitrate range for mime strings with same other attributes.
+//
+// Note: MimeSupportabilityCache leverage the assumption that if the
+// platform can support a codec with bitrate of n, the codec should also support
+// any bitrate less than n. If that assumption is not true, please do NOT enable
+// MimeSupportabilityCache.
+//
+// Note: anytime the platform codec capabilities have changed, please call
+// MimeSupportabilityCache::ClearCachedMimeSupportabilities() to clear the
+// outdated results.
+//
+// TODO: add unit tests for MimeSupportabilityCache.
+class MimeSupportabilityCache {
+ public:
+  static MimeSupportabilityCache* GetInstance();
+
+  // When cache is not enabled, GetMimeSupportability() will return cached
+  // ParsedMimeInfo with kSupportabilityUnknown, and CacheMimeSupportability()
+  // will do nothing.
+  bool IsEnabled() const { return is_enabled_; }
+  void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
+
+  // Set the max number of the cached ParsedMimeInfos and its supportabilities.
+  void SetCacheMaxSize(size_t size) { max_size_ = size; }
+
+  // Get cached ParsedMimeInfo and mime supportability. If there's no cached
+  // ParsedMimeInfo, it will parse the mime string and cache the result.
+  // If we cannot get a valid ParsedMimeInfo from |mime|,
+  // GetMimeSupportability() will return kSupportabilityNotSupported with an
+  // invalid ParsedMimeInfo. Ideally, we should decouple mime parsing and
+  // supportability cache, but considering that the cache is only for internal
+  // use, to avoid repeated lookups, we do parsing in this function for now.
+  // Note that |mime| and |mime_info| cannot be null.
+  Supportability GetMimeSupportability(const char* mime,
+                                       ParsedMimeInfo* mime_info);
+
+  // Update cached supportability of the mime string.
+  // Note that if |supportability| is kSupportabilityUnknown or we cannot
+  // get a valid ParsedMimeInfo from |mime|, CacheMimeSupportability()
+  // will not cache the supportability.
+  void CacheMimeSupportability(const char* mime, Supportability supportability);
+
+  // Clear all cached supportabilities. But it will not remove cached
+  // ParsedMimeInfos, as for the same mime string, the parsed results should be
+  // always the same.
+  void ClearCachedMimeSupportabilities();
+
+  void DumpCache();
+
+ private:
+  const int kDefaultCacheMaxSize = 2000;
+
+  struct Entry {
+    ParsedMimeInfo mime_info;
+    int max_supported_bitrate = -1;
+    int min_unsupported_bitrate = INT_MAX;
+
+    explicit Entry(const std::string& mime) : mime_info(mime) {}
+  };
+
+  // Class can only be instanced via the singleton
+  MimeSupportabilityCache() {}
+  ~MimeSupportabilityCache() {}
+
+  MimeSupportabilityCache(const MimeSupportabilityCache&) = delete;
+  MimeSupportabilityCache& operator=(const MimeSupportabilityCache&) = delete;
+
+  Entry& GetEntry_Locked(const std::string& mime_string);
+  Supportability IsBitrateSupported_Locked(const Entry& entry,
+                                           int bitrate) const;
+  void UpdateBitrateSupportability_Locked(Entry* entry,
+                                          int bitrate,
+                                          Supportability supportability);
+
+  typedef std::unordered_map<std::string, Entry> Entries;
+
+  Mutex mutex_;
+  Entries entries_;
+  std::queue<Entries::iterator> fifo_queue_;
+  std::atomic_int max_size_{kDefaultCacheMaxSize};
+  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..6c53aa7
--- /dev/null
+++ b/starboard/shared/starboard/media/mime_util.cc
@@ -0,0 +1,289 @@
+// 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/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 {
+
+// 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);
+}
+
+}  // namespace
+
+SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime,
+                                           const char* key_system) {
+  SB_DCHECK(mime);
+  SB_DCHECK(key_system);
+
+  // Get cached ParsedMimeInfo with its supportability. If it is not found in
+  // the cache, MimeSupportabilityCache would parse the mime string and return
+  // the ParsedMimeInfo with kSupportabilityUnknown.
+  ParsedMimeInfo mime_info;
+  Supportability mime_supportability =
+      MimeSupportabilityCache::GetInstance()->GetMimeSupportability(mime,
+                                                                    &mime_info);
+
+  if (mime_info.disable_cache()) {
+    // Disable all caches if required.
+    mime_supportability = kSupportabilityUnknown;
+    MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false);
+    KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false);
+  }
+
+  // Reject mime if cached result is not supported.
+  if (mime_supportability == kSupportabilityNotSupported) {
+    return kSbMediaSupportTypeNotSupported;
+  }
+
+  // MimeSupportabilityCache::GetMimeSupportability() returns
+  // kSupportabilityNotSupported if ParsedMimeInfo is not valid, so |mime_info|
+  // must be valid here.
+  SB_DCHECK(mime_info.is_valid());
+
+  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;
+    }
+  }
+
+  // At this point, |key_system| is supported. Return supported here if
+  // mime is also supported. Otherwise, |mime_supportability| must be unknown.
+  if (mime_supportability == kSupportabilitySupported) {
+    return kSbMediaSupportTypeProbably;
+  }
+  SB_DCHECK(mime_supportability == kSupportabilityUnknown);
+
+  // Call platform functions to check if it's supported.
+  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.
+  MimeSupportabilityCache::GetInstance()->CacheMimeSupportability(
+      mime, mime_supportability);
+
+  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/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;