Import Cobalt 23.master.0.308575

Includes the following patches:
  https://cobalt-review.googlesource.com/c/cobalt/+/6430
    by arnaud.ferraris@collabora.corp-partner.google.com
diff --git a/.codespellignorelines b/.codespellignorelines
index 90892e7..0079aa8 100644
--- a/.codespellignorelines
+++ b/.codespellignorelines
@@ -6,3 +6,4 @@
       gr_texture_.reset(new GrBackendTexture(texture_size.width(),
                                              texture_size.height(),
                                              GrMipMapped::kNo, texture_info));
+Onces represent initializations that should only ever happen once per process,
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 8a5bc66..e6ccec8 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3195,7 +3195,7 @@
     ":base_unittests_bundle_data",
   ]
   if (is_starboard){
-    content_deps = [
+    data_deps += [
       ":base_unittests_bundle_data",
       "//third_party/icu:icudata",
     ]
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn
index 302256c..1ba068f 100644
--- a/cobalt/base/BUILD.gn
+++ b/cobalt/base/BUILD.gn
@@ -122,5 +122,5 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
 }
diff --git a/cobalt/bindings/contexts.py b/cobalt/bindings/contexts.py
index c61b3cf..9285877 100644
--- a/cobalt/bindings/contexts.py
+++ b/cobalt/bindings/contexts.py
@@ -235,6 +235,18 @@
         self.resolve_typedef(element_idl_type))
     return '::cobalt::script::Sequence< %s >' % element_cobalt_type
 
+  def idl_promise_type_to_cobalt(self, idl_type):
+    """Map IDL promise type to C++ promise type implementation."""
+    assert is_promise_type(idl_type), 'Expected promise type.'
+    result_idl_type = idl_type.result_type
+    assert not is_object_type(result_idl_type), 'Object type not supported.'
+    assert (not result_idl_type.is_callback_function and
+            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
+    return result
+
   def idl_union_type_to_cobalt(self, idl_type):
     """Map IDL union type to C++ union type implementation."""
     # Flatten the union type. Order matters for our implementation.
@@ -300,7 +312,7 @@
     elif idl_type.is_dictionary:
       cobalt_type = get_interface_name(idl_type)
     elif is_promise_type(idl_type):
-      cobalt_type = '::cobalt::script::NativePromise'
+      cobalt_type = self.idl_promise_type_to_cobalt(idl_type)
     elif is_array_buffer_or_view_type(idl_type):
       cobalt_type = '::cobalt::script::{}'.format(idl_type.base_type)
     assert cobalt_type, 'Unsupported idl_type %s' % idl_type
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index e9808f3..fce4063 100644
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -65,7 +65,7 @@
     'disable_eval_with_csp',
     # http_cache is disabled due to flakiness. Planned update to make use of
     # transferSize rather than load timings to make it more consistent.
-    # 'http_cache',
+    'http_cache',
     'persistent_cookie',
     'soft_mic_platform_service_test',
     'web_debugger',
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 17a9a85..c319c72 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -57,7 +57,7 @@
     "//cobalt/base",
     "//net",
   ]
-  content_deps = [
+  data_deps = [
     "//cobalt/dom:licenses",
     "//cobalt/network:copy_ssl_certificates",
     "//cobalt/speech:speech_testdata",
@@ -65,15 +65,15 @@
     "//third_party/icu:icudata",
   ]
   if (cobalt_font_package == "empty") {
-    content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+    data_deps += [ "//cobalt/content/fonts:copy_font_data" ]
   } else {
-    content_deps += [
+    data_deps += [
       "//cobalt/content/fonts:copy_fonts",
       "//cobalt/content/fonts:fonts_xml",
     ]
   }
   if (!is_gold) {
-    content_deps += [
+    data_deps += [
       "//cobalt/debug/backend/content:copy_backend_web_files",
       "//cobalt/debug/console/content:copy_console_web_files",
       "//cobalt/debug/remote/content:copy_remote_web_files",
@@ -84,9 +84,6 @@
       "//third_party/devtools:supported_css_properties",
     ]
   }
-  if (defined(platform_i18n_config_path)) {
-    content_deps += [ platform_i18n_config_path ]
-  }
 }
 
 ##############################
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index e28dd00..f626b80 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -210,6 +210,7 @@
   "//cobalt/web/event_listener.idl",
   "//cobalt/web/event_target.idl",
   "//cobalt/web/message_event.idl",
+  "//cobalt/web/message_port.idl",
   "//cobalt/web/navigator_ua_data.idl",
   "//cobalt/web/on_error_event_listener.idl",
   "//cobalt/web/security_policy_violation_event.idl",
@@ -233,7 +234,8 @@
   "//cobalt/worker/client.idl",
   "//cobalt/worker/clients.idl",
   "//cobalt/worker/dedicated_worker_global_scope.idl",
-  "//cobalt/worker/message_port.idl",
+  "//cobalt/worker/extendable_event.idl",
+  "//cobalt/worker/extendable_message_event.idl",
   "//cobalt/worker/navigation_preload_manager.idl",
   "//cobalt/worker/service_worker.idl",
   "//cobalt/worker/service_worker_container.idl",
@@ -347,6 +349,8 @@
   "//cobalt/websocket/close_event_init.idl",
   "//cobalt/worker/client_type.idl",
   "//cobalt/worker/client_query_options.idl",
+  "//cobalt/worker/extendable_event_init.idl",
+  "//cobalt/worker/extendable_message_event_init.idl",
   "//cobalt/worker/frame_type.idl",
   "//cobalt/worker/navigation_preload_state.idl",
   "//cobalt/worker/registration_options.idl",
diff --git a/cobalt/browser/user_agent_platform_info.cc b/cobalt/browser/user_agent_platform_info.cc
index 1264c06..4c334e9 100644
--- a/cobalt/browser/user_agent_platform_info.cc
+++ b/cobalt/browser/user_agent_platform_info.cc
@@ -146,40 +146,6 @@
 }
 #endif
 
-struct ConnectionTypeName {
-  SbSystemConnectionType connection_type;
-  char connection_type_string[26];
-};
-
-const ConnectionTypeName kConnectionTypeStrings[] = {
-    {kSbSystemConnectionTypeWired, "Wired"},
-    {kSbSystemConnectionTypeWireless, "Wireless"},
-    {kSbSystemConnectionTypeUnknown, "UnspecifiedConnectionType"}};
-
-std::string CreateConnectionTypeString(
-    const base::Optional<SbSystemConnectionType>& connection_type) {
-  if (connection_type) {
-    for (auto& map : kConnectionTypeStrings) {
-      if (map.connection_type == connection_type) {
-        return std::string(map.connection_type_string);
-      }
-    }
-  }
-  return "UnspecifiedConnectionType";
-}
-
-#if !defined(COBALT_BUILD_TYPE_GOLD)
-SbSystemConnectionType GetConnectionType(std::string connection_type_string) {
-  for (auto& map : kConnectionTypeStrings) {
-    if (!SbStringCompareNoCase(map.connection_type_string,
-                               connection_type_string.c_str())) {
-      return map.connection_type;
-    }
-  }
-  return kSbSystemConnectionTypeUnknown;
-}
-#endif
-
 static bool isAsciiAlphaDigit(int c) {
   return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c);
 }
@@ -360,9 +326,6 @@
     info.set_model(value);
   }
 
-  // Connection type
-  info.set_connection_type(SbSystemGetConnectionType());
-
 // Apply overrides from command line
 #if !defined(COBALT_BUILD_TYPE_GOLD)
   if (base::CommandLine::InitializedForCurrentProcess()) {
@@ -408,9 +371,6 @@
         } else if (!input.first.compare("aux_field")) {
           info.set_aux_field(input.second);
           LOG(INFO) << "Set aux field to " << input.second;
-        } else if (!input.first.compare("connection_type")) {
-          info.set_connection_type(GetConnectionType(input.second));
-          LOG(INFO) << "Set connection type to " << input.second;
         } else if (!input.first.compare("javascript_engine_version")) {
           info.set_javascript_engine_version(input.second);
           LOG(INFO) << "Set javascript engine version to " << input.second;
@@ -505,16 +465,6 @@
   aux_field_ = Sanitize(aux_field, isTCHARorForwardSlash);
 }
 
-void UserAgentPlatformInfo::set_connection_type(
-    base::Optional<SbSystemConnectionType> connection_type) {
-  if (connection_type) {
-    connection_type_ = connection_type;
-    connection_type_string_ = CreateConnectionTypeString(connection_type_);
-  } else {
-    connection_type_string_ = "";
-  }
-}
-
 void UserAgentPlatformInfo::set_javascript_engine_version(
     const std::string& javascript_engine_version) {
   javascript_engine_version_ =
diff --git a/cobalt/browser/user_agent_platform_info.h b/cobalt/browser/user_agent_platform_info.h
index 3c13ea7..1f379f6 100644
--- a/cobalt/browser/user_agent_platform_info.h
+++ b/cobalt/browser/user_agent_platform_info.h
@@ -59,12 +59,6 @@
   base::Optional<std::string> brand() const override { return brand_; }
   base::Optional<std::string> model() const override { return model_; }
   const std::string& aux_field() const override { return aux_field_; }
-  base::Optional<SbSystemConnectionType> connection_type() const override {
-    return connection_type_;
-  }
-  const std::string& connection_type_string() const override {
-    return connection_type_string_;
-  }
   const std::string& javascript_engine_version() const override {
     return javascript_engine_version_;
   }
@@ -97,8 +91,6 @@
   void set_brand(base::Optional<std::string> brand);
   void set_model(base::Optional<std::string> model);
   void set_aux_field(const std::string& aux_field);
-  void set_connection_type(
-      base::Optional<SbSystemConnectionType> connection_type);
   void set_javascript_engine_version(
       const std::string& javascript_engine_version);
   void set_rasterizer_type(const std::string& rasterizer_type);
@@ -121,8 +113,6 @@
   base::Optional<std::string> brand_;
   base::Optional<std::string> model_;
   std::string aux_field_;
-  base::Optional<SbSystemConnectionType> connection_type_;
-  std::string connection_type_string_;
   std::string javascript_engine_version_;
   std::string rasterizer_type_;
   std::string evergreen_type_;
diff --git a/cobalt/browser/user_agent_string.cc b/cobalt/browser/user_agent_string.cc
index c048b18..3b36311 100644
--- a/cobalt/browser/user_agent_string.cc
+++ b/cobalt/browser/user_agent_string.cc
@@ -86,9 +86,9 @@
                         platform_info.starboard_version().c_str());
   }
 
-  // Device/FirmwareVersion (Brand, Model, ConnectionType)
+  // Device/FirmwareVersion (Brand, Model)
   base::StringAppendF(
-      &user_agent, ", %s_%s_%s_%s/%s (%s, %s, %s)",
+      &user_agent, ", %s_%s_%s_%s/%s (%s, %s)",
       platform_info.original_design_manufacturer()
           .value_or(kUnknownFieldName)
           .c_str(),
@@ -97,8 +97,7 @@
       platform_info.model_year().value_or("0").c_str(),
       platform_info.firmware_version().value_or(kUnknownFieldName).c_str(),
       platform_info.brand().value_or(kUnknownFieldName).c_str(),
-      platform_info.model().value_or(kUnknownFieldName).c_str(),
-      platform_info.connection_type_string().c_str());
+      platform_info.model().value_or(kUnknownFieldName).c_str());
 
   if (!platform_info.aux_field().empty()) {
     base::StringAppendF(&user_agent, " %s", platform_info.aux_field().c_str());
diff --git a/cobalt/browser/user_agent_string_test.cc b/cobalt/browser/user_agent_string_test.cc
index bbd66a7..9ce2eb4 100644
--- a/cobalt/browser/user_agent_string_test.cc
+++ b/cobalt/browser/user_agent_string_test.cc
@@ -35,7 +35,6 @@
   platform_info.set_brand("");
   platform_info.set_model("");
   platform_info.set_aux_field("");
-  platform_info.set_connection_type(base::nullopt);
   platform_info.set_javascript_engine_version("");
   platform_info.set_rasterizer_type("");
   platform_info.set_evergreen_version("");
@@ -295,30 +294,10 @@
 
   const char* tv_info_str =
       "ApertureScienceInnovators_OTT_PbodyOrangeAtlasBlue_2013/001 "
-      "(Aperture Science Labs, GLaDOS, )";
+      "(Aperture Science Labs, GLaDOS)";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
-TEST(UserAgentStringFactoryTest, WithWiredConnection) {
-  UserAgentPlatformInfo platform_info =
-      CreateOnlyOSNameAndVersionPlatformInfo();
-  platform_info.set_connection_type(kSbSystemConnectionTypeWired);
-  platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
-  std::string user_agent_string = CreateUserAgentString(platform_info);
-
-  EXPECT_NE(std::string::npos, user_agent_string.find(", Wired)"));
-}
-
-TEST(UserAgentStringFactoryTest, WithWirelessConnection) {
-  UserAgentPlatformInfo platform_info =
-      CreateOnlyOSNameAndVersionPlatformInfo();
-  platform_info.set_connection_type(kSbSystemConnectionTypeWireless);
-  platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
-  std::string user_agent_string = CreateUserAgentString(platform_info);
-
-  EXPECT_NE(std::string::npos, user_agent_string.find(", Wireless)"));
-}
-
 TEST(UserAgentStringFactoryTest, WithOnlyBrandModelAndDeviceType) {
   UserAgentPlatformInfo platform_info =
       CreateOnlyOSNameAndVersionPlatformInfo();
@@ -328,7 +307,7 @@
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
   const char* tv_info_str =
-      ", Unknown_OTT_Unknown_0/Unknown (Aperture Science, GLaDOS, )";
+      ", Unknown_OTT_Unknown_0/Unknown (Aperture Science, GLaDOS)";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
@@ -339,7 +318,7 @@
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
   const char* tv_info_str =
-      "Starboard/6, Unknown_OTT_Unknown_0/Unknown (Unknown, Unknown, )";
+      "Starboard/6, Unknown_OTT_Unknown_0/Unknown (Unknown, Unknown)";
   EXPECT_NE(std::string::npos, user_agent_string.find(tv_info_str));
 }
 
diff --git a/cobalt/build/build_id.py b/cobalt/build/build_id.py
index a2f2ed3..1abe051 100755
--- a/cobalt/build/build_id.py
+++ b/cobalt/build/build_id.py
@@ -22,10 +22,10 @@
 #ifndef _COBALT_BUILD_ID_H_
 #define _COBALT_BUILD_ID_H_
 
-#define COBALT_BUILD_VERSION_DATE "{date_rep}"
+#ifndef COBALT_BUILD_VERSION_NUMBER
 #define COBALT_BUILD_VERSION_NUMBER "{version_number}"
-#define COBALT_BUILD_VERSION_TIMESTAMP "{timestamp}"
-#define COBALT_BUILD_VERSION_USERNAME "{username}"
+#endif  // COBALT_BUILD_VERSION_NUMBER
+
 
 #endif  // _COBALT_BUILD_ID_H_
 """
diff --git a/cobalt/content/ssl/certs/0a775a30.0 b/cobalt/content/ssl/certs/0a775a30.0
index 7d540a4..b2b5a77 100644
--- a/cobalt/content/ssl/certs/0a775a30.0
+++ b/cobalt/content/ssl/certs/0a775a30.0
@@ -1,13 +1,13 @@
 -----BEGIN CERTIFICATE-----
-MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
-736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
-DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
-fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
-njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
+MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
+jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
+4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
+VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
+ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/0b9bc432.0 b/cobalt/content/ssl/certs/0b9bc432.0
new file mode 100644
index 0000000..7d903ed
--- /dev/null
+++ b/cobalt/content/ssl/certs/0b9bc432.0
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/1001acf7.0 b/cobalt/content/ssl/certs/1001acf7.0
index 5496703..a13aa05 100644
--- a/cobalt/content/ssl/certs/1001acf7.0
+++ b/cobalt/content/ssl/certs/1001acf7.0
@@ -1,31 +1,31 @@
 -----BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
-MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
-QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
-MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
-cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
-f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
-mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
-zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
-fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
-vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
-Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
-zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
-Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
-k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
-DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
-lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
-Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
-d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
-XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
-gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
-d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
-J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
-DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
-+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
-F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
-SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
-E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/1636090b.0 b/cobalt/content/ssl/certs/1636090b.0
deleted file mode 100644
index 5ecc81c..0000000
--- a/cobalt/content/ssl/certs/1636090b.0
+++ /dev/null
@@ -1,25 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
-RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
-dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
-YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
-NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
-EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
-cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
-c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
-BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
-dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
-fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
-bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
-75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
-FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
-HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
-5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
-b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
-A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
-6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
-TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
-dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
-Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
-l7WdmplNsDz4SgCbZN2fOUvRJ9e4
------END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/3bde41ac.0 b/cobalt/content/ssl/certs/3bde41ac.0
index de97f84..8426523 100644
--- a/cobalt/content/ssl/certs/3bde41ac.0
+++ b/cobalt/content/ssl/certs/3bde41ac.0
@@ -1,8 +1,8 @@
 -----BEGIN CERTIFICATE-----
-MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
 BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
-cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
-MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
+MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
 Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
 MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
 thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
@@ -15,21 +15,21 @@
 EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
 KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
 6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
-OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
-VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
-VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
-cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
-ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
-AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
-661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
-am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
-ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
-PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
-3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
-SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
-3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
-ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
-StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
-Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
-jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
+tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
+IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
+AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
+ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
+iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
+Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
+hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
+Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
+EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
+1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
+CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
+5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
+f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
+ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
+GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/5931b5bc.0 b/cobalt/content/ssl/certs/5931b5bc.0
new file mode 100644
index 0000000..25da5fa
--- /dev/null
+++ b/cobalt/content/ssl/certs/5931b5bc.0
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
+NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC
+/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD
+wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3
+OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
+y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
+gfM0agPnIjhQW+0ZT0MW
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/5a7722fb.0 b/cobalt/content/ssl/certs/5a7722fb.0
new file mode 100644
index 0000000..049a4da
--- /dev/null
+++ b/cobalt/content/ssl/certs/5a7722fb.0
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw
+gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn
+cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD
+VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2
+NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r
+YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh
+IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF
+Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ
+KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK
+fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB
+Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C
+MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp
+ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6
+7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx
+vmjkI6TZraE3
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/626dceaf.0 b/cobalt/content/ssl/certs/626dceaf.0
index 984f1d1..bd3f3a7 100644
--- a/cobalt/content/ssl/certs/626dceaf.0
+++ b/cobalt/content/ssl/certs/626dceaf.0
@@ -1,31 +1,31 @@
 -----BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
-MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
-QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
-MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
-cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
-CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
-GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
-XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
-re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
-PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
-mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
-8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
-x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
-nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
-kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
-twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
-8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
-vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
-z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
-pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
-pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
-R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
-RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
-0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
-5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
-izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
-yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
+MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
+nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
+6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
+MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
+RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
+f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
+dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
+Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
+G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
+gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
+vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
+0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
+B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
+NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
+yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
+HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
+xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
+TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
+JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
+7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
+6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/66445960.0 b/cobalt/content/ssl/certs/66445960.0
new file mode 100644
index 0000000..9592787
--- /dev/null
+++ b/cobalt/content/ssl/certs/66445960.0
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQEL
+BQAwgYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUt
+VHVncmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYw
+JAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIFJTQSB2MzAeFw0yMDAzMTgw
+OTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMG
+QW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
+Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBD
+QSBSU0EgdjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J7
+7gnJY9LTQ91ew6aEOErxjYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscx
+uj7X/iWpKo429NEvx7epXTPcMHD4QGxLsqYxYdE0PD0xesevxKenhOGXpOhL9hd8
+7jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF/YP9f4RtNGx/ardLAQO/
+rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8qQedmCeFL
+l+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bG
+wzrwbMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4
+znKS4iicvObpCdg604nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBO
+M/J+JjKsBY04pOZ2PJ8QaQ5tndLBeSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK
+5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiMbIedBi3x7+PmBvrFZhNb/FAH
+nnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbgh3cXTJ2w2Amo
+DVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSy
+tK7mLfcm1ap1LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEL
+BQADggIBAImocn+M684uGMQQgC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ
+6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN438o2Fi+CiJ+8EUdPdk3ILY7r3y18
+Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/qln0F7psTpURs+APQ
+3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3sSdPk
+vmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn9
+9t2HVhjYsCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQ
+mhty3QUBjYZgv6Rn7rWlDdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YA
+VSgU7NbHEqIbZULpkejLPoeJVF3Zr52XnGnnCv8PWniLYypMfUeUP95L6VPQMPHF
+9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFHIK+WEj5jlB0E5y67hscM
+moi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiXYY60MGo8
+bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/7a3adc42.0 b/cobalt/content/ssl/certs/7a3adc42.0
new file mode 100644
index 0000000..240fb75
--- /dev/null
+++ b/cobalt/content/ssl/certs/7a3adc42.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL
+BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x
+FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx
+MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s
+THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc
+IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU
+AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+
+GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9
+8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH
+flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt
+J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim
+0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN
+pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ
+UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW
+OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB
+AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet
+8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
+nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j
+bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM
+Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv
+TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS
+S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr
+I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9
+b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB
+UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P
+Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven
+sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/7a780d93.0 b/cobalt/content/ssl/certs/7a780d93.0
new file mode 100644
index 0000000..d62495e
--- /dev/null
+++ b/cobalt/content/ssl/certs/7a780d93.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
+PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
+dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
+YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
+1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
+vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
+aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
+1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
+r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
+cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
+wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
+6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
+2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
+Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
+eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
+d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
+PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
+8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
+1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
+rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
+taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
+lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
+yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
+Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
+yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
+wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
+OV+KmalBWQewLK8=
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/8508e720.0 b/cobalt/content/ssl/certs/8508e720.0
new file mode 100644
index 0000000..e0e6251
--- /dev/null
+++ b/cobalt/content/ssl/certs/8508e720.0
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
+CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
+bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
+eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
+QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
+hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
+ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
+BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/8f103249.0 b/cobalt/content/ssl/certs/8f103249.0
new file mode 100644
index 0000000..567fe25
--- /dev/null
+++ b/cobalt/content/ssl/certs/8f103249.0
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
+CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
+AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
+NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ
+MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq
+AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9
+vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9
+lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD
+n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT
+7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o
+6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC
+TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6
+WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R
+DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI
+pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj
+YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy
+rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
+8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi
+0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM
+A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS
+SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K
+TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF
+6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er
+3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt
+Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT
+VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW
+ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA
+rBPuUBQemMc=
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/90c5a3c8.0 b/cobalt/content/ssl/certs/90c5a3c8.0
new file mode 100644
index 0000000..efc55dc
--- /dev/null
+++ b/cobalt/content/ssl/certs/90c5a3c8.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
+Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3
+YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw
+qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv
+Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6
+lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz
+Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ
+KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK
+FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj
+HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr
+y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ
+/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM
+a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6
+fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
+SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
+7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc
+SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza
+ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc
+XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg
+iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho
+L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF
+Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr
+kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+
+vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU
+YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/9846683b.0 b/cobalt/content/ssl/certs/9846683b.0
new file mode 100644
index 0000000..802c4c8
--- /dev/null
+++ b/cobalt/content/ssl/certs/9846683b.0
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
+Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
+MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
+bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
+7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
+0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
+B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
+BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
+LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
+DXZDjC5Ty3zfDBeWUA==
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/9ef4a08a.0 b/cobalt/content/ssl/certs/9ef4a08a.0
new file mode 100644
index 0000000..82db921
--- /dev/null
+++ b/cobalt/content/ssl/certs/9ef4a08a.0
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
+NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS
+zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0
+QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/
+VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW
+wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV
+dWNbFJWcHwHP2NVypw87
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/a3418fda.0 b/cobalt/content/ssl/certs/a3418fda.0
index 07372d3..fdb2d1b 100644
--- a/cobalt/content/ssl/certs/a3418fda.0
+++ b/cobalt/content/ssl/certs/a3418fda.0
@@ -1,13 +1,13 @@
 -----BEGIN CERTIFICATE-----
-MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
-hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
-xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
-CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
-sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/b0e59380.0 b/cobalt/content/ssl/certs/b0e59380.0
index e9d0c33..7670528 100644
--- a/cobalt/content/ssl/certs/b0e59380.0
+++ b/cobalt/content/ssl/certs/b0e59380.0
@@ -1,13 +1,12 @@
 -----BEGIN CERTIFICATE-----
-MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
-MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
-bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
-DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
-QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
-FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
-DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
-uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
-kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
-ewv4n4Q=
+MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
+VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
+MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
+UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
+uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
+HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
+bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
 -----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/d52c538d.0 b/cobalt/content/ssl/certs/d52c538d.0
new file mode 100644
index 0000000..b007dff
--- /dev/null
+++ b/cobalt/content/ssl/certs/d52c538d.0
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
+HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
+NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
+IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
+2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
+wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
+pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
+nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
+sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
+Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
+Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
+KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
+XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
+tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
+TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
+AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
+GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
+PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
+O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
+REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
+AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
+/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
+MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
+qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
+ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
+-----END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/ed858448.0 b/cobalt/content/ssl/certs/ed858448.0
new file mode 100644
index 0000000..adb1219
--- /dev/null
+++ b/cobalt/content/ssl/certs/ed858448.0
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw
+RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY
+BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz
+MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u
+LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0
+v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd
+e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw
+V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA
+AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG
+GJTO
+-----END CERTIFICATE-----
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index b3ed558..03ed2cb 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -436,5 +436,5 @@
     "//url",
   ]
 
-  content_deps = [ ":licenses" ]
+  data_deps = [ ":licenses" ]
 }
diff --git a/cobalt/dom/html_video_element.cc b/cobalt/dom/html_video_element.cc
index de2e639..a973f3e 100644
--- a/cobalt/dom/html_video_element.cc
+++ b/cobalt/dom/html_video_element.cc
@@ -25,7 +25,7 @@
 namespace cobalt {
 namespace dom {
 
-using media::VideoFrameProvider;
+using media::DecodeTargetProvider;
 using media::WebMediaPlayer;
 
 const char HTMLVideoElement::kTagName[] = "video";
@@ -98,9 +98,10 @@
   }
 }
 
-scoped_refptr<VideoFrameProvider> HTMLVideoElement::GetVideoFrameProvider() {
+scoped_refptr<DecodeTargetProvider>
+HTMLVideoElement::GetDecodeTargetProvider() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return player() ? player()->GetVideoFrameProvider() : NULL;
+  return player() ? player()->GetDecodeTargetProvider() : NULL;
 }
 
 WebMediaPlayer::SetBoundsCB HTMLVideoElement::GetSetBoundsCB() {
diff --git a/cobalt/dom/html_video_element.h b/cobalt/dom/html_video_element.h
index 1f5f64d..b98b6f6 100644
--- a/cobalt/dom/html_video_element.h
+++ b/cobalt/dom/html_video_element.h
@@ -30,7 +30,7 @@
 //   https://www.w3.org/TR/html50/embedded-content-0.html#the-video-element
 class HTMLVideoElement : public HTMLMediaElement {
  public:
-  typedef media::VideoFrameProvider VideoFrameProvider;
+  typedef media::DecodeTargetProvider DecodeTargetProvider;
 
   static const char kTagName[];
 
@@ -52,7 +52,7 @@
   // From HTMLElement
   scoped_refptr<HTMLVideoElement> AsHTMLVideoElement() override { return this; }
 
-  scoped_refptr<VideoFrameProvider> GetVideoFrameProvider();
+  scoped_refptr<DecodeTargetProvider> GetDecodeTargetProvider();
 
   WebMediaPlayer::SetBoundsCB GetSetBoundsCB();
 
diff --git a/cobalt/dom/intersection_observer.idl b/cobalt/dom/intersection_observer.idl
index 635a47e..053e3e3 100644
--- a/cobalt/dom/intersection_observer.idl
+++ b/cobalt/dom/intersection_observer.idl
@@ -21,7 +21,7 @@
 interface IntersectionObserver {
   readonly attribute Element? root;
   readonly attribute DOMString rootMargin;
-  // TODO make this be FrozenArray<double> when available
+  // TODO(b/236750294): Make this be FrozenArray<double> when available.
   readonly attribute sequence<double> thresholds;
   void observe(Element target);
   void unobserve(Element target);
diff --git a/cobalt/dom/performance_observer.idl b/cobalt/dom/performance_observer.idl
index 35112e7..f8e67c9 100644
--- a/cobalt/dom/performance_observer.idl
+++ b/cobalt/dom/performance_observer.idl
@@ -24,7 +24,6 @@
   [RaisesException] void observe (optional PerformanceObserverInit options);
   void disconnect ();
   PerformanceEntryList takeRecords();
-  // TODO: make this be FrozenArray<DOMString> when available
+  // TODO(b/236750294): Make this be FrozenArray<DOMString> when available.
   [SameObject] static readonly attribute sequence<DOMString> supportedEntryTypes;
 };
-
diff --git a/cobalt/dom/user_agent_data_test.cc b/cobalt/dom/user_agent_data_test.cc
index 783a806..2fd56e4 100644
--- a/cobalt/dom/user_agent_data_test.cc
+++ b/cobalt/dom/user_agent_data_test.cc
@@ -101,7 +101,6 @@
   platform_info_->set_brand("");
   platform_info_->set_model("");
   platform_info_->set_aux_field("");
-  platform_info_->set_connection_type(base::nullopt);
   platform_info_->set_javascript_engine_version("");
   platform_info_->set_rasterizer_type("");
   platform_info_->set_evergreen_version("");
diff --git a/cobalt/encoding/BUILD.gn b/cobalt/encoding/BUILD.gn
index e1f0665..904043f 100644
--- a/cobalt/encoding/BUILD.gn
+++ b/cobalt/encoding/BUILD.gn
@@ -52,5 +52,5 @@
     "//testing/gtest",
   ]
 
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
 }
diff --git a/cobalt/extension/demuxer.h b/cobalt/extension/demuxer.h
new file mode 100644
index 0000000..59a3055
--- /dev/null
+++ b/cobalt/extension/demuxer.h
@@ -0,0 +1,408 @@
+// 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.
+//
+// Contains extension code allowing partners to provide their own demuxer.
+// CobaltExtensionDemuxerApi is the main API.
+
+#ifndef COBALT_EXTENSION_DEMUXER_H_
+#define COBALT_EXTENSION_DEMUXER_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "starboard/time.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kCobaltExtensionDemuxerApi "dev.cobalt.extension.Demuxer"
+
+// This must stay in sync with ::media::PipelineStatus. Missing values are
+// either irrelevant to the demuxer or are deprecated values of PipelineStatus.
+typedef enum CobaltExtensionDemuxerStatus {
+  kCobaltExtensionDemuxerOk = 0,
+  kCobaltExtensionDemuxerErrorNetwork = 2,
+  kCobaltExtensionDemuxerErrorAbort = 5,
+  kCobaltExtensionDemuxerErrorInitializationFailed = 6,
+  kCobaltExtensionDemuxerErrorRead = 9,
+  kCobaltExtensionDemuxerErrorInvalidState = 11,
+  kCobaltExtensionDemuxerErrorCouldNotOpen = 12,
+  kCobaltExtensionDemuxerErrorCouldNotParse = 13,
+  kCobaltExtensionDemuxerErrorNoSupportedStreams = 14
+} CobaltExtensionDemuxerStatus;
+
+// Type of side data associated with a buffer.
+typedef enum CobaltExtensionDemuxerSideDataType {
+  kCobaltExtensionDemuxerUnknownSideDataType = 0,
+  kCobaltExtensionDemuxerMatroskaBlockAdditional = 1,
+} CobaltExtensionDemuxerSideDataType;
+
+// This must stay in sync with ::media::AudioCodec.
+typedef enum CobaltExtensionDemuxerAudioCodec {
+  kCobaltExtensionDemuxerCodecUnknownAudio = 0,
+  kCobaltExtensionDemuxerCodecAAC = 1,
+  kCobaltExtensionDemuxerCodecMP3 = 2,
+  kCobaltExtensionDemuxerCodecPCM = 3,
+  kCobaltExtensionDemuxerCodecVorbis = 4,
+  kCobaltExtensionDemuxerCodecFLAC = 5,
+  kCobaltExtensionDemuxerCodecAMR_NB = 6,
+  kCobaltExtensionDemuxerCodecAMR_WB = 7,
+  kCobaltExtensionDemuxerCodecPCM_MULAW = 8,
+  kCobaltExtensionDemuxerCodecGSM_MS = 9,
+  kCobaltExtensionDemuxerCodecPCM_S16BE = 10,
+  kCobaltExtensionDemuxerCodecPCM_S24BE = 11,
+  kCobaltExtensionDemuxerCodecOpus = 12,
+  kCobaltExtensionDemuxerCodecEAC3 = 13,
+  kCobaltExtensionDemuxerCodecPCM_ALAW = 14,
+  kCobaltExtensionDemuxerCodecALAC = 15,
+  kCobaltExtensionDemuxerCodecAC3 = 16
+} CobaltExtensionDemuxerAudioCodec;
+
+// This must stay in sync with ::media::VideoCodec.
+typedef enum CobaltExtensionDemuxerVideoCodec {
+  kCobaltExtensionDemuxerCodecUnknownVideo = 0,
+  kCobaltExtensionDemuxerCodecH264,
+  kCobaltExtensionDemuxerCodecVC1,
+  kCobaltExtensionDemuxerCodecMPEG2,
+  kCobaltExtensionDemuxerCodecMPEG4,
+  kCobaltExtensionDemuxerCodecTheora,
+  kCobaltExtensionDemuxerCodecVP8,
+  kCobaltExtensionDemuxerCodecVP9,
+  kCobaltExtensionDemuxerCodecHEVC,
+  kCobaltExtensionDemuxerCodecDolbyVision,
+  kCobaltExtensionDemuxerCodecAV1,
+} CobaltExtensionDemuxerVideoCodec;
+
+// This must stay in sync with ::media::SampleFormat.
+typedef enum CobaltExtensionDemuxerSampleFormat {
+  kCobaltExtensionDemuxerSampleFormatUnknown = 0,
+  kCobaltExtensionDemuxerSampleFormatU8,   // Unsigned 8-bit w/ bias of 128.
+  kCobaltExtensionDemuxerSampleFormatS16,  // Signed 16-bit.
+  kCobaltExtensionDemuxerSampleFormatS32,  // Signed 32-bit.
+  kCobaltExtensionDemuxerSampleFormatF32,  // Float 32-bit.
+  kCobaltExtensionDemuxerSampleFormatPlanarS16,  // Signed 16-bit planar.
+  kCobaltExtensionDemuxerSampleFormatPlanarF32,  // Float 32-bit planar.
+  kCobaltExtensionDemuxerSampleFormatPlanarS32,  // Signed 32-bit planar.
+  kCobaltExtensionDemuxerSampleFormatS24,        // Signed 24-bit.
+} CobaltExtensionDemuxerSampleFormat;
+
+// This must stay in sync with ::media::ChannelLayout.
+typedef enum CobaltExtensionDemuxerChannelLayout {
+  kCobaltExtensionDemuxerChannelLayoutNone = 0,
+  kCobaltExtensionDemuxerChannelLayoutUnsupported = 1,
+  kCobaltExtensionDemuxerChannelLayoutMono = 2,
+  kCobaltExtensionDemuxerChannelLayoutStereo = 3,
+  kCobaltExtensionDemuxerChannelLayout2_1 = 4,
+  kCobaltExtensionDemuxerChannelLayoutSurround = 5,
+  kCobaltExtensionDemuxerChannelLayout4_0 = 6,
+  kCobaltExtensionDemuxerChannelLayout2_2 = 7,
+  kCobaltExtensionDemuxerChannelLayoutQuad = 8,
+  kCobaltExtensionDemuxerChannelLayout5_0 = 9,
+  kCobaltExtensionDemuxerChannelLayout5_1 = 10,
+  kCobaltExtensionDemuxerChannelLayout5_0Back = 11,
+  kCobaltExtensionDemuxerChannelLayout5_1Back = 12,
+  kCobaltExtensionDemuxerChannelLayout7_0 = 13,
+  kCobaltExtensionDemuxerChannelLayout7_1 = 14,
+  kCobaltExtensionDemuxerChannelLayout7_1Wide = 15,
+  kCobaltExtensionDemuxerChannelLayoutStereoDownmix = 16,
+  kCobaltExtensionDemuxerChannelLayout2point1 = 17,
+  kCobaltExtensionDemuxerChannelLayout3_1 = 18,
+  kCobaltExtensionDemuxerChannelLayout4_1 = 19,
+  kCobaltExtensionDemuxerChannelLayout6_0 = 20,
+  kCobaltExtensionDemuxerChannelLayout6_0Front = 21,
+  kCobaltExtensionDemuxerChannelLayoutHexagonal = 22,
+  kCobaltExtensionDemuxerChannelLayout6_1 = 23,
+  kCobaltExtensionDemuxerChannelLayout6_1Back = 24,
+  kCobaltExtensionDemuxerChannelLayout6_1Front = 25,
+  kCobaltExtensionDemuxerChannelLayout7_0Front = 26,
+  kCobaltExtensionDemuxerChannelLayout7_1WideBack = 27,
+  kCobaltExtensionDemuxerChannelLayoutOctagonal = 28,
+  kCobaltExtensionDemuxerChannelLayoutDiscrete = 29,
+  kCobaltExtensionDemuxerChannelLayoutStereoAndKeyboardMic = 30,
+  kCobaltExtensionDemuxerChannelLayout4_1QuadSide = 31,
+  kCobaltExtensionDemuxerChannelLayoutBitstream = 32
+} CobaltExtensionDemuxerChannelLayout;
+
+// This must stay in sync with ::media::VideoCodecProfile.
+typedef enum CobaltExtensionDemuxerVideoCodecProfile {
+  kCobaltExtensionDemuxerVideoCodecProfileUnknown = -1,
+  kCobaltExtensionDemuxerH264ProfileMin = 0,
+  kCobaltExtensionDemuxerH264ProfileBaseline =
+      kCobaltExtensionDemuxerH264ProfileMin,
+  kCobaltExtensionDemuxerH264ProfileMain = 1,
+  kCobaltExtensionDemuxerH264ProfileExtended = 2,
+  kCobaltExtensionDemuxerH264ProfileHigh = 3,
+  kCobaltExtensionDemuxerH264ProfileHigh10Profile = 4,
+  kCobaltExtensionDemuxerH264ProfileHigh422Profile = 5,
+  kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile = 6,
+  kCobaltExtensionDemuxerH264ProfileScalableBaseline = 7,
+  kCobaltExtensionDemuxerH264ProfileScalableHigh = 8,
+  kCobaltExtensionDemuxerH264ProfileStereoHigh = 9,
+  kCobaltExtensionDemuxerH264ProfileMultiviewHigh = 10,
+  kCobaltExtensionDemuxerH264ProfileMax =
+      kCobaltExtensionDemuxerH264ProfileMultiviewHigh,
+  kCobaltExtensionDemuxerVp8ProfileMin = 11,
+  kCobaltExtensionDemuxerVp8ProfileAny = kCobaltExtensionDemuxerVp8ProfileMin,
+  kCobaltExtensionDemuxerVp8ProfileMax = kCobaltExtensionDemuxerVp8ProfileAny,
+  kCobaltExtensionDemuxerVp9ProfileMin = 12,
+  kCobaltExtensionDemuxerVp9ProfileProfile0 =
+      kCobaltExtensionDemuxerVp9ProfileMin,
+  kCobaltExtensionDemuxerVp9ProfileProfile1 = 13,
+  kCobaltExtensionDemuxerVp9ProfileProfile2 = 14,
+  kCobaltExtensionDemuxerVp9ProfileProfile3 = 15,
+  kCobaltExtensionDemuxerVp9ProfileMax =
+      kCobaltExtensionDemuxerVp9ProfileProfile3,
+  kCobaltExtensionDemuxerHevcProfileMin = 16,
+  kCobaltExtensionDemuxerHevcProfileMain =
+      kCobaltExtensionDemuxerHevcProfileMin,
+  kCobaltExtensionDemuxerHevcProfileMain10 = 17,
+  kCobaltExtensionDemuxerHevcProfileMainStillPicture = 18,
+  kCobaltExtensionDemuxerHevcProfileMax =
+      kCobaltExtensionDemuxerHevcProfileMainStillPicture,
+  kCobaltExtensionDemuxerDolbyVisionProfile0 = 19,
+  kCobaltExtensionDemuxerDolbyVisionProfile4 = 20,
+  kCobaltExtensionDemuxerDolbyVisionProfile5 = 21,
+  kCobaltExtensionDemuxerDolbyVisionProfile7 = 22,
+  kCobaltExtensionDemuxerTheoraProfileMin = 23,
+  kCobaltExtensionDemuxerTheoraProfileAny =
+      kCobaltExtensionDemuxerTheoraProfileMin,
+  kCobaltExtensionDemuxerTheoraProfileMax =
+      kCobaltExtensionDemuxerTheoraProfileAny,
+  kCobaltExtensionDemuxerAv1ProfileMin = 24,
+  kCobaltExtensionDemuxerAv1ProfileProfileMain =
+      kCobaltExtensionDemuxerAv1ProfileMin,
+  kCobaltExtensionDemuxerAv1ProfileProfileHigh = 25,
+  kCobaltExtensionDemuxerAv1ProfileProfilePro = 26,
+  kCobaltExtensionDemuxerAv1ProfileMax =
+      kCobaltExtensionDemuxerAv1ProfileProfilePro,
+  kCobaltExtensionDemuxerDolbyVisionProfile8 = 27,
+  kCobaltExtensionDemuxerDolbyVisionProfile9 = 28,
+} CobaltExtensionDemuxerVideoCodecProfile;
+
+// This must be kept in sync with gfx::ColorSpace::RangeID.
+typedef enum CobaltExtensionDemuxerColorSpaceRangeId {
+  kCobaltExtensionDemuxerColorSpaceRangeIdInvalid = 0,
+  kCobaltExtensionDemuxerColorSpaceRangeIdLimited = 1,
+  kCobaltExtensionDemuxerColorSpaceRangeIdFull = 2,
+  kCobaltExtensionDemuxerColorSpaceRangeIdDerived = 3
+} CobaltExtensionDemuxerColorSpaceRangeId;
+
+// This must be kept in sync with media::VideoDecoderConfig::AlphaMode.
+typedef enum CobaltExtensionDemuxerAlphaMode {
+  kCobaltExtensionDemuxerHasAlpha,
+  kCobaltExtensionDemuxerIsOpaque
+} CobaltExtensionDemuxerAlphaMode;
+
+// This must be kept in sync with ::media::DemuxerStream::Type.
+typedef enum CobaltExtensionDemuxerStreamType {
+  kCobaltExtensionDemuxerStreamTypeUnknown,
+  kCobaltExtensionDemuxerStreamTypeAudio,
+  kCobaltExtensionDemuxerStreamTypeVideo,
+  kCobaltExtensionDemuxerStreamTypeText
+} CobaltExtensionDemuxerStreamType;
+
+// This must be kept in sync with media::EncryptionScheme.
+typedef enum CobaltExtensionDemuxerEncryptionScheme {
+  kCobaltExtensionDemuxerEncryptionSchemeUnencrypted,
+  kCobaltExtensionDemuxerEncryptionSchemeCenc,
+  kCobaltExtensionDemuxerEncryptionSchemeCbcs,
+} CobaltExtensionDemuxerEncryptionScheme;
+
+typedef struct CobaltExtensionDemuxerAudioDecoderConfig {
+  CobaltExtensionDemuxerAudioCodec codec;
+  CobaltExtensionDemuxerSampleFormat sample_format;
+  CobaltExtensionDemuxerChannelLayout channel_layout;
+  CobaltExtensionDemuxerEncryptionScheme encryption_scheme;
+  int samples_per_second;
+
+  uint8_t* extra_data;  // Not owned by this struct.
+  int64_t extra_data_size;
+} CobaltExtensionDemuxerAudioDecoderConfig;
+
+typedef struct CobaltExtensionDemuxerVideoDecoderConfig {
+  CobaltExtensionDemuxerVideoCodec codec;
+  CobaltExtensionDemuxerVideoCodecProfile profile;
+
+  // These fields represent the color space.
+  int color_space_primaries;
+  int color_space_transfer;
+  int color_space_matrix;
+  CobaltExtensionDemuxerColorSpaceRangeId color_space_range_id;
+
+  CobaltExtensionDemuxerAlphaMode alpha_mode;
+
+  // These fields represent the coded size.
+  int coded_width;
+  int coded_height;
+
+  // These fields represent the visible rectangle.
+  int visible_rect_x;
+  int visible_rect_y;
+  int visible_rect_width;
+  int visible_rect_height;
+
+  // These fields represent the natural size.
+  int natural_width;
+  int natural_height;
+
+  CobaltExtensionDemuxerEncryptionScheme encryption_scheme;
+
+  uint8_t* extra_data;  // Not owned by this struct.
+  int64_t extra_data_size;
+} CobaltExtensionDemuxerVideoDecoderConfig;
+
+typedef struct CobaltExtensionDemuxerSideData {
+  uint8_t* data;  // Not owned by this struct.
+  // Number of bytes in |data|.
+  int64_t data_size;
+  // Specifies the format of |data|.
+  CobaltExtensionDemuxerSideDataType type;
+} CobaltExtensionDemuxerSideData;
+
+typedef struct CobaltExtensionDemuxerBuffer {
+  // The media data for this buffer. Ownership is not transferred via this
+  // struct.
+  uint8_t* data;
+  // Number of bytes in |data|.
+  int64_t data_size;
+  // An array of side data elements containing any side data for this buffer.
+  // Ownership is not transferred via this struct.
+  CobaltExtensionDemuxerSideData* side_data;
+  // Number of elements in |side_data|.
+  int64_t side_data_elements;
+  // Playback time in microseconds.
+  SbTime pts;
+  // Duration of this buffer in microseconds.
+  SbTime duration;
+  // True if this buffer contains a keyframe.
+  bool is_keyframe;
+  // Signifies the end of the stream. If this is true, the other fields will be
+  // ignored.
+  bool end_of_stream;
+} CobaltExtensionDemuxerBuffer;
+
+// Note: |buffer| is the input to this function, not the output. Cobalt
+// implements this function to read media data provided by the implementer of
+// CobaltExtensionDemuxer.
+typedef void (*CobaltExtensionDemuxerReadCB)(
+    CobaltExtensionDemuxerBuffer* buffer, void* user_data);
+
+// A fully synchronous demuxer API. Threading concerns are handled by the code
+// that uses this API.
+// When calling the defined functions, the |user_data| argument must be the
+// void* user_data field stored in this struct.
+typedef struct CobaltExtensionDemuxer {
+  // Initialize must only be called once for a demuxer; subsequent calls can
+  // fail.
+  CobaltExtensionDemuxerStatus (*Initialize)(void* user_data);
+
+  CobaltExtensionDemuxerStatus (*Seek)(SbTime seek_time, void* user_data);
+
+  // Returns the starting time for the media file; it is always positive.
+  SbTime (*GetStartTime)(void* user_data);
+
+  // Returns the time -- in microseconds since Windows epoch -- represented by
+  // presentation timestamp 0. If the timestamps are not associated with a time,
+  // returns 0.
+  SbTime (*GetTimelineOffset)(void* user_data);
+
+  // Calls |read_cb| with a buffer of type |type| and the user data provided by
+  // |read_cb_user_data|. |read_cb| is a synchronous function, so the data
+  // passed to it can safely be freed after |read_cb| returns. |read_cb| must be
+  // called exactly once, and it must be called before Read returns.
+  //
+  // An error can be handled in one of two ways:
+  // 1. Pass a null buffer to read_cb. This will cause the pipeline to handle
+  //    the situation as an error. Alternatively,
+  // 2. Pass an "end of stream" buffer to read_cb. This will cause the relevant
+  //    stream to end normally.
+  void (*Read)(CobaltExtensionDemuxerStreamType type,
+               CobaltExtensionDemuxerReadCB read_cb, void* read_cb_user_data,
+               void* user_data);
+
+  // Returns true and populates |audio_config| if an audio stream is present;
+  // returns false otherwise. |config| must not be null.
+  bool (*GetAudioConfig)(CobaltExtensionDemuxerAudioDecoderConfig* config,
+                         void* user_data);
+
+  // Returns true and populates |video_config| if a video stream is present;
+  // returns false otherwise. |config| must not be null.
+  bool (*GetVideoConfig)(CobaltExtensionDemuxerVideoDecoderConfig* config,
+                         void* user_data);
+
+  // Returns the duration, in microseconds.
+  SbTime (*GetDuration)(void* user_data);
+
+  // Will be passed to all functions.
+  void* user_data;
+} CobaltExtensionDemuxer;
+
+typedef struct CobaltExtensionDemuxerDataSource {
+  // Reads up to |bytes_requested|, writing the data into |data| and returning
+  // the number of bytes read. |data| must be able to store at least
+  // |bytes_requested| bytes. Calling BlockingRead advances the read position.
+  int (*BlockingRead)(uint8_t* data, int bytes_requested, void* user_data);
+
+  // Seeks to |position| (specified in bytes) in the data source.
+  void (*SeekTo)(int position, void* user_data);
+
+  // Returns the offset into the data source, in bytes.
+  int64_t (*GetPosition)(void* user_data);
+
+  // Returns the size of the data source, in bytes.
+  int64_t (*GetSize)(void* user_data);
+
+  // Whether this represents a streaming data source.
+  bool is_streaming;
+
+  // Will be passed to all functions.
+  void* user_data;
+} CobaltExtensionDemuxerDataSource;
+
+typedef struct CobaltExtensionDemuxerApi {
+  // Name should be the string |kCobaltExtensionDemuxerApi|.
+  // This helps to validate that the extension API is correct.
+  const char* name;
+
+  // This specifies the version of the API that is implemented.
+  uint32_t version;
+
+  // The fields below this point were added in version 1 or later.
+
+  // Creates a demuxer for the content provided by |data_source|. Ownership of
+  // |data_source| is not transferred to this function.
+  //
+  // Ownership of the returned demuxer is transferred to the caller, but it must
+  // be deleted via DestroyDemuxer (below). The caller must not manually delete
+  // the demuxer.
+  CobaltExtensionDemuxer* (*CreateDemuxer)(
+      CobaltExtensionDemuxerDataSource* data_source,
+      CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
+      int64_t supported_audio_codecs_size,
+      CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
+      int64_t supported_video_codecs_size);
+
+  // Destroys |demuxer|. After calling this, |demuxer| must not be dereferenced
+  // or deleted by the caller.
+  void (*DestroyDemuxer)(CobaltExtensionDemuxer* demuxer);
+} CobaltExtensionDemuxerApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+
+#endif  // COBALT_EXTENSION_DEMUXER_H_
diff --git a/cobalt/h5vcc/h5vcc_system.cc b/cobalt/h5vcc/h5vcc_system.cc
index a912506..b28e941 100644
--- a/cobalt/h5vcc/h5vcc_system.cc
+++ b/cobalt/h5vcc/h5vcc_system.cc
@@ -34,10 +34,8 @@
 }
 
 std::string H5vccSystem::build_id() const {
-  return base::StringPrintf(
-      "Built on %s (%s) at version #%s by %s", COBALT_BUILD_VERSION_DATE,
-      COBALT_BUILD_VERSION_TIMESTAMP, COBALT_BUILD_VERSION_NUMBER,
-      COBALT_BUILD_VERSION_USERNAME);
+  return base::StringPrintf("Built at version #%s",
+                            COBALT_BUILD_VERSION_NUMBER);
 }
 
 std::string H5vccSystem::platform() const {
@@ -59,9 +57,9 @@
 #if SB_API_VERSION >= 14
   const size_t kSystemPropertyMaxLength = 1024;
   char property[kSystemPropertyMaxLength] = {0};
-  if (!SbSystemGetProperty(kSbSystemPropretyAdvertisingId, property,
+  if (!SbSystemGetProperty(kSbSystemPropertyAdvertisingId, property,
                            SB_ARRAY_SIZE_INT(property))) {
-    DLOG(FATAL) << "Failed to get kSbSystemPropretyAdvertisingId.";
+    DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
   } else {
     result = property;
   }
@@ -73,9 +71,9 @@
 #if SB_API_VERSION >= 14
   const size_t kSystemPropertyMaxLength = 1024;
   char property[kSystemPropertyMaxLength] = {0};
-  if (!SbSystemGetProperty(kSbSystemPropretyLimitAdTracking, property,
+  if (!SbSystemGetProperty(kSbSystemPropertyLimitAdTracking, property,
                            SB_ARRAY_SIZE_INT(property))) {
-    DLOG(FATAL) << "Failed to get kSbSystemPropretyAdvertisingId.";
+    DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
   } else {
     result = std::atoi(property);
   }
diff --git a/cobalt/layout/box_generator.cc b/cobalt/layout/box_generator.cc
index a0e0438..6eee421 100644
--- a/cobalt/layout/box_generator.cc
+++ b/cobalt/layout/box_generator.cc
@@ -44,7 +44,7 @@
 #include "cobalt/layout/used_style.h"
 #include "cobalt/layout/white_space_processing.h"
 #include "cobalt/loader/image/lottie_animation.h"
-#include "cobalt/media/base/video_frame_provider.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/render_tree/image.h"
 #include "cobalt/web_animations/keyframe_effect_read_only.h"
 #include "starboard/decode_target.h"
@@ -52,19 +52,20 @@
 namespace cobalt {
 namespace layout {
 
-using media::VideoFrameProvider;
+using media::DecodeTargetProvider;
 
 namespace {
 
 scoped_refptr<render_tree::Image> GetVideoFrame(
-    const scoped_refptr<VideoFrameProvider>& frame_provider,
+    const scoped_refptr<DecodeTargetProvider>& decode_target_provider,
     render_tree::ResourceProvider* resource_provider) {
   TRACE_EVENT0("cobalt::layout", "GetVideoFrame()");
-  SbDecodeTarget decode_target = frame_provider->GetCurrentSbDecodeTarget();
+  SbDecodeTarget decode_target =
+      decode_target_provider->GetCurrentSbDecodeTarget();
   if (SbDecodeTargetIsValid(decode_target)) {
     return resource_provider->CreateImageFromSbDecodeTarget(decode_target);
   } else {
-    DCHECK(frame_provider);
+    DCHECK(decode_target_provider);
     return NULL;
   }
 }
@@ -338,12 +339,12 @@
   // If the optional is disengaged, then we don't know if punch out is enabled
   // or not.
   base::Optional<ReplacedBox::ReplacedBoxMode> replaced_box_mode;
-  if (video_element->GetVideoFrameProvider()) {
-    VideoFrameProvider::OutputMode output_mode =
-        video_element->GetVideoFrameProvider()->GetOutputMode();
-    if (output_mode != VideoFrameProvider::kOutputModeInvalid) {
+  if (video_element->GetDecodeTargetProvider()) {
+    DecodeTargetProvider::OutputMode output_mode =
+        video_element->GetDecodeTargetProvider()->GetOutputMode();
+    if (output_mode != DecodeTargetProvider::kOutputModeInvalid) {
       replaced_box_mode =
-          (output_mode == VideoFrameProvider::kOutputModePunchOut)
+          (output_mode == DecodeTargetProvider::kOutputModePunchOut)
               ? ReplacedBox::ReplacedBoxMode::kPunchOutVideo
               : ReplacedBox::ReplacedBoxMode::kVideo;
     }
@@ -351,8 +352,8 @@
 
   ReplacedBoxGenerator replaced_box_generator(
       video_element->css_computed_style_declaration(),
-      video_element->GetVideoFrameProvider()
-          ? base::Bind(GetVideoFrame, video_element->GetVideoFrameProvider(),
+      video_element->GetDecodeTargetProvider()
+          ? base::Bind(GetVideoFrame, video_element->GetDecodeTargetProvider(),
                        resource_provider)
           : ReplacedBox::ReplaceImageCB(),
       video_element->GetSetBoundsCB(), *paragraph_, text_position,
diff --git a/cobalt/layout_tests/BUILD.gn b/cobalt/layout_tests/BUILD.gn
index 092b8df..328176d 100644
--- a/cobalt/layout_tests/BUILD.gn
+++ b/cobalt/layout_tests/BUILD.gn
@@ -64,15 +64,15 @@
     "//url",
   ]
 
-  content_deps = [
+  data_deps = [
     "//cobalt/layout_tests/testdata:layout_copy_test_data",
     "//cobalt/network:copy_ssl_certificates",
     "//third_party/icu:icudata",
   ]
   if (cobalt_font_package == "empty") {
-    content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+    data_deps += [ "//cobalt/content/fonts:copy_font_data" ]
   } else {
-    content_deps += [
+    data_deps += [
       "//cobalt/content/fonts:copy_fonts",
       "//cobalt/content/fonts:fonts_xml",
     ]
@@ -99,5 +99,5 @@
     "//url",
   ]
 
-  content_deps = [ "//cobalt/layout_tests/testdata:layout_copy_test_data" ]
+  data_deps = [ "//cobalt/layout_tests/testdata:layout_copy_test_data" ]
 }
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index 9378b97..3b8f6e0 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -203,8 +203,6 @@
   ]
 
   data_deps = [ ":copy_loader_test_data" ]
-
-  content_deps = [ ":copy_loader_test_data" ]
 }
 
 copy("copy_loader_test_data") {
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index 50a4629..f8499cb 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -30,6 +30,7 @@
     "base/audio_bus.h",
     "base/data_source.cc",
     "base/data_source.h",
+    "base/decode_target_provider.h",
     "base/decoder_buffer_cache.cc",
     "base/decoder_buffer_cache.h",
     "base/drm_system.cc",
@@ -45,7 +46,6 @@
     "base/sbplayer_set_bounds_helper.h",
     "base/starboard_player.cc",
     "base/starboard_player.h",
-    "base/video_frame_provider.h",
     "decoder_buffer_allocator.cc",
     "decoder_buffer_allocator.h",
     "decoder_buffer_memory_info.h",
@@ -64,6 +64,8 @@
     "progressive/avc_parser.h",
     "progressive/data_source_reader.cc",
     "progressive/data_source_reader.h",
+    "progressive/demuxer_extension_wrapper.cc",
+    "progressive/demuxer_extension_wrapper.h",
     "progressive/mp4_map.cc",
     "progressive/mp4_map.h",
     "progressive/mp4_parser.cc",
@@ -106,6 +108,7 @@
   testonly = true
 
   sources = [
+    "progressive/demuxer_extension_wrapper_test.cc",
     "progressive/mock_data_source_reader.h",
     "progressive/mp4_map_unittest.cc",
     "progressive/rbsp_stream_unittest.cc",
@@ -120,5 +123,6 @@
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
     "//testing/gtest",
+    "//third_party/chromium/media:media",
   ]
 }
diff --git a/cobalt/media/base/video_frame_provider.h b/cobalt/media/base/decode_target_provider.h
similarity index 79%
rename from cobalt/media/base/video_frame_provider.h
rename to cobalt/media/base/decode_target_provider.h
index 0a9dfe9..bf7ed26 100644
--- a/cobalt/media/base/video_frame_provider.h
+++ b/cobalt/media/base/decode_target_provider.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_BASE_VIDEO_FRAME_PROVIDER_H_
-#define COBALT_MEDIA_BASE_VIDEO_FRAME_PROVIDER_H_
+#ifndef COBALT_MEDIA_BASE_DECODE_TARGET_PROVIDER_H_
+#define COBALT_MEDIA_BASE_DECODE_TARGET_PROVIDER_H_
 
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
@@ -24,13 +24,13 @@
 namespace cobalt {
 namespace media {
 
-// The VideoFrameProvider manages the backlog for video frames. It has the
+// The DecodeTargetProvider manages the backlog for video frames. It has the
 // following functionalities:
 // 1. It caches the video frames ready to be displayed.
 // 2. It decides which frame to be displayed at the current time.
 // 3. It removes frames that will no longer be displayed.
-class VideoFrameProvider
-    : public base::RefCountedThreadSafe<VideoFrameProvider> {
+class DecodeTargetProvider
+    : public base::RefCountedThreadSafe<DecodeTargetProvider> {
  public:
   enum OutputMode {
     kOutputModePunchOut,
@@ -38,7 +38,7 @@
     kOutputModeInvalid,
   };
 
-  VideoFrameProvider() : output_mode_(kOutputModeInvalid) {}
+  DecodeTargetProvider() : output_mode_(kOutputModeInvalid) {}
 
   typedef base::Callback<SbDecodeTarget()> GetCurrentSbDecodeTargetFunction;
 
@@ -47,15 +47,15 @@
     output_mode_ = output_mode;
   }
 
-  VideoFrameProvider::OutputMode GetOutputMode() const {
+  DecodeTargetProvider::OutputMode GetOutputMode() const {
     base::AutoLock auto_lock(lock_);
     return output_mode_;
   }
 
   // For Starboard platforms that have a decode-to-texture player, we enable
-  // this VideoFrameProvider to act as a bridge for Cobalt code to query
+  // this DecodeTargetProvider to act as a bridge for Cobalt code to query
   // for the current SbDecodeTarget.  In effect, we bypass all of
-  // VideoFrameProvider's logic in this case, instead relying on the
+  // DecodeTargetProvider's logic in this case, instead relying on the
   // Starboard implementation to provide us with the current video frame when
   // needed.
   void SetGetCurrentSbDecodeTargetFunction(
@@ -84,10 +84,10 @@
   OutputMode output_mode_;
   GetCurrentSbDecodeTargetFunction get_current_sb_decode_target_function_;
 
-  DISALLOW_COPY_AND_ASSIGN(VideoFrameProvider);
+  DISALLOW_COPY_AND_ASSIGN(DecodeTargetProvider);
 };
 
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_BASE_VIDEO_FRAME_PROVIDER_H_
+#endif  // COBALT_MEDIA_BASE_DECODE_TARGET_PROVIDER_H_
diff --git a/cobalt/media/base/pipeline.h b/cobalt/media/base/pipeline.h
index 9228cd3..e729c41 100644
--- a/cobalt/media/base/pipeline.h
+++ b/cobalt/media/base/pipeline.h
@@ -21,8 +21,8 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/media/base/media_export.h"
-#include "cobalt/media/base/video_frame_provider.h"
 #include "starboard/drm.h"
 #include "starboard/window.h"
 #include "third_party/chromium/media/base/demuxer.h"
@@ -96,7 +96,7 @@
       const GetDecodeTargetGraphicsContextProviderFunc&
           get_decode_target_graphics_context_provider_func,
       bool allow_resume_after_suspend, MediaLog* media_log,
-      VideoFrameProvider* video_frame_provider);
+      DecodeTargetProvider* decode_target_provider);
 
   virtual ~Pipeline() {}
 
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index d0ae591..b9c8f65 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -103,7 +103,7 @@
       const GetDecodeTargetGraphicsContextProviderFunc&
           get_decode_target_graphics_context_provider_func,
       bool allow_resume_after_suspend, MediaLog* media_log,
-      VideoFrameProvider* video_frame_provider);
+      DecodeTargetProvider* decode_target_provider);
   ~SbPlayerPipeline() override;
 
   void Suspend() override;
@@ -308,7 +308,7 @@
   base::CVal<bool> ended_;
   base::CVal<SbPlayerState> player_state_;
 
-  VideoFrameProvider* video_frame_provider_;
+  DecodeTargetProvider* decode_target_provider_;
 
   // Read audio from the stream if |timestamp_of_last_written_audio_| is less
   // than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10
@@ -345,7 +345,7 @@
     const GetDecodeTargetGraphicsContextProviderFunc&
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, MediaLog* media_log,
-    VideoFrameProvider* video_frame_provider)
+    DecodeTargetProvider* decode_target_provider)
     : pipeline_identifier_(
           base::StringPrintf("%X", g_pipeline_identifier_counter++)),
       task_runner_(task_runner),
@@ -383,7 +383,7 @@
                                        pipeline_identifier_.c_str()),
                     kSbPlayerStateInitialized,
                     "The underlying SbPlayer state of the media pipeline."),
-      video_frame_provider_(video_frame_provider),
+      decode_target_provider_(decode_target_provider),
       last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime",
                                           pipeline_identifier_.c_str()),
                        0, "Last media time reported by the underlying player."),
@@ -912,7 +912,7 @@
     player_.reset(new StarboardPlayer(
         task_runner_, source_url, window_, this, set_bounds_helper_.get(),
         allow_resume_after_suspend_, *decode_to_texture_output_mode_,
-        on_encrypted_media_init_data_encountered_cb_, video_frame_provider_));
+        on_encrypted_media_init_data_encountered_cb_, decode_target_provider_));
     if (player_->IsValid()) {
       SetPlaybackRateTask(playback_rate_);
       SetVolumeTask(volume_);
@@ -1015,7 +1015,7 @@
         task_runner_, get_decode_target_graphics_context_provider_func_,
         audio_config, audio_mime_type, video_config, video_mime_type, window_,
         drm_system, this, set_bounds_helper_.get(), allow_resume_after_suspend_,
-        *decode_to_texture_output_mode_, video_frame_provider_,
+        *decode_to_texture_output_mode_, decode_target_provider_,
         max_video_capabilities_));
     if (player_->IsValid()) {
       SetPlaybackRateTask(playback_rate_);
@@ -1562,10 +1562,10 @@
     const GetDecodeTargetGraphicsContextProviderFunc&
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, MediaLog* media_log,
-    VideoFrameProvider* video_frame_provider) {
+    DecodeTargetProvider* decode_target_provider) {
   return new SbPlayerPipeline(
       window, task_runner, get_decode_target_graphics_context_provider_func,
-      allow_resume_after_suspend, media_log, video_frame_provider);
+      allow_resume_after_suspend, media_log, decode_target_provider);
 }
 
 }  // namespace media
diff --git a/cobalt/media/base/starboard_player.cc b/cobalt/media/base/starboard_player.cc
index a43af76..8aeb271 100644
--- a/cobalt/media/base/starboard_player.cc
+++ b/cobalt/media/base/starboard_player.cc
@@ -90,7 +90,7 @@
     bool prefer_decode_to_texture,
     const OnEncryptedMediaInitDataEncounteredCB&
         on_encrypted_media_init_data_encountered_cb,
-    VideoFrameProvider* const video_frame_provider)
+    DecodeTargetProvider* const decode_target_provider)
     : url_(url),
       task_runner_(task_runner),
       callback_helper_(
@@ -101,7 +101,7 @@
       allow_resume_after_suspend_(allow_resume_after_suspend),
       on_encrypted_media_init_data_encountered_cb_(
           on_encrypted_media_init_data_encountered_cb),
-      video_frame_provider_(video_frame_provider),
+      decode_target_provider_(decode_target_provider),
       is_url_based_(true) {
   DCHECK(host_);
   DCHECK(set_bounds_helper_);
@@ -126,7 +126,7 @@
     SbWindow window, SbDrmSystem drm_system, Host* host,
     SbPlayerSetBoundsHelper* set_bounds_helper, bool allow_resume_after_suspend,
     bool prefer_decode_to_texture,
-    VideoFrameProvider* const video_frame_provider,
+    DecodeTargetProvider* const decode_target_provider,
     const std::string& max_video_capabilities)
     : task_runner_(task_runner),
       get_decode_target_graphics_context_provider_func_(
@@ -140,7 +140,7 @@
       allow_resume_after_suspend_(allow_resume_after_suspend),
       audio_config_(audio_config),
       video_config_(video_config),
-      video_frame_provider_(video_frame_provider),
+      decode_target_provider_(decode_target_provider),
       max_video_capabilities_(max_video_capabilities)
 #if SB_HAS(PLAYER_WITH_URL)
       ,
@@ -151,7 +151,7 @@
   DCHECK(audio_config.IsValidConfig() || video_config.IsValidConfig());
   DCHECK(host_);
   DCHECK(set_bounds_helper_);
-  DCHECK(video_frame_provider_);
+  DCHECK(decode_target_provider_);
 
   audio_sample_info_.codec = kSbMediaAudioCodecNone;
   video_sample_info_.codec = kSbMediaVideoCodecNone;
@@ -181,8 +181,9 @@
   callback_helper_->ResetPlayer();
   set_bounds_helper_->SetPlayer(NULL);
 
-  video_frame_provider_->SetOutputMode(VideoFrameProvider::kOutputModeInvalid);
-  video_frame_provider_->ResetGetCurrentSbDecodeTargetFunction();
+  decode_target_provider_->SetOutputMode(
+      DecodeTargetProvider::kOutputModeInvalid);
+  decode_target_provider_->ResetGetCurrentSbDecodeTargetFunction();
 
   if (SbPlayerIsValid(player_)) {
     SbPlayerDestroy(player_);
@@ -447,8 +448,9 @@
 
   state_ = kSuspended;
 
-  video_frame_provider_->SetOutputMode(VideoFrameProvider::kOutputModeInvalid);
-  video_frame_provider_->ResetGetCurrentSbDecodeTargetFunction();
+  decode_target_provider_->SetOutputMode(
+      DecodeTargetProvider::kOutputModeInvalid);
+  decode_target_provider_->ResetGetCurrentSbDecodeTargetFunction();
 
   SbPlayerDestroy(player_);
 
@@ -489,19 +491,19 @@
 }
 
 namespace {
-VideoFrameProvider::OutputMode ToVideoFrameProviderOutputMode(
+DecodeTargetProvider::OutputMode ToVideoFrameProviderOutputMode(
     SbPlayerOutputMode output_mode) {
   switch (output_mode) {
     case kSbPlayerOutputModeDecodeToTexture:
-      return VideoFrameProvider::kOutputModeDecodeToTexture;
+      return DecodeTargetProvider::kOutputModeDecodeToTexture;
     case kSbPlayerOutputModePunchOut:
-      return VideoFrameProvider::kOutputModePunchOut;
+      return DecodeTargetProvider::kOutputModePunchOut;
     case kSbPlayerOutputModeInvalid:
-      return VideoFrameProvider::kOutputModeInvalid;
+      return DecodeTargetProvider::kOutputModeInvalid;
   }
 
   NOTREACHED();
-  return VideoFrameProvider::kOutputModeInvalid;
+  return DecodeTargetProvider::kOutputModeInvalid;
 }
 
 }  // namespace
@@ -540,10 +542,10 @@
   if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
     // If the player is setup to decode to texture, then provide Cobalt with
     // a method of querying that texture.
-    video_frame_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
+    decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
         &StarboardPlayer::GetCurrentSbDecodeTarget, base::Unretained(this)));
   }
-  video_frame_provider_->SetOutputMode(
+  decode_target_provider_->SetOutputMode(
       ToVideoFrameProviderOutputMode(output_mode_));
 
   set_bounds_helper_->SetPlayer(this);
@@ -601,10 +603,10 @@
   if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
     // If the player is setup to decode to texture, then provide Cobalt with
     // a method of querying that texture.
-    video_frame_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
+    decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
         &StarboardPlayer::GetCurrentSbDecodeTarget, base::Unretained(this)));
   }
-  video_frame_provider_->SetOutputMode(
+  decode_target_provider_->SetOutputMode(
       ToVideoFrameProviderOutputMode(output_mode_));
   set_bounds_helper_->SetPlayer(this);
 
diff --git a/cobalt/media/base/starboard_player.h b/cobalt/media/base/starboard_player.h
index a71ec3b..1e75888 100644
--- a/cobalt/media/base/starboard_player.h
+++ b/cobalt/media/base/starboard_player.h
@@ -23,9 +23,9 @@
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/lock.h"
 #include "base/time/time.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/media/base/decoder_buffer_cache.h"
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
-#include "cobalt/media/base/video_frame_provider.h"
 #include "starboard/media.h"
 #include "starboard/player.h"
 #include "third_party/chromium/media/base/audio_decoder_config.h"
@@ -75,7 +75,7 @@
       bool allow_resume_after_suspend, bool prefer_decode_to_texture,
       const OnEncryptedMediaInitDataEncounteredCB&
           encrypted_media_init_data_encountered_cb,
-      VideoFrameProvider* const video_frame_provider);
+      DecodeTargetProvider* const decode_target_provider);
 #endif  // SB_HAS(PLAYER_WITH_URL)
   // Create a StarboardPlayer with normal player
   StarboardPlayer(
@@ -89,7 +89,7 @@
       SbDrmSystem drm_system, Host* host,
       SbPlayerSetBoundsHelper* set_bounds_helper,
       bool allow_resume_after_suspend, bool prefer_decode_to_texture,
-      VideoFrameProvider* const video_frame_provider,
+      DecodeTargetProvider* const decode_target_provider,
       const std::string& max_video_capabilities);
 
   ~StarboardPlayer();
@@ -276,7 +276,7 @@
   // Keep track of the output mode we are supposed to output to.
   SbPlayerOutputMode output_mode_;
 
-  VideoFrameProvider* const video_frame_provider_;
+  DecodeTargetProvider* const decode_target_provider_;
 
   // Keep copies of the mime type strings instead of using the ones in the
   // DemuxerStreams to ensure that the strings are always valid.
diff --git a/cobalt/media/player/web_media_player.h b/cobalt/media/player/web_media_player.h
index d3b7abd..68be9b6 100644
--- a/cobalt/media/player/web_media_player.h
+++ b/cobalt/media/player/web_media_player.h
@@ -19,7 +19,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "cobalt/media/base/video_frame_provider.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/media/player/buffered_data_source.h"
 #include "url/gurl.h"
 
@@ -155,7 +155,7 @@
 
   virtual PlayerStatistics GetStatistics() const = 0;
 
-  virtual scoped_refptr<VideoFrameProvider> GetVideoFrameProvider() {
+  virtual scoped_refptr<DecodeTargetProvider> GetDecodeTargetProvider() {
     return NULL;
   }
 
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index 19aec68..4fa422e 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -4,6 +4,7 @@
 #include "cobalt/media/player/web_media_player_impl.h"
 
 #include <cmath>
+#include <cstring>
 #include <limits>
 #include <memory>
 #include <string>
@@ -21,7 +22,10 @@
 #include "cobalt/base/instance_counter.h"
 #include "cobalt/media/base/drm_system.h"
 #include "cobalt/media/player/web_media_player_proxy.h"
+#include "cobalt/media/progressive/data_source_reader.h"
+#include "cobalt/media/progressive/demuxer_extension_wrapper.h"
 #include "cobalt/media/progressive/progressive_demuxer.h"
+#include "starboard/system.h"
 #include "starboard/types.h"
 #include "third_party/chromium/media/base/bind_to_current_loop.h"
 #include "third_party/chromium/media/base/limits.h"
@@ -130,7 +134,7 @@
 
   ON_INSTANCE_CREATED(WebMediaPlayerImpl);
 
-  video_frame_provider_ = new VideoFrameProvider();
+  decode_target_provider_ = new DecodeTargetProvider();
 
   media_log_->AddEvent<::media::MediaLogEvent::kWebMediaPlayerCreated>();
 
@@ -138,7 +142,7 @@
   pipeline_ = Pipeline::Create(window, pipeline_thread_.task_runner(),
                                get_decode_target_graphics_context_provider_func,
                                allow_resume_after_suspend_, media_log_,
-                               video_frame_provider_.get());
+                               decode_target_provider_.get());
 
   // Also we want to be notified of |main_loop_| destruction.
   main_loop_->AddDestructionObserver(this);
@@ -275,8 +279,19 @@
 
   is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https");
 
-  progressive_demuxer_.reset(new ProgressiveDemuxer(
-      pipeline_thread_.task_runner(), proxy_->data_source(), media_log_));
+  // Attempt to use the demuxer provided via Cobalt Extension, if available.
+  progressive_demuxer_ = DemuxerExtensionWrapper::Create(
+      proxy_->data_source(), pipeline_thread_.task_runner());
+
+  if (progressive_demuxer_) {
+    LOG(INFO) << "Using DemuxerExtensionWrapper.";
+  } else {
+    // Either the demuxer Cobalt extension was not provided, or it failed to
+    // create a demuxer; fall back to the ProgressiveDemuxer.
+    LOG(INFO) << "Using ProgressiveDemuxer.";
+    progressive_demuxer_.reset(new ProgressiveDemuxer(
+        pipeline_thread_.task_runner(), proxy_->data_source(), media_log_));
+  }
 
   state_.is_progressive = true;
   StartPipeline(progressive_demuxer_.get());
@@ -544,8 +559,9 @@
   return statistics;
 }
 
-scoped_refptr<VideoFrameProvider> WebMediaPlayerImpl::GetVideoFrameProvider() {
-  return video_frame_provider_;
+scoped_refptr<DecodeTargetProvider>
+WebMediaPlayerImpl::GetDecodeTargetProvider() {
+  return decode_target_provider_;
 }
 
 WebMediaPlayerImpl::SetBoundsCB WebMediaPlayerImpl::GetSetBoundsCB() {
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h
index c53b434..67d5405 100644
--- a/cobalt/media/player/web_media_player_impl.h
+++ b/cobalt/media/player/web_media_player_impl.h
@@ -59,8 +59,8 @@
 #include "base/threading/thread.h"
 #include "base/time/time.h"
 #include "cobalt/math/size.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/media/base/pipeline.h"
-#include "cobalt/media/base/video_frame_provider.h"
 #include "cobalt/media/player/web_media_player.h"
 #include "cobalt/media/player/web_media_player_delegate.h"
 #include "third_party/chromium/media/base/demuxer.h"
@@ -173,7 +173,7 @@
 
   PlayerStatistics GetStatistics() const override;
 
-  scoped_refptr<VideoFrameProvider> GetVideoFrameProvider() override;
+  scoped_refptr<DecodeTargetProvider> GetDecodeTargetProvider() override;
 
   SetBoundsCB GetSetBoundsCB() override;
 
@@ -292,7 +292,7 @@
   WebMediaPlayerClient* const client_;
   WebMediaPlayerDelegate* const delegate_;
   const bool allow_resume_after_suspend_;
-  scoped_refptr<VideoFrameProvider> video_frame_provider_;
+  scoped_refptr<DecodeTargetProvider> decode_target_provider_;
 
   scoped_refptr<WebMediaPlayerProxy> proxy_;
 
diff --git a/cobalt/media/progressive/demuxer_extension_wrapper.cc b/cobalt/media/progressive/demuxer_extension_wrapper.cc
new file mode 100644
index 0000000..f4c8139
--- /dev/null
+++ b/cobalt/media/progressive/demuxer_extension_wrapper.cc
@@ -0,0 +1,1121 @@
+// 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 "cobalt/media/progressive/demuxer_extension_wrapper.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "cobalt/extension/demuxer.h"
+#include "starboard/system.h"
+#include "third_party/chromium/media/base/audio_codecs.h"
+#include "third_party/chromium/media/base/bind_to_current_loop.h"
+#include "third_party/chromium/media/base/encryption_scheme.h"
+#include "third_party/chromium/media/base/sample_format.h"
+#include "third_party/chromium/media/base/starboard_utils.h"
+#include "third_party/chromium/media/base/video_types.h"
+#include "third_party/chromium/media/cobalt/ui/gfx/color_space.h"
+#include "third_party/chromium/media/cobalt/ui/gfx/geometry/rect.h"
+#include "third_party/chromium/media/cobalt/ui/gfx/geometry/size.h"
+#include "third_party/chromium/media/filters/h264_to_annex_b_bitstream_converter.h"
+#include "third_party/chromium/media/formats/mp4/box_definitions.h"
+
+namespace cobalt {
+namespace media {
+
+using ::media::AudioCodec;
+using ::media::AudioDecoderConfig;
+using ::media::ChannelLayout;
+using ::media::DecoderBuffer;
+using ::media::DemuxerHost;
+using ::media::DemuxerStream;
+using ::media::EncryptionScheme;
+using ::media::H264ToAnnexBBitstreamConverter;
+using ::media::MediaTrack;
+using ::media::PipelineStatus;
+using ::media::PipelineStatusCallback;
+using ::media::PipelineStatusCB;
+using ::media::Ranges;
+using ::media::SampleFormat;
+using ::media::VideoCodec;
+using ::media::VideoCodecProfile;
+using ::media::VideoColorSpace;
+using ::media::VideoDecoderConfig;
+using ::media::VideoPixelFormat;
+using ::media::VideoTransformation;
+using ::media::mp4::AVCDecoderConfigurationRecord;
+
+// Used to convert a lambda to a pure C function.
+// |user_data| is a callback of type T, which takes a U*.
+template <typename T, typename U>
+static void CallCB(U* u, void* user_data) {
+  (*static_cast<T*>(user_data))(u);
+}
+
+// Converts AVCC h.264 frames to Annex B. This is necessary because the decoder
+// expects packets in Annex B format.
+class DemuxerExtensionWrapper::H264AnnexBConverter {
+ public:
+  // Creates an H264AnnexBConverter from the MP4 file's header data.
+  static std::unique_ptr<H264AnnexBConverter> Create(const uint8_t* extra_data,
+                                                     size_t extra_data_size) {
+    if (!extra_data || extra_data_size == 0) {
+      LOG(ERROR) << "Invalid inputs to H264AnnexBConverter::Create.";
+      return nullptr;
+    }
+    AVCDecoderConfigurationRecord config;
+    std::unique_ptr<H264ToAnnexBBitstreamConverter> converter(
+        new H264ToAnnexBBitstreamConverter);
+    if (!converter->ParseConfiguration(
+            extra_data, static_cast<int>(extra_data_size), &config)) {
+      LOG(ERROR) << "Could not parse AVCC config.";
+      return nullptr;
+    }
+    return std::unique_ptr<H264AnnexBConverter>(
+        new H264AnnexBConverter(std::move(config), std::move(converter)));
+  }
+
+  // Disallow copy and assign.
+  H264AnnexBConverter(const H264AnnexBConverter&) = delete;
+  H264AnnexBConverter& operator=(const H264AnnexBConverter&) = delete;
+
+  ~H264AnnexBConverter() = default;
+
+  // Attempts to convert the data in |data| from AVCC to AnnexB format,
+  // returning the data as a DecoderBuffer. Upon failure, the data will be
+  // returned unmodified in the DecoderBuffer.
+  scoped_refptr<DecoderBuffer> Convert(const uint8_t* data, size_t data_size) {
+    const auto* const config = config_.has_value() ? &*config_ : nullptr;
+
+    std::vector<uint8_t> rewritten(
+        converter_->CalculateNeededOutputBufferSize(data, data_size, config));
+
+    uint32_t rewritten_size = rewritten.size();
+    if (rewritten.empty() ||
+        !converter_->ConvertNalUnitStreamToByteStream(
+            data, data_size, config, rewritten.data(), &rewritten_size)) {
+      // TODO(b/231994311): Add the buffer's side_data here, for HDR10+ support.
+      return DecoderBuffer::CopyFrom(data, data_size);
+    } else {
+      // The data was successfully rewritten.
+
+      // The SPS and PPS NALUs -- generated from the config -- should only be
+      // sent with the first real NALU.
+      config_ = base::nullopt;
+
+      // TODO(b/231994311): Add the buffer's side_data here, for HDR10+ support.
+      return DecoderBuffer::CopyFrom(rewritten.data(), rewritten.size());
+    }
+  }
+
+ private:
+  explicit H264AnnexBConverter(
+      AVCDecoderConfigurationRecord config,
+      std::unique_ptr<H264ToAnnexBBitstreamConverter> converter)
+      : config_(std::move(config)), converter_(std::move(converter)) {}
+
+  // This config data is only sent with the first NALU (as SPS and PPS NALUs).
+  base::Optional<AVCDecoderConfigurationRecord> config_;
+  std::unique_ptr<H264ToAnnexBBitstreamConverter> converter_;
+};
+
+DemuxerExtensionStream::DemuxerExtensionStream(
+    CobaltExtensionDemuxer* demuxer,
+    scoped_refptr<base::SequencedTaskRunner> message_loop,
+    CobaltExtensionDemuxerVideoDecoderConfig config)
+    : demuxer_(demuxer), message_loop_(std::move(message_loop)) {
+  CHECK(demuxer_);
+  CHECK(message_loop_);
+  std::vector<uint8_t> extra_data;
+  if (config.extra_data_size > 0 && config.extra_data != nullptr) {
+    extra_data.assign(config.extra_data,
+                      config.extra_data + config.extra_data_size);
+  }
+
+  video_config_.emplace(
+      static_cast<VideoCodec>(config.codec),
+      static_cast<VideoCodecProfile>(config.profile),
+      static_cast<VideoDecoderConfig::AlphaMode>(config.alpha_mode),
+      VideoColorSpace(
+          config.color_space_primaries, config.color_space_transfer,
+          config.color_space_matrix,
+          static_cast<gfx::ColorSpace::RangeID>(config.color_space_range_id)),
+      VideoTransformation(), gfx::Size(config.coded_width, config.coded_height),
+      gfx::Rect(config.visible_rect_x, config.visible_rect_y,
+                config.visible_rect_width, config.visible_rect_height),
+      gfx::Size(config.natural_width, config.natural_height), extra_data,
+      static_cast<EncryptionScheme>(config.encryption_scheme));
+
+  LOG_IF(ERROR, !video_config_->IsValidConfig())
+      << "Video config is not valid!";
+}
+
+DemuxerExtensionStream::DemuxerExtensionStream(
+    CobaltExtensionDemuxer* demuxer,
+    scoped_refptr<base::SequencedTaskRunner> message_loop,
+    CobaltExtensionDemuxerAudioDecoderConfig config)
+    : demuxer_(demuxer), message_loop_(std::move(message_loop)) {
+  CHECK(demuxer_);
+  CHECK(message_loop_);
+  std::vector<uint8_t> extra_data;
+  if (config.extra_data_size > 0 && config.extra_data != nullptr) {
+    extra_data.assign(config.extra_data,
+                      config.extra_data + config.extra_data_size);
+  }
+
+  audio_config_.emplace(
+      static_cast<AudioCodec>(config.codec),
+      static_cast<SampleFormat>(config.sample_format),
+      static_cast<ChannelLayout>(config.channel_layout),
+      config.samples_per_second, extra_data,
+      static_cast<EncryptionScheme>(config.encryption_scheme));
+
+  LOG_IF(ERROR, !audio_config_->IsValidConfig())
+      << "Audio config is not valid!";
+}
+
+void DemuxerExtensionStream::Read(ReadCB read_cb) {
+  DCHECK(!read_cb.is_null());
+  base::AutoLock auto_lock(lock_);
+  if (stopped_) {
+    LOG(INFO) << "Already stopped.";
+    std::move(read_cb).Run(
+        DemuxerStream::kOk,
+        scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer()));
+    return;
+  }
+
+  // Buffers are only queued when there are no pending reads.
+  CHECK(buffer_queue_.empty() || read_queue_.empty());
+
+  if (buffer_queue_.empty()) {
+    read_queue_.push_back(std::move(read_cb));
+    return;
+  }
+
+  // We already have a buffer queued. Send the oldest buffer back.
+  scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front();
+  if (!buffer->end_of_stream()) {
+    // Do not pop EOS buffers, so that subsequent read requests also get EOS.
+    total_buffer_size_ -= buffer->data_size();
+    buffer_queue_.pop_front();
+  }
+
+  std::move(read_cb).Run(DemuxerStream::kOk, buffer);
+}
+
+AudioDecoderConfig DemuxerExtensionStream::audio_decoder_config() {
+  DCHECK(audio_config_.has_value());
+  return *audio_config_;
+}
+
+VideoDecoderConfig DemuxerExtensionStream::video_decoder_config() {
+  DCHECK(video_config_.has_value());
+  return *video_config_;
+}
+
+DemuxerStream::Type DemuxerExtensionStream::type() const {
+  const uint8_t is_audio = static_cast<int>(audio_config_.has_value());
+  const uint8_t is_video = static_cast<int>(video_config_.has_value());
+  DCHECK((is_audio ^ is_video) == 1);
+  return is_audio ? Type::AUDIO : Type::VIDEO;
+}
+
+Ranges<base::TimeDelta> DemuxerExtensionStream::GetBufferedRanges() {
+  return buffered_ranges_;
+}
+
+void DemuxerExtensionStream::EnqueueBuffer(
+    scoped_refptr<DecoderBuffer> buffer) {
+  base::AutoLock auto_lock(lock_);
+
+  if (stopped_) {
+    // It is possible due to pipelining -- both downstream and within the
+    // demuxer -- that several pipelined reads will be enqueuing packets on a
+    // stopped stream. These will be dropped.
+    LOG(WARNING) << "attempted to enqueue packet on stopped stream";
+    return;
+  }
+
+  if (buffer->end_of_stream()) {
+    LOG(INFO) << "Received EOS";
+  } else if (buffer->timestamp() != ::media::kNoTimestamp) {
+    if (last_buffer_timestamp_ != ::media::kNoTimestamp &&
+        last_buffer_timestamp_ < buffer->timestamp()) {
+      buffered_ranges_.Add(last_buffer_timestamp_, buffer->timestamp());
+    }
+    last_buffer_timestamp_ = buffer->timestamp();
+  } else {
+    LOG(WARNING) << "Bad timestamp info on enqueued buffer.";
+  }
+
+  if (read_queue_.empty()) {
+    buffer_queue_.push_back(buffer);
+    if (!buffer->end_of_stream()) {
+      total_buffer_size_ += buffer->data_size();
+    }
+    return;
+  }
+
+  // A pending read implies that the buffer queue was empty; otherwise it should
+  // never have been added to the read queue in the first place.
+  CHECK_EQ(buffer_queue_.size(), 0);
+  ReadCB read_cb(std::move(read_queue_.front()));
+  read_queue_.pop_front();
+  std::move(read_cb).Run(DemuxerStream::kOk, std::move(buffer));
+}
+
+void DemuxerExtensionStream::FlushBuffers() {
+  base::AutoLock auto_lock(lock_);
+  buffer_queue_.clear();
+  total_buffer_size_ = 0;
+  last_buffer_timestamp_ = ::media::kNoTimestamp;
+}
+
+void DemuxerExtensionStream::Stop() {
+  DCHECK(message_loop_->RunsTasksInCurrentSequence());
+
+  base::AutoLock auto_lock(lock_);
+  buffer_queue_.clear();
+  total_buffer_size_ = 0;
+  last_buffer_timestamp_ = ::media::kNoTimestamp;
+  // Fulfill any pending callbacks with EOS buffers set to end timestamp.
+  for (auto& read_cb : read_queue_) {
+    std::move(read_cb).Run(
+        DemuxerStream::kOk,
+        scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer()));
+  }
+  read_queue_.clear();
+  stopped_ = true;
+}
+
+base::TimeDelta DemuxerExtensionStream::GetLastBufferTimestamp() const {
+  base::AutoLock auto_lock(lock_);
+  return last_buffer_timestamp_;
+}
+
+size_t DemuxerExtensionStream::GetTotalBufferSize() const {
+  base::AutoLock auto_lock(lock_);
+  return total_buffer_size_;
+}
+
+PositionalDataSource::PositionalDataSource(
+    scoped_refptr<DataSourceReader> reader)
+    : reader_(std::move(reader)), position_(0) {
+  CHECK(reader_);
+}
+
+PositionalDataSource::~PositionalDataSource() = default;
+
+void PositionalDataSource::Stop() { reader_->Stop(); }
+
+int PositionalDataSource::BlockingRead(uint8_t* data, int bytes_requested) {
+  const int bytes_read =
+      reader_->BlockingRead(position_, bytes_requested, data);
+  if (bytes_read != DataSourceReader::kReadError) {
+    position_ += bytes_read;
+  }
+  return bytes_read;
+}
+
+void PositionalDataSource::SeekTo(int position) { position_ = position; }
+
+int64_t PositionalDataSource::GetPosition() const { return position_; }
+
+int64_t PositionalDataSource::GetSize() { return reader_->FileSize(); }
+
+// Functions for converting a PositionalDataSource to
+// CobaltExtensionDemuxerDataSource.
+static int CobaltExtensionDemuxerDataSource_BlockingReadRead(
+    uint8_t* data, int bytes_requested, void* user_data) {
+  return static_cast<PositionalDataSource*>(user_data)->BlockingRead(
+      data, bytes_requested);
+}
+
+static void CobaltExtensionDemuxerDataSource_SeekTo(int position,
+                                                    void* user_data) {
+  static_cast<PositionalDataSource*>(user_data)->SeekTo(position);
+}
+
+static int64_t CobaltExtensionDemuxerDataSource_GetPosition(void* user_data) {
+  return static_cast<PositionalDataSource*>(user_data)->GetPosition();
+}
+
+static int64_t CobaltExtensionDemuxerDataSource_GetSize(void* user_data) {
+  return static_cast<PositionalDataSource*>(user_data)->GetSize();
+}
+
+std::unique_ptr<DemuxerExtensionWrapper> DemuxerExtensionWrapper::Create(
+    DataSource* data_source,
+    scoped_refptr<base::SequencedTaskRunner> message_loop,
+    const CobaltExtensionDemuxerApi* demuxer_api) {
+  if (demuxer_api == nullptr) {
+    // Attempt to use the Cobalt extension.
+    demuxer_api = static_cast<const CobaltExtensionDemuxerApi*>(
+        SbSystemGetExtension(kCobaltExtensionDemuxerApi));
+    if (!demuxer_api ||
+        strcmp(demuxer_api->name, kCobaltExtensionDemuxerApi) != 0) {
+      return nullptr;
+    }
+  }
+
+  DCHECK(demuxer_api);
+  if (demuxer_api->version < 1) {
+    LOG(ERROR) << "Demuxer API version is too low: " << demuxer_api->version;
+    return nullptr;
+  }
+
+  if (!data_source || !message_loop) {
+    LOG(ERROR) << "data_source and message_loop cannot be null.";
+    return nullptr;
+  }
+
+  scoped_refptr<DataSourceReader> reader = new DataSourceReader;
+  reader->SetDataSource(data_source);
+
+  std::unique_ptr<PositionalDataSource> positional_data_source(
+      new PositionalDataSource(std::move(reader)));
+
+  std::unique_ptr<CobaltExtensionDemuxerDataSource> c_data_source(
+      new CobaltExtensionDemuxerDataSource{
+          /*BlockingRead=*/&CobaltExtensionDemuxerDataSource_BlockingReadRead,
+          /*SeekTo=*/&CobaltExtensionDemuxerDataSource_SeekTo,
+          /*GetPosition=*/&CobaltExtensionDemuxerDataSource_GetPosition,
+          /*GetSize=*/&CobaltExtensionDemuxerDataSource_GetSize,
+          /*is_streaming=*/false,
+          /*user_data=*/positional_data_source.get()});
+
+  // TODO(b/231632632): Populate these vectors.
+  std::vector<CobaltExtensionDemuxerAudioCodec> supported_audio_codecs;
+  std::vector<CobaltExtensionDemuxerVideoCodec> supported_video_codecs;
+
+  CobaltExtensionDemuxer* demuxer = demuxer_api->CreateDemuxer(
+      c_data_source.get(), supported_audio_codecs.data(),
+      supported_audio_codecs.size(), supported_video_codecs.data(),
+      supported_video_codecs.size());
+
+  if (!demuxer) {
+    LOG(ERROR) << "Failed to create a CobaltExtensionDemuxer.";
+    return nullptr;
+  }
+
+  return std::unique_ptr<DemuxerExtensionWrapper>(new DemuxerExtensionWrapper(
+      demuxer_api, demuxer, std::move(positional_data_source),
+      std::move(c_data_source), std::move(message_loop)));
+}
+
+DemuxerExtensionWrapper::DemuxerExtensionWrapper(
+    const CobaltExtensionDemuxerApi* demuxer_api,
+    CobaltExtensionDemuxer* demuxer,
+    std::unique_ptr<PositionalDataSource> data_source,
+    std::unique_ptr<CobaltExtensionDemuxerDataSource> c_data_source,
+    scoped_refptr<base::SequencedTaskRunner> message_loop)
+    : demuxer_api_(demuxer_api),
+      impl_(demuxer),
+      data_source_(std::move(data_source)),
+      c_data_source_(std::move(c_data_source)),
+      blocking_thread_("DemuxerExtensionWrapperBlockingThread"),
+      message_loop_(std::move(message_loop)) {
+  CHECK(demuxer_api_);
+  CHECK(impl_);
+  CHECK(data_source_);
+  CHECK(c_data_source_);
+  CHECK(message_loop_);
+}
+
+DemuxerExtensionWrapper::~DemuxerExtensionWrapper() {
+  if (impl_) {
+    demuxer_api_->DestroyDemuxer(impl_);
+  }
+  // Explicitly stop |blocking_thread_| to ensure that it stops before the
+  // destruction of any other members.
+  blocking_thread_.Stop();
+}
+
+std::vector<DemuxerStream*> DemuxerExtensionWrapper::GetAllStreams() {
+  std::vector<DemuxerStream*> streams;
+  if (audio_stream_.has_value()) {
+    streams.push_back(&*audio_stream_);
+  }
+  if (video_stream_.has_value()) {
+    streams.push_back(&*video_stream_);
+  }
+  return streams;
+}
+
+std::string DemuxerExtensionWrapper::GetDisplayName() const {
+  return "DemuxerExtensionWrapper";
+}
+void DemuxerExtensionWrapper::Initialize(DemuxerHost* host,
+                                         PipelineStatusCallback status_cb) {
+  DCHECK(message_loop_->RunsTasksInCurrentSequence());
+  host_ = host;
+
+  // Start the blocking thread and have it download and parse the media config.
+  if (!blocking_thread_.Start()) {
+    LOG(ERROR) << "Unable to start blocking thread";
+    std::move(status_cb).Run(::media::DEMUXER_ERROR_COULD_NOT_PARSE);
+    return;
+  }
+
+  // |status_cb| cannot be called until this function returns, so we post a task
+  // here.
+  base::PostTaskAndReplyWithResult(
+      blocking_thread_.message_loop()->task_runner().get(), FROM_HERE,
+      base::BindOnce(impl_->Initialize, impl_->user_data),
+      base::BindOnce(&DemuxerExtensionWrapper::OnInitializeDone,
+                     base::Unretained(this), std::move(status_cb)));
+}
+
+void DemuxerExtensionWrapper::OnInitializeDone(
+    PipelineStatusCallback status_cb, CobaltExtensionDemuxerStatus status) {
+  if (status == kCobaltExtensionDemuxerOk) {
+    // Set up the stream(s) on this end.
+    CobaltExtensionDemuxerAudioDecoderConfig audio_config = {};
+    if (impl_->GetAudioConfig(&audio_config, impl_->user_data)) {
+      if (audio_config.encryption_scheme !=
+          kCobaltExtensionDemuxerEncryptionSchemeUnencrypted) {
+        // TODO(b/232957482): Determine whether we need to handle this case.
+        LOG(ERROR)
+            << "Encrypted audio is not supported for progressive playback.";
+        std::move(status_cb).Run(::media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+        return;
+      }
+      audio_stream_.emplace(impl_, message_loop_, std::move(audio_config));
+    }
+    CobaltExtensionDemuxerVideoDecoderConfig video_config = {};
+    if (impl_->GetVideoConfig(&video_config, impl_->user_data)) {
+      if (video_config.encryption_scheme !=
+          kCobaltExtensionDemuxerEncryptionSchemeUnencrypted) {
+        // TODO(b/232957482): Determine whether we need to handle this case.
+        LOG(ERROR)
+            << "Encrypted video is not supported for progressive playback.";
+        std::move(status_cb).Run(::media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+        return;
+      }
+      if (video_config.extra_data && video_config.extra_data_size > 0 &&
+          video_config.codec == kCobaltExtensionDemuxerCodecH264) {
+        // This is probably an AVCC stream. We'll need to convert each packet
+        // from AVCC to AnnexB, so we create the converter based on the "extra
+        // data". This extra data will be passed in the form of SPS and PPS NALU
+        // packets in the AnnexB stream.
+        h264_converter_ = H264AnnexBConverter::Create(
+            video_config.extra_data, video_config.extra_data_size);
+        video_config.extra_data = nullptr;
+        video_config.extra_data_size = 0;
+      }
+      video_stream_.emplace(impl_, message_loop_, std::move(video_config));
+    }
+
+    if (!audio_stream_.has_value() && !video_stream_.has_value()) {
+      // Even though initialization seems to have succeeded, something is wrong
+      // if there are no streams.
+      LOG(ERROR) << "No streams are present";
+      std::move(status_cb).Run(::media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+      return;
+    }
+
+    host_->SetDuration(base::TimeDelta::FromMicroseconds(
+        impl_->GetDuration(impl_->user_data)));
+
+    // Begin downloading data.
+    Request(audio_stream_.has_value() ? DemuxerStream::AUDIO
+                                      : DemuxerStream::VIDEO);
+  } else {
+    LOG(ERROR) << "Initialization failed with status " << status;
+  }
+  std::move(status_cb).Run(static_cast<PipelineStatus>(status));
+}
+
+void DemuxerExtensionWrapper::AbortPendingReads() {}
+
+void DemuxerExtensionWrapper::StartWaitingForSeek(base::TimeDelta seek_time) {}
+
+void DemuxerExtensionWrapper::CancelPendingSeek(base::TimeDelta seek_time) {}
+
+void DemuxerExtensionWrapper::Seek(base::TimeDelta time,
+                                   PipelineStatusCallback status_cb) {
+  // It's safe to use base::Unretained here because blocking_thread_ will be
+  // stopped in this class's destructor.
+  blocking_thread_.message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&DemuxerExtensionWrapper::SeekTask, base::Unretained(this),
+                     time, BindToCurrentLoop(std::move(status_cb))));
+}
+
+// TODO(b/232984963): Determine whether it's OK to have reads and seeks on the
+// same thread.
+void DemuxerExtensionWrapper::SeekTask(base::TimeDelta time,
+                                       PipelineStatusCallback status_cb) {
+  CHECK(blocking_thread_.message_loop()
+            ->task_runner()
+            ->RunsTasksInCurrentSequence());
+
+  // clear any enqueued buffers on demuxer streams
+  if (video_stream_.has_value()) video_stream_->FlushBuffers();
+  if (audio_stream_.has_value()) audio_stream_->FlushBuffers();
+
+  const CobaltExtensionDemuxerStatus status =
+      impl_->Seek(time.InMicroseconds(), impl_->user_data);
+
+  if (status != kCobaltExtensionDemuxerOk) {
+    LOG(ERROR) << "Seek failed with status " << status;
+    std::move(status_cb).Run(::media::PIPELINE_ERROR_READ);
+    return;
+  }
+
+  // If all streams had finished downloading, we need to restart the request.
+  const bool issue_new_request =
+      (!video_stream_.has_value() || video_reached_eos_) &&
+      (!audio_stream_.has_value() || audio_reached_eos_);
+  audio_reached_eos_ = false;
+  video_reached_eos_ = false;
+  flushing_ = true;
+  std::move(status_cb).Run(::media::PIPELINE_OK);
+
+  if (issue_new_request) {
+    IssueNextRequest();
+  }
+}
+
+Ranges<base::TimeDelta> DemuxerExtensionWrapper::GetBufferedRanges() {
+  DCHECK(audio_stream_.has_value() || video_stream_.has_value());
+
+  if (!audio_stream_.has_value()) {
+    return video_stream_->GetBufferedRanges();
+  }
+  if (!video_stream_.has_value()) {
+    return audio_stream_->GetBufferedRanges();
+  }
+  return video_stream_->GetBufferedRanges().IntersectionWith(
+      audio_stream_->GetBufferedRanges());
+}
+
+void DemuxerExtensionWrapper::Stop() {
+  DCHECK(message_loop_->RunsTasksInCurrentSequence());
+  {
+    base::AutoLock lock(lock_for_stopped_);
+    stopped_ = true;
+  }
+  data_source_->Stop();
+}
+
+base::TimeDelta DemuxerExtensionWrapper::GetStartTime() const {
+  return base::TimeDelta::FromMicroseconds(
+      impl_->GetStartTime(impl_->user_data));
+}
+
+base::Time DemuxerExtensionWrapper::GetTimelineOffset() const {
+  const SbTime reported_time = impl_->GetTimelineOffset(impl_->user_data);
+  return reported_time == 0
+             ? base::Time()
+             : base::Time::FromDeltaSinceWindowsEpoch(
+                   base::TimeDelta::FromMicroseconds(reported_time));
+}
+
+int64_t DemuxerExtensionWrapper::GetMemoryUsage() const {
+  NOTREACHED();
+  return 0;
+}
+
+void DemuxerExtensionWrapper::OnEnabledAudioTracksChanged(
+    const std::vector<MediaTrack::Id>& track_ids, base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  NOTREACHED();
+}
+
+void DemuxerExtensionWrapper::OnSelectedVideoTrackChanged(
+    const std::vector<MediaTrack::Id>& track_ids, base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  NOTREACHED();
+}
+
+void DemuxerExtensionWrapper::Request(DemuxerStream::Type type) {
+  static const auto kRequestDelay = base::TimeDelta::FromMilliseconds(100);
+
+  if (type == DemuxerStream::AUDIO) {
+    DCHECK(audio_stream_.has_value());
+  } else {
+    DCHECK(video_stream_.has_value());
+  }
+
+  if (!blocking_thread_.task_runner()->BelongsToCurrentThread()) {
+    blocking_thread_.task_runner()->PostTask(
+        FROM_HERE, base::Bind(&DemuxerExtensionWrapper::Request,
+                              base::Unretained(this), type));
+    return;
+  }
+
+  if (HasStopped()) {
+    return;
+  }
+
+  const size_t total_buffer_size =
+      (audio_stream_.has_value() ? audio_stream_->GetTotalBufferSize() : 0) +
+      (video_stream_.has_value() ? video_stream_->GetTotalBufferSize() : 0);
+
+  int progressive_budget = 0;
+  if (video_stream_.has_value()) {
+    const VideoDecoderConfig video_config =
+        video_stream_->video_decoder_config();
+    // Only sdr video is supported in progressive mode.
+    // TODO(b/231994311): Figure out how to set this value properly.
+    constexpr int kBitDepth = 8;
+    progressive_budget = SbMediaGetProgressiveBufferBudget(
+        MediaVideoCodecToSbMediaVideoCodec(video_config.codec()),
+        video_config.visible_rect().size().width(),
+        video_config.visible_rect().size().height(), kBitDepth);
+  } else {
+    progressive_budget = SbMediaGetAudioBufferBudget();
+  }
+
+  if (total_buffer_size >= progressive_budget) {
+    // Retry after a delay.
+    blocking_thread_.message_loop()->task_runner()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&DemuxerExtensionWrapper::Request, base::Unretained(this),
+                   type),
+        kRequestDelay);
+    return;
+  }
+
+  scoped_refptr<DecoderBuffer> decoder_buffer;
+  bool called_cb = false;
+  auto read_cb = [this, type, &decoder_buffer,
+                  &called_cb](CobaltExtensionDemuxerBuffer* buffer) {
+    called_cb = true;
+    if (!buffer) {
+      return;
+    }
+
+    if (buffer->end_of_stream) {
+      decoder_buffer = DecoderBuffer::CreateEOSBuffer();
+      return;
+    }
+
+    if (h264_converter_ && type == DemuxerExtensionStream::VIDEO) {
+      // This converts from AVCC to AnnexB format for h.264 video.
+      decoder_buffer =
+          h264_converter_->Convert(buffer->data, buffer->data_size);
+    } else {
+      // TODO(b/231994311): Add the buffer's side_data here, for HDR10+ support.
+      decoder_buffer = DecoderBuffer::CopyFrom(buffer->data, buffer->data_size);
+    }
+
+    decoder_buffer->set_timestamp(
+        base::TimeDelta::FromMicroseconds(buffer->pts));
+    decoder_buffer->set_duration(
+        base::TimeDelta::FromMicroseconds(buffer->duration));
+    decoder_buffer->set_is_key_frame(buffer->is_keyframe);
+  };
+  impl_->Read(static_cast<CobaltExtensionDemuxerStreamType>(type),
+              &CallCB<decltype(read_cb), CobaltExtensionDemuxerBuffer>,
+              &read_cb, impl_->user_data);
+
+  if (!called_cb) {
+    LOG(ERROR)
+        << "Demuxer extension implementation did not call the read callback.";
+    host_->OnDemuxerError(::media::PIPELINE_ERROR_READ);
+    return;
+  }
+  if (!decoder_buffer) {
+    LOG(ERROR) << "Received a null buffer from the demuxer.";
+    host_->OnDemuxerError(::media::PIPELINE_ERROR_READ);
+    return;
+  }
+
+  auto& stream =
+      (type == DemuxerStream::AUDIO) ? *audio_stream_ : *video_stream_;
+  bool& eos_status =
+      (type == DemuxerStream::AUDIO) ? audio_reached_eos_ : video_reached_eos_;
+
+  eos_status = decoder_buffer->end_of_stream();
+  stream.EnqueueBuffer(std::move(decoder_buffer));
+  if (!eos_status) {
+    host_->OnBufferedTimeRangesChanged(GetBufferedRanges());
+  }
+
+  // If we reach this point, enqueueing the buffer was successful.
+  IssueNextRequest();
+  return;
+}
+
+void DemuxerExtensionWrapper::IssueNextRequest() {
+  {
+    base::AutoLock lock(lock_for_stopped_);
+    if (stopped_) {
+      LOG(INFO) << "Already stopped; request loop is stopping.";
+      return;
+    }
+  }
+
+  DemuxerStream::Type type = DemuxerStream::UNKNOWN;
+  if (audio_reached_eos_ || video_reached_eos_) {
+    // If we have eos in one or both buffers, the decision is easy.
+    if ((audio_reached_eos_ && video_reached_eos_) ||
+        (audio_reached_eos_ && !video_stream_.has_value()) ||
+        (video_reached_eos_ && !audio_stream_.has_value())) {
+      LOG(INFO) << "All streams at EOS, request loop is stopping.";
+      return;
+    }
+    // Only one of two streams is at eos; download data for the stream NOT at
+    // eos.
+    type = audio_reached_eos_ ? DemuxerStream::VIDEO : DemuxerStream::AUDIO;
+  } else if (!audio_stream_.has_value() || !video_stream_.has_value()) {
+    // If only one stream is present and not at eos, just download that data.
+    type =
+        audio_stream_.has_value() ? DemuxerStream::AUDIO : DemuxerStream::VIDEO;
+  } else {
+    // Both streams are present, and neither is at eos. Priority order for
+    // figuring out what to download next.
+    const base::TimeDelta audio_stamp = audio_stream_->GetLastBufferTimestamp();
+    const base::TimeDelta video_stamp = video_stream_->GetLastBufferTimestamp();
+    // If the audio demuxer stream is empty, always fill it first.
+    if (audio_stamp == ::media::kNoTimestamp) {
+      type = DemuxerStream::AUDIO;
+    } else if (video_stamp == ::media::kNoTimestamp) {
+      // The video demuxer stream is empty; we need data for it.
+      type = DemuxerStream::VIDEO;
+    } else if (video_stamp < audio_stamp) {
+      // Video is earlier; fill it first.
+      type = DemuxerStream::VIDEO;
+    } else {
+      type = DemuxerStream::AUDIO;
+    }
+  }
+
+  DCHECK_NE(type, DemuxerStream::UNKNOWN);
+  // We cannot call Request() directly even if this function is also run on
+  // |blocking_thread_| as otherwise it is possible that this function is
+  // running in a tight loop and seek/stop requests would have no chance to kick
+  // in.
+  blocking_thread_.task_runner()->PostTask(
+      FROM_HERE, base::Bind(&DemuxerExtensionWrapper::Request,
+                            base::Unretained(this), type));
+}
+
+bool DemuxerExtensionWrapper::HasStopped() {
+  base::AutoLock lock(lock_for_stopped_);
+  return stopped_;
+}
+
+namespace {
+
+// Ensure that the demuxer extension's enums match up with the internal enums.
+// This doesn't affect any code, but prevents compilation if there's a mismatch
+// somewhere.
+#define DEMUXER_EXTENSION_ENUM_EQ(a, b) \
+  COMPILE_ASSERT(static_cast<int>(a) == static_cast<int>(b), mismatching_enums)
+
+// Pipeline status.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerOk, ::media::PIPELINE_OK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorNetwork,
+                          ::media::PIPELINE_ERROR_NETWORK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorAbort,
+                          ::media::PIPELINE_ERROR_ABORT);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorInitializationFailed,
+                          ::media::PIPELINE_ERROR_INITIALIZATION_FAILED);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorRead,
+                          ::media::PIPELINE_ERROR_READ);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorInvalidState,
+                          ::media::PIPELINE_ERROR_INVALID_STATE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorCouldNotOpen,
+                          ::media::DEMUXER_ERROR_COULD_NOT_OPEN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorCouldNotParse,
+                          ::media::DEMUXER_ERROR_COULD_NOT_PARSE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerErrorNoSupportedStreams,
+                          ::media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+
+// Audio codecs.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecUnknownAudio,
+                          ::media::AudioCodec::kUnknown);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecAAC,
+                          ::media::AudioCodec::kAAC);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecMP3,
+                          ::media::AudioCodec::kMP3);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecPCM,
+                          ::media::AudioCodec::kPCM);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecVorbis,
+                          ::media::AudioCodec::kVorbis);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecFLAC,
+                          ::media::AudioCodec::kFLAC);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecAMR_NB,
+                          ::media::AudioCodec::kAMR_NB);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecAMR_WB,
+                          ::media::AudioCodec::kAMR_WB);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecPCM_MULAW,
+                          ::media::AudioCodec::kPCM_MULAW);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecGSM_MS,
+                          ::media::AudioCodec::kGSM_MS);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecPCM_S16BE,
+                          ::media::AudioCodec::kPCM_S16BE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecPCM_S24BE,
+                          ::media::AudioCodec::kPCM_S24BE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecOpus,
+                          ::media::AudioCodec::kOpus);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecEAC3,
+                          ::media::AudioCodec::kEAC3);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecPCM_ALAW,
+                          ::media::AudioCodec::kPCM_ALAW);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecALAC,
+                          ::media::AudioCodec::kALAC);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecAC3,
+                          ::media::AudioCodec::kAC3);
+
+
+// Video codecs.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecUnknownVideo,
+                          ::media::VideoCodec::kUnknown);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecH264,
+                          ::media::VideoCodec::kH264);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecVC1,
+                          ::media::VideoCodec::kVC1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecMPEG2,
+                          ::media::VideoCodec::kMPEG2);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecMPEG4,
+                          ::media::VideoCodec::kMPEG4);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecTheora,
+                          ::media::VideoCodec::kTheora);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecVP8,
+                          ::media::VideoCodec::kVP8);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecVP9,
+                          ::media::VideoCodec::kVP9);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecHEVC,
+                          ::media::VideoCodec::kHEVC);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecDolbyVision,
+                          ::media::VideoCodec::kDolbyVision);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerCodecAV1,
+                          ::media::VideoCodec::kAV1);
+
+// Sample formats.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatUnknown,
+                          ::media::kUnknownSampleFormat);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatU8,
+                          ::media::kSampleFormatU8);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatS16,
+                          ::media::kSampleFormatS16);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatS32,
+                          ::media::kSampleFormatS32);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatF32,
+                          ::media::kSampleFormatF32);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatPlanarS16,
+                          ::media::kSampleFormatPlanarS16);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatPlanarF32,
+                          ::media::kSampleFormatPlanarF32);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatPlanarS32,
+                          ::media::kSampleFormatPlanarS32);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerSampleFormatS24,
+                          ::media::kSampleFormatS24);
+
+
+// Channel layouts.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutNone,
+                          ::media::CHANNEL_LAYOUT_NONE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutUnsupported,
+                          ::media::CHANNEL_LAYOUT_UNSUPPORTED);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutMono,
+                          ::media::CHANNEL_LAYOUT_MONO);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutStereo,
+                          ::media::CHANNEL_LAYOUT_STEREO);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout2_1,
+                          ::media::CHANNEL_LAYOUT_2_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutSurround,
+                          ::media::CHANNEL_LAYOUT_SURROUND);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout4_0,
+                          ::media::CHANNEL_LAYOUT_4_0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout2_2,
+                          ::media::CHANNEL_LAYOUT_2_2);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutQuad,
+                          ::media::CHANNEL_LAYOUT_QUAD);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout5_0,
+                          ::media::CHANNEL_LAYOUT_5_0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout5_1,
+                          ::media::CHANNEL_LAYOUT_5_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout5_0Back,
+                          ::media::CHANNEL_LAYOUT_5_0_BACK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout5_1Back,
+                          ::media::CHANNEL_LAYOUT_5_1_BACK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout7_0,
+                          ::media::CHANNEL_LAYOUT_7_0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout7_1,
+                          ::media::CHANNEL_LAYOUT_7_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout7_1Wide,
+                          ::media::CHANNEL_LAYOUT_7_1_WIDE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutStereoDownmix,
+                          ::media::CHANNEL_LAYOUT_STEREO_DOWNMIX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout2point1,
+                          ::media::CHANNEL_LAYOUT_2POINT1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout3_1,
+                          ::media::CHANNEL_LAYOUT_3_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout4_1,
+                          ::media::CHANNEL_LAYOUT_4_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout6_0,
+                          ::media::CHANNEL_LAYOUT_6_0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout6_0Front,
+                          ::media::CHANNEL_LAYOUT_6_0_FRONT);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutHexagonal,
+                          ::media::CHANNEL_LAYOUT_HEXAGONAL);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout6_1,
+                          ::media::CHANNEL_LAYOUT_6_1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout6_1Back,
+                          ::media::CHANNEL_LAYOUT_6_1_BACK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout6_1Front,
+                          ::media::CHANNEL_LAYOUT_6_1_FRONT);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout7_0Front,
+                          ::media::CHANNEL_LAYOUT_7_0_FRONT);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout7_1WideBack,
+                          ::media::CHANNEL_LAYOUT_7_1_WIDE_BACK);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutOctagonal,
+                          ::media::CHANNEL_LAYOUT_OCTAGONAL);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutDiscrete,
+                          ::media::CHANNEL_LAYOUT_DISCRETE);
+DEMUXER_EXTENSION_ENUM_EQ(
+    kCobaltExtensionDemuxerChannelLayoutStereoAndKeyboardMic,
+    ::media::CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayout4_1QuadSide,
+                          ::media::CHANNEL_LAYOUT_4_1_QUAD_SIDE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerChannelLayoutBitstream,
+                          ::media::CHANNEL_LAYOUT_BITSTREAM);
+
+// Video codec profiles.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVideoCodecProfileUnknown,
+                          ::media::VIDEO_CODEC_PROFILE_UNKNOWN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileMin,
+                          ::media::H264PROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileBaseline,
+                          ::media::H264PROFILE_BASELINE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileMain,
+                          ::media::H264PROFILE_MAIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileExtended,
+                          ::media::H264PROFILE_EXTENDED);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileHigh,
+                          ::media::H264PROFILE_HIGH);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileHigh10Profile,
+                          ::media::H264PROFILE_HIGH10PROFILE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileHigh422Profile,
+                          ::media::H264PROFILE_HIGH422PROFILE);
+DEMUXER_EXTENSION_ENUM_EQ(
+    kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile,
+    ::media::H264PROFILE_HIGH444PREDICTIVEPROFILE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileScalableBaseline,
+                          ::media::H264PROFILE_SCALABLEBASELINE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileScalableHigh,
+                          ::media::H264PROFILE_SCALABLEHIGH);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileStereoHigh,
+                          ::media::H264PROFILE_STEREOHIGH);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileMultiviewHigh,
+                          ::media::H264PROFILE_MULTIVIEWHIGH);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerH264ProfileMax,
+                          ::media::H264PROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp8ProfileMin,
+                          ::media::VP8PROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp8ProfileAny,
+                          ::media::VP8PROFILE_ANY);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp8ProfileMax,
+                          ::media::VP8PROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileMin,
+                          ::media::VP9PROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileProfile0,
+                          ::media::VP9PROFILE_PROFILE0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileProfile1,
+                          ::media::VP9PROFILE_PROFILE1);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileProfile2,
+                          ::media::VP9PROFILE_PROFILE2);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileProfile3,
+                          ::media::VP9PROFILE_PROFILE3);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerVp9ProfileMax,
+                          ::media::VP9PROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHevcProfileMin,
+                          ::media::HEVCPROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHevcProfileMain,
+                          ::media::HEVCPROFILE_MAIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHevcProfileMain10,
+                          ::media::HEVCPROFILE_MAIN10);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHevcProfileMainStillPicture,
+                          ::media::HEVCPROFILE_MAIN_STILL_PICTURE);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHevcProfileMax,
+                          ::media::HEVCPROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile0,
+                          ::media::DOLBYVISION_PROFILE0);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile4,
+                          ::media::DOLBYVISION_PROFILE4);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile5,
+                          ::media::DOLBYVISION_PROFILE5);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile7,
+                          ::media::DOLBYVISION_PROFILE7);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerTheoraProfileMin,
+                          ::media::THEORAPROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerTheoraProfileAny,
+                          ::media::THEORAPROFILE_ANY);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerTheoraProfileMax,
+                          ::media::THEORAPROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerAv1ProfileMin,
+                          ::media::AV1PROFILE_MIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerAv1ProfileProfileMain,
+                          ::media::AV1PROFILE_PROFILE_MAIN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerAv1ProfileProfileHigh,
+                          ::media::AV1PROFILE_PROFILE_HIGH);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerAv1ProfileProfilePro,
+                          ::media::AV1PROFILE_PROFILE_PRO);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerAv1ProfileMax,
+                          ::media::AV1PROFILE_MAX);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile8,
+                          ::media::DOLBYVISION_PROFILE8);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerDolbyVisionProfile9,
+                          ::media::DOLBYVISION_PROFILE9);
+
+// Color range IDs.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerColorSpaceRangeIdInvalid,
+                          gfx::ColorSpace::RangeID::INVALID);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerColorSpaceRangeIdLimited,
+                          gfx::ColorSpace::RangeID::LIMITED);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerColorSpaceRangeIdFull,
+                          gfx::ColorSpace::RangeID::FULL);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerColorSpaceRangeIdDerived,
+                          gfx::ColorSpace::RangeID::DERIVED);
+
+// Alpha modes.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerHasAlpha,
+                          ::media::VideoDecoderConfig::AlphaMode::kHasAlpha);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerIsOpaque,
+                          ::media::VideoDecoderConfig::AlphaMode::kIsOpaque);
+
+// Demuxer stream types.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerStreamTypeUnknown,
+                          ::media::DemuxerStream::Type::UNKNOWN);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerStreamTypeAudio,
+                          ::media::DemuxerStream::Type::AUDIO);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerStreamTypeVideo,
+                          ::media::DemuxerStream::Type::VIDEO);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerStreamTypeText,
+                          ::media::DemuxerStream::Type::TEXT);
+
+// Encryption schemes.
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerEncryptionSchemeUnencrypted,
+                          ::media::EncryptionScheme::kUnencrypted);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerEncryptionSchemeCenc,
+                          ::media::EncryptionScheme::kCenc);
+DEMUXER_EXTENSION_ENUM_EQ(kCobaltExtensionDemuxerEncryptionSchemeCbcs,
+                          ::media::EncryptionScheme::kCbcs);
+
+#undef DEMUXER_EXTENSION_ENUM_EQ
+
+}  // namespace
+
+}  // namespace media
+}  // namespace cobalt
diff --git a/cobalt/media/progressive/demuxer_extension_wrapper.h b/cobalt/media/progressive/demuxer_extension_wrapper.h
new file mode 100644
index 0000000..138b922
--- /dev/null
+++ b/cobalt/media/progressive/demuxer_extension_wrapper.h
@@ -0,0 +1,248 @@
+// 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.
+//
+// Contains classes that wrap the Demuxer Cobalt Extension, providing an
+// implementation of a Cobalt demuxer. The main API is DemuxerExtensionWrapper.
+
+#ifndef COBALT_MEDIA_PROGRESSIVE_DEMUXER_EXTENSION_WRAPPER_H_
+#define COBALT_MEDIA_PROGRESSIVE_DEMUXER_EXTENSION_WRAPPER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/threading/thread.h"
+#include "cobalt/extension/demuxer.h"
+#include "cobalt/media/progressive/data_source_reader.h"
+#include "third_party/chromium/media/base/audio_decoder_config.h"
+#include "third_party/chromium/media/base/decoder_buffer.h"
+#include "third_party/chromium/media/base/demuxer.h"
+#include "third_party/chromium/media/base/pipeline_status.h"
+#include "third_party/chromium/media/base/ranges.h"
+#include "third_party/chromium/media/base/video_decoder_config.h"
+
+namespace cobalt {
+namespace media {
+
+// Represents an audio or video stream. Reads data via the demuxer Cobalt
+// Extension.
+class DemuxerExtensionStream : public ::media::DemuxerStream {
+ public:
+  // Represents a video stream.
+  explicit DemuxerExtensionStream(
+      CobaltExtensionDemuxer* demuxer,
+      scoped_refptr<base::SequencedTaskRunner> message_loop,
+      CobaltExtensionDemuxerVideoDecoderConfig config);
+  // Represents an audio stream.
+  explicit DemuxerExtensionStream(
+      CobaltExtensionDemuxer* demuxer,
+      scoped_refptr<base::SequencedTaskRunner> message_loop,
+      CobaltExtensionDemuxerAudioDecoderConfig config);
+
+  // Disallow copy and assign.
+  DemuxerExtensionStream(const DemuxerExtensionStream&) = delete;
+  DemuxerExtensionStream& operator=(const DemuxerExtensionStream&) = delete;
+
+  ~DemuxerExtensionStream() = default;
+
+  // Functions used by DemuxerExtensionWrapper.
+  ::media::Ranges<base::TimeDelta> GetBufferedRanges();
+  void EnqueueBuffer(scoped_refptr<::media::DecoderBuffer> buffer);
+  void FlushBuffers();
+  void Stop();
+  base::TimeDelta GetLastBufferTimestamp() const;
+  size_t GetTotalBufferSize() const;
+
+  // DemuxerStream implementation:
+  void Read(ReadCB read_cb) override;
+  ::media::AudioDecoderConfig audio_decoder_config() override;
+  ::media::VideoDecoderConfig video_decoder_config() override;
+  Type type() const override;
+
+  void EnableBitstreamConverter() override { NOTIMPLEMENTED(); }
+
+  bool SupportsConfigChanges() override { return false; }
+
+ private:
+  typedef std::deque<scoped_refptr<::media::DecoderBuffer>> BufferQueue;
+  typedef std::deque<ReadCB> ReadQueue;
+
+  CobaltExtensionDemuxer* demuxer_ = nullptr;  // Not owned.
+  base::Optional<::media::VideoDecoderConfig> video_config_;
+  base::Optional<::media::AudioDecoderConfig> audio_config_;
+
+  // Protects everything below.
+  mutable base::Lock lock_;
+  // Keeps track of all time ranges this object has seen since creation.
+  // The demuxer uses these ranges to update the pipeline about what data
+  // it has demuxed.
+  ::media::Ranges<base::TimeDelta> buffered_ranges_;
+  // The last timestamp of buffer enqueued. This is used in two places:
+  //   1. Used with the timestamp of the current frame to calculate the
+  //      buffer range.
+  //   2. Used by the demuxer to deteminate what type of frame to get next.
+  base::TimeDelta last_buffer_timestamp_ = ::media::kNoTimestamp;
+  bool stopped_ = false;
+
+  BufferQueue buffer_queue_;
+  ReadQueue read_queue_;
+
+  scoped_refptr<base::SequencedTaskRunner> message_loop_;
+
+  size_t total_buffer_size_ = 0;
+};
+
+// Wraps a DataSourceReader in an even simpler API, where each read increments
+// the read location. This better matches the C data source API.
+class PositionalDataSource {
+ public:
+  explicit PositionalDataSource(scoped_refptr<DataSourceReader> reader);
+
+  // Disallow copy and assign.
+  PositionalDataSource(const PositionalDataSource&) = delete;
+  PositionalDataSource& operator=(const PositionalDataSource&) = delete;
+
+  ~PositionalDataSource();
+
+  void Stop();
+
+  // Reads up to |bytes_requested|, writing the data into |data|.
+  int BlockingRead(uint8_t* data, int bytes_requested);
+
+  // Seeks to |position|.
+  void SeekTo(int position);
+
+  // Returns the current read position.
+  int64_t GetPosition() const;
+
+  // Returns the size of the file.
+  //
+  // TODO(b/231744342): investigate whether we need to fix
+  // DataSourceReader::FileSize(). In testing, it sometimes returned inaccurate
+  // results before a file was fully downloaded. That behavior affects what this
+  // function returns.
+  int64_t GetSize();
+
+ private:
+  scoped_refptr<DataSourceReader> reader_;
+  int64_t position_ = 0;
+};
+
+// Wraps the demuxer Cobalt Extension in the internal media::Demuxer API.
+// Instances should be created via the Create method.
+class DemuxerExtensionWrapper : public ::media::Demuxer {
+ public:
+  // Constructs a new DemuxerExtensionWrapper, returning null on failure. If
+  // |data_source| or |message_loop| is null, or if a demuxer cannot be created,
+  // this will return null. If |demuxer_api| is null, we will attempt to use the
+  // corresponding Cobalt extension.
+  static std::unique_ptr<DemuxerExtensionWrapper> Create(
+      DataSource* data_source,
+      scoped_refptr<base::SequencedTaskRunner> message_loop,
+      const CobaltExtensionDemuxerApi* demuxer_api = nullptr);
+
+  // Disallow copy and assign.
+  DemuxerExtensionWrapper(const DemuxerExtensionWrapper&) = delete;
+  DemuxerExtensionWrapper& operator=(const DemuxerExtensionWrapper&) = delete;
+
+  ~DemuxerExtensionWrapper() override;
+
+  // Demuxer implementation:
+  std::vector<::media::DemuxerStream*> GetAllStreams() override;
+  std::string GetDisplayName() const override;
+  void Initialize(::media::DemuxerHost* host,
+                  ::media::PipelineStatusCallback status_cb) override;
+  void AbortPendingReads() override;
+  void StartWaitingForSeek(base::TimeDelta seek_time) override;
+  void CancelPendingSeek(base::TimeDelta seek_time) override;
+  void Seek(base::TimeDelta time,
+            ::media::PipelineStatusCallback status_cb) override;
+  void Stop() override;
+  base::TimeDelta GetStartTime() const override;
+  base::Time GetTimelineOffset() const override;
+  int64_t GetMemoryUsage() const override;
+  void OnEnabledAudioTracksChanged(
+      const std::vector<::media::MediaTrack::Id>& track_ids,
+      base::TimeDelta curr_time, TrackChangeCB change_completed_cb) override;
+  void OnSelectedVideoTrackChanged(
+      const std::vector<::media::MediaTrack::Id>& track_ids,
+      base::TimeDelta curr_time, TrackChangeCB change_completed_cb) override;
+
+  absl::optional<::media::container_names::MediaContainerName>
+  GetContainerForMetrics() const override {
+    NOTREACHED();
+    return absl::nullopt;
+  }
+
+ private:
+  // Only a forward declaration here, since the specifics of this class are an
+  // implementation detail.
+  class H264AnnexBConverter;
+
+  // Arguments must not be null.
+  explicit DemuxerExtensionWrapper(
+      const CobaltExtensionDemuxerApi* demuxer_api,
+      CobaltExtensionDemuxer* demuxer,
+      std::unique_ptr<PositionalDataSource> data_source,
+      std::unique_ptr<CobaltExtensionDemuxerDataSource> c_data_source,
+      scoped_refptr<base::SequencedTaskRunner> message_loop);
+
+  void OnInitializeDone(::media::PipelineStatusCallback status_cb,
+                        CobaltExtensionDemuxerStatus status);
+  void Request(::media::DemuxerStream::Type type);
+  bool HasStopped();
+  void IssueNextRequest();
+  void SeekTask(base::TimeDelta time,
+                ::media::PipelineStatusCallback status_cb);
+
+  // Returns the range of buffered data. If both audio and video streams are
+  // present, this is the intersection of their buffered ranges; otherwise, it
+  // is whatever range of data is buffered.
+  ::media::Ranges<base::TimeDelta> GetBufferedRanges();
+
+  const CobaltExtensionDemuxerApi* demuxer_api_ = nullptr;  // Not owned.
+  // Owned by this class. Construction/destruction is done via demuxer_api_.
+  CobaltExtensionDemuxer* impl_ = nullptr;
+  std::unique_ptr<PositionalDataSource> data_source_;
+  std::unique_ptr<CobaltExtensionDemuxerDataSource> c_data_source_;
+  ::media::DemuxerHost* host_ = nullptr;
+  mutable base::Lock lock_for_stopped_;
+  // Indicates whether Stop has been called.
+  bool stopped_ = false;
+  bool video_reached_eos_ = false;
+  bool audio_reached_eos_ = false;
+  bool flushing_ = false;
+
+  base::Optional<DemuxerExtensionStream> video_stream_;
+  base::Optional<DemuxerExtensionStream> audio_stream_;
+
+  std::unique_ptr<H264AnnexBConverter> h264_converter_;
+
+  // Thread for blocking I/O operations.
+  base::Thread blocking_thread_;
+
+  scoped_refptr<base::SequencedTaskRunner> message_loop_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<DemuxerExtensionWrapper> weak_factory_{this};
+};
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_PROGRESSIVE_DEMUXER_EXTENSION_WRAPPER_H_
diff --git a/cobalt/media/progressive/demuxer_extension_wrapper_test.cc b/cobalt/media/progressive/demuxer_extension_wrapper_test.cc
new file mode 100644
index 0000000..3aae16a
--- /dev/null
+++ b/cobalt/media/progressive/demuxer_extension_wrapper_test.cc
@@ -0,0 +1,701 @@
+// 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 "cobalt/media/progressive/demuxer_extension_wrapper.h"
+
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <vector>
+
+#include "base/synchronization/waitable_event.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "cobalt/extension/demuxer.h"
+#include "cobalt/media/decoder_buffer_allocator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/chromium/media/base/demuxer.h"
+
+namespace cobalt {
+namespace media {
+namespace {
+
+using ::testing::_;
+using ::testing::AtMost;
+using ::testing::ElementsAreArray;
+using ::testing::ExplainMatchResult;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NiceMock;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::Return;
+using ::testing::UnorderedElementsAre;
+
+// Matches a DemuxerStream and verifies that the type is |type|.
+MATCHER_P(TypeIs, type, "") { return arg.type() == type; }
+
+// Matches a DecoderBuffer and verifies that the data in the buffer is |data|.
+MATCHER_P(BufferHasData, data, "") {
+  return ExplainMatchResult(
+      ElementsAreArray(data),
+      std::tuple<const uint8_t*, size_t>{arg.data(), arg.data_size()},
+      result_listener);
+}
+
+class MockDemuxerHost : public ::media::DemuxerHost {
+ public:
+  MockDemuxerHost() = default;
+
+  MockDemuxerHost(const MockDemuxerHost&) = delete;
+  MockDemuxerHost& operator=(const MockDemuxerHost&) = delete;
+
+  ~MockDemuxerHost() override = default;
+
+  MOCK_METHOD1(OnBufferedTimeRangesChanged,
+               void(const ::media::Ranges<base::TimeDelta>&));
+  MOCK_METHOD1(SetDuration, void(base::TimeDelta duration));
+  MOCK_METHOD1(OnDemuxerError, void(::media::PipelineStatus error));
+};
+
+class MockDataSource : public DataSource {
+ public:
+  MockDataSource() {
+    // Set reasonable default behavior for functions that are expected to
+    // interact with callbacks and/or output parameters.
+    ON_CALL(*this, Read(_, _, _, _))
+        .WillByDefault(Invoke(+[](int64_t position, int size, uint8_t* data,
+                                  const DataSource::ReadCB& read_cb) {
+          memset(data, 0, size);
+          read_cb.Run(size);
+        }));
+    ON_CALL(*this, GetSize(_)).WillByDefault(Invoke(+[](int64_t* size_out) {
+      *size_out = 0;
+      return true;
+    }));
+  }
+
+  ~MockDataSource() override = default;
+
+  MOCK_METHOD4(Read, void(int64_t position, int size, uint8_t* data,
+                          const DataSource::ReadCB& read_cb));
+  MOCK_METHOD0(Stop, void());
+  MOCK_METHOD0(Abort, void());
+  MOCK_METHOD1(GetSize, bool(int64_t* size_out));
+  MOCK_METHOD0(IsStreaming, bool());
+  MOCK_METHOD1(SetBitrate, void(int bitrate));
+};
+
+// Mock class for receiving calls to the Cobalt Extension demuxer. Based on the
+// CobaltExtensionDemuxer struct.
+class MockCobaltExtensionDemuxer {
+ public:
+  MOCK_METHOD0(Initialize, CobaltExtensionDemuxerStatus());
+  MOCK_METHOD1(Seek, CobaltExtensionDemuxerStatus(int64_t seek_time_us));
+  MOCK_METHOD0(GetStartTime, SbTime());
+  MOCK_METHOD0(GetTimelineOffset, SbTime());
+  MOCK_METHOD3(Read, void(CobaltExtensionDemuxerStreamType type,
+                          CobaltExtensionDemuxerReadCB read_cb,
+                          void* read_cb_user_data));
+  MOCK_METHOD1(GetAudioConfig,
+               bool(CobaltExtensionDemuxerAudioDecoderConfig* config));
+  MOCK_METHOD1(GetVideoConfig,
+               bool(CobaltExtensionDemuxerVideoDecoderConfig* config));
+  MOCK_METHOD0(GetDuration, SbTime());
+
+  // Pure C functions to be used in CobaltExtensionDemuxer. These expect
+  // |user_data| to be a pointer to a MockCobaltExtensionDemuxer.
+  static CobaltExtensionDemuxerStatus InitializeImpl(void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->Initialize();
+  }
+
+  static CobaltExtensionDemuxerStatus SeekImpl(int64_t seek_time_us,
+                                               void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->Seek(
+        seek_time_us);
+  }
+
+  static SbTime GetStartTimeImpl(void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetStartTime();
+  }
+
+  static SbTime GetTimelineOffsetImpl(void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)
+        ->GetTimelineOffset();
+  }
+  static void ReadImpl(CobaltExtensionDemuxerStreamType type,
+                       CobaltExtensionDemuxerReadCB read_cb,
+                       void* read_cb_user_data, void* user_data) {
+    static_cast<MockCobaltExtensionDemuxer*>(user_data)->Read(
+        type, read_cb, read_cb_user_data);
+  }
+
+  static bool GetAudioConfigImpl(
+      CobaltExtensionDemuxerAudioDecoderConfig* config, void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetAudioConfig(
+        config);
+  }
+
+  static bool GetVideoConfigImpl(
+      CobaltExtensionDemuxerVideoDecoderConfig* config, void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetVideoConfig(
+        config);
+  }
+
+  static SbTime GetDurationImpl(void* user_data) {
+    return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetDuration();
+  }
+};
+
+// Forward declaration for the purpose of defining GetMockDemuxerApi. Defined
+// below.
+class MockDemuxerApi;
+
+// Returns a pointer to a MockDemuxerApi with static storage duration. The
+// returned pointer should not be deleted. Defined below.
+MockDemuxerApi* GetMockDemuxerApi();
+
+// Mock class for receiving calls to the Cobalt Extension demuxer API. Based on
+// the CobaltExtensionDemuxerApi struct.
+class MockDemuxerApi {
+ public:
+  MOCK_METHOD5(CreateDemuxer,
+               CobaltExtensionDemuxer*(
+                   CobaltExtensionDemuxerDataSource* data_source,
+                   CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
+                   int64_t supported_audio_codecs_size,
+                   CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
+                   int64_t supported_video_codecs_size));
+  MOCK_METHOD1(DestroyDemuxer, void(CobaltExtensionDemuxer* demuxer));
+
+  // Pure C functions to be used in CobaltExtensionDemuxer. These expect
+  // |user_data| to be a pointer to a MockDemuxerApi.
+  static CobaltExtensionDemuxer* CreateDemuxerImpl(
+      CobaltExtensionDemuxerDataSource* data_source,
+      CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
+      int64_t supported_audio_codecs_size,
+      CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
+      int64_t supported_video_codecs_size) {
+    return GetMockDemuxerApi()->CreateDemuxer(
+        data_source, supported_audio_codecs, supported_audio_codecs_size,
+        supported_video_codecs, supported_video_codecs_size);
+  }
+
+  static void DestroyDemuxerImpl(CobaltExtensionDemuxer* demuxer) {
+    GetMockDemuxerApi()->DestroyDemuxer(demuxer);
+  }
+};
+
+MockDemuxerApi* GetMockDemuxerApi() {
+  static auto* const demuxer_api = []() {
+    auto* inner_demuxer_api = new MockDemuxerApi;
+    // This mock won't be destructed.
+    testing::Mock::AllowLeak(inner_demuxer_api);
+    return inner_demuxer_api;
+  }();
+
+  return demuxer_api;
+}
+
+CobaltExtensionDemuxer* CreateCDemuxer(
+    MockCobaltExtensionDemuxer* mock_demuxer) {
+  CHECK(mock_demuxer);
+  return new CobaltExtensionDemuxer{
+      &MockCobaltExtensionDemuxer::InitializeImpl,
+      &MockCobaltExtensionDemuxer::SeekImpl,
+      &MockCobaltExtensionDemuxer::GetStartTimeImpl,
+      &MockCobaltExtensionDemuxer::GetTimelineOffsetImpl,
+      &MockCobaltExtensionDemuxer::ReadImpl,
+      &MockCobaltExtensionDemuxer::GetAudioConfigImpl,
+      &MockCobaltExtensionDemuxer::GetVideoConfigImpl,
+      &MockCobaltExtensionDemuxer::GetDurationImpl,
+      mock_demuxer};
+}
+
+// A test fixture is used to verify and clear the global mock demuxer, and to
+// manage the lifetime of the ScopedTaskEnvironment.
+class DemuxerExtensionWrapperTest : public ::testing::Test {
+ protected:
+  DemuxerExtensionWrapperTest() = default;
+
+  ~DemuxerExtensionWrapperTest() override {
+    testing::Mock::VerifyAndClearExpectations(GetMockDemuxerApi());
+  }
+
+  // Waits |time_limit| for |done| to occur. Returns true if the event occurred,
+  // false otherwise. While waiting, allows other threads to run and runs the
+  // task runner until idle.
+  bool WaitForEvent(base::WaitableEvent& done,
+                    base::TimeDelta time_limit = base::Seconds(1)) {
+    const base::Time deadline = base::Time::Now() + base::Seconds(1);
+    while (base::Time::Now() < deadline) {
+      task_environment_.RunUntilIdle();
+      base::PlatformThread::YieldCurrentThread();
+      if (done.IsSignaled()) {
+        break;
+      }
+    }
+    return done.IsSignaled();
+  }
+
+  // This must be deleted last.
+  base::test::ScopedTaskEnvironment task_environment_{
+      base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
+  // This is necessary in order to allocate DecoderBuffers. This is done
+  // internally in DemuxerExtensionWrapper.
+  DecoderBufferAllocator allocator_;
+};
+
+TEST_F(DemuxerExtensionWrapperTest, SuccessfullyInitializes) {
+  // This must outlive the DemuxerExtensionWrapper.
+  NiceMock<MockDemuxerHost> mock_host;
+  MockDataSource data_source;
+  MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi();  // Not owned.
+  NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
+
+  // In this test we don't care about what data is read.
+  ON_CALL(mock_demuxer, Read(_, _, _))
+      .WillByDefault(Invoke([](CobaltExtensionDemuxerStreamType type,
+                               CobaltExtensionDemuxerReadCB read_cb,
+                               void* read_cb_user_data) {
+        // Simulate EOS.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  const CobaltExtensionDemuxerApi api = {
+      /*name=*/kCobaltExtensionDemuxerApi,
+      /*version=*/1,
+      /*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
+      /*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
+  };
+
+  auto c_demuxer =
+      std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
+  EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
+      .WillOnce(Return(c_demuxer.get()));
+  EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
+
+  std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
+      DemuxerExtensionWrapper::Create(
+          &data_source, base::SequencedTaskRunnerHandle::Get(), &api);
+
+  ASSERT_THAT(demuxer_wrapper, NotNull());
+
+  base::WaitableEvent init_done;
+  base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
+      initialize_cb;
+  EXPECT_CALL(mock_demuxer, Initialize())
+      .WillOnce(Return(kCobaltExtensionDemuxerOk));
+  // Simulate an audio file.
+  EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecAAC;
+        config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
+        config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->samples_per_second = 44100;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull())).WillOnce(Return(false));
+  EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
+      .WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
+
+  demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
+
+  EXPECT_TRUE(WaitForEvent(init_done));
+}
+
+TEST_F(DemuxerExtensionWrapperTest, ProvidesAudioAndVideoStreams) {
+  // This must outlive the DemuxerExtensionWrapper.
+  NiceMock<MockDemuxerHost> mock_host;
+  MockDataSource data_source;
+  MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi();  // Not owned.
+  NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
+
+  // In this test we don't care about what data is read.
+  ON_CALL(mock_demuxer, Read(_, _, _))
+      .WillByDefault(Invoke([](CobaltExtensionDemuxerStreamType type,
+                               CobaltExtensionDemuxerReadCB read_cb,
+                               void* read_cb_user_data) {
+        // Simulate EOS.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  const CobaltExtensionDemuxerApi api = {
+      /*name=*/kCobaltExtensionDemuxerApi,
+      /*version=*/1,
+      /*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
+      /*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
+  };
+
+  auto c_demuxer =
+      std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
+  EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
+      .WillOnce(Return(c_demuxer.get()));
+  EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
+
+  std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
+      DemuxerExtensionWrapper::Create(
+          &data_source, base::SequencedTaskRunnerHandle::Get(), &api);
+
+  ASSERT_THAT(demuxer_wrapper, NotNull());
+
+  base::WaitableEvent init_done;
+  base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
+      initialize_cb;
+  EXPECT_CALL(mock_demuxer, Initialize())
+      .WillOnce(Return(kCobaltExtensionDemuxerOk));
+  // Simulate an audio+video file.
+  EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecAAC;
+        config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
+        config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->samples_per_second = 44100;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecH264;
+        config->profile = kCobaltExtensionDemuxerH264ProfileMain;
+        config->color_space_primaries = 1;
+        config->color_space_transfer = 1;
+        config->color_space_matrix = 1;
+        config->color_space_range_id =
+            kCobaltExtensionDemuxerColorSpaceRangeIdFull;
+        config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
+        config->coded_width = 1920;
+        config->coded_height = 1080;
+        config->visible_rect_x = 0;
+        config->visible_rect_y = 0;
+        config->visible_rect_width = 1920;
+        config->visible_rect_height = 1080;
+        config->natural_width = 1920;
+        config->natural_height = 1080;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
+      .WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
+
+  demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
+
+  EXPECT_TRUE(WaitForEvent(init_done));
+
+  std::vector<::media::DemuxerStream*> streams =
+      demuxer_wrapper->GetAllStreams();
+  EXPECT_THAT(streams,
+              UnorderedElementsAre(
+                  Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
+                  Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
+}
+
+TEST_F(DemuxerExtensionWrapperTest, ReadsAudioData) {
+  // This must outlive the DemuxerExtensionWrapper.
+  NiceMock<MockDemuxerHost> mock_host;
+  MockDataSource data_source;
+  MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi();  // Not owned.
+  NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
+
+  const CobaltExtensionDemuxerApi api = {
+      /*name=*/kCobaltExtensionDemuxerApi,
+      /*version=*/1,
+      /*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
+      /*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
+  };
+
+  auto c_demuxer =
+      std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
+  EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
+      .WillOnce(Return(c_demuxer.get()));
+  EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
+
+  std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
+      DemuxerExtensionWrapper::Create(
+          &data_source, base::SequencedTaskRunnerHandle::Get(), &api);
+
+  ASSERT_THAT(demuxer_wrapper, NotNull());
+
+  base::WaitableEvent init_done;
+  base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
+      initialize_cb;
+  EXPECT_CALL(mock_demuxer, Initialize())
+      .WillOnce(Return(kCobaltExtensionDemuxerOk));
+  // Simulate an audio+video file.
+  EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecAAC;
+        config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
+        config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->samples_per_second = 44100;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecH264;
+        config->profile = kCobaltExtensionDemuxerH264ProfileMain;
+        config->color_space_primaries = 1;
+        config->color_space_transfer = 1;
+        config->color_space_matrix = 1;
+        config->color_space_range_id =
+            kCobaltExtensionDemuxerColorSpaceRangeIdFull;
+        config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
+        config->coded_width = 1920;
+        config->coded_height = 1080;
+        config->visible_rect_x = 0;
+        config->visible_rect_y = 0;
+        config->visible_rect_width = 1920;
+        config->visible_rect_height = 1080;
+        config->natural_width = 1920;
+        config->natural_height = 1080;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
+      .WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
+
+  std::vector<uint8_t> buffer_data = {1, 2, 3, 4, 5};
+  EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeAudio, _, _))
+      .WillOnce(Invoke([&buffer_data](CobaltExtensionDemuxerStreamType type,
+                                      CobaltExtensionDemuxerReadCB read_cb,
+                                      void* read_cb_user_data) {
+        // Send one "real" buffer.
+        CobaltExtensionDemuxerBuffer buffer = {};
+        buffer.data = buffer_data.data();
+        buffer.data_size = buffer_data.size();
+        buffer.side_data = nullptr;
+        buffer.side_data_elements = 0;
+        buffer.pts = 0;
+        buffer.duration = 1000;
+        buffer.is_keyframe = true;
+        buffer.end_of_stream = false;
+        read_cb(&buffer, read_cb_user_data);
+      }))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
+                          CobaltExtensionDemuxerReadCB read_cb,
+                          void* read_cb_user_data) {
+        // Simulate the audio stream being done.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  // The impl may or may not try reading video data. If it does, return EOS.
+  EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeVideo, _, _))
+      .Times(AtMost(1))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
+                          CobaltExtensionDemuxerReadCB read_cb,
+                          void* read_cb_user_data) {
+        // Simulate the video stream being done.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
+
+  EXPECT_TRUE(WaitForEvent(init_done));
+
+  std::vector<::media::DemuxerStream*> streams =
+      demuxer_wrapper->GetAllStreams();
+  ASSERT_THAT(streams,
+              UnorderedElementsAre(
+                  Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
+                  Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
+  ::media::DemuxerStream* audio_stream =
+      streams[0]->type() == ::media::DemuxerStream::Type::AUDIO ? streams[0]
+                                                                : streams[1];
+
+  base::MockCallback<base::OnceCallback<void(
+      ::media::DemuxerStream::Status, scoped_refptr<::media::DecoderBuffer>)>>
+      read_cb;
+  base::WaitableEvent read_done;
+  EXPECT_CALL(read_cb, Run(::media::DemuxerStream::kOk,
+                           Pointee(BufferHasData(buffer_data))))
+      .WillOnce(InvokeWithoutArgs([&read_done]() { read_done.Signal(); }));
+
+  audio_stream->Read(read_cb.Get());
+  EXPECT_TRUE(WaitForEvent(read_done));
+}
+
+TEST_F(DemuxerExtensionWrapperTest, ReadsVideoData) {
+  // This must outlive the DemuxerExtensionWrapper.
+  NiceMock<MockDemuxerHost> mock_host;
+  MockDataSource data_source;
+  MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi();  // Not owned.
+  NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
+
+  const CobaltExtensionDemuxerApi api = {
+      /*name=*/kCobaltExtensionDemuxerApi,
+      /*version=*/1,
+      /*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
+      /*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
+  };
+
+  auto c_demuxer =
+      std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
+  EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
+      .WillOnce(Return(c_demuxer.get()));
+  EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
+
+  std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
+      DemuxerExtensionWrapper::Create(
+          &data_source, base::SequencedTaskRunnerHandle::Get(), &api);
+
+  ASSERT_THAT(demuxer_wrapper, NotNull());
+
+  base::WaitableEvent init_done;
+  base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
+      initialize_cb;
+  EXPECT_CALL(mock_demuxer, Initialize())
+      .WillOnce(Return(kCobaltExtensionDemuxerOk));
+  // Simulate an audio+video file.
+  EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecAAC;
+        config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
+        config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->samples_per_second = 44100;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
+        config->codec = kCobaltExtensionDemuxerCodecH264;
+        config->profile = kCobaltExtensionDemuxerH264ProfileMain;
+        config->color_space_primaries = 1;
+        config->color_space_transfer = 1;
+        config->color_space_matrix = 1;
+        config->color_space_range_id =
+            kCobaltExtensionDemuxerColorSpaceRangeIdFull;
+        config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
+        config->coded_width = 1920;
+        config->coded_height = 1080;
+        config->visible_rect_x = 0;
+        config->visible_rect_y = 0;
+        config->visible_rect_width = 1920;
+        config->visible_rect_height = 1080;
+        config->natural_width = 1920;
+        config->natural_height = 1080;
+        config->encryption_scheme =
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
+        config->extra_data = nullptr;
+        config->extra_data_size = 0;
+
+        return true;
+      }));
+  EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
+      .WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
+
+  std::vector<uint8_t> buffer_data = {1, 2, 3, 4, 5};
+  EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeVideo, _, _))
+      .WillOnce(Invoke([&buffer_data](CobaltExtensionDemuxerStreamType type,
+                                      CobaltExtensionDemuxerReadCB read_cb,
+                                      void* read_cb_user_data) {
+        // Send one "real" buffer.
+        CobaltExtensionDemuxerBuffer buffer = {};
+        buffer.data = buffer_data.data();
+        buffer.data_size = buffer_data.size();
+        buffer.side_data = nullptr;
+        buffer.side_data_elements = 0;
+        buffer.pts = 0;
+        buffer.duration = 1000;
+        buffer.is_keyframe = true;
+        buffer.end_of_stream = false;
+        read_cb(&buffer, read_cb_user_data);
+      }))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
+                          CobaltExtensionDemuxerReadCB read_cb,
+                          void* read_cb_user_data) {
+        // Simulate the video stream being done.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  // The impl may or may not try reading audio data. If it does, return EOS.
+  EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeAudio, _, _))
+      .Times(AtMost(1))
+      .WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
+                          CobaltExtensionDemuxerReadCB read_cb,
+                          void* read_cb_user_data) {
+        // Simulate the audio stream being done.
+        CobaltExtensionDemuxerBuffer eos_buffer = {};
+        eos_buffer.end_of_stream = true;
+        read_cb(&eos_buffer, read_cb_user_data);
+      }));
+
+  demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
+
+  EXPECT_TRUE(WaitForEvent(init_done));
+
+  std::vector<::media::DemuxerStream*> streams =
+      demuxer_wrapper->GetAllStreams();
+  ASSERT_THAT(streams,
+              UnorderedElementsAre(
+                  Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
+                  Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
+  ::media::DemuxerStream* video_stream =
+      streams[0]->type() == ::media::DemuxerStream::Type::VIDEO ? streams[0]
+                                                                : streams[1];
+
+  base::MockCallback<base::OnceCallback<void(
+      ::media::DemuxerStream::Status, scoped_refptr<::media::DecoderBuffer>)>>
+      read_cb;
+  base::WaitableEvent read_done;
+  EXPECT_CALL(read_cb, Run(::media::DemuxerStream::kOk,
+                           Pointee(BufferHasData(buffer_data))))
+      .WillOnce(InvokeWithoutArgs([&read_done]() { read_done.Signal(); }));
+
+  video_stream->Read(read_cb.Get());
+  EXPECT_TRUE(WaitForEvent(read_done));
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace cobalt
diff --git a/cobalt/media/sandbox/web_media_player_helper.cc b/cobalt/media/sandbox/web_media_player_helper.cc
index 9de31c7..3b8088e 100644
--- a/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/cobalt/media/sandbox/web_media_player_helper.cc
@@ -99,7 +99,7 @@
 }
 
 SbDecodeTarget WebMediaPlayerHelper::GetCurrentDecodeTarget() const {
-  return player_->GetVideoFrameProvider()->GetCurrentSbDecodeTarget();
+  return player_->GetDecodeTargetProvider()->GetCurrentSbDecodeTarget();
 }
 
 bool WebMediaPlayerHelper::IsPlaybackFinished() const {
diff --git a/cobalt/media/sandbox/web_media_player_helper.h b/cobalt/media/sandbox/web_media_player_helper.h
index 939f0ae..21a5877 100644
--- a/cobalt/media/sandbox/web_media_player_helper.h
+++ b/cobalt/media/sandbox/web_media_player_helper.h
@@ -20,7 +20,7 @@
 
 #include "base/callback.h"
 #include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/media/base/video_frame_provider.h"
+#include "cobalt/media/base/decode_target_provider.h"
 #include "cobalt/media/media_module.h"
 #include "cobalt/media/player/web_media_player.h"
 #include "third_party/chromium/media/cobalt/ui/gfx/geometry/size.h"
diff --git a/cobalt/media_session/media_metadata.idl b/cobalt/media_session/media_metadata.idl
index 891b011..84f7326 100644
--- a/cobalt/media_session/media_metadata.idl
+++ b/cobalt/media_session/media_metadata.idl
@@ -19,6 +19,6 @@
   attribute DOMString title;
   attribute DOMString artist;
   attribute DOMString album;
-  // TODO make this be FrozenArray<MediaImage> when available
+  // TODO(b/236750294): Make this be FrozenArray<MediaImage> when available.
   attribute sequence<MediaImage> artwork;
 };
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index 499a31e..a68d213 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -92,13 +92,13 @@
     "//cobalt/content/ssl/certs/09789157.0",
     "//cobalt/content/ssl/certs/0a775a30.0",
     "//cobalt/content/ssl/certs/0b1b94ef.0",
+    "//cobalt/content/ssl/certs/0b9bc432.0",
     "//cobalt/content/ssl/certs/0bf05006.0",
     "//cobalt/content/ssl/certs/0f5dc4f3.0",
     "//cobalt/content/ssl/certs/0f6fa695.0",
     "//cobalt/content/ssl/certs/1001acf7.0",
     "//cobalt/content/ssl/certs/106f3e4d.0",
     "//cobalt/content/ssl/certs/14bc7599.0",
-    "//cobalt/content/ssl/certs/1636090b.0",
     "//cobalt/content/ssl/certs/18856ac4.0",
     "//cobalt/content/ssl/certs/1d3472b9.0",
     "//cobalt/content/ssl/certs/1e08bfd1.0",
@@ -127,6 +127,8 @@
     "//cobalt/content/ssl/certs/5443e9e3.0",
     "//cobalt/content/ssl/certs/54657681.0",
     "//cobalt/content/ssl/certs/57bcb2da.0",
+    "//cobalt/content/ssl/certs/5931b5bc.0",
+    "//cobalt/content/ssl/certs/5a7722fb.0",
     "//cobalt/content/ssl/certs/5ad8a5d6.0",
     "//cobalt/content/ssl/certs/5cd81ad7.0",
     "//cobalt/content/ssl/certs/5d3033c5.0",
@@ -136,6 +138,7 @@
     "//cobalt/content/ssl/certs/607986c7.0",
     "//cobalt/content/ssl/certs/626dceaf.0",
     "//cobalt/content/ssl/certs/653b494a.0",
+    "//cobalt/content/ssl/certs/66445960.0",
     "//cobalt/content/ssl/certs/68dd7389.0",
     "//cobalt/content/ssl/certs/6b99d060.0",
     "//cobalt/content/ssl/certs/6d41d539.0",
@@ -146,19 +149,26 @@
     "//cobalt/content/ssl/certs/76faf6c0.0",
     "//cobalt/content/ssl/certs/7719f463.0",
     "//cobalt/content/ssl/certs/773e07ad.0",
+    "//cobalt/content/ssl/certs/7a3adc42.0",
+    "//cobalt/content/ssl/certs/7a780d93.0",
     "//cobalt/content/ssl/certs/7aaf71c0.0",
     "//cobalt/content/ssl/certs/7f3d5d1d.0",
     "//cobalt/content/ssl/certs/8160b96c.0",
+    "//cobalt/content/ssl/certs/8508e720.0",
     "//cobalt/content/ssl/certs/8cb5ee0f.0",
     "//cobalt/content/ssl/certs/8d86cdd1.0",
     "//cobalt/content/ssl/certs/8d89cda1.0",
+    "//cobalt/content/ssl/certs/8f103249.0",
+    "//cobalt/content/ssl/certs/90c5a3c8.0",
     "//cobalt/content/ssl/certs/930ac5d2.0",
     "//cobalt/content/ssl/certs/93bc0acc.0",
     "//cobalt/content/ssl/certs/9482e63a.0",
+    "//cobalt/content/ssl/certs/9846683b.0",
     "//cobalt/content/ssl/certs/988a38cb.0",
     "//cobalt/content/ssl/certs/9b5697b0.0",
     "//cobalt/content/ssl/certs/9c8dfbd4.0",
     "//cobalt/content/ssl/certs/9d04f354.0",
+    "//cobalt/content/ssl/certs/9ef4a08a.0",
     "//cobalt/content/ssl/certs/9f727ac7.0",
     "//cobalt/content/ssl/certs/a3418fda.0",
     "//cobalt/content/ssl/certs/a94d09e5.0",
@@ -180,6 +190,7 @@
     "//cobalt/content/ssl/certs/cd8c0d63.0",
     "//cobalt/content/ssl/certs/ce5e74ef.0",
     "//cobalt/content/ssl/certs/d4dae3dd.0",
+    "//cobalt/content/ssl/certs/d52c538d.0",
     "//cobalt/content/ssl/certs/d6325660.0",
     "//cobalt/content/ssl/certs/d7e8dc79.0",
     "//cobalt/content/ssl/certs/d887a5bb.0",
@@ -194,6 +205,7 @@
     "//cobalt/content/ssl/certs/e868b802.0",
     "//cobalt/content/ssl/certs/e8de2f56.0",
     "//cobalt/content/ssl/certs/ecccd8db.0",
+    "//cobalt/content/ssl/certs/ed858448.0",
     "//cobalt/content/ssl/certs/ee64a828.0",
     "//cobalt/content/ssl/certs/eed8c118.0",
     "//cobalt/content/ssl/certs/ef954a4e.0",
diff --git a/cobalt/renderer/BUILD.gn b/cobalt/renderer/BUILD.gn
index 2fbc58b..21a89fc 100644
--- a/cobalt/renderer/BUILD.gn
+++ b/cobalt/renderer/BUILD.gn
@@ -146,11 +146,10 @@
     ":renderer_download_lottie_test_data",
   ]
 
-  content_deps = data_deps
   if (cobalt_font_package == "empty") {
-    content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+    data_deps += [ "//cobalt/content/fonts:copy_font_data" ]
   } else {
-    content_deps += [
+    data_deps += [
       "//cobalt/content/fonts:copy_fonts",
       "//cobalt/content/fonts:fonts_xml",
     ]
diff --git a/cobalt/renderer/sandbox/BUILD.gn b/cobalt/renderer/sandbox/BUILD.gn
index faebec3..30d7def 100644
--- a/cobalt/renderer/sandbox/BUILD.gn
+++ b/cobalt/renderer/sandbox/BUILD.gn
@@ -31,7 +31,7 @@
     "//cobalt/trace_event",
   ]
 
-  content_deps = [ "//cobalt/renderer/test/scenes:scenes_copy_test_data" ]
+  data_deps = [ "//cobalt/renderer/test/scenes:scenes_copy_test_data" ]
 }
 
 # This target will build a sandbox application that allows for easy
@@ -50,5 +50,5 @@
     "//cobalt/trace_event",
   ]
 
-  content_deps = [ "//cobalt/renderer/test/scenes:scenes_copy_test_data" ]
+  data_deps = [ "//cobalt/renderer/test/scenes:scenes_copy_test_data" ]
 }
diff --git a/cobalt/script/promise.h b/cobalt/script/promise.h
index d21d915..6fa4b54 100644
--- a/cobalt/script/promise.h
+++ b/cobalt/script/promise.h
@@ -42,16 +42,21 @@
  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 = 0;
+  virtual void Resolve(const T& result) const { NOTREACHED(); }
 
   // Call the |reject| function passed as an argument to the Promise's executor
   // function.
-  virtual void Reject() const = 0;
-  virtual void Reject(SimpleExceptionType exception) const = 0;
-  virtual void Reject(const scoped_refptr<ScriptException>& result) const = 0;
+  virtual void Reject() const { NOTREACHED(); }
+  virtual void Reject(SimpleExceptionType exception) const { NOTREACHED(); }
+  virtual void Reject(const scoped_refptr<ScriptException>& result) const {
+    NOTREACHED();
+  }
 
   // Returns the value of the [[PromiseState]] field.
-  virtual PromiseState State() const = 0;
+  virtual PromiseState State() const {
+    NOTREACHED();
+    return PromiseState::kRejected;
+  }
 
   virtual ~Promise() {}
 };
diff --git a/cobalt/script/v8c/conversion_helpers.h b/cobalt/script/v8c/conversion_helpers.h
index d83c23f..85aa597 100644
--- a/cobalt/script/v8c/conversion_helpers.h
+++ b/cobalt/script/v8c/conversion_helpers.h
@@ -584,6 +584,7 @@
 template <typename T>
 void ToJSValue(v8::Isolate* isolate, const script::Sequence<T>& sequence,
                v8::Local<v8::Value>* out_value) {
+  // https://webidl.spec.whatwg.org/#es-sequence
   // 1. Let n be the length of S.
   using size_type = typename script::Sequence<T>::size_type;
   size_type count = sequence.size();
@@ -697,15 +698,27 @@
   }
 }
 
+// Promise -> JSValue
 template <typename T>
 void ToJSValue(v8::Isolate* isolate,
                const ScriptValue<Promise<T>>* promise_holder,
                v8::Local<v8::Value>* out_value);
 
+// Promise -> JSValue
 template <typename T>
 void ToJSValue(v8::Isolate* isolate, ScriptValue<Promise<T>>* promise_holder,
                v8::Local<v8::Value>* out_value);
 
+// JSValue -> Promise
+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::Handle<T> -> JSValue
 template <typename T>
 void ToJSValue(v8::Isolate* isolate, const Handle<T>& local,
diff --git a/cobalt/site/docs/development/setup-android.md b/cobalt/site/docs/development/setup-android.md
index c4b241a..ac393b0 100644
--- a/cobalt/site/docs/development/setup-android.md
+++ b/cobalt/site/docs/development/setup-android.md
@@ -9,36 +9,14 @@
 ## Preliminary Setup
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for
+<a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the
+sections <b>Set up your workstation</b> and <b>Set up developer tools</b>, then
+return and complete the following steps.
 </aside>
 
-1.  Additional build dependencies may need to be installed:
-    ```
-    sudo apt-get install python python-pip
-    ```
-
-    If `python-pip` is not available via your package manager, you can install `pip` following [recommended instructions](https://pip.pypa.io/en/stable/installing/) from the official Python guide.
-
-    There are also some Python module requirements:
-
-    ```
-    python -m pip install requests
-    ```
-
-1.  Install ccache to support build acceleration. ccache is automatically used
-    when available, otherwise defaults to unaccelerated building:
-
-    ```
-    $ sudo apt-get install ccache
-    ```
-
-    We recommend adjusting the cache size as needed to increase cache hits:
-
-    ```
-    $ ccache --max-size=20G
-    ```
-
 1.  Download and install [Android Studio](https://developer.android.com/studio/).
+
 1.  Run `cobalt/build/gn.py -p android-x86` to configure the Cobalt build,
     which also installs the SDK and NDK. (This step will have to be repeated
     with 'android-arm' or 'android-arm64' to target those architectures.) The
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index 498b5f3..b17e8bd 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -22,7 +22,7 @@
         pkgconf ninja-build bison yasm binutils clang libgles2-mesa-dev \
         mesa-common-dev libpulse-dev libavresample-dev libasound2-dev \
         libxrender-dev libxcomposite-dev libxml2-dev curl git \
-        python3.8-venv python2
+        python3.8-venv
     ```
 
 1.  Install Node.js via `nvm`:
@@ -52,14 +52,6 @@
     $ ccache --max-size=20G
     ```
 
-1.  Install necessary python2 packages for GYP. Until Cobalt 23, when we have
-    migrated our build system to GN, we still require some python2 packages:
-
-    ```
-    $ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py | python2
-    $ python2 -m pip install --user requests selenium six
-    ```
-
 1.  Clone the Cobalt code repository. The following `git` command creates a
     `cobalt` directory that contains the repository:
 
diff --git a/cobalt/site/docs/development/setup-raspi.md b/cobalt/site/docs/development/setup-raspi.md
index 597361b..60a2099 100644
--- a/cobalt/site/docs/development/setup-raspi.md
+++ b/cobalt/site/docs/development/setup-raspi.md
@@ -39,7 +39,10 @@
 ## Set up your workstation
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for
+<a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the
+sections <b>Set up your workstation</b> and <b>Set up developer tools</b>, then
+return and complete the following steps.
 </aside>
 
 The following steps install the cross-compiling toolchain on your workstation.
diff --git a/cobalt/site/docs/gen/cobalt/doc/memory_tuning.md b/cobalt/site/docs/gen/cobalt/doc/memory_tuning.md
index 44a94b6..1c09787 100644
--- a/cobalt/site/docs/gen/cobalt/doc/memory_tuning.md
+++ b/cobalt/site/docs/gen/cobalt/doc/memory_tuning.md
@@ -42,7 +42,7 @@
 sub-system, the type, and where it came from.
 
 **SETTING NAME:** This is the name of the memory setting. If a setting can be
-manually set through the command line or the build system, then it will be
+manually set through the command line or Cobalt extension, then it will be
 accessible by using this name. For example adding the command line argument
 `--image_cache_size_in_bytes=25165824` will manually set the Image Cache Size to
 24 megabytes. Also note that this is also equivalent:
@@ -68,9 +68,10 @@
     * `Starboard API`
       * The value used was reported by the result of a Starboard API function call.
       * Example: `SbSystemGetUsedCPUMemory()`
-    * `Build`
-      * Specified by the platform specific `*.gyp(i)` build file.
-      * For example: see `image_cache_size_in_bytes` in [`build/config/base.gypi`](../build/config/base.gypi)
+    * `Configuration Extension`
+      * Specified by the `CobaltExtensionConfigurationApi`.
+      * For example: see `CobaltImageCacheSizeInBytes` in
+        [`cobalt/configuration/configuration.cc`](../configuration/configuration.cc).
     * `CmdLine`
       * Read the memory setting value from the command line.
       * For example: `cobalt --image_cache_size_in_bytes=24MB`.
@@ -114,9 +115,6 @@
  * `CmdLine`
    * `max_cobalt_cpu_usage`: --max_cobalt_cpu_usage was used as a command argument.
    * `max_cobalt_gpu_usage`: --max_cobalt_gpu_usage was used as a command argument.
- * `Build`
-   * `max_cobalt_cpu_usage`: max_cobalt_cpu_usage was specified in a platform gyp file.
-   * `max_cobalt_gpu_usage`: max_cobalt_gpu_usage was specified in a platform gyp file.
 
 **TOTAL**: Represents the maximum available memory for settings. This value
 came from **SOURCE**.
@@ -136,20 +134,10 @@
 
 ## Setting Maximum Memory Values ##
 
-The max cpu and gpu memory of the system can be set either by command line or
-by modifying the gyp build file.
-
-Command Line:
+The max cpu and gpu memory of the system can be set by command line:
   * `--max_cobalt_cpu_usage=160MB`
   * `--max_cobalt_gpu_usage=160MB`
 
-Build settings:
-  * `starboard/<PLATFORM>/gyp_configuration.gypi`
-    * `max_cobalt_cpu_usage`
-    * `max_cobalt_gpu_usage`
-
-Command Line settings will override build settings.
-
 ### Memory Scaling ###
 
 There are two primary ways in which the memory consumption settings will scale down.
@@ -181,20 +169,22 @@
 Let's say that we are configuring platform called "XXX":
 
 We will configure XXX such that:
-  * `image_cache_size_in_bytes` will be set to 32MB in the build settings.
-  * `skia_atlas_texture_dimensions` will be set to `2048x2048x2` in the build settings.
+  * `image_cache_size_in_bytes` will be set to 32MB in the configuration
+     extension.
+  * `skia_atlas_texture_dimensions` will be set to `2048x2048x2` in the
+    configuration extension.
   * `max_cobalt_cpu_usage` will be set to 160MB on the command line.
 
 **Configuring `image_cache_size_in_bytes` to be 32MB:**
-  * in `starboard\<PLATFORM>\gyp_configuration.gypi`
-    * add `'image_cache_size_in_bytes': 32 * 1024 * 1024,`
+
+  * Implement the `CobaltExtensionConfigurationApi` function
+    `CobaltImageCacheSizeInBytes` to return `32 * 1024 * 1024`.
 
 **Configuring `skia_atlas_texture_dimensions` to be 2048x2048x2:**
 
-  * in `src\starboard\XXX\gyp_configuration.gypi`
-    * add `'skia_glyph_atlas_width': '2048'`
-    * add `'skia_glyph_atlas_height': '2048'`
-    * (note that the third dimension is assumed)
+  * Implement the `CobaltExtensionConfigurationApi` functions
+    `CobaltSkiaGlyphAtlasWidth` and `CobaltSkiaGlyphAtlasHeight` to both return
+    `2048`.
 
 **Configuring `max_cobalt_cpu_usage` to be 160MB:**
 
@@ -219,17 +209,17 @@
       CPU Memory settings will scale down their consumption in order to stay under
       the `max_cobalt_cpu_usage`. If memory consumption exceeds this value during
       runtime then a memory warning will be printed to stdout.
-    * Set via command line or else build system or else starboard.
+    * Set via command line or else starboard.
       * starboard value will bind to `SbSystemGetTotalCPUMemory()`.
   * `max_cobalt_gpu_usage`
     * This setting will set the maximum gpu memory that the app will consume.
       GPU Memory settings will scale down their consumption in order to stay under
       the `max_cobalt_gpu_usage`. If memory consumption exceeds this value during
       runtime then a memory warning will be printed to stdout.
-    * Set via command line or else build system or else starboard.
+    * Set via command line or else starboard.
       * starboard value will bind to `SbSystemGetTotalGPUMemory()`.
     * Note that `SbSystemGetTotalGPUMemory()` is optional. If no value exists
-      for `max_cobalt_gpu_usage` in build/commandline/starboard settings then no
+      for `max_cobalt_gpu_usage` in commandline/starboard settings then no
       GPU memory checking is performed.
 
 #### Memory Setting API ####
@@ -237,11 +227,11 @@
   * `image_cache_size_in_bytes`
     * See documentation *Image cache capacity* in `performance_tuning.md` for what
       this setting does.
-    * Set via command line, or else build system, or else automatically by Cobalt.
+    * Set via command line, or else Cobalt extension, or else automatically by Cobalt.
   * `remote_typeface_cache_size_in_bytes`
     * Determines the capacity of the remote typefaces cache which manages all typefaces
       downloaded from a web page.
-    * Set via command line, or else build system, or else automatically by Cobalt.
+    * Set via command line, or else Cobalt extension, or else automatically by Cobalt.
   * `skia_atlas_texture_dimensions`
     * Determines the size in pixels of the glyph atlas where rendered glyphs are
       cached. The resulting memory usage is 2 bytes of GPU memory per pixel.
@@ -251,18 +241,20 @@
       larger, such as for higher resolution displays.
       The negative default values indicates to the Cobalt that these settings
       should be automatically set.
-    * Set via command line, or else build system, or else automatically by Cobalt.
-    * Note that in the gyp build system, this setting is represented as two values:
-      * `skia_glyph_atlas_width` and
-      * `skia_glyph_atlas_height`
+    * Set via command line, or else through the Cobalt configuration extension,
+      or else automatically by Cobalt.
+    * Note that in the Cobalt configuration extension, this setting is
+      represented as two functions:
+      * `CobaltSkiaGlyphAtlasWidth` and
+      * `CobaltSkiaGlyphAtlasHeight`
   * `skia_cache_size_in_bytes`
     * See documentation *Glyph atlas size* in `performance_tuning.md` for what this
       setting does.
-    * Set via command line, or else build system or else automatically by Cobalt.
+    * Set via command line, or else Cobalt extension system or else automatically by Cobalt.
   * `software_surface_cache_size_in_bytes`
     * See documentation *Scratch Surface cache capacity* in `performance_tuning.md`
       for what this setting does.
-    * Set via command line, or else build system, or else automatically by Cobalt.
+    * Set via command line, or else Cobalt extension, or else automatically by Cobalt.
 
 #### Units for Command Line Settings ####
 
diff --git a/cobalt/site/docs/gen/cobalt/doc/performance_tuning.md b/cobalt/site/docs/gen/cobalt/doc/performance_tuning.md
index bf3ea5d..90936c3 100644
--- a/cobalt/site/docs/gen/cobalt/doc/performance_tuning.md
+++ b/cobalt/site/docs/gen/cobalt/doc/performance_tuning.md
@@ -14,11 +14,8 @@
 has a set of tags keywords to make it easy to search for items related
 to a specific type of performance metric (e.g. "framerate").
 
-Many of the tweaks involve adding a new gyp variable to your platform's
-`gyp_configuration.gypi` file.  The default values for these variables are
-defined in either
-[`base_configuration.gypi`](../../starboard/build/base_configuration.gypi) or
-[`cobalt_configuration.gypi`](../build/cobalt_configuration.gypi).
+Many of the tweaks involve implementing a Cobalt extension and returning
+different values from specific functions.
 
 ### Use a Release Build
 
@@ -43,12 +40,12 @@
 as it can, limited only by the display's refresh rate, which is usually 60Hz.
 By artificially throttling this rate to a lower value, like 30Hz, CPU
 resources can be freed to work on other tasks.  You can enable framerate
-throttling by setting a value for `cobalt_minimum_frame_time_in_milliseconds`
-in your platform's `gyp_configuration.gypi` file.  Setting it to 33, for
-example, will throttle Cobalt's renderer to 30 frames per second.
+throttling by setting a return value for
+`GetMinimumFrameIntervalInMilliseconds` in your platform's Cobalt graphics
+extension.  Setting it to 33, for example, will throttle Cobalt's renderer to
+30 frames per second (as there'll be one frame every 33 milliseconds).
 
-**Tags:** *gyp_configuration.gypi, framerate, startup, browse-to-watch,
-           input latency.*
+**Tags:** *Cobalt extension, framerate, startup, browse-to-watch, input latency.*
 
 
 ### Image cache capacity
@@ -61,12 +58,12 @@
 make more network requests and image decodes for previously seen images.
 Cobalt will automatically set the image cache capacity to a reasonable value,
 but if you wish to override this, you can do so by setting the
-`image_cache_size_in_bytes` variable in your `gyp_configuration.gypi` file.  For
-the YouTube web app, we have found that at 1080p, 32MB will allow around
+`CobaltImageCacheSizeInBytes` function in your Cobalt configuration extension.
+For the YouTube web app, we have found that at 1080p, 32MB will allow around
 5 thumbnail shelves to stay resident at a time, with 720p and 4K resolutions
 using proportionally less and more memory, respectively.
 
-**Tags:** *gyp_configuration.gypi, cpu memory, gpu memory.*
+**Tags:** *Cobalt extension, cpu memory, gpu memory.*
 
 
 ### Image cache capacity multiplier during video playback
@@ -79,32 +76,11 @@
 more likely that Cobalt will have to re-download and decode images after
 returning from video playback.  Note that this feature is not well tested.
 The feature can be activated by setting
-`image_cache_capacity_multiplier_when_playing_video` to a value between
-`0.0` and `1.0` in your `gyp_configuration.gypi` file.  The image cache
+`CobaltImageCacheCapacityMultiplierWhenPlayingVideo` to a value between
+`0.0` and `1.0` in your Cobalt configuration extension.  The image cache
 capacity will be multiplied by this value during video playback.
 
-**Tags:** *gyp_configuration.gypi, gpu memory.*
-
-
-### Scratch Surface cache capacity
-
-This only affects GLES renderers.  While rasterizing a frame, it is
-occasionally necessary to render to a temporary offscreen surface and then
-apply that surface to the original render target.  Offscreen surface
-rendering may also need to be performed multiple times per frame.  The
-scratch surface cache will keep allocated a set of scratch textures that
-will be reused (within and across frames) for offscreen rendering.  Reusing
-offscreen surfaces allows render target allocations, which can be expensive
-on some platforms, to be minimized.  However, it has been found that some
-platforms (especially those with tiled renderers, like the Raspberry Pi's
-Broadcom VideoCore), reading and writing again and again to the same texture
-can result in performance degradation.  Memory may also be potentially saved
-by disabling this cache, since when it is enabled, if the cache is filled, it
-may be occupying memory that it is not currently using.  This setting can
-be adjusted by setting `surface_cache_size_in_bytes` in your
-`gyp_configuration.gypi` file.  A value of `0` will disable the surface cache.
-
-**Tags:** *gyp_configuration.gypi, gpu memory, framerate.*
+**Tags:** *Cobalt extension, gpu memory.*
 
 
 ### Glyph atlas size
@@ -118,12 +94,12 @@
 modifications to this setting, be sure to test many languages, as some
 are more demanding (e.g. Chinese and Japanese) on the glyph cache than
 others.  This value can be adjusted by changing the values of
-the `skia_glyph_atlas_width` and `skia_glyph_atlas_height` variables in your
-`gyp_configuration.gypi` file.  Note that by default, these will be
+the `CobaltSkiaGlyphAtlasWidth` and `CobaltSkiaGlyphAtlasHeight` variables in
+your Cobalt configuration extension.  Note that by default, these will be
 automatically configured by Cobalt to values found to be optimal for
 the application's resolution.
 
-**Tags:** *gyp_configuration.gypi, gpu memory, input latency, framerate.*
+**Tags:** *Cobalt extension, gpu memory, input latency, framerate.*
 
 
 ### Software surface cache capacity
@@ -135,9 +111,10 @@
 renders, the results are cached and re-used across frames.  The software
 surface cache is crucial to achieving an acceptable framerate on Blitter API
 platforms.  The size of this cache is specified by the
-`software_surface_cache_size_in_bytes` variable in `gyp_configuration.gypi`.
+`CobaltSoftwareSurfaceCacheSizeInBytes` variable in your Cobalt configuration
+extension.
 
-**Tags:** *gyp_configuration.gypi, gpu memory, framerate.*
+**Tags:** *Cobalt extension, gpu memory, framerate.*
 
 
 ### Toggle Just-In-Time JavaScript Compilation
@@ -152,7 +129,7 @@
 it by implementing the CobaltExtensionConfigurationApi method
 `CobaltEnableJit()` to return `true` to enable JIT, or `false` to disable it.
 
-**Tags:** *gyp_configuration.gypi, startup, browse-to-watch, input latency,
+**Tags:** *Cobalt extension, startup, browse-to-watch, input latency,
            cpu memory.*
 
 
@@ -198,7 +175,7 @@
 on a Blitter API platform, enabling this functionality will result in the
 allocation and blit of a fullscreen "intermediate" back buffer target.
 
-**Tags:** *startup, framerate, gpu memory.*
+**Tags:** *Cobalt extension, startup, framerate, gpu memory.*
 
 
 ### Ensure that thread priorities are respected
@@ -221,27 +198,34 @@
 
 Huge performance improvements can be obtained by ensuring that the right
 optimizations are enabled by your compiler and linker flag settings.  You
-can set these up within `gyp_configuration.gypi` by adjusting the list
-variables `compiler_flags` and `linker_flags`.  See also
-`compiler_flags_gold` and `linker_flags_gold` which describe flags that
-apply only to gold builds where performance is critical.  Note that
-unless you explicitly set this up, it is unlikely that compiler/linker
-flags will carry over from external shell environment settings; they
-must be set explicitly in `gyp_configuration.gypi`.
+can set these up within `platform_configuration/BUILD.gn` by adjusting the
+`cflags`, `ldflags`, and more. Keep in mind you can use `is_gold` and other
+such boolean values to only apply optimizations where performance is critical.
+Note that unless you explicitly set this up, it is unlikely that
+compiler/linker flags will carry over from external shell environment settings;
+they must be set explicitly in `platform_configuration/BUILD.gn`.
 
 **Tags:** *framerate, startup, browse-to-watch, input latency*
 
 #### Optimize for size vs speed
 
-For qa and gold configs, different compiler flags can be used for gyp targets
+For qa and gold configs, different compiler flags can be used for GN targets
 which should be optimized for size vs speed. This can be used to reduce the
-executable size with minimal impact on performance. On top of the base
-`compiler_flags_qa` and `compiler_flags_gold`, the gyp variables
-`compiler_flags_qa_size`, `compiler_flags_qa_speed`, `compiler_flags_gold_size`,
-and `compiler_flags_gold_speed` will be used. Performance-critical gyp targets
-specify `optimize_target_for_speed`: 1, and these will use compiler flags
-`compiler_flags_<config>` + `compiler_flags_<config>_speed`; other gyp targets
-will use `compiler_flags_<config>` + `compiler_flags_<config>_size`.
+executable size with minimal impact on performance.
+
+To set these flags, implement size and/or speed configs (preferably in
+`platform_configuration/BUILD.gn`), and point to them using the
+`size_config_path` and `speed_config_path` variables, set in
+`platform_configuration/configuration.gni`.
+
+Performance-critical targets remove the `size` config and add the `speed` config like so:
+
+```
+configs -= [ "//starboard/build/config:size" ]
+configs += [ "//starboard/build/config:speed" ]
+```
+
+The size config is applied by default.
 
 **Tags:** *cpu memory, package size*
 
@@ -282,11 +266,11 @@
 memory allocations.  It should be carefully evaluated however whether
 dlmalloc performs better or worse than your system allocator, in terms
 of both memory fragmentation efficiency as well as runtime performance.
-To use dlmalloc, you should adjust your starboard_platform.gyp file to
+To use dlmalloc, you should adjust your `starboard_platform` target to
 use the Starboard [`starboard/memory.h`](../../starboard/memory.h) function
 implementations defined in
 [`starboard/shared/dlmalloc/`](../../starboard/shared/dlmalloc).  To use
-your system allocator, you should adjust your starboard_platform.gyp file
+your system allocator, you should adjust your `starboard_platform` target
 to use the Starboard [`starboard/memory.h`](../../starboard/memory.h) function
 implementations defined in
 [`starboard/shared/iso/`](../../starboard/shared/iso).
@@ -303,11 +287,11 @@
 memory.  However, depending on your platform and your system allocator,
 overall memory usage may improve if media buffer allocations were made
 normally via the system allocator instead.  This can be achieved by setting
-`cobalt_media_buffer_initial_capacity` and `cobalt_media_buffer_allocation_unit`
-to 0 in gyp_configuration.gypi.  Note also that if you choose to pre-allocate
-memory, for 1080p video it has been found that 24MB is a good media buffer size.
-The pre-allocated media buffer capacity size can be adjusted by modifying the
-value of `cobalt_media_buffer_initial_capacity` mentioned above.
+`SbMediaGetInitialBufferCapacity` and `SbMediaGetBufferAllocationUnit`
+to 0 in your implementation of media.h.  Note also that if you choose to
+pre-allocate memory, for 1080p video it has been found that 24MB is a good
+media buffer size. The pre-allocated media buffer capacity size can be adjusted
+by modifying the value of `SbMediaGetInitialBufferCapacity` mentioned above.
 
 **Tags:** *configuration_public.h, cpu memory.*
 
@@ -315,12 +299,9 @@
 ### Adjust media buffer size settings
 
 Many of the parameters around media buffer allocation can be adjusted in your
-gyp_configuration.gypi file.  The variables in question are the family of
-`cobalt_media_*` variables, whose default values are specified in
-[`cobalt_configuration.gypi`](../build/cobalt_configuration.gypi).  In
-particular, if your maximum video output resolution is less than 1080, then you
-may lower the budgets for many of the categories according to your maximum
-resolution.
+media.h Starboard implementation. In particular, if your maximum video output
+resolution is less than 1080, then you may lower the budgets for many of the
+categories according to your maximum resolution.
 
 **Tags:** *cpu memory*
 
diff --git a/cobalt/site/docs/gen/cobalt/doc/voice_search.md b/cobalt/site/docs/gen/cobalt/doc/voice_search.md
index f5d54a2..717e3cc 100644
--- a/cobalt/site/docs/gen/cobalt/doc/voice_search.md
+++ b/cobalt/site/docs/gen/cobalt/doc/voice_search.md
@@ -6,19 +6,20 @@
 
 Cobalt enables voice search through either:
 
-1. A subset of the [MediaRecorder Web API](https://www.w3.org/TR/mediastream-recording/#mediarecorder-api).
+1. A subset of the [MediaRecorder Web API](https://www.w3.org/TR/mediastream-recording/#mediarecorder-api)
 2. A subset of the [Speech Recognition Web API](https://w3c.github.io/speech-api/#speechreco-section)
 
 Only one or the other can be used, and we recommend that the MediaRecorder API
-is followed, as we are considering deprecating the Speech Recognition API.
-
-**The Speech Recognition API is deprecated as of Starboard 13.**
+is followed, as the Speech Recognition API is deprecated as of Starboard 13.
 
 In both approaches, in order to check whether to enable voice control or not,
 web apps will call the [MediaDevices.enumerateDevices()](https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-enumeratedevices%28%29)
 Web API function within which Cobalt will in turn call a subset of the
 [Starboard SbMicrophone API](../../starboard/microphone.h).
 
+Partners can add microphone support and microphone gesture options using the
+optional SoftMicPlatformService, detailed below.
+
 ## MediaRecorder API
 
 To enable the MediaRecorder API in Cobalt, the complete
@@ -67,3 +68,49 @@
 
 Web applications are expected to use the MediaRecorder API. This in turn relies
 on the SbMicrophone API as detailed above.
+
+## SoftMicPlatformService
+
+In `starboard/linux/shared/soft_mic_platform_service.cc` there is an example
+stub implementation of the SoftMicPlatformService. Platforms can optionally
+implement this [CobaltPlatformService](https://cobalt.dev/gen/cobalt/doc/\
+platform_services.html) to specify if they support the `soft mic` and/or `hard mic`
+for voice search. The `soft mic` refers to the software activation of the microphone
+for voice search through the UI microphone button on the Youtube Web Application
+search page. The `hard mic` refers to hardware button activation of the microphone
+for voice search. Platforms can also specify the optional `micGesture`. This
+specifies the type of UI prompt the YouTube Web Application should display to guide
+the user to start voice search. The options include an empty or `null` value for no
+prompt, `"TAP"` for tap the `soft mic` and/or `hard mic` to start voice search, or
+`"HOLD"` for hold the `soft mic` and/or the `hard mic` to start voice search.
+
+The Web Application messages to the platform will be singular strings, encoded with
+enclosing quotation marks to make them JSON compliant:
+
+```
+"\"notifySearchActive\""
+
+
+"\"notifySearchInactive\""
+```
+
+These messages notify the platform when the user is entering or exiting the Youtube
+Web Application search page. Only a synchronous `true` or `false` response is sent
+from the platform to confirm that the message was correctly received and parsed.
+
+```
+"\"getMicSupport\""
+```
+
+A similar synchronous `true` or `false` response is sent from the platform confirming
+the message was correctly received and parsed. The platform will also send an
+asynchronous string encoded JSON object with the above mentioned microphone
+preferences:
+
+```
+"{
+    'hasHardMicSupport' : boolean,
+    'hasSoftMicSupport' : boolean,
+    'micGesture' : string,
+ }"
+```
diff --git a/cobalt/site/docs/gen/starboard/build/doc/migrating_gyp_to_gn.md b/cobalt/site/docs/gen/starboard/build/doc/migrating_gyp_to_gn.md
index 3a9c87e..5645ced 100644
--- a/cobalt/site/docs/gen/starboard/build/doc/migrating_gyp_to_gn.md
+++ b/cobalt/site/docs/gen/starboard/build/doc/migrating_gyp_to_gn.md
@@ -16,6 +16,12 @@
 There are a few ways to get a binary. Follow the instructions for whichever way
 you prefer [here][gn_getting_a_binary].
 
+### Setup your workstation and developer tools
+
+Some environment settings and tools have changed with the GN migration. Please
+go through the [setup guide][dev_setup_linux] to make sure your development
+environment is properly configured.
+
 ### Read the Docs
 
 Most of the documentation for GN is located [here][gn_doc_home].
@@ -182,6 +188,14 @@
 those as examples for how to use it correctly. Here's the linux-x64x11
 [toolchain/BUILD.gn file](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/starboard/linux/x64x11/toolchain/BUILD.gn).
 
+When migrating a host toolchain, you'll need to set the `toolchain_args` within
+it. This should always set the `current_os` and `current_cpu` if the `host_os`
+and `host_cpu` differ from the `target_os` and `target_cpu`, respectively. V8
+requires that 32 bit targets be compiled with 32 bit-compatible hosts (see
+[here](https://cobalt.googlesource.com/cobalt/+/refs/heads/master/third_party/v8/src/base/build_config.h)),
+so you should make sure the `current_cpu` set in the `"host"` toolchain is
+compatible with your `target_cpu`.
+
 ## Checking Your Migration
 
 There are a few different ways to ensure you've migrated a target successfully.
@@ -254,6 +268,7 @@
 able to be built for your platform.
 
 [cobalt_porting_guide]: https://cobalt.dev/starboard/porting.html
+[dev_setup_linux]: https://cobalt.dev/development/setup-linux.html
 [gn_check_tool]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_check
 [gn_doc_home]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs
 [gn_format_tool]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_format
diff --git a/cobalt/site/docs/gen/starboard/build/doc/migration_changes.md b/cobalt/site/docs/gen/starboard/build/doc/migration_changes.md
index d00e3d0..83f3dfa 100644
--- a/cobalt/site/docs/gen/starboard/build/doc/migration_changes.md
+++ b/cobalt/site/docs/gen/starboard/build/doc/migration_changes.md
@@ -25,7 +25,6 @@
 `sb_evergreen_compatible` (0/1)           | `sb_is_evergreen_compatible` (true/false)            | `//starboard/build/config/base_configuration.gni`
 `sb_evergreen_compatible_libunwind` (0/1) | `sb_evergreen_compatible_use_libunwind` (true/false) | `//starboard/build/config/base_configuration.gni`
 `sb_evergreen_compatible_lite` (0/1)      | `sb_evergreen_compatible_enable_lite` (true/false)   | `//starboard/build/config/base_configuration.gni`
-`in_app_dial` (0/1)                       | `enable_in_app_dial` (true/false)                    | `//starboard/build/config/base_configuration.gni`
 `sb_disable_cpp14_audit`                  | (none)                                               |
 `sb_disable_microphone_idl`               | (none)                                               |
 `starboard_path`                          | (none)                                               |
diff --git a/cobalt/site/docs/gen/starboard/doc/c99.md b/cobalt/site/docs/gen/starboard/doc/c99.md
index 433e1d5..0c32d93 100644
--- a/cobalt/site/docs/gen/starboard/doc/c99.md
+++ b/cobalt/site/docs/gen/starboard/doc/c99.md
@@ -19,7 +19,7 @@
 [starboard/client_porting](../../starboard/client_porting) directory many
 non-Evergreen platforms still have a lot of direct system dependencies. These
 dependencies do not exist for Evergreen platforms as Cobalt is statically
-linked with the [musl](../../third_party/musl/musl.gyp) libc library.
+linked with the [musl](../../third_party/musl/BUILD.gn) libc library.
 
 3. Starting with Starboard 13 a limited set of C99 symbols will be allowed.
 This set will expand gradually while the corresponding Starboard APIs will be
diff --git a/cobalt/site/docs/gen/starboard/doc/evergreen/cobalt_evergreen_overview.md b/cobalt/site/docs/gen/starboard/doc/evergreen/cobalt_evergreen_overview.md
index 0106cd5..37024ea 100644
--- a/cobalt/site/docs/gen/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/cobalt/site/docs/gen/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -261,9 +261,9 @@
 `nplb_evergreen_compat_tests`. These tests ensure that the platform is
 configured appropriately for Evergreen.
 
-To enable the test, set the `sb_evergreen_compatible gyp` variable to 1 in the
-`gyp_configuration.gypi`. For more details please take a look at the Raspberry
-Pi 2 gyp files.
+To enable the test, set the `sb_is_evergreen_compatible` GN variable to `true`
+in the platform's `configuration.gni`. For more details please take a look at
+the Raspberry Pi 2 GN files.
 
 There is a reference implementation available for Raspberry Pi 2 with
 instructions available [here](cobalt_evergreen_reference_port_raspi2.md).
diff --git a/cobalt/site/docs/gen/starboard/doc/howto_decode_to_texture.md b/cobalt/site/docs/gen/starboard/doc/howto_decode_to_texture.md
index 8f22450..b15eb5e 100644
--- a/cobalt/site/docs/gen/starboard/doc/howto_decode_to_texture.md
+++ b/cobalt/site/docs/gen/starboard/doc/howto_decode_to_texture.md
@@ -26,7 +26,7 @@
 From [`starboard/player.h`](../player.h),
 
 * `SbPlayerCreate()`
-* `SbPlayerOutputModeSupported()`
+* `SbPlayerGetPreferredOutputMode()`
 * `SbPlayerGetCurrentFrame()`
 
 From [`starboard/decode_target.h`](../decode_target.h),
@@ -47,17 +47,20 @@
 ![Decode-to-texture sequence diagram](resources/decode_to_texture_sequence.png)
 
 1. An application with the desire to make use of decode-to-texture will first
-   call `SbPlayerOutputModeSupported()`, passing in
-   `kSbPlayerOutputModeDecodeToTexture` for its `output_mode` parameter.  If
-   the function returns false, the application learns that decode-to-texture
-   is not supported by the platform and it will not continue with a
-   decode-to-texture flow.
+   call `SbPlayerGetPreferredOutputMode()`, passing in
+   `kSbPlayerOutputModeDecodeToTexture` for its `creation_param->output_mode`
+   parameter.  If the function doesn't return
+   `kSbPlayerOutputModeDecodeToTexture`, the application learns that
+   decode-to-texture is not supported by the platform and it will not continue
+   with a decode-to-texture flow.
 
-2. If `SbPlayerOutputModeSupported()` returns true, the application will call
+2. If `SbPlayerGetPreferredOutputMode()` returns
+   `kSbPlayerOutputModeDecodeToTexture`, the application will call
    `SbPlayerCreate()`, passing in `kSbPlayerOutputModeDecodeToTexture` for
-   the `output_mode` parameter, and also providing a valid `provider`
-   parameter (more on this later).  At this point, the Starboard platform is
-   expected to have created a player with the decode-to-texture output mode.
+   the `creation_param->output_mode` parameter, and also providing a valid
+   `provider` parameter (more on this later).  At this point, the Starboard
+   platform is expected to have created a player with the decode-to-texture
+    output mode.
 
 3. Once the player is started and playback has begun, the application's
    renderer thread (this may be a different thread than the one that called
diff --git a/cobalt/site/docs/gen/starboard/doc/starboard_abi.md b/cobalt/site/docs/gen/starboard/doc/starboard_abi.md
index 619703c..9272001 100644
--- a/cobalt/site/docs/gen/starboard/doc/starboard_abi.md
+++ b/cobalt/site/docs/gen/starboard/doc/starboard_abi.md
@@ -43,21 +43,20 @@
 [//starboard/sabi](../sabi)
 for examples). Starboard ABI files are JSON, and should all contain identical
 keys with the values being appropriate for the architecture. Each platform must
-override the new
-[GetPathToSabiJsonFile](../build/platform_configuration.py##339)
-method in its platform configuration to return the path to the desired Starboard
-ABI file (e.g.
-[//starboard/linux/shared/gyp\_configuration.py](../linux/shared/gyp_configuration.py)).
+set the `sabi_path` GN variables in its
+`platform_configuration/configuration.gni` file to return the path to the
+desired Starboard ABI file (e.g.
+[//starboard/linux/x64x11/shared/platform_configuration/configuration.gni](../linux/x64x11/shared/platform_configuration/configuration.gni)).
 By default, an empty and invalid Starboard ABI file is provided.
 
-Additionally, all platforms must include the
-[sabi.gypi](../sabi/sabi.gypi)
-in their build configuration. This file will consume the specified Starboard ABI
-file, resulting in the creation of a set of GYP variables and preprocessor
+Additionally, all platforms must include the `//starboard/build/config/sabi`
+config from [sabi/BUILD.gn](../build/config/sabi/BUILD.gn)
+in their platform configs. This file will consume the specified Starboard ABI
+file, resulting in the creation of a set of GN variables and preprocessor
 macros. The preprocessor macros are passed directly to the compiler and replace
 the macros you might be familiar with, such as `SB_HAS_32_BIT_LONG`.
 
-The newly defined GYP variables need to be transformed into toolchain specific
+The newly defined GN variables need to be transformed into toolchain specific
 flags; these flags are what actually makes the build result in a binary for the
 desired architecture. These flags will, in most cases, be identical to the flags
 already being used for building.
@@ -68,7 +67,7 @@
 
 ### Post-Starboard ABI File Cleanup
 
-A number of GYP variables and preprocessor macros should no longer be defined
+A number of GN variables and preprocessor macros should no longer be defined
 directly, and instead the Starboard ABI file will be used to define them. These
 definitions need to be removed.
 
@@ -83,10 +82,6 @@
 *   `SB_HAS_32_BIT_POINTERS`
 *   `SB_HAS_64_BIT_POINTERS`
 
-From `gyp_configuration.gypi`:
-
-*   `target_arch`
-
 ## FAQ
 
 ### What Starboard ABI files are provided?
diff --git a/cobalt/site/docs/gen/starboard/tools/doc/testing.md b/cobalt/site/docs/gen/starboard/tools/doc/testing.md
index fa54425..12d372b 100644
--- a/cobalt/site/docs/gen/starboard/tools/doc/testing.md
+++ b/cobalt/site/docs/gen/starboard/tools/doc/testing.md
@@ -65,9 +65,9 @@
 ## Filtering Tests
 
 To filter out tests that you do not want to run for a specific platform,
-implement a method within the platform's `PlatformConfiguration` subclass called
-`GetTestFilters()`. The `PlatformConfiguration` subclass lives in the
-`gyp_configuration.py` file for each platform. If the tests are
+implement a method within the platform's `TestFilters` subclass called
+`GetTestFilters()`. The `TestFilters` subclass lives in the
+`test_filters.py` file for each platform. If the tests are
 application-specific, you may define `GetTestFilters()` on an optional
 `ApplicationConfiguration` subclass, which will be found in the
 `<platform-directory>/<application-name>/configuration.py` subdirectory. See
diff --git a/cobalt/site/docs/reference/starboard/configuration-public.md b/cobalt/site/docs/reference/starboard/configuration-public.md
index 4f281ca..94dab4e 100644
--- a/cobalt/site/docs/reference/starboard/configuration-public.md
+++ b/cobalt/site/docs/reference/starboard/configuration-public.md
@@ -50,7 +50,6 @@
 
 | Properties |
 | :--- |
-| **`SB_HAS_BLITTER`**<br><br>Specifies whether this platform supports a performant accelerated blitter API. The basic requirement is a scaled, clipped, alpha-blended blit.<br><br>The default value in the Stub implementation is `0` |
 | **`SB_HAS_BILINEAR_FILTERING_SUPPORT`**<br><br>Indicates whether or not the given platform supports bilinear filtering. This can be checked to enable/disable renderer tests that verify that this is working properly.<br><br>The default value in the Stub implementation is `1` |
 | **`SB_HAS_NV12_TEXTURE_SUPPORT`**<br><br>Indicates whether or not the given platform supports rendering of NV12 textures. These textures typically originate from video decoders.<br><br>The default value in the Stub implementation is `0` |
 | **`SB_HAS_VIRTUAL_REALITY`**<br><br>The default value in the Stub implementation is `1` |
@@ -66,17 +65,17 @@
 
 ## Media Configuration
 
+ After a seek is triggered, the default behavior is to append video frames from the last key frame before the seek time and append audio frames from the seek time because usually all audio frames are key frames.  On platforms that cannot decode video frames without displaying them, this will cause the video being played without audio for several seconds after seeking.  When the following macro is defined, the app will append audio frames start from the timestamp that is before the timestamp of the video key frame being appended.
+
 | Properties |
 | :--- |
-| **`SB_HAS_QUIRK_SEEK_TO_KEYFRAME`**<br><br>After a seek is triggered, the default behavior is to append video frames from the last key frame before the seek time and append audio frames from the seek time because usually all audio frames are key frames.  On platforms that cannot decode video frames without displaying them, this will cause the video being played without audio for several seconds after seeking.  When the following macro is defined, the app will append audio frames start from the timestamp that is before the timestamp of the video key frame being appended.  This quirk has been deprecated in Starboard version 12 or later.  Please see `configuration_public.md` for more details.<br><br>By default, this property is undefined. |
-| **`SB_HAS_QUIRK_SUPPORT_INT16_AUDIO_SAMPLES`**<br><br>The implementation is allowed to support kSbMediaAudioSampleTypeInt16 only when this macro is defined.<br><br>By default, this property is undefined. |
+| **`SB_HAS_QUIRK_SUPPORT_INT16_AUDIO_SAMPLES`**<br><br> The implementation is allowed to support kSbMediaAudioSampleTypeInt16 only when this macro is defined.<br><br>By default, this property is undefined. |
 
 
 ## Memory Configuration
 
 | Properties |
 | :--- |
-| **`SB_HAS_MMAP`**<br><br>Whether this platform has and should use an MMAP function to map physical memory to the virtual address space.<br><br>The default value in the Stub implementation is `1` |
 | **`SB_CAN_MAP_EXECUTABLE_MEMORY`**<br><br>Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is required for platforms that want to JIT.<br><br>The default value in the Stub implementation is `1` |
 
 
@@ -108,5 +107,3 @@
 | Properties |
 | :--- |
 | **`SB_HAS_TIME_THREAD_NOW`**<br><br>Whether this platform has an API to retrieve how long the current thread has spent in the executing state.<br><br>The default value in the Stub implementation is `1` |
-
-
diff --git a/cobalt/site/docs/reference/starboard/gn-configuration.md b/cobalt/site/docs/reference/starboard/gn-configuration.md
new file mode 100644
index 0000000..cd877fa
--- /dev/null
+++ b/cobalt/site/docs/reference/starboard/gn-configuration.md
@@ -0,0 +1,48 @@
+---
+layout: doc
+title: "Starboard: configuration.gni Reference Guide"
+---
+
+| Variables |
+| :--- |
+| **`abort_on_allocation_failure`**<br><br> Halt execution on failure to allocate memory.<br><br>The default value is `true`. |
+| **`asan_symbolizer_path`**<br><br> A symbolizer path for ASAN can be added to allow translation of callstacks.<br><br>The default value is `"$clang_base_path/bin/llvm-symbolizer"`. |
+| **`cobalt_licenses_platform`**<br><br> Sub-directory to copy license file to.<br><br>The default value is `"default"`. |
+| **`cobalt_platform_dependencies`**<br><br> TODO(b/173248397): Migrate to CobaltExtensions or PlatformServices. List of platform-specific targets that get compiled into cobalt.<br><br>The default value is `[]`. |
+| **`cobalt_v8_emit_builtins_as_inline_asm`**<br><br> Some compiler can not compile with raw assembly(.S files) and v8 converts asm to inline assembly for these platforms.<br><br>The default value is `false`. |
+| **`default_renderer_options_dependency`**<br><br> Override this value to adjust the default rasterizer setting for your platform.<br><br>The default value is `"//cobalt/renderer:default_options"`. |
+| **`enable_account_manager`**<br><br> Set to true to enable H5vccAccountManager.<br><br>The default value is `false`. |
+| **`enable_in_app_dial`**<br><br> Enables or disables the DIAL server that runs inside Cobalt. Note: Only enable if there's no system-wide DIAL support.<br><br>The default value is `false`. |
+| **`enable_sso`**<br><br> Set to true to enable H5vccSSO (Single Sign On).<br><br>The default value is `false`. |
+| **`enable_xhr_header_filtering`**<br><br> Set to true to enable filtering of HTTP headers before sending.<br><br>The default value is `false`. |
+| **`executable_configs`**<br><br> Target-specific configurations for each platform.<br><br>The default value is `[]`. |
+| **`final_executable_type`**<br><br> The variables allow changing the target type on platforms where the native code may require an additional packaging step (ex. Android).<br><br>The default value is `"executable"`. |
+| **`gl_type`**<br><br> The source of EGL and GLES headers and libraries. Valid values (case and everything sensitive!):<ul><li><code>none</code> - No EGL + GLES implementation is available on this platform.<li><code>system_gles2</code> - Use the system implementation of EGL + GLES2. The headers and libraries must be on the system include and link paths.<li><code>glimp</code> - Cobalt's own EGL + GLES2 implementation. This requires a valid Glimp implementation for the platform.<li><code>angle</code> - A DirectX-to-OpenGL adaptation layer. This requires a valid ANGLE implementation for the platform.<br><br>The default value is `"system_gles2"`. |
+| **`gtest_target_type`**<br><br> The variables allow changing the target type on platforms where the native code may require an additional packaging step (ex. Android).<br><br>The default value is `"executable"`. |
+| **`has_platform_targets`**<br><br> Whether the platform has platform-specific targets to depend on.<br><br>The default value is `false`. |
+| **`install_target_path`**<br><br> The path to the gni file containing the install_target template which defines how the build should produce the install/ directory.<br><br>The default value is `"//starboard/build/install/no_install.gni"`. |
+| **`loadable_module_configs`**<br><br> Target-specific configurations for each platform.<br><br>The default value is `[]`. |
+| **`path_to_yasm`**<br><br> Where yasm can be found on the host device.<br><br>The default value is `"yasm"`. |
+| **`platform_tests_path`**<br><br> Set to the starboard_platform_tests target if the platform implements them.<br><br>The default value is `""`. |
+| **`sabi_path`**<br><br> Where the Starboard ABI file for this platform can be found.<br><br>The default value is `"starboard/sabi/default/sabi.json"`. |
+| **`sb_api_version`**<br><br> The Starboard API version of the current build configuration. The default value is meant to be overridden by a Starboard ABI file.<br><br>The default value is `15`. |
+| **`sb_enable_benchmark`**<br><br> Used to enable benchmarks.<br><br>The default value is `false`. |
+| **`sb_enable_cpp17_audit`**<br><br> Enables an NPLB audit of C++17 support.<br><br>The default value is `true`. |
+| **`sb_enable_lib`**<br><br> Enables embedding Cobalt as a shared library within another app. This requires a 'lib' starboard implementation for the corresponding platform.<br><br>The default value is `false`. |
+| **`sb_enable_opus_sse`**<br><br> Enables optimizations on SSE compatible platforms.<br><br>The default value is `true`. |
+| **`sb_evergreen_compatible_enable_lite`**<br><br> Whether to adopt Evergreen Lite on the Evergreen compatible platform.<br><br>The default value is `false`. |
+| **`sb_evergreen_compatible_use_libunwind`**<br><br> Whether to use the libunwind library on Evergreen compatible platform.<br><br>The default value is `false`. |
+| **`sb_filter_based_player`**<br><br> Used to indicate that the player is filter based.<br><br>The default value is `true`. |
+| **`sb_is_evergreen`**<br><br> Whether this is an Evergreen build.<br><br>The default value is `false`. |
+| **`sb_is_evergreen_compatible`**<br><br> Whether this is an Evergreen compatible platform. A compatible platform can run the elf_loader and launch the Evergreen build.<br><br>The default value is `false`. |
+| **`sb_libevent_method`**<br><br> The event polling mechanism available on this platform to support libevent. Platforms may redefine to 'poll' if necessary. Other mechanisms, e.g. devpoll, kqueue, select, are not yet supported.<br><br>The default value is `"epoll"`. |
+| **`sb_static_contents_output_data_dir`**<br><br> Directory path to static contents' data.<br><br>The default value is `"$root_out_dir/content/data"`. |
+| **`sb_use_no_rtti`**<br><br>The default value is `false`. |
+| **`separate_install_targets_for_bundling`**<br><br> Set to true to separate install target directories.<br><br>The default value is `false`. |
+| **`shared_library_configs`**<br><br> Target-specific configurations for each platform.<br><br>The default value is `[]`. |
+| **`source_set_configs`**<br><br> Target-specific configurations for each platform.<br><br>The default value is `[]`. |
+| **`static_library_configs`**<br><br> Target-specific configurations for each platform.<br><br>The default value is `[]`. |
+| **`use_skia_next`**<br><br> Flag to use a future version of Skia, currently not available<br><br>The default value is `false`. |
+| **`use_thin_archive`**<br><br> Whether or not to link with thin archives.<br><br>The default value is `true`. |
+| **`v8_enable_pointer_compression_override`**<br><br> Set to true to enable pointer compression for v8.<br><br>The default value is `true`. |
+| **`yasm_exists`**<br><br> Enables the yasm compiler to be used to compile .asm files.<br><br>The default value is `false`. |
diff --git a/cobalt/site/docs/reference/starboard/gyp-configuration.md b/cobalt/site/docs/reference/starboard/gyp-configuration.md
deleted file mode 100644
index 99943c3..0000000
--- a/cobalt/site/docs/reference/starboard/gyp-configuration.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-layout: doc
-title: "Starboard: gyp_configuration Reference Guide"
----
-
-| Variables |
-| :--- |
-| **`abort_on_allocation_failure`**<br><br> Halt execution on failure to allocate memory.<br><br>The default value is `1`. |
-| **`cobalt`**<br><br> Whether Cobalt is being built.<br><br>The default value is `1`. |
-| **`cobalt_compiled_by_msvc`**<br><br> Some compiler can not compile with raw assembly(.S files) and v8 converts asm to inline assembly for these platforms.<br><br>The default value is `0`. |
-| **`cobalt_config`**<br><br> Contains the current build configuration.<br><br>The default value is `'gold'`. |
-| **`cobalt_egl_swap_interval`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltEglSwapInterval instead. Cobalt will call eglSwapInterval() and specify this value before calling eglSwapBuffers() each frame.<br><br>The default value is `-1`. |
-| **`cobalt_enable_jit`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltEnableJit instead.<br><br>The default value is `-1`. |
-| **`cobalt_enable_quic`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltEnableQuic instead.<br><br>The default value is `-1`. |
-| **`cobalt_fastbuild`**<br><br> Contains the current build configuration.<br><br>The default value is `0`. |
-| **`cobalt_font_package`**<br><br> Contains the current font package selection.  This can be used to trade font quality, coverage, and latency for different font package sizes. The font package can be one of the following options:<ul><li><code>standard</code> - The default package. It includes all sans-serif, serif, and FCC fonts, non-CJK fallback fonts in both 'normal' and 'bold' weights, 'normal' weight CJK ('bold' weight CJK is synthesized from it), historic script fonts, and color emojis. This package is ~38.3MB.<li><code>limited</code> - A significantly smaller package than 'standard'. This package removes all but 'normal' and 'bold' weighted sans-serif and serif, removes the FCC fonts (which must be provided by the system or downloaded from the web), replaces standard CJK with low quality CJK, removes historic script fonts, and replaces colored emojis with uncolored ones. Because low quality CJK cannot synthesize bold, bold glyphs are unavailable in Chinese, Japanese and Korean. This package is ~8.3MB.<li><code>minimal</code> - The smallest possible font package. It only includes Roboto's Basic Latin characters. Everything else must be provided by the system or downloaded from the web. This package is ~40.0KB.</li></ul> NOTE: When bold is needed, but unavailable, it is typically synthesized, resulting in lower quality glyphs than those generated directly from a bold font. However, this does not occur with low quality CJK, which is not high enough quality to synthesize. Its glyphs always have a 'normal' weight.<br><br>The default value is `'standard'`. |
-| **`cobalt_font_package_override_fallback_color_emoji`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_emoji`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_historic`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_lang_cjk`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_lang_cjk_low_quality`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_lang_non_cjk`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_fallback_symbols`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_named_fcc_fonts`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_named_sans_serif`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_font_package_override_named_serif`**<br><br> Font package overrides can be used to modify the files included within the selected package. The following values are available: -1 -- The package value for the specified category is not overridden. 0 -- The package value is overridden and no fonts for the specified category are included. 1 -- The package value is overridden and fonts from the specified category with a weight of 'normal' and a style of 'normal' are included. 2 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or bold' and a style of 'normal' are included. 3 -- The package value is overridden and fonts from the specified category with a weight of either 'normal' or 'bold' and a style of either 'normal' or 'italic' are included. 4 -- The package value is overridden and all available fonts from the specified category are included. This may include additional weights beyond 'normal' and 'bold'. See content/fonts/README.md for details on the specific values used by each of the packages use for the various font categories.<br><br>The default value is `-1`. |
-| **`cobalt_gc_zeal`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltGcZeal instead. Can be set to enable zealous garbage collection, if |javascript_engine| supports it.  Zealous garbage collection will cause garbage collection to occur much more frequently than normal, for the purpose of finding or reproducing bugs.<br><br>The default value is `-1`. |
-| **`cobalt_licenses_platform`**<br><br>The default value is `'default'`. |
-| **`cobalt_media_buffer_alignment`**<br><br> The media buffer will be allocated using the following alignment.  Set this to a larger value may increase the memory consumption of media buffers.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_allocation_unit`**<br><br> When the media stack needs more memory to store media buffers, it will allocate extra memory in units of |cobalt_media_buffer_allocation_unit|. This can be set to 0, in which case the media stack will allocate extra memory on demand.  When |cobalt_media_buffer_initial_capacity| and this value are both set to 0, the media stack will allocate individual buffers directly using SbMemory functions.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_initial_capacity`**<br><br> The amount of memory that will be used to store media buffers allocated during system startup.  To allocate a large chunk at startup helps with reducing fragmentation and can avoid failures to allocate incrementally. This can be set to 0.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_max_capacity_1080p`**<br><br> The maximum amount of memory that will be used to store media buffers when video resolution is no larger than 1080p. This must be larger than sum of 1080p video budget and non-video budget.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_max_capacity_4k`**<br><br> The maximum amount of memory that will be used to store media buffers when video resolution is 4k. If 0, then memory can grow without bound. This must be larger than sum of 4k video budget and non-video budget.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_non_video_budget`**<br><br> Specifies the maximum amount of memory used by audio or text buffers of media source before triggering a garbage collection.  A large value will cause more memory being used by audio buffers but will also make JavaScript app less likely to re-download audio data.  Note that the JavaScript app may experience significant difficulty if this value is too low.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_padding`**<br><br> Extra bytes allocated at the end of a media buffer to ensure that the buffer can be use optimally by specific instructions like SIMD.  Set to 0 to remove any padding.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_pool_allocate_on_demand`**<br><br> When either |cobalt_media_buffer_initial_capacity| or |cobalt_media_buffer_allocation_unit| isn't zero, media buffers will be allocated using a memory pool.  Set the following variable to 1 to allocate the media buffer pool memory on demand and return all memory to the system when there is no media buffer allocated.  Setting the following value to 0 results in that Cobalt will allocate |cobalt_media_buffer_initial_capacity| bytes for media buffer on startup and will not release any media buffer memory back to the system even if there is no media buffers allocated.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_progressive_budget`**<br><br> The memory used when playing mp4 videos that is not in DASH format.  The resolution of such videos shouldn't go beyond 1080p.  Its value should be less than the sum of 'cobalt_media_buffer_non_video_budget' and 'cobalt_media_buffer_video_budget_1080p' but not less than 8 MB.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_storage_type`**<br><br> This can be set to "memory" or "file".  When it is set to "memory", the media buffers will be stored in main memory allocated by SbMemory functions.  When it is set to "file", the media buffers will be stored in a temporary file in the system cache folder acquired by calling SbSystemGetPath() with "kSbSystemPathCacheDirectory".  Note that when its value is "file" the media stack will still allocate memory to cache the the buffers in use.<br><br>The default value is `''`. |
-| **`cobalt_media_buffer_video_budget_1080p`**<br><br> Specifies the maximum amount of memory used by video buffers of media source before triggering a garbage collection when the video resolution is lower than 1080p (1920x1080).  A large value will cause more memory being used by video buffers but will also make JavaScript app less likely to re-download video data.  Note that the JavaScript app may experience significant difficulty if this value is too low.<br><br>The default value is `-1`. |
-| **`cobalt_media_buffer_video_budget_4k`**<br><br> Specifies the maximum amount of memory used by video buffers of media source before triggering a garbage collection when the video resolution is lower than 4k (3840x2160).  A large value will cause more memory being used by video buffers but will also make JavaScript app less likely to re-download video data.  Note that the JavaScript app may experience significant difficulty if this value is too low.<br><br>The default value is `-1`. |
-| **`cobalt_media_source_garbage_collection_duration_threshold_in_seconds`**<br><br> Specifies the duration threshold of media source garbage collection.  When the accumulated duration in a source buffer exceeds this value, the media source implementation will try to eject existing buffers from the cache. This is usually triggered when the video being played has a simple content and the encoded data is small.  In such case this can limit how much is allocated for the book keeping data of the media buffers and avoid OOM of system heap. This should be set to 170 for most of the platforms.  But it can be further reduced on systems with extremely low memory.<br><br>The default value is `-1`. |
-| **`cobalt_minimum_frame_time_in_milliseconds`**<br><br> Allow throttling of the frame rate. This is expressed in terms of milliseconds and can be a floating point number. Keep in mind that swapping frames may take some additional processing time, so it may be better to specify a lower delay. For example, '33' instead of '33.33' for 30 Hz refresh. This value is deprecated in favor of the usage of CobaltExtensionGraphicsApi::GetMinimumFrameIntervalInMilliseconds API. The default value has been moved into cobalt/renderer/pipeline.cc.<br><br>The default value is `'-1'`. |
-| **`cobalt_platform_dependencies`**<br><br> List of platform-specific targets that get compiled into cobalt.<br><br>The default value is `[]`. |
-| **`cobalt_splash_screen_file`**<br><br> The path to a splash screen to copy into content/data/web which can be accessed via a file URL starting with "file:///cobalt/browser/splash_screen/". If '', no file is copied.<br><br>The default value is `''`. |
-| **`cobalt_user_on_exit_strategy`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltUserOnExitStrategy instead. This variable defines what Cobalt's preferred strategy should be for handling internally triggered application exit requests (e.g. the user chooses to back out of the application).<ul><li><code>stop</code> - The application should call SbSystemRequestStop() on exit, resulting in a complete shutdown of the application.<li><code>suspend</code> - The application should call SbSystemRequestSuspend() on exit, resulting in the application being "minimized".<li><code>noexit</code> - The application should never allow the user to trigger an exit, this will be managed by the system.<br><br>The default value is `''`. |
-| **`cobalt_v8_emit_builtins_as_inline_asm`**<br><br> Some compiler can not compile with raw assembly(.S files) and v8 converts asm to inline assembly for these platforms.<br><br>The default value is `0`. |
-| **`cobalt_version`**<br><br> Build version number.<br><br>The default value is `'<(BUILD_NUMBER)'`. |
-| **`cobalt_webapi_extension_generated_header_idl_files`**<br><br> We need to define some variables inside of an inner 'variables' scope so that they can be referenced by other outer variables here.  Also, it allows for the specification of default values that get referenced by a top level scope.<br><br>The default value is `[]`. |
-| **`cobalt_webapi_extension_source_idl_files`**<br><br> We need to define some variables inside of an inner 'variables' scope so that they can be referenced by other outer variables here.  Also, it allows for the specification of default values that get referenced by a top level scope.<br><br>The default value is `[]`. |
-| **`compiler_flags_<config>`**<br><br>The following configurations are supported: <ul><li><code>[default]</code></li><li><code>c_debug</code></li><li><code>c_devel</code></li><li><code>c_gold</code></li><li><code>c_gold_size</code></li><li><code>c_gold_speed</code></li><li><code>c_host</code></li><li><code>c_qa</code></li><li><code>c_qa_size</code></li><li><code>c_qa_speed</code></li><li><code>cc_debug</code></li><li><code>cc_devel</code></li><li><code>cc_gold</code></li><li><code>cc_gold_size</code></li><li><code>cc_gold_speed</code></li><li><code>cc_host</code></li><li><code>cc_qa</code></li><li><code>cc_qa_size</code></li><li><code>cc_qa_speed</code></li><li><code>debug</code></li><li><code>devel</code></li><li><code>gold</code></li><li><code>gold_size</code></li><li><code>gold_speed</code></li><li><code>host</code></li><li><code>qa</code></li><li><code>qa_size</code></li><li><code>qa_speed</ul> |
-| **`default_renderer_options_dependency`**<br><br> Override this value to adjust the default rasterizer setting for your platform.<br><br>The default value is `'<(DEPTH)/cobalt/renderer/default_options_starboard.gyp:default_options'`. |
-| **`defines_debug`**<br><br> and linker_executable_flags_(config) to distinguish the flags for SharedLibraryLinker and ExecutableLinker.<br><br>The default value is `[]`. |
-| **`defines_devel`**<br><br>The default value is `[]`. |
-| **`defines_gold`**<br><br>The default value is `[]`. |
-| **`defines_host`**<br><br>The default value is `[]`. |
-| **`defines_qa`**<br><br> For qa and gold configs, different compiler flags may be specified for gyp targets that should be built for size than for speed. Targets which specify 'optimize_target_for_speed == 1', will compile with flags: ['compiler_flags_*<config>', 'compiler_flags_*<config>_speed']. Otherwise, targets will use compiler flags: ['compiler_flags_*<config>', 'compiler_flags_*<config>_size']. Platforms may decide to use the same optimization flags for both target types by leaving the '*_size' and '*_speed' variables empty.<br><br>The default value is `[]`. |
-| **`disable_v8_pointer_compression`**<br><br> In cross-compiling for modules like V8, we need a gyp flag to tell that Cobalt is being compiled by MSVC and certain MSVC options should be specified. This is needed even with 'msvs_settings' since the later is not used by platforms that only uses MSVC for host build.<br><br>The default value is `0`. |
-| **`enable_account_manager`**<br><br> Set to 1 to enable H5vccAccountManager.<br><br>The default value is `0`. |
-| **`enable_configure_request_job_factory`**<br><br> Set to 1 to enable setting Interceptors on the URLRequestJobFactory<br><br>The default value is `0`. |
-| **`enable_crash_log`**<br><br> Set to 1 to enable H5vccCrashLog.<br><br>The default value is `1`. |
-| **`enable_map_to_mesh`**<br><br> Enable support for the map to mesh filter, which is primarily used to implement spherical video playback. This setting is deprecated in favor of the cobalt graphics extension (CobaltGraphicsExtensionApi) function `IsMapToMeshEnabled()`. If the CobaltGraphicsExtensionApi is not implemented, then Cobalt will fall back onto a default. For starboard API versions 12 and later, the default is true (i.e. Cobalt will assume map to mesh is supported). For earlier starboard API versions, if this gyp variable is redefined to a value other than -1, it will use the new value as the default. If it is not redefined, the default is false.<br><br>The default value is `-1`. |
-| **`enable_sso`**<br><br> Set to 1 to enable H5vccSSO (Single Sign On).<br><br>The default value is `0`. |
-| **`enable_xhr_header_filtering`**<br><br> Set to 1 to enable filtering of HTTP headers before sending.<br><br>The default value is `0`. |
-| **`encoded_image_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltEncodedImageCacheSizeInBytes instead. Determines the capacity of the encoded image cache, which manages encoded images downloaded from a web page. These images are cached within CPU memory.  This not only reduces network traffic to download the encoded images, but also allows the downloaded images to be held during suspend. Note that there is also a cache for the decoded images whose capacity is specified in |image_cache_size_in_bytes|.  The decoded images are often cached in the GPU memory and will be released during suspend.<br><br> If a system meet the following requirements: 1. Has a fast image decoder. 2. Has enough CPU memory, or has a unified memory architecture that allows sharing of CPU and GPU memory. Then it may consider to set |encoded_image_cache_size_in_bytes| to a much bigger value, and set the value of |image_cache_size_in_bytes| to a much smaller value. This allows the app to cache significant more images.<br><br> Set this to 0 can disable the cache completely.<br><br>The default value is `-1`. |
-| **`fallback_splash_screen_url`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltFallbackSplashScreenUrl instead. The URL of default build time splash screen - see cobalt/doc/splash_screen.md for information about this.<br><br>The default value is `''`. |
-| **`final_executable_type`**<br><br> The variables allow changing the target type on platforms where the native code may require an additional packaging step (ex. Android).<br><br>The default value is `'executable'`. |
-| **`gl_type`**<br><br> The source of EGL and GLES headers and libraries. Valid values (case and everything sensitive!):<ul><li><code>none</code> - No EGL + GLES implementation is available on this platform.<li><code>system_gles3</code> - Deprecated. Use system_gles2 instead. Use the system implementation of EGL + GLES3. The headers and libraries must be on the system include and link paths.<li><code>system_gles2</code> - Use the system implementation of EGL + GLES2. The headers and libraries must be on the system include and link paths.<li><code>glimp</code> - Cobalt's own EGL + GLES2 implementation. This requires a valid Glimp implementation for the platform.<li><code>angle</code> - A DirectX-to-OpenGL adaptation layer. This requires a valid ANGLE implementation for the platform.</li></ul> Choosing an unsupported value will result in a GYP error: "cobalt/renderer/egl_and_gles/egl_and_gles_<gl_type>.gyp not found"<br><br>The default value is `'system_gles2'`. |
-| **`gtest_target_type`**<br><br> The variables allow changing the target type on platforms where the native code may require an additional packaging step (ex. Android).<br><br>The default value is `'executable'`. |
-| **`host_executable_suffix`**<br><br> In cross-compiling for modules like V8, we need a gyp flag to tell that Cobalt is being compiled by MSVC and certain MSVC options should be specified. This is needed even with 'msvs_settings' since the later is not used by platforms that only uses MSVC for host build.<br><br>The default value is `'<(EXECUTABLE_SUFFIX)'`. |
-| **`host_os`**<br><br> Contains the name of the hosting OS. The value is defined by the gyp wrapper script.<br><br>The default value is `'win'`. |
-| **`image_cache_capacity_multiplier_when_playing_video`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltImageCacheCapactityMultiplierWhenPlayingVideo. Modifying this value to be non-1.0f will result in the image cache capacity being cleared and then temporarily reduced for the duration that a video is playing.  This can be useful for some platforms if they are particularly constrained for (GPU) memory during video playback.  When playing a video, the image cache is reduced to: image_cache_size_in_bytes * image_cache_capacity_multiplier_when_playing_video.<br><br>The default value is `''`. |
-| **`image_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltImageCacheSizeInBytes instead. Determines the capacity of the image cache, which manages image surfaces downloaded from a web page.  While it depends on the platform, often (and ideally) these images are cached within GPU memory. Set to -1 to automatically calculate the value at runtime, based on features like windows dimensions and the value of SbSystemGetTotalGPUMemory().<br><br>The default value is `-1`. |
-| **`in_app_dial`**<br><br> Set to 1 to build with DIAL support.<br><br>The default value is `0`. |
-| **`linker_flags_<config>`**<br><br>The following configurations are supported: <ul><li><code>[default]</code></li><li><code>debug</code></li><li><code>devel</code></li><li><code>gold</code></li><li><code>host</code></li><li><code>qa</ul> |
-| **`local_font_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltLocalTypeFaceCacheSizeInBytes instead. Determines the capacity of the local font cache, which manages all fonts loaded from local files. Newly encountered sections of font files are lazily loaded into the cache, enabling subsequent requests to the same file sections to be handled via direct memory access. Once the limit is reached, further requests are handled via file stream. Setting the value to 0 disables memory caching and causes all font file accesses to be done using file streams.<br><br>The default value is `-1`. |
-| **`max_cobalt_cpu_usage`**<br><br> Max Cobalt CPU usage specifies that the cobalt program should keep it's size below the specified size. This setting is deprecated. Implement starboard API function SbSystemGetTotalCPUMemory() instead.<br><br>The default value is `-1`. |
-| **`max_cobalt_gpu_usage`**<br><br> Max Cobalt GPU usage specifies that the cobalt program should keep it's size below the specified size. This setting is deprecated. Implement starboard API function SbSystemGetTotalGPUMemory() instead.<br><br>The default value is `-1`. |
-| **`mesh_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltMeshCacheSizeInBytes instead. Determines the capacity of the mesh cache. Each mesh is held compressed in main memory, to be inflated into a GPU buffer when needed for projection. When set to 'auto', will be adjusted according to whether the enable_map_to_mesh is true or not.  If enable_map_to_mesh is false, then the mesh cache size will be set to 0.<br><br>The default value is `-1`. |
-| **`offscreen_target_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltOffscreenTargetCacheSizeInBytes instead. Determines the amount of GPU memory the offscreen target atlases will use. This is specific to the direct-GLES rasterizer and caches any render tree nodes which require skia for rendering. Two atlases will be allocated from this memory or multiple atlases of the frame size if the limit allows. It is recommended that enough memory be reserved for two RGBA atlases about a quarter of the frame size.<br><br>The default value is `-1`. |
-| **`optimize_target_for_speed`**<br><br> This variable dictates whether a given gyp target should be compiled with optimization flags for size vs. speed.<br><br>The default value is `0`. |
-| **`path_to_yasm`**<br><br> Where yasm can be found on the target device.<br><br>The default value is `"yasm"`. |
-| **`platform_libraries`**<br><br>The default value is `[]`. |
-| **`rasterizer_type`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltRasterizerType instead. Defines what kind of rasterizer will be used.  This can be adjusted to force a stub graphics implementation. It can be one of the following options:<ul><li><code>direct-gles</code> - Uses a light wrapper over OpenGL ES to handle most draw elements. This will fall back to the skia hardware rasterizer for some render tree node types, but is generally faster on the CPU and GPU. This can handle 360 rendering.<li><code>hardware</code> - As much hardware acceleration of graphics commands as possible. This uses skia to wrap OpenGL ES commands. Required for 360 rendering.<li><code>stub</code> - Stub graphics rasterization.  A rasterizer object will still be available and valid, but it will do nothing.<br><br>The default value is `''`. |
-| **`reduce_cpu_memory_by`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltReduceCpuMemoryBy instead. When specified this value will reduce the cpu memory consumption by the specified amount. -1 disables the value. When this value is specified then max_cobalt_cpu_usage will not be used in memory_constrainer, but will still be used for triggering a warning if the engine consumes more memory than this value specifies.<br><br>The default value is `-1`. |
-| **`reduce_gpu_memory_by`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltReduceGpuMemoryBy instead. When specified this value will reduce the gpu memory consumption by the specified amount. -1 disables the value. When this value is specified then max_cobalt_gpu_usage will not be used in memory_constrainer, but will still be used for triggering a warning if the engine consumes more memory than this value specifies.<br><br>The default value is `-1`. |
-| **`remote_font_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltRemoteTypefaceCacheSizeInBytes instead. Determines the capacity of the remote font cache, which manages all fonts downloaded from a web page.<br><br>The default value is `-1`. |
-| **`render_dirty_region_only`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltRenderDirtyRegionOnly instead. If set to 1, will enable support for rendering only the regions of the display that are modified due to animations, instead of re-rendering the entire scene each frame.  This feature can reduce startup time where usually there is a small loading spinner animating on the screen.  On GLES renderers, Cobalt will attempt to implement this support by using eglSurfaceAttrib(..., EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED), otherwise the dirty region will be silently disabled.  On Blitter API platforms, if this is enabled, we explicitly create an extra offscreen full-size intermediate surface to render into.  Note that some GLES driver implementations may internally allocate an extra full screen surface to support this feature, and many have been noticed to not properly support this functionality (but they report that they do), and for these reasons this value is defaulted to 0.<br><br>The default value is `-1`. |
-| **`sb_api_version`**<br><br> The Starboard API version of the current build configuration. The default value is meant to be overridden by a Starboard ABI file.<br><br>The default value is `0`. |
-| **`sb_deploy_output_dir`**<br><br> Top-level directory for staging deploy build output. Platform deploy actions should use <(target_deploy_dir) defined in deploy.gypi to place artifacts for each deploy target in its own subdirectoy.<br><br>The default value is `'<(sb_deploy_output_dir)'`. |
-| **`sb_disable_cpp17_audit`**<br><br> Disables an NPLB audit of C++17 support.<br><br>The default value is `0`. |
-| **`sb_disable_microphone_idl`**<br><br> When this is set to true, the web bindings for the microphone are disabled<br><br>The default value is `0`. |
-| **`sb_enable_benchmark`**<br><br> Used to enable benchmarks.<br><br>The default value is `0`. |
-| **`sb_enable_lib`**<br><br> Enables embedding Cobalt as a shared library within another app. This requires a 'lib' starboard implementation for the corresponding platform.<br><br>The default value is `'<(sb_enable_lib)'`. |
-| **`sb_evergreen`**<br><br> Whether this is an evergreen build.<br><br>The default value is `0`. |
-| **`sb_evergreen_compatible`**<br><br> Whether this is an evergreen compatible platform. A compatible platform can run the elf_loader and launch the evergreen build.<br><br>The default value is `'<(sb_evergreen_compatible)'`. |
-| **`sb_evergreen_compatible_libunwind`**<br><br> Whether to use the libunwind library on evergreen compatible platform.<br><br>The default value is `'<(sb_evergreen_compatible_libunwind)'`. |
-| **`sb_evergreen_compatible_lite`**<br><br> Whether to adopt Evergreen Lite on the evergreen compatible platform.<br><br>The default value is `'<(sb_evergreen_compatible_lite)'`. |
-| **`sb_filter_based_player`**<br><br> Used to indicate that the player is filter based.<br><br>The default value is `1`. |
-| **`sb_libevent_method`**<br><br> The event polling mechanism available on this platform to support libevent. Platforms may redefine to 'poll' if necessary. Other mechanisms, e.g. devpoll, kqueue, select, are not yet supported.<br><br>The default value is `'epoll'`. |
-| **`sb_pedantic_warnings`**<br><br> Enabling this variable enables pedantic levels of warnings for the current toolchain.<br><br>The default value is `0`. |
-| **`sb_static_contents_output_data_dir`**<br><br> Directory path to static contents' data.<br><br>The default value is `'<(sb_static_contents_output_data_dir)'`. |
-| **`sb_target_platform`**<br><br> The target platform id as a string, like 'linux-x64x11', 'win32', etc..<br><br>The default value is `''`. |
-| **`skia_cache_size_in_bytes`**<br><br> Determines the capacity of the skia cache.  The Skia cache is maintained within Skia and is used to cache the results of complicated effects such as shadows, so that Skia draw calls that are used repeatedly across frames can be cached into surfaces.  This setting is only relevant when using the hardware-accelerated Skia rasterizer (e.g. as opposed to the Blitter API).<br><br>The default value is `-1`. |
-| **`skia_glyph_atlas_height`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi functions CobaltSkiaGlyphAtlasWidth and CobaltSkiaGlyphAtlasHeight, respectively. Determines the size in pixels of the glyph atlas where rendered glyphs are cached. The resulting memory usage is 2 bytes of GPU memory per pixel. When a value is used that is too small, thrashing may occur that will result in visible stutter. Such thrashing is more likely to occur when CJK language glyphs are rendered and when the size of the glyphs in pixels is larger, such as for higher resolution displays. The negative default values indicates to the engine that these settings should be automatically set.<br><br>The default value is `'-1'`. |
-| **`skia_glyph_atlas_width`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi functions CobaltSkiaGlyphAtlasWidth and CobaltSkiaGlyphAtlasHeight, respectively. Determines the size in pixels of the glyph atlas where rendered glyphs are cached. The resulting memory usage is 2 bytes of GPU memory per pixel. When a value is used that is too small, thrashing may occur that will result in visible stutter. Such thrashing is more likely to occur when CJK language glyphs are rendered and when the size of the glyphs in pixels is larger, such as for higher resolution displays. The negative default values indicates to the engine that these settings should be automatically set.<br><br>The default value is `'-1'`. |
-| **`software_surface_cache_size_in_bytes`**<br><br> Deprecated. Implement the CobaltExtensionConfigurationApi function CobaltSoftwareSurfaceCacheSizeInBytes instead. Only relevant if you are using the Blitter API. Determines the capacity of the software surface cache, which is used to cache all surfaces that are rendered via a software rasterizer to avoid re-rendering them.<br><br>The default value is `-1`. |
-| **`starboard_path`**<br><br> The relative path from src/ to the directory containing the starboard_platform.gyp file.  It is currently set to 'starboard/<(target_arch)' to make semi-starboard platforms work. moved to starboard.<br><br>The default value is `'starboard/<(target_arch)'`. |
-| **`target_os`**<br><br> The operating system of the target, separate from the target_arch. In many cases, an 'unknown' value is fine, but, if set to 'linux', then we can assume some things, and it'll save us some configuration time.<br><br>The default value is `'unknown'`. |
-| **`tizen_os`**<br><br> Temporary indicator for Tizen - should eventually move to feature defines.<br><br>The default value is `0`. |
-| **`yasm_exists`**<br><br> Enables the yasm compiler to be used to compile .asm files.<br><br>The default value is `0`. |
diff --git a/cobalt/site/docs/reference/starboard/modules/11/blitter.md b/cobalt/site/docs/reference/starboard/modules/11/blitter.md
deleted file mode 100644
index f7d2a6c..0000000
--- a/cobalt/site/docs/reference/starboard/modules/11/blitter.md
+++ /dev/null
@@ -1,302 +0,0 @@
----
-layout: doc
-title: "Starboard Module Reference: blitter.h"
----
-
-The Blitter API provides support for issuing simple blit-style draw commands to
-either an offscreen surface or to a Starboard `SbWindow` object. Blitter is
-jargon that means "BLock Transfer," which might be abbreviated as BLT and,
-hence, the word "blit."
-
-This API is designed to allow implementations make use of GPU hardware
-acceleration, if it is available. Draw commands exist for solid-color rectangles
-and rasterization/blitting of rectangular images onto rectangular target
-patches.
-
-## Threading Concerns ##
-
-Note that in general the Blitter API is not thread safe, except for all
-`SbBlitterDevice`-related functions. All functions that are not required to
-internally ensure any thread safety guarantees are prefaced with a comment
-indicating that they are not thread safe.
-
-Functions which claim to not be thread safe can still be used from multiple
-threads, but manual synchronization must be performed in order to ensure their
-parameters are not referenced at the same time on another thread by another
-function.
-
-### Examples ###
-
-*   Multiple threads should not issue commands to the same `SbBlitterContext`
-    object unless they are manually synchronized.
-
-*   Multiple threads should not issue draw calls to the same render target, even
-    if the draw calls are made within separate contexts. In this case, be sure
-    to manually synchronize through the use of synchronization primitives and
-    use of the `SbBlitterFlushContext()` command.
-
-*   Multiple threads can operate on the swap chain, but they must perform manual
-    synchronization.
-
-## Enums ##
-
-### SbBlitterPixelDataFormat ###
-
-Defines the set of pixel formats that can be used with the Blitter API
-`SbBlitterPixelData` objects. Note that not all of these formats are guaranteed
-to be supported by a particular device, so before using these formats in pixel
-data creation commands, it should be checked that they are supported first (e.g.
-via `SbBlitterIsPixelFormatSupportedByPixelData()`). `SbBlitterPixelDataFormat`
-specifies specific orderings of the color channels, and when doing so, byte-
-order is used, e.g. "RGBA" implies that a value for red is stored in the byte
-with the lowest memory address. All pixel values are assumed to be in
-premultiplied alpha format.
-
-#### Values ####
-
-*   `kSbBlitterPixelDataFormatARGB8`
-
-    32-bit pixels with 8-bits per channel, the alpha component in the byte with
-    the lowest address and blue in the byte with the highest address.
-*   `kSbBlitterPixelDataFormatBGRA8`
-
-    32-bit pixels with 8-bits per channel, the blue component in the byte with
-    the lowest address and alpha in the byte with the highest address.
-*   `kSbBlitterPixelDataFormatRGBA8`
-
-    32-bit pixels with 8-bits per channel, the red component in the byte with
-    the lowest address and alpha in the byte with the highest address.
-*   `kSbBlitterPixelDataFormatA8`
-
-    8-bit pixels that contain only a single alpha channel. When rendered,
-    surfaces in this format will have `(R, G, B)` values of `(255, 255, 255)`.
-*   `kSbBlitterNumPixelDataFormats`
-
-    Constant that indicates how many unique pixel formats Starboard supports.
-*   `kSbBlitterInvalidPixelDataFormat`
-
-### SbBlitterSurfaceFormat ###
-
-Enumeration that describes the color format of surfaces. Note that
-`SbBlitterSurfaceFormat` does not differentiate between permutations of the
-color channel ordering (e.g. RGBA vs ARGB) since client code will never be able
-to access surface pixels directly. This is the main difference between
-`SbBlitterPixelDataFormat`, which does explicitly dictate an ordering.
-
-#### Values ####
-
-*   `kSbBlitterSurfaceFormatRGBA8`
-
-    32-bit RGBA color, with 8 bits per channel.
-*   `kSbBlitterSurfaceFormatA8`
-
-    8-bit alpha-only color.
-*   `kSbBlitterNumSurfaceFormats`
-
-    Constant that indicates how many unique surface formats Starboard supports.
-*   `kSbBlitterInvalidSurfaceFormat`
-
-## Typedefs ##
-
-### SbBlitterColor ###
-
-A simple 32-bit color representation that is a parameter to many Blitter
-functions.
-
-#### Definition ####
-
-```
-typedef uint32_t SbBlitterColor
-```
-
-## Structs ##
-
-### SbBlitterRect ###
-
-Defines a rectangle via a point `(x, y)` and a size `(width, height)`. This
-structure is used as a parameter type in various blit calls.
-
-#### Members ####
-
-*   `int x`
-*   `int y`
-*   `int width`
-*   `int height`
-
-### SbBlitterSurfaceInfo ###
-
-`SbBlitterSurfaceInfo` collects information about surfaces that can be queried
-from them at any time.
-
-#### Members ####
-
-*   `int width`
-*   `int height`
-*   `SbBlitterSurfaceFormat format`
-
-## Functions ##
-
-### SB_DEPRECATED ###
-
-This function achieves the same effect as calling `SbBlitterBlitRectToRect()`
-`num_rects` times with each of the `num_rects` values of `src_rects` and
-`dst_rects`. This function allows for greater efficiency than looped calls to
-`SbBlitterBlitRectToRect()`.
-
-This function is not thread-safe.
-
-The return value indicates whether the draw call succeeded.
-
-#### Declaration ####
-
-```
-SB_DEPRECATED(bool SbBlitterBlitRectsToRects(SbBlitterContext context, SbBlitterSurface source_surface, const SbBlitterRect *src_rects, const SbBlitterRect *dst_rects, int num_rects))
-```
-
-### SbBlitterAFromColor ###
-
-Extract alpha from a `SbBlitterColor` object.
-
-#### Declaration ####
-
-```
-static uint8_t SbBlitterAFromColor(SbBlitterColor color)
-```
-
-### SbBlitterBFromColor ###
-
-Extract blue from a `SbBlitterColor` object.
-
-#### Declaration ####
-
-```
-static uint8_t SbBlitterBFromColor(SbBlitterColor color)
-```
-
-### SbBlitterBytesPerPixelForFormat ###
-
-A convenience function to return the number of bytes per pixel for a given pixel
-format.
-
-#### Declaration ####
-
-```
-static int SbBlitterBytesPerPixelForFormat(SbBlitterPixelDataFormat format)
-```
-
-### SbBlitterColorFromRGBA ###
-
-A convenience function to create a `SbBlitterColor` object from separate 8-bit
-RGBA components.
-
-#### Declaration ####
-
-```
-static SbBlitterColor SbBlitterColorFromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
-```
-
-### SbBlitterGFromColor ###
-
-Extract green from a `SbBlitterColor` object.
-
-#### Declaration ####
-
-```
-static uint8_t SbBlitterGFromColor(SbBlitterColor color)
-```
-
-### SbBlitterIsContextValid ###
-
-Checks whether a blitter context is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsContextValid(SbBlitterContext context)
-```
-
-### SbBlitterIsDeviceValid ###
-
-Checks whether a blitter device is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsDeviceValid(SbBlitterDevice device)
-```
-
-### SbBlitterIsPixelDataValid ###
-
-Checks whether a pixel data object is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsPixelDataValid(SbBlitterPixelData pixel_data)
-```
-
-### SbBlitterIsRenderTargetValid ###
-
-Checks whether a render target is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsRenderTargetValid(SbBlitterRenderTarget render_target)
-```
-
-### SbBlitterIsSurfaceValid ###
-
-Checks whether a surface is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsSurfaceValid(SbBlitterSurface surface)
-```
-
-### SbBlitterIsSwapChainValid ###
-
-Checks whether a swap chain is invalid.
-
-#### Declaration ####
-
-```
-static bool SbBlitterIsSwapChainValid(SbBlitterSwapChain swap_chain)
-```
-
-### SbBlitterMakeRect ###
-
-Convenience function to setup a rectangle with the specified parameters.
-
-#### Declaration ####
-
-```
-static SbBlitterRect SbBlitterMakeRect(int x, int y, int width, int height)
-```
-
-### SbBlitterPixelDataFormatToSurfaceFormat ###
-
-This function maps SbBlitterPixelDataFormat values to their corresponding
-`SbBlitterSurfaceFormat` value. Note that many `SbBlitterPixelDataFormat`s
-correspond to the same `SbBlitterSurfaceFormat`, so this function is not
-invertible. When creating a `SbBlitterSurface` object from a
-`SbBlitterPixelData` object, the `SbBlitterSurface`'s format will be computed
-from the `SbBlitterPixelData` object by using this function.
-
-#### Declaration ####
-
-```
-static SbBlitterSurfaceFormat SbBlitterPixelDataFormatToSurfaceFormat(SbBlitterPixelDataFormat pixel_format)
-```
-
-### SbBlitterRFromColor ###
-
-Extract red from a `SbBlitterColor` object.
-
-#### Declaration ####
-
-```
-static uint8_t SbBlitterRFromColor(SbBlitterColor color)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/character.md b/cobalt/site/docs/reference/starboard/modules/11/character.md
deleted file mode 100644
index 432dcae..0000000
--- a/cobalt/site/docs/reference/starboard/modules/11/character.md
+++ /dev/null
@@ -1,104 +0,0 @@
----
-layout: doc
-title: "Starboard Module Reference: character.h"
----
-
-Provides functions for interacting with characters.
-
-## Functions ##
-
-### SbCharacterIsAlphanumeric ###
-
-Indicates whether the given 8-bit character `c` (as an int) is alphanumeric in
-the current locale.
-
-`c`: The character to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbCharacterIsAlphanumeric(int c)
-```
-
-### SbCharacterIsDigit ###
-
-Indicates whether the given 8-bit character `c` (as an int) is a decimal digit
-in the current locale.
-
-`c`: The character to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbCharacterIsDigit(int c)
-```
-
-### SbCharacterIsHexDigit ###
-
-Indicates whether the given 8-bit character `c` (as an int) is a hexadecimal in
-the current locale.
-
-`c`: The character to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbCharacterIsHexDigit(int c)
-```
-
-### SbCharacterIsSpace ###
-
-Indicates whether the given 8-bit character `c` (as an int) is a space in the
-current locale.
-
-`c`: The character to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbCharacterIsSpace(int c)
-```
-
-### SbCharacterIsUpper ###
-
-Indicates whether the given 8-bit character `c` (as an int) is uppercase in the
-current locale.
-
-`c`: The character to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbCharacterIsUpper(int c)
-```
-
-### SbCharacterToLower ###
-
-Converts the given 8-bit character (as an int) to lowercase in the current
-locale and returns an 8-bit character. If there is no lowercase version of the
-character, or the character is already lowercase, the function just returns the
-character as-is.
-
-`c`: The character to be converted.
-
-#### Declaration ####
-
-```
-int SbCharacterToLower(int c)
-```
-
-### SbCharacterToUpper ###
-
-Converts the given 8-bit character (as an int) to uppercase in the current
-locale and returns an 8-bit character. If there is no uppercase version of the
-character, or the character is already uppercase, the function just returns the
-character as-is.
-
-`c`: The character to be converted.
-
-#### Declaration ####
-
-```
-int SbCharacterToUpper(int c)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/cryptography.md b/cobalt/site/docs/reference/starboard/modules/11/cryptography.md
deleted file mode 100644
index 89730d7..0000000
--- a/cobalt/site/docs/reference/starboard/modules/11/cryptography.md
+++ /dev/null
@@ -1,235 +0,0 @@
----
-layout: doc
-title: "Starboard Module Reference: cryptography.h"
----
-
-Hardware-accelerated cryptography. Platforms should **only** only implement this
-when there are **hardware-accelerated** hardware-accelerated cryptography
-facilities. Applications must fall back to platform-independent CPU-based
-algorithms if the cipher algorithm isn't supported in hardware.
-
-## Tips for Porters ##
-
-You should implement cipher algorithms in this descending order of priority to
-maximize usage for SSL.
-
-1.  GCM - The preferred block cipher mode for OpenSSL, mainly due to speed.
-
-1.  CTR - This can be used internally with GCM, as long as the CTR
-    implementation only uses the last 4 bytes of the IV for the counter. (i.e.
-    96-bit IV, 32-bit counter)
-
-1.  ECB - This can be used (with a null IV) with any of the other cipher block
-    modes to accelerate the core AES algorithm if none of the streaming modes
-    can be accelerated.
-
-1.  CBC - GCM is always preferred if the server and client both support it. If
-    not, they will generally negotiate down to AES-CBC. If this happens, and CBC
-    is supported by SbCryptography, then it will be accelerated appropriately.
-    But, most servers should support GCM, so it is not likely to come up much,
-    which is why it is the lowest priority.
-
-Further reading on block cipher modes:
-
-[https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
-
-[https://crbug.com/442572](https://crbug.com/442572)
-
-[https://crypto.stackexchange.com/questions/10775/practical-disadvantages-of-gcm-mode-encryption](https://crypto.stackexchange.com/questions/10775/practical-disadvantages-of-gcm-mode-encryption)
-
-## Macros ##
-
-### kSbCryptographyAlgorithmAes ###
-
-String literal for the AES symmetric block cipher. [https://en.wikipedia.org/wiki/Advanced_Encryption_Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
-
-### kSbCryptographyInvalidTransformer ###
-
-Well-defined value for an invalid transformer.
-
-## Enums ##
-
-### SbCryptographyBlockCipherMode ###
-
-The method of chaining encrypted blocks in a sequence. [https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
-
-#### Values ####
-
-*   `kSbCryptographyBlockCipherModeCbc`
-
-    Cipher Block Chaining mode.
-*   `kSbCryptographyBlockCipherModeCfb`
-
-    Cipher Feedback mode.
-*   `kSbCryptographyBlockCipherModeCtr`
-
-    Counter mode: A nonce is combined with an increasing counter.
-*   `kSbCryptographyBlockCipherModeEcb`
-
-    Electronic Code Book mode: No chaining.
-*   `kSbCryptographyBlockCipherModeOfb`
-
-    Output Feedback mode.
-*   `kSbCryptographyBlockCipherModeGcm`
-
-    Galois/Counter Mode.
-
-### SbCryptographyDirection ###
-
-The direction of a cryptographic transformation.
-
-#### Values ####
-
-*   `kSbCryptographyDirectionEncode`
-
-    Cryptographic transformations that encode/encrypt data into a target format.
-*   `kSbCryptographyDirectionDecode`
-
-    Cryptographic transformations that decode/decrypt data into its original
-    form.
-
-## Typedefs ##
-
-### SbCryptographyTransformer ###
-
-A handle to a cryptographic transformer.
-
-#### Definition ####
-
-```
-typedef SbCryptographyTransformerPrivate* SbCryptographyTransformer
-```
-
-## Functions ##
-
-### SbCryptographyCreateTransformer ###
-
-Creates an SbCryptographyTransformer with the given initialization data. It can
-then be used to transform a series of data blocks. Returns
-kSbCryptographyInvalidTransformer if the algorithm isn't supported, or if the
-parameters are not compatible with the algorithm.
-
-An SbCryptographyTransformer contains all state to start decrypting a sequence
-of cipher blocks according to the cipher block mode. It is not thread-safe, but
-implementations must allow different SbCryptographyTransformer instances to
-operate on different threads.
-
-All parameters must not be assumed to live longer than the call to this
-function. They must be copied by the implementation to be retained.
-
-This function determines success mainly based on whether the combination of
-`algorithm`, `direction`, `block_size_bits`, and `mode` is supported and whether
-all the sizes passed in are sufficient for the selected parameters. In
-particular, this function cannot verify that the key and IV used were correct
-for the ciphertext, were it to be used in the decode direction. The caller must
-make that verification.
-
-For example, to decrypt AES-128-CTR:
-SbCryptographyCreateTransformer(kSbCryptographyAlgorithmAes, 128,
-kSbCryptographyDirectionDecode, kSbCryptographyBlockCipherModeCtr, ...);
-
-`algorithm`: A string that represents the cipher algorithm. `block_size_bits`:
-The block size variant of the algorithm to use, in bits. `direction`: The
-direction in which to transform the data. `mode`: The block cipher mode to use.
-`initialization_vector`: The Initialization Vector (IV) to use. May be NULL for
-block cipher modes that don't use it, or don't set it at init time.
-`initialization_vector_size`: The size, in bytes, of the IV. `key`: The key to
-use for this transformation. `key_size`: The size, in bytes, of the key.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-SbCryptographyTransformer SbCryptographyCreateTransformer(const char *algorithm, int block_size_bits, SbCryptographyDirection direction, SbCryptographyBlockCipherMode mode, const void *initialization_vector, int initialization_vector_size, const void *key, int key_size)
-```
-
-### SbCryptographyDestroyTransformer ###
-
-Destroys the given `transformer` instance.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-void SbCryptographyDestroyTransformer(SbCryptographyTransformer transformer)
-```
-
-### SbCryptographyGetTag ###
-
-Calculates the authenticator tag for a transformer and places up to
-`out_tag_size` bytes of it in `out_tag`. Returns whether it was able to get the
-tag, which mainly has to do with whether it is compatible with the current block
-cipher mode.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-bool SbCryptographyGetTag(SbCryptographyTransformer transformer, void *out_tag, int out_tag_size)
-```
-
-### SbCryptographyIsTransformerValid ###
-
-Returns whether the given transformer handle is valid.
-
-#### Declaration ####
-
-```
-static bool SbCryptographyIsTransformerValid(SbCryptographyTransformer transformer)
-```
-
-### SbCryptographySetAuthenticatedData ###
-
-Sets additional authenticated data (AAD) for a transformer, for chaining modes
-that support it (GCM). Returns whether the data was successfully set. This can
-fail if the chaining mode doesn't support AAD, if the parameters are invalid, or
-if the internal state is invalid for setting AAD.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-bool SbCryptographySetAuthenticatedData(SbCryptographyTransformer transformer, const void *data, int data_size)
-```
-
-### SbCryptographySetInitializationVector ###
-
-Sets the initialization vector (IV) for a transformer, replacing the internally-
-set IV. The block cipher mode algorithm will update the IV appropriately after
-every block, so this is not necessary unless the stream is discontiguous in some
-way. This happens with AES-GCM in TLS.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-void SbCryptographySetInitializationVector(SbCryptographyTransformer transformer, const void *initialization_vector, int initialization_vector_size)
-```
-
-### SbCryptographyTransform ###
-
-Transforms one or more `block_size_bits`-sized blocks of `in_data`, with the
-given `transformer`, placing the result in `out_data`. Returns the number of
-bytes that were written to `out_data`, unless there was an error, in which case
-it will return a negative number.
-
-`transformer`: A transformer initialized with an algorithm, IV, cipherkey, and
-so on. `in_data`: The data to be transformed. `in_data_size`: The size of the
-data to be transformed, in bytes. Must be a multiple of the transformer's
-`block-size_bits`, or an error will be returned. `out_data`: A buffer where the
-transformed data should be placed. Must have at least capacity for
-`in_data_size` bytes. May point to the same memory as `in_data`.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-int SbCryptographyTransform(SbCryptographyTransformer transformer, const void *in_data, int in_data_size, void *out_data)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/double.md b/cobalt/site/docs/reference/starboard/modules/11/double.md
deleted file mode 100644
index 6667b50..0000000
--- a/cobalt/site/docs/reference/starboard/modules/11/double.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-layout: doc
-title: "Starboard Module Reference: double.h"
----
-
-Provides double-precision floating point helper functions.
-
-## Functions ##
-
-### SbDoubleAbsolute ###
-
-Returns the absolute value of the given double-precision floating-point number
-`d`, preserving `NaN` and infinity.
-
-`d`: The number to be adjusted.
-
-#### Declaration ####
-
-```
-double SbDoubleAbsolute(const double d)
-```
-
-### SbDoubleExponent ###
-
-Returns `base` taken to the power of `exponent`.
-
-`base`: The number to be adjusted. `exponent`: The power to which the `base`
-number should be raised.
-
-#### Declaration ####
-
-```
-double SbDoubleExponent(const double base, const double exponent)
-```
-
-### SbDoubleFloor ###
-
-Floors double-precision floating-point number `d` to the nearest integer.
-
-`d`: The number to be floored.
-
-#### Declaration ####
-
-```
-double SbDoubleFloor(const double d)
-```
-
-### SbDoubleIsFinite ###
-
-Determines whether double-precision floating-point number `d` represents a
-finite number.
-
-`d`: The number to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbDoubleIsFinite(const double d)
-```
-
-### SbDoubleIsNan ###
-
-Determines whether double-precision floating-point number `d` represents "Not a
-Number."
-
-`d`: The number to be evaluated.
-
-#### Declaration ####
-
-```
-bool SbDoubleIsNan(const double d)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/string.md b/cobalt/site/docs/reference/starboard/modules/11/string.md
deleted file mode 100644
index eece136..0000000
--- a/cobalt/site/docs/reference/starboard/modules/11/string.md
+++ /dev/null
@@ -1,510 +0,0 @@
----
-layout: doc
-title: "Starboard Module Reference: string.h"
----
-
-Defines functions for interacting with c-style strings.
-
-## Functions ##
-
-### SbStringAToI ###
-
-Parses a string into a base-10 integer. This is a shorthand replacement for
-`atoi`.
-
-`value`: The string to be converted.
-
-#### Declaration ####
-
-```
-static int SbStringAToI(const char *value)
-```
-
-### SbStringAToL ###
-
-Parses a string into a base-10, long integer. This is a shorthand replacement
-for `atol`.
-
-`value`: The string to be converted. NOLINTNEXTLINE(runtime/int)
-
-#### Declaration ####
-
-```
-static long SbStringAToL(const char *value)
-```
-
-### SbStringCompare ###
-
-Compares the first `count` characters of two 8-bit character strings. The return
-value is:
-
-*   `< 0` if `string1` is ASCII-betically lower than `string2`.
-
-*   `0` if the two strings are equal.
-
-*   `> 0` if `string1` is ASCII-betically higher than `string2`.
-
-This function is meant to be a drop-in replacement for `strncmp`.
-
-`string1`: The first 8-bit character string to compare. `string2`: The second
-8-bit character string to compare. `count`: The number of characters to compare.
-
-#### Declaration ####
-
-```
-int SbStringCompare(const char *string1, const char *string2, size_t count)
-```
-
-### SbStringCompareAll ###
-
-Compares two entire 8-bit character strings. The return value is:
-
-*   `< 0` if `string1` is ASCII-betically lower than `string2`.
-
-*   `0` if the two strings are equal.
-
-*   `> 0` if `string1` is ASCII-betically higher than `string2`.
-
-This function is meant to be a drop-in replacement for `strcmp`.
-
-`string1`: The first 8-bit character string to compare. `string2`: The second
-8-bit character string to compare.
-
-#### Declaration ####
-
-```
-int SbStringCompareAll(const char *string1, const char *string2)
-```
-
-### SbStringCompareNoCase ###
-
-Compares two strings, ignoring differences in case. The return value is:
-
-*   `< 0` if `string1` is ASCII-betically lower than `string2`.
-
-*   `0` if the two strings are equal.
-
-*   `> 0` if `string1` is ASCII-betically higher than `string2`.
-
-This function is meant to be a drop-in replacement for `strcasecmp`.
-
-`string1`: The first string to compare. `string2`: The second string to compare.
-
-#### Declaration ####
-
-```
-int SbStringCompareNoCase(const char *string1, const char *string2)
-```
-
-### SbStringCompareNoCaseN ###
-
-Compares the first `count` characters of two strings, ignoring differences in
-case. The return value is:
-
-*   `< 0` if `string1` is ASCII-betically lower than `string2`.
-
-*   `0` if the two strings are equal.
-
-*   `> 0` if `string1` is ASCII-betically higher than `string2`.
-
-This function is meant to be a drop-in replacement for `strncasecmp`.
-
-`string1`: The first string to compare. `string2`: The second string to compare.
-`count`: The number of characters to compare.
-
-#### Declaration ####
-
-```
-int SbStringCompareNoCaseN(const char *string1, const char *string2, size_t count)
-```
-
-### SbStringCompareWide ###
-
-Compares the first `count` characters of two 16-bit character strings. The
-return value is:
-
-*   `< 0` if `string1` is ASCII-betically lower than `string2`.
-
-*   `0` if the two strings are equal.
-
-*   `> 0` if `string1` is ASCII-betically higher than `string2`.
-
-This function is meant to be a drop-in replacement for `wcsncmp`.
-
-`string1`: The first 16-bit character string to compare.weird `string2`: The
-second 16-bit character string to compare. `count`: The number of characters to
-compare.
-
-#### Declaration ####
-
-```
-int SbStringCompareWide(const wchar_t *string1, const wchar_t *string2, size_t count)
-```
-
-### SbStringConcat ###
-
-Appends `source` to `out_destination` as long as `out_destination` has enough
-storage space to hold the concatenated string.
-
-This function is meant to be a drop-in replacement for `strlcat`. Also note that
-this function's signature is NOT compatible with `strncat`.
-
-`out_destination`: The string to which the `source` string is appended.
-`source`: The string to be appended to the destination string.
-`destination_size`: The amount of storage space available for the concatenated
-string.
-
-#### Declaration ####
-
-```
-int SbStringConcat(char *out_destination, const char *source, int destination_size)
-```
-
-### SbStringConcatUnsafe ###
-
-An inline wrapper for an unsafe SbStringConcat that assumes that the
-`out_destination` provides enough storage space for the concatenated string.
-Note that this function's signature is NOT compatible with `strcat`.
-
-`out_destination`: The string to which the `source` string is appended.
-`source`: The string to be appended to the destination string.
-
-#### Declaration ####
-
-```
-static int SbStringConcatUnsafe(char *out_destination, const char *source)
-```
-
-### SbStringConcatWide ###
-
-Identical to SbStringCat, but for wide characters.
-
-`out_destination`: The string to which the `source` string is appended.
-`source`: The string to be appended to the destination string.
-`destination_size`: The amount of storage space available for the concatenated
-string.
-
-#### Declaration ####
-
-```
-int SbStringConcatWide(wchar_t *out_destination, const wchar_t *source, int destination_size)
-```
-
-### SbStringCopy ###
-
-Copies as much of a `source` string as possible and null-terminates it, given
-that `destination_size` characters of storage are available. This function is
-meant to be a drop-in replacement for `strlcpy`.
-
-The return value specifies the length of `source`.
-
-`out_destination`: The location to which the string is copied. `source`: The
-string to be copied. `destination_size`: The amount of the source string to
-copy.
-
-#### Declaration ####
-
-```
-int SbStringCopy(char *out_destination, const char *source, int destination_size)
-```
-
-### SbStringCopyUnsafe ###
-
-An inline wrapper for an unsafe SbStringCopy that assumes that the destination
-provides enough storage space for the entire string. The return value is a
-pointer to the destination string. This function is meant to be a drop-in
-replacement for `strcpy`.
-
-`out_destination`: The location to which the string is copied. `source`: The
-string to be copied.
-
-#### Declaration ####
-
-```
-static char* SbStringCopyUnsafe(char *out_destination, const char *source)
-```
-
-### SbStringCopyWide ###
-
-Identical to SbStringCopy, but for wide characters.
-
-`out_destination`: The location to which the string is copied. `source`: The
-string to be copied. `destination_size`: The amount of the source string to
-copy.
-
-#### Declaration ####
-
-```
-int SbStringCopyWide(wchar_t *out_destination, const wchar_t *source, int destination_size)
-```
-
-### SbStringDuplicate ###
-
-Copies `source` into a buffer that is allocated by this function and that can be
-freed with SbMemoryDeallocate. This function is meant to be a drop-in
-replacement for `strdup`.
-
-`source`: The string to be copied.
-
-#### Declaration ####
-
-```
-char* SbStringDuplicate(const char *source)
-```
-
-### SbStringFindCharacter ###
-
-Finds the first occurrence of a `character` in `str`. The return value is a
-pointer to the found character in the given string or `NULL` if the character is
-not found. Note that this function's signature does NOT match that of the
-`strchr` function.
-
-`str`: The string to search for the character. `character`: The character to
-find in the string.
-
-#### Declaration ####
-
-```
-const char* SbStringFindCharacter(const char *str, char character)
-```
-
-### SbStringFindLastCharacter ###
-
-Finds the last occurrence of a specified character in a string. The return value
-is a pointer to the found character in the given string or `NULL` if the
-character is not found. Note that this function's signature does NOT match that
-of the `strrchr` function.
-
-`str`: The string to search for the character. `character`: The character to
-find in the string.
-
-#### Declaration ####
-
-```
-const char* SbStringFindLastCharacter(const char *str, char character)
-```
-
-### SbStringFindString ###
-
-Finds the first occurrence of `str2` in `str1`. The return value is a pointer to
-the beginning of the found string or `NULL` if the string is not found. This
-function is meant to be a drop-in replacement for `strstr`.
-
-`str1`: The string in which to search for the presence of `str2`. `str2`: The
-string to locate in `str1`.
-
-#### Declaration ####
-
-```
-const char* SbStringFindString(const char *str1, const char *str2)
-```
-
-### SbStringFormat ###
-
-Produces a string formatted with `format` and `arguments`, placing as much of
-the result that will fit into `out_buffer`. The return value specifies the
-number of characters that the format would produce if `buffer_size` were
-infinite.
-
-This function is meant to be a drop-in replacement for `vsnprintf`.
-
-`out_buffer`: The location where the formatted string is stored. `buffer_size`:
-The size of `out_buffer`. `format`: A string that specifies how the data should
-be formatted. `arguments`: Variable arguments used in the string.
-
-#### Declaration ####
-
-```
-int SbStringFormat(char *out_buffer, size_t buffer_size, const char *format, va_list arguments) SB_PRINTF_FORMAT(3
-```
-
-### SbStringFormatF ###
-
-An inline wrapper of SbStringFormat that converts from ellipsis to va_args. This
-function is meant to be a drop-in replacement for `snprintf`.
-
-`out_buffer`: The location where the formatted string is stored. `buffer_size`:
-The size of `out_buffer`. `format`: A string that specifies how the data should
-be formatted. `...`: Arguments used in the string.
-
-#### Declaration ####
-
-```
-int static int static int SbStringFormatF(char *out_buffer, size_t buffer_size, const char *format,...) SB_PRINTF_FORMAT(3
-```
-
-### SbStringFormatUnsafeF ###
-
-An inline wrapper of SbStringFormat that is meant to be a drop-in replacement
-for the unsafe but commonly used `sprintf`.
-
-`out_buffer`: The location where the formatted string is stored. `format`: A
-string that specifies how the data should be formatted. `...`: Arguments used in
-the string.
-
-#### Declaration ####
-
-```
-static int static int SbStringFormatUnsafeF(char *out_buffer, const char *format,...) SB_PRINTF_FORMAT(2
-```
-
-### SbStringFormatWide ###
-
-This function is identical to SbStringFormat, but is for wide characters. It is
-meant to be a drop-in replacement for `vswprintf`.
-
-`out_buffer`: The location where the formatted string is stored. `buffer_size`:
-The size of `out_buffer`. `format`: A string that specifies how the data should
-be formatted. `arguments`: Variable arguments used in the string.
-
-#### Declaration ####
-
-```
-int SbStringFormatWide(wchar_t *out_buffer, size_t buffer_size, const wchar_t *format, va_list arguments)
-```
-
-### SbStringFormatWideF ###
-
-An inline wrapper of SbStringFormatWide that converts from ellipsis to
-`va_args`.
-
-`out_buffer`: The location where the formatted string is stored. `buffer_size`:
-The size of `out_buffer`. `format`: A string that specifies how the data should
-be formatted. `...`: Arguments used in the string.
-
-#### Declaration ####
-
-```
-static int SbStringFormatWideF(wchar_t *out_buffer, size_t buffer_size, const wchar_t *format,...)
-```
-
-### SbStringGetLength ###
-
-Returns the length, in characters, of `str`.
-
-`str`: A zero-terminated ASCII string.
-
-#### Declaration ####
-
-```
-size_t SbStringGetLength(const char *str)
-```
-
-### SbStringGetLengthWide ###
-
-Returns the length of a wide character string. (This function is the same as
-SbStringGetLength, but for a string comprised of wide characters.) This function
-assumes that there are no multi-element characters.
-
-`str`: A zero-terminated ASCII string.
-
-#### Declaration ####
-
-```
-size_t SbStringGetLengthWide(const wchar_t *str)
-```
-
-### SbStringParseDouble ###
-
-Extracts a string that represents an integer from the beginning of `start` into
-a double.
-
-This function is meant to be a drop-in replacement for `strtod`, except that it
-is explicitly declared to return a double.
-
-`start`: The string that begins with the number to be converted. `out_end`: If
-provided, the function places a pointer to the end of the consumed portion of
-the string into `out_end`.
-
-#### Declaration ####
-
-```
-double SbStringParseDouble(const char *start, char **out_end)
-```
-
-### SbStringParseSignedInteger ###
-
-Extracts a string that represents an integer from the beginning of `start` into
-a signed integer in the given `base`. This function is meant to be a drop-in
-replacement for `strtol`.
-
-`start`: The string that begins with the number to be converted. `out_end`: If
-provided, the function places a pointer to the end of the consumed portion of
-the string into `out_end`. `base`: The base into which the number will be
-converted. The value must be between `2` and `36`, inclusive.
-NOLINTNEXTLINE(runtime/int)
-
-#### Declaration ####
-
-```
-long SbStringParseSignedInteger(const char *start, char **out_end, int base)
-```
-
-### SbStringParseUInt64 ###
-
-Extracts a string that represents an integer from the beginning of `start` into
-an unsigned 64-bit integer in the given `base`.
-
-This function is meant to be a drop-in replacement for `strtoull`, except that
-it is explicitly declared to return `uint64_t`.
-
-`start`: The string that begins with the number to be converted. `out_end`: If
-provided, the function places a pointer to the end of the consumed portion of
-the string into `out_end`. `base`: The base into which the number will be
-converted. The value must be between `2` and `36`, inclusive.
-
-#### Declaration ####
-
-```
-uint64_t SbStringParseUInt64(const char *start, char **out_end, int base)
-```
-
-### SbStringParseUnsignedInteger ###
-
-Extracts a string that represents an integer from the beginning of `start` into
-an unsigned integer in the given `base`. This function is meant to be a drop-in
-replacement for `strtoul`.
-
-`start`: The string that begins with the number to be converted. `out_end`: If
-provided, the function places a pointer to the end of the consumed portion of
-the string into `out_end`. `base`: The base into which the number will be
-converted. The value must be between `2` and `36`, inclusive.
-NOLINTNEXTLINE(runtime/int)
-
-#### Declaration ####
-
-```
-unsigned long SbStringParseUnsignedInteger(const char *start, char **out_end, int base)
-```
-
-### SbStringScan ###
-
-Scans `buffer` for `pattern`, placing the extracted values in `arguments`. The
-return value specifies the number of successfully matched items, which may be
-`0`.
-
-This function is meant to be a drop-in replacement for `vsscanf`.
-
-`buffer`: The string to scan for the pattern. `pattern`: The string to search
-for in `buffer`. `arguments`: Values matching `pattern` that were extracted from
-`buffer`.
-
-#### Declaration ####
-
-```
-int SbStringScan(const char *buffer, const char *pattern, va_list arguments)
-```
-
-### SbStringScanF ###
-
-An inline wrapper of SbStringScan that converts from ellipsis to `va_args`. This
-function is meant to be a drop-in replacement for `sscanf`. `buffer`: The string
-to scan for the pattern. `pattern`: The string to search for in `buffer`. `...`:
-Values matching `pattern` that were extracted from `buffer`.
-
-#### Declaration ####
-
-```
-static int SbStringScanF(const char *buffer, const char *pattern,...)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/12/configuration.md b/cobalt/site/docs/reference/starboard/modules/12/configuration.md
index 038d8ff..0cb5c19 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/configuration.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/configuration.md
@@ -139,7 +139,11 @@
 
 ### SB_RESTRICT ###
 
-Include the platform-specific configuration.
+Include the platform-specific configuration. This macro is set by GN in
+starboard/build/config/BUILD.gn and passed in on the command line for all
+targets and all configurations.Makes a pointer-typed parameter restricted so
+that the compiler can make certain optimizations because it knows the pointers
+are unique.
 
 ### SB_SIZE_OF(DATATYPE) ###
 
diff --git a/cobalt/site/docs/reference/starboard/modules/12/decode_target.md b/cobalt/site/docs/reference/starboard/modules/12/decode_target.md
index 852af38..2e61c1e 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/decode_target.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/decode_target.md
@@ -5,8 +5,7 @@
 
 A target for decoding image and video data into. This corresponds roughly to an
 EGLImage, but that extension may not be fully supported on all GL platforms.
-SbDecodeTarget supports multi-plane targets. The SbBlitter API is supported as
-well, and this is able to more-or-less unify the two.
+SbDecodeTarget supports multi-plane targets.
 
 An SbDecodeTarget can be passed into any function which decodes video or image
 data. This allows the application to allocate fast graphics memory, and have
@@ -117,7 +116,7 @@
 *   `kSbDecodeTargetFormat3Plane10BitYUVI420`
 
     A decoder target format consisting of 10bit Y, U, and V planes, in that
-    order.
+    order. Each pixel is stored in 16 bits.
 *   `kSbDecodeTargetFormat1PlaneUYVY`
 
     A decoder target format consisting of a single plane with pixels laid out in
@@ -394,4 +393,3 @@
 ```
 static void SbDecodeTargetRunInGlesContext(SbDecodeTargetGraphicsContextProvider *provider, SbDecodeTargetGlesContextRunnerTarget target, void *target_context)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/12/event.md b/cobalt/site/docs/reference/starboard/modules/12/event.md
index 226d79e..cd85db6 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/event.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/event.md
@@ -127,9 +127,9 @@
 
 `PRELOADING` can only happen as the first application state. In this state, the
 application should start and run as normal, but will not receive any input, and
-should not try to initialize graphics resources (via GL or `SbBlitter`). In
-`PRELOADING`, the application can receive `Start` or `Suspend` events. `Start`
-will receive the same data that was passed into `Preload`.
+should not try to initialize graphics resources via GL. In `PRELOADING`, the
+application can receive `Start` or `Suspend` events. `Start` will receive the
+same data that was passed into `Preload`.
 
 In the `STARTED` state, the application is in the foreground and can expect to
 do all of the normal things it might want to do. Once in the `STARTED` state, it
@@ -164,10 +164,10 @@
 *   `kSbEventTypePreload`
 
     Applications should perform initialization and prepare to react to
-    subsequent events, but must not initialize any graphics resources (through
-    GL or SbBlitter). The intent of this event is to allow the application to do
-    as much work as possible ahead of time, so that when the application is
-    first brought to the foreground, it's as fast as a resume.
+    subsequent events, but must not initialize any graphics resources through
+    GL. The intent of this event is to allow the application to do as much work
+    as possible ahead of time, so that when the application is first brought to
+    the foreground, it's as fast as a resume.
 
     The `kSbEventTypeStart` event may be sent at any time, regardless of
     initialization state. Input events will not be sent in the `PRELOADING`
@@ -319,8 +319,8 @@
     ticket value kSbEventOnScreenKeyboardInvalidTicket.
 *   `kSbEventTypeAccessibilityCaptionSettingsChanged`
 
-    SB_HAS(ON_SCREEN_KEYBOARD)One or more of the fields returned by
-    SbAccessibilityGetCaptionSettings has changed.
+    One or more of the fields returned by SbAccessibilityGetCaptionSettings has
+    changed.
 *   `kSbEventTypeAccessiblityTextToSpeechSettingsChanged`
 
     The platform's text-to-speech settings have changed.
@@ -453,4 +453,3 @@
 ```
 SbEventId SbEventSchedule(SbEventCallback callback, void *context, SbTime delay)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/12/image.md b/cobalt/site/docs/reference/starboard/modules/12/image.md
index 634dab8..3050953 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/image.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/image.md
@@ -13,8 +13,6 @@
 
 ## SbImageIsDecodeSupported and SbImageDecode Example ##
 
-Let's assume that we're on a Blitter platform.
-
 ```
 SbDecodeTargetProvider* provider = GetProviderFromSomewhere();
 void* data = GetCompressedJPEGFromSomewhere();
@@ -28,9 +26,6 @@
 
 SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size,
                                              mime_type, format);
-SbBlitterSurface surface =
-    SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
-// Do stuff with surface...
 ```
 
 ## Functions ##
@@ -79,4 +74,3 @@
 ```
 bool SbImageIsDecodeSupported(const char *mime_type, SbDecodeTargetFormat format)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/12/media.md b/cobalt/site/docs/reference/starboard/modules/12/media.md
index 9b4d7bb..a9baced 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/media.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/media.md
@@ -517,9 +517,8 @@
 ### SbMediaGetBufferAlignment ###
 
 The media buffer will be allocated using the returned alignment. Set this to a
-larger value may increase the memory consumption of media buffers.
-
-`type`: the media type of the stream (audio or video).
+larger value may increase the memory consumption of media buffers.`type`: the
+media type of the stream (audio or video).
 
 #### Declaration ####
 
@@ -562,9 +561,7 @@
 
 Extra bytes allocated at the end of a media buffer to ensure that the buffer can
 be use optimally by specific instructions like SIMD. Set to 0 to remove any
-padding.
-
-`type`: the media type of the stream (audio or video).
+padding.`type`: the media type of the stream (audio or video).
 
 #### Declaration ####
 
@@ -604,7 +601,12 @@
 ### SbMediaGetMaxBufferCapacity ###
 
 The maximum amount of memory that will be used to store media buffers. This must
-be larger than sum of the video budget and audio budget.
+be larger than sum of the video budget and audio budget. This is a soft limit
+and the app will continue to allocate media buffers even if the accumulated
+memory used by the media buffers exceeds the maximum buffer capacity. The
+allocation of media buffers may only fail when there is not enough memory in the
+system to fulfill the request, under which case the app will be terminated as
+under other OOM situations.
 
 `codec`: the video codec associated with the buffer. `resolution_width`: the
 width of the video resolution. `resolution_height`: the height of the video
@@ -717,4 +719,3 @@
 ```
 void SbMediaSetAudioWriteDuration(SbTime duration)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/12/system.md b/cobalt/site/docs/reference/starboard/modules/12/system.md
index 0a135ce..1fe2ddb 100644
--- a/cobalt/site/docs/reference/starboard/modules/12/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/12/system.md
@@ -667,10 +667,6 @@
 Computes a HMAC-SHA256 digest of `message` into `digest` using the application's
 certification secret.
 
-This function may be implemented as an alternative to implementing
-SbSystemGetProperty(kSbSystemPropertyBase64EncodedCertificationSecret), however
-both should not be implemented.
-
 The output will be written into `digest`. `digest_size_in_bytes` must be 32 (or
 greater), since 32-bytes will be written into it. Returns false in the case of
 an error, or if it is not implemented. In this case the contents of `digest`
@@ -730,4 +726,3 @@
 ```
 bool SbSystemSymbolize(const void *address, char *out_buffer, int buffer_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/13/configuration.md b/cobalt/site/docs/reference/starboard/modules/13/configuration.md
index 476e313..630f1cf 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/configuration.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/configuration.md
@@ -134,7 +134,12 @@
 
 ### SB_RESTRICT ###
 
-Include the platform-specific configuration.
+Include the platform-specific configuration. This macro is set by GN in
+starboard/build/config/BUILD.gn and passed in on the command line for all
+targets and all configurations.Makes a pointer-typed parameter restricted so
+that the compiler can make certain optimizations because it knows the pointers
+are unique.
+
 ### SB_SIZE_OF(DATATYPE) ###
 
 Determines at compile-time the size of a data type, or 0 if the data type that
diff --git a/cobalt/site/docs/reference/starboard/modules/13/decode_target.md b/cobalt/site/docs/reference/starboard/modules/13/decode_target.md
index 852af38..2e61c1e 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/decode_target.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/decode_target.md
@@ -5,8 +5,7 @@
 
 A target for decoding image and video data into. This corresponds roughly to an
 EGLImage, but that extension may not be fully supported on all GL platforms.
-SbDecodeTarget supports multi-plane targets. The SbBlitter API is supported as
-well, and this is able to more-or-less unify the two.
+SbDecodeTarget supports multi-plane targets.
 
 An SbDecodeTarget can be passed into any function which decodes video or image
 data. This allows the application to allocate fast graphics memory, and have
@@ -117,7 +116,7 @@
 *   `kSbDecodeTargetFormat3Plane10BitYUVI420`
 
     A decoder target format consisting of 10bit Y, U, and V planes, in that
-    order.
+    order. Each pixel is stored in 16 bits.
 *   `kSbDecodeTargetFormat1PlaneUYVY`
 
     A decoder target format consisting of a single plane with pixels laid out in
@@ -394,4 +393,3 @@
 ```
 static void SbDecodeTargetRunInGlesContext(SbDecodeTargetGraphicsContextProvider *provider, SbDecodeTargetGlesContextRunnerTarget target, void *target_context)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/13/event.md b/cobalt/site/docs/reference/starboard/modules/13/event.md
index 601e343..8d20c6f 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/event.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/event.md
@@ -127,9 +127,9 @@
 
 `PRELOADING` can only happen as the first application state. In this state, the
 application should start and run as normal, but will not receive any input, and
-should not try to initialize graphics resources (via GL or `SbBlitter`). In
-`PRELOADING`, the application can receive `Start` or `Suspend` events. `Start`
-will receive the same data that was passed into `Preload`.
+should not try to initialize graphics resources via GL. In `PRELOADING`, the
+application can receive `Start` or `Suspend` events. `Start` will receive the
+same data that was passed into `Preload`.
 
 In the `STARTED` state, the application is in the foreground and can expect to
 do all of the normal things it might want to do. Once in the `STARTED` state, it
@@ -322,8 +322,8 @@
     ticket value kSbEventOnScreenKeyboardInvalidTicket.
 *   `kSbEventTypeAccessibilityCaptionSettingsChanged`
 
-    SB_HAS(ON_SCREEN_KEYBOARD)One or more of the fields returned by
-    SbAccessibilityGetCaptionSettings has changed.
+    One or more of the fields returned by SbAccessibilityGetCaptionSettings has
+    changed.
 *   `kSbEventTypeAccessibilityTextToSpeechSettingsChanged`
 
     The platform's text-to-speech settings have changed.
@@ -475,4 +475,3 @@
 ```
 SbEventId SbEventSchedule(SbEventCallback callback, void *context, SbTime delay)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/13/image.md b/cobalt/site/docs/reference/starboard/modules/13/image.md
index 634dab8..3050953 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/image.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/image.md
@@ -13,8 +13,6 @@
 
 ## SbImageIsDecodeSupported and SbImageDecode Example ##
 
-Let's assume that we're on a Blitter platform.
-
 ```
 SbDecodeTargetProvider* provider = GetProviderFromSomewhere();
 void* data = GetCompressedJPEGFromSomewhere();
@@ -28,9 +26,6 @@
 
 SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size,
                                              mime_type, format);
-SbBlitterSurface surface =
-    SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
-// Do stuff with surface...
 ```
 
 ## Functions ##
@@ -79,4 +74,3 @@
 ```
 bool SbImageIsDecodeSupported(const char *mime_type, SbDecodeTargetFormat format)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/13/media.md b/cobalt/site/docs/reference/starboard/modules/13/media.md
index 6b46874..968db5b 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/media.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/media.md
@@ -517,9 +517,8 @@
 ### SbMediaGetBufferAlignment ###
 
 The media buffer will be allocated using the returned alignment. Set this to a
-larger value may increase the memory consumption of media buffers.
-
-`type`: the media type of the stream (audio or video).
+larger value may increase the memory consumption of media buffers.`type`: the
+media type of the stream (audio or video).
 
 #### Declaration ####
 
@@ -562,9 +561,7 @@
 
 Extra bytes allocated at the end of a media buffer to ensure that the buffer can
 be use optimally by specific instructions like SIMD. Set to 0 to remove any
-padding.
-
-`type`: the media type of the stream (audio or video).
+padding.`type`: the media type of the stream (audio or video).
 
 #### Declaration ####
 
@@ -604,7 +601,12 @@
 ### SbMediaGetMaxBufferCapacity ###
 
 The maximum amount of memory that will be used to store media buffers. This must
-be larger than sum of the video budget and audio budget.
+be larger than sum of the video budget and audio budget. This is a soft limit
+and the app will continue to allocate media buffers even if the accumulated
+memory used by the media buffers exceeds the maximum buffer capacity. The
+allocation of media buffers may only fail when there is not enough memory in the
+system to fulfill the request, under which case the app will be terminated as
+under other OOM situations.
 
 `codec`: the video codec associated with the buffer. `resolution_width`: the
 width of the video resolution. `resolution_height`: the height of the video
@@ -699,4 +701,3 @@
 ```
 void SbMediaSetAudioWriteDuration(SbTime duration)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/13/system.md b/cobalt/site/docs/reference/starboard/modules/13/system.md
index d6ce62e..dd71ecf 100644
--- a/cobalt/site/docs/reference/starboard/modules/13/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/13/system.md
@@ -686,10 +686,6 @@
 Computes a HMAC-SHA256 digest of `message` into `digest` using the application's
 certification secret.
 
-This function may be implemented as an alternative to implementing
-SbSystemGetProperty(kSbSystemPropertyBase64EncodedCertificationSecret), however
-both should not be implemented.
-
 The output will be written into `digest`. `digest_size_in_bytes` must be 32 (or
 greater), since 32-bytes will be written into it. Returns false in the case of
 an error, or if it is not implemented. In this case the contents of `digest`
@@ -732,4 +728,3 @@
 ```
 bool SbSystemSymbolize(const void *address, char *out_buffer, int buffer_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/accessibility.md b/cobalt/site/docs/reference/starboard/modules/14/accessibility.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/accessibility.md
rename to cobalt/site/docs/reference/starboard/modules/14/accessibility.md
index a6c8460..1a5adcb 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/accessibility.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/accessibility.md
@@ -251,4 +251,3 @@
 ```
 bool SbAccessibilitySetCaptionsEnabled(bool enabled)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/atomic.md b/cobalt/site/docs/reference/starboard/modules/14/atomic.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/atomic.md
rename to cobalt/site/docs/reference/starboard/modules/14/atomic.md
index 5f79990..9741f3c 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/atomic.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/atomic.md
@@ -108,4 +108,3 @@
 ```
 static SbAtomic8 SbAtomicRelease_CompareAndSwap8(volatile SbAtomic8 *ptr, SbAtomic8 old_value, SbAtomic8 new_value)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/audio_sink.md b/cobalt/site/docs/reference/starboard/modules/14/audio_sink.md
similarity index 95%
rename from cobalt/site/docs/reference/starboard/modules/11/audio_sink.md
rename to cobalt/site/docs/reference/starboard/modules/14/audio_sink.md
index 000ddf3..3b3789a 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/audio_sink.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/audio_sink.md
@@ -27,15 +27,12 @@
 ### SbAudioSinkConsumeFramesFunc ###
 
 Callback used to report frames consumed. The consumed frames will be removed
-from the source frame buffer to free space for new audio frames.When
-`frames_consumed` is updated asynchnously and the last time that it has been
-updated is known, it can be passed in `frames_consumed_at` so the audio time
-calculating can be more accurate.
+from the source frame buffer to free space for new audio frames.
 
 #### Definition ####
 
 ```
-typedef void(* SbAudioSinkConsumeFramesFunc) (int frames_consumed, SbTime frames_consumed_at, void *context)
+typedef void(* SbAudioSinkConsumeFramesFunc) (int frames_consumed, void *context)
 ```
 
 ### SbAudioSinkFrameBuffers ###
@@ -168,4 +165,3 @@
 ```
 bool SbAudioSinkIsValid(SbAudioSink audio_sink)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/byte_swap.md b/cobalt/site/docs/reference/starboard/modules/14/byte_swap.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/byte_swap.md
rename to cobalt/site/docs/reference/starboard/modules/14/byte_swap.md
index 580bc5d..c83927d 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/byte_swap.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/byte_swap.md
@@ -73,4 +73,3 @@
 ```
 uint64_t SbByteSwapU64(uint64_t value)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/condition_variable.md b/cobalt/site/docs/reference/starboard/modules/14/condition_variable.md
similarity index 87%
rename from cobalt/site/docs/reference/starboard/modules/11/condition_variable.md
rename to cobalt/site/docs/reference/starboard/modules/14/condition_variable.md
index 9fdcdb1..e58e106 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/condition_variable.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/condition_variable.md
@@ -5,6 +5,12 @@
 
 Defines an interface for condition variables.
 
+## Macros ##
+
+### SB_CONDITION_VARIABLE_MAX_SIZE ###
+
+Max size of the SbConditionVariable type.
+
 ## Enums ##
 
 ### SbConditionVariableResult ###
@@ -24,6 +30,19 @@
     The wait failed, either because a parameter wasn't valid, or the condition
     variable has already been destroyed, or something similar.
 
+## Typedefs ##
+
+### SbConditionVariable ###
+
+An opaque handle to a condition variable type with reserved memory buffer of
+size SB_CONDITION_VARIABLE_MAX_SIZE and aligned at void pointer type.
+
+#### Definition ####
+
+```
+typedef union SbConditionVariable  SbConditionVariable
+```
+
 ## Functions ##
 
 ### SbConditionVariableBroadcast ###
@@ -55,7 +74,7 @@
 
 ### SbConditionVariableDestroy ###
 
-Destroys the specified SbConditionVariable. The return value indicates whether
+Destroys the specified SbConditionVariable . The return value indicates whether
 the destruction was successful. The behavior is undefined if other threads are
 currently waiting on this condition variable.
 
@@ -119,4 +138,3 @@
 ```
 SbConditionVariableResult SbConditionVariableWaitTimed(SbConditionVariable *condition, SbMutex *mutex, SbTime timeout_duration)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/configuration.md b/cobalt/site/docs/reference/starboard/modules/14/configuration.md
similarity index 89%
rename from cobalt/site/docs/reference/starboard/modules/11/configuration.md
rename to cobalt/site/docs/reference/starboard/modules/14/configuration.md
index f3f522e..630f1cf 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/configuration.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/configuration.md
@@ -53,11 +53,6 @@
 SB_DEPRECATED_EXTERNAL(...) annotates the function as deprecated for external
 clients, but not deprecated for starboard.
 
-### SB_DISALLOW_COPY_AND_ASSIGN(TypeName) ###
-
-A macro to disallow the copy constructor and operator= functions This should be
-used in the private: declarations for a class
-
 ### SB_EXPERIMENTAL_API_VERSION ###
 
 The API version that is currently open for changes, and therefore is not stable
@@ -85,12 +80,6 @@
 variable `gl_type` gl_type which indicates what kind of GL implementation is
 available.
 
-### SB_HAS_MEDIA_IS_VIDEO_SUPPORTED_REFINEMENT ###
-
-Legal values for SB_PREFERRED_RGBA_BYTE_ORDER are defined in this file above as
-SB_PREFERRED_RGBA_BYTE_ORDER_*. If your platform uses GLES, you should set this
-to SB_PREFERRED_RGBA_BYTE_ORDER_RGBA.
-
 ### SB_HAS_QUIRK(SB_FEATURE) ###
 
 Determines at compile-time whether this platform has a quirk.
@@ -103,10 +92,6 @@
 
 Determines at compile-time an inherent aspect of this platform.
 
-### SB_IS_LITTLE_ENDIAN ###
-
-Whether the current platform is little endian.
-
 ### SB_LIKELY(x) ###
 
 Macro for hinting that an expression is likely to be true.
@@ -149,7 +134,11 @@
 
 ### SB_RESTRICT ###
 
-Include the platform-specific configuration.
+Include the platform-specific configuration. This macro is set by GN in
+starboard/build/config/BUILD.gn and passed in on the command line for all
+targets and all configurations.Makes a pointer-typed parameter restricted so
+that the compiler can make certain optimizations because it knows the pointers
+are unique.
 
 ### SB_SIZE_OF(DATATYPE) ###
 
diff --git a/cobalt/site/docs/reference/starboard/modules/14/configuration_constants.md b/cobalt/site/docs/reference/starboard/modules/14/configuration_constants.md
new file mode 100644
index 0000000..51212e9
--- /dev/null
+++ b/cobalt/site/docs/reference/starboard/modules/14/configuration_constants.md
@@ -0,0 +1,149 @@
+---
+layout: doc
+title: "Starboard Module Reference: configuration_constants.h"
+---
+
+Declares all configuration variables we will need to use at runtime. These
+variables describe the current platform in detail to allow cobalt to make
+runtime decisions based on per platform configurations.
+
+## Variables ##
+
+### kSbDefaultMmapThreshold ###
+
+Determines the threshold of allocation size that should be done with mmap (if
+available), rather than allocated within the core heap.
+
+### kSbFileAltSepChar ###
+
+The current platform's alternate file path component separator character. This
+is like SB_FILE_SEP_CHAR, except if your platform supports an alternate
+character, then you can place that here. For example, on windows machines, the
+primary separator character is probably '\', but the alternate is '/'.
+
+### kSbFileAltSepString ###
+
+The string form of SB_FILE_ALT_SEP_CHAR.
+
+### kSbFileMaxName ###
+
+The current platform's maximum length of the name of a single directory entry,
+not including the absolute path.
+
+### kSbFileMaxOpen ###
+
+The current platform's maximum number of files that can be opened at the same
+time by one process.
+
+### kSbFileMaxPath ###
+
+The current platform's maximum length of an absolute path.
+
+### kSbFileSepChar ###
+
+The current platform's file path component separator character. This is the
+character that appears after a directory in a file path. For example, the
+absolute canonical path of the file "/path/to/a/file.txt" uses '/' as a path
+component separator character.
+
+### kSbFileSepString ###
+
+The string form of SB_FILE_SEP_CHAR.
+
+### kSbHasAc3Audio ###
+
+Allow ac3 and ec3 support
+
+### kSbHasMediaWebmVp9Support ###
+
+Specifies whether this platform has webm/vp9 support. This should be set to non-
+zero on platforms with webm/vp9 support.
+
+### kSbHasThreadPrioritySupport ###
+
+Whether the current platform supports thread priorities.
+
+### kSbMallocAlignment ###
+
+Determines the alignment that allocations should have on this platform.
+
+### kSbMaxSystemPathCacheDirectorySize ###
+
+The maximum size the cache directory is allowed to use in bytes.
+
+### kSbMaxThreadLocalKeys ###
+
+The maximum number of thread local storage keys supported by this platform. This
+comes from _POSIX_THREAD_KEYS_MAX. The value of PTHREAD_KEYS_MAX is higher, but
+unit tests show that the implementation doesn't support nearly as many keys.
+
+### kSbMaxThreadNameLength ###
+
+The maximum length of the name for a thread, including the NULL-terminator.
+
+### kSbMaxThreads ###
+
+Defines the maximum number of simultaneous threads for this platform. Some
+platforms require sharing thread handles with other kinds of system handles,
+like mutexes, so we want to keep this manageable.
+
+### kSbMediaMaxAudioBitrateInBitsPerSecond ###
+
+The maximum audio bitrate the platform can decode. The following value equals to
+5M bytes per seconds which is more than enough for compressed audio.
+
+### kSbMediaMaxVideoBitrateInBitsPerSecond ###
+
+The maximum video bitrate the platform can decode. The following value equals to
+8M bytes per seconds which is more than enough for compressed video.
+
+### kSbMediaVideoFrameAlignment ###
+
+Specifies how video frame buffers must be aligned on this platform.
+
+### kSbMemoryLogPath ###
+
+Defines the path where memory debugging logs should be written to.
+
+### kSbMemoryPageSize ###
+
+The memory page size, which controls the size of chunks on memory that
+allocators deal with, and the alignment of those chunks. This doesn't have to be
+the hardware-defined physical page size, but it should be a multiple of it.
+
+### kSbNetworkReceiveBufferSize ###
+
+Specifies the network receive buffer size in bytes, set via
+SbSocketSetReceiveBufferSize().
+
+Setting this to 0 indicates that SbSocketSetReceiveBufferSize() should not be
+called. Use this for OSs (such as Linux) where receive buffer auto-tuning is
+better.
+
+On some platforms, this may affect max TCP window size which may dramatically
+affect throughput in the presence of latency.
+
+If your platform does not have a good TCP auto-tuning mechanism, a setting of
+(128 * 1024) here is recommended.
+
+### kSbPathSepChar ###
+
+The current platform's search path component separator character. When
+specifying an ordered list of absolute paths of directories to search for a
+given reason, this is the character that appears between entries. For example,
+the search path of "/etc/search/first:/etc/search/second" uses ':' as a search
+path component separator character.
+
+### kSbPathSepString ###
+
+The string form of SB_PATH_SEP_CHAR.
+
+### kSbPreferredRgbaByteOrder ###
+
+Specifies the preferred byte order of color channels in a pixel. Refer to
+starboard/configuration.h for the possible values. EGL/GLES platforms should
+generally prefer a byte order of RGBA, regardless of endianness.
+
+### kSbUserMaxSignedIn ###
+
+The maximum number of users that can be signed in at the same time.
diff --git a/cobalt/site/docs/reference/starboard/modules/11/cpu_features.md b/cobalt/site/docs/reference/starboard/modules/14/cpu_features.md
similarity index 98%
rename from cobalt/site/docs/reference/starboard/modules/11/cpu_features.md
rename to cobalt/site/docs/reference/starboard/modules/14/cpu_features.md
index d1f8a7e..7a4bcc0 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/cpu_features.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/cpu_features.md
@@ -87,6 +87,9 @@
 *   `bool has_vfp3`
 
     VFP version 3
+*   `bool has_vfp4`
+
+    VFP version 4
 *   `bool has_vfp3_d32`
 
     VFP version 3 with 32 D-registers.
@@ -168,6 +171,9 @@
 *   `bool has_sse3`
 
     SSE3 extensions.
+*   `bool has_pclmulqdq`
+
+    PCLMULQDQ instruction.
 *   `bool has_ssse3`
 
     Supplemental SSE3 extensions.
@@ -253,4 +259,3 @@
 ```
 bool SbCPUFeaturesGet(SbCPUFeatures *features)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/decode_target.md b/cobalt/site/docs/reference/starboard/modules/14/decode_target.md
similarity index 97%
rename from cobalt/site/docs/reference/starboard/modules/11/decode_target.md
rename to cobalt/site/docs/reference/starboard/modules/14/decode_target.md
index 2899dba..0e912d5 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/decode_target.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/decode_target.md
@@ -5,8 +5,7 @@
 
 A target for decoding image and video data into. This corresponds roughly to an
 EGLImage, but that extension may not be fully supported on all GL platforms.
-SbDecodeTarget supports multi-plane targets. The SbBlitter API is supported as
-well, and this is able to more-or-less unify the two.
+SbDecodeTarget supports multi-plane targets.
 
 An SbDecodeTarget can be passed into any function which decodes video or image
 data. This allows the application to allocate fast graphics memory, and have
@@ -117,7 +116,12 @@
 *   `kSbDecodeTargetFormat3Plane10BitYUVI420`
 
     A decoder target format consisting of 10bit Y, U, and V planes, in that
-    order.
+    order. Each pixel is stored in 16 bits.
+*   `kSbDecodeTargetFormat3Plane10BitYUVI420Compact`
+
+    A decoder target format consisting of 10bit Y, U, and V planes, in that
+    order. The plane data is stored in a compact format. Every three 10-bit
+    pixels are packed into 32 bits.
 *   `kSbDecodeTargetFormat1PlaneUYVY`
 
     A decoder target format consisting of a single plane with pixels laid out in
@@ -208,10 +212,6 @@
 
 #### Members ####
 
-*   `SbBlitterDevice device`
-
-    The SbBlitterDevice object that will be used to render any produced
-    SbDecodeTargets.
 *   `void * egl_display`
 
     A reference to the EGLDisplay object that hosts the EGLContext that will be
@@ -289,9 +289,6 @@
 
 #### Members ####
 
-*   `SbBlitterSurface surface`
-
-    A handle to the Blitter surface that can be used for rendering.
 *   `uint32_t texture`
 
     A handle to the GL texture that can be used for rendering.
@@ -401,4 +398,3 @@
 ```
 static void SbDecodeTargetRunInGlesContext(SbDecodeTargetGraphicsContextProvider *provider, SbDecodeTargetGlesContextRunnerTarget target, void *target_context)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/directory.md b/cobalt/site/docs/reference/starboard/modules/14/directory.md
similarity index 84%
rename from cobalt/site/docs/reference/starboard/modules/11/directory.md
rename to cobalt/site/docs/reference/starboard/modules/14/directory.md
index 2ec9cca..dcdf48a 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/directory.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/directory.md
@@ -23,18 +23,6 @@
 typedef struct SbDirectoryPrivate* SbDirectory
 ```
 
-## Structs ##
-
-### SbDirectoryEntry ###
-
-Represents a directory entry.
-
-#### Members ####
-
-*   `char name`
-
-    The name of this directory entry.
-
 ## Functions ##
 
 ### SbDirectoryCanOpen ###
@@ -86,15 +74,19 @@
 in the directory stream.
 
 This function returns `true` if there was a next directory, and `false` at the
-end of the directory stream.
+end of the directory stream or if `out_entry_size` is smaller than
+kSbFileMaxName.
 
 `directory`: The directory stream from which to retrieve the next directory.
-`out_entry`: The variable to be populated with the next directory entry.
+`out_entry`: The null terminated string to be populated with the next directory
+entry. The space allocated for this string should be equal to `out_entry_size`.
+`out_entry_size`: The size of the space allocated for `out_entry`. This should
+be at least equal to kSbFileMaxName.
 
 #### Declaration ####
 
 ```
-bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry *out_entry)
+bool SbDirectoryGetNext(SbDirectory directory, char *out_entry, size_t out_entry_size)
 ```
 
 ### SbDirectoryIsValid ###
@@ -123,4 +115,3 @@
 ```
 SbDirectory SbDirectoryOpen(const char *path, SbFileError *out_error)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/drm.md b/cobalt/site/docs/reference/starboard/modules/14/drm.md
similarity index 86%
rename from cobalt/site/docs/reference/starboard/modules/11/drm.md
rename to cobalt/site/docs/reference/starboard/modules/14/drm.md
index 23810d1..c7f487e 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/drm.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/drm.md
@@ -18,6 +18,15 @@
 
 ## Enums ##
 
+### SbDrmEncryptionScheme ###
+
+Encryption scheme of the input sample, as defined in ISO/IEC 23001 part 7.
+
+#### Values ####
+
+*   `kSbDrmEncryptionSchemeAesCtr`
+*   `kSbDrmEncryptionSchemeAesCbc`
+
 ### SbDrmKeyStatus ###
 
 Status of a particular media key. [https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus](https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus)
@@ -160,6 +169,15 @@
 
 ## Structs ##
 
+### SbDrmEncryptionPattern ###
+
+Encryption pattern of the input sample, as defined in ISO/IEC 23001 part 7.
+
+#### Members ####
+
+*   `uint32_t crypt_byte_block`
+*   `uint32_t skip_byte_block`
+
 ### SbDrmKeyId ###
 
 #### Members ####
@@ -176,6 +194,12 @@
 
 #### Members ####
 
+*   `SbDrmEncryptionScheme encryption_scheme`
+
+    The encryption scheme of this sample.
+*   `SbDrmEncryptionPattern encryption_pattern`
+
+    The encryption pattern of this sample.
 *   `uint8_t initialization_vector`
 
     The Initialization Vector needed to decrypt this sample.
@@ -276,6 +300,36 @@
 void SbDrmGenerateSessionUpdateRequest(SbDrmSystem drm_system, int ticket, const char *type, const void *initialization_data, int initialization_data_size)
 ```
 
+### SbDrmGetMetrics ###
+
+Get the metrics of the underlying drm system.
+
+When it is called on an implementation that supports drm system metrics, it
+should return a pointer containing the metrics as a blob, encoded using url safe
+base64 without padding and line wrapping, with the size of the encoded result in
+`size` on return. For example, on Android API level 28 or later, it should
+return the result of MediaDrm.getPropertyByteArray("metrics"), encoded using url
+safe base64 without padding and line wrapping. On systems using Widevine CE CDM
+with oemcrypto 16 or later, it should return the metrics retrieved via
+Cdm::getMetrics(), encoded using url safe base64 without padding and line
+wrapping. The returned pointer should remain valid and its content should remain
+unmodified until the next time this function is called on the associated
+`drm_system` or the `drm_system` is destroyed.
+
+When the metrics is empty on supported system, it should return a non-null
+pointer with `size` set to 0.
+
+It should return NULL when there is no metrics support in the underlying drm
+system, or when the drm system implementation fails to retrieve the metrics.
+
+The caller will never set `size` to NULL.
+
+#### Declaration ####
+
+```
+const void* SbDrmGetMetrics(SbDrmSystem drm_system, int *size)
+```
+
 ### SbDrmIsServerCertificateUpdatable ###
 
 Returns true if server certificate of `drm_system` can be updated via
@@ -355,4 +409,3 @@
 ```
 void SbDrmUpdateSession(SbDrmSystem drm_system, int ticket, const void *key, int key_size, const void *session_id, int session_id_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/egl.md b/cobalt/site/docs/reference/starboard/modules/14/egl.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/egl.md
rename to cobalt/site/docs/reference/starboard/modules/14/egl.md
index b1e120b..c8c2120 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/egl.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/egl.md
@@ -144,4 +144,3 @@
     config, void *native_pixmap, const SbEglAttrib *attrib_list)`
 *   `SbEglBoolean(*eglWaitSync)(SbEglDisplay dpy, SbEglSync sync, SbEglInt32
     flags)`
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/event.md b/cobalt/site/docs/reference/starboard/modules/14/event.md
similarity index 77%
rename from cobalt/site/docs/reference/starboard/modules/11/event.md
rename to cobalt/site/docs/reference/starboard/modules/14/event.md
index bdf9d3a..8d20c6f 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/event.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/event.md
@@ -127,9 +127,9 @@
 
 `PRELOADING` can only happen as the first application state. In this state, the
 application should start and run as normal, but will not receive any input, and
-should not try to initialize graphics resources (via GL or `SbBlitter`). In
-`PRELOADING`, the application can receive `Start` or `Suspend` events. `Start`
-will receive the same data that was passed into `Preload`.
+should not try to initialize graphics resources via GL. In `PRELOADING`, the
+application can receive `Start` or `Suspend` events. `Start` will receive the
+same data that was passed into `Preload`.
 
 In the `STARTED` state, the application is in the foreground and can expect to
 do all of the normal things it might want to do. Once in the `STARTED` state, it
@@ -163,65 +163,68 @@
 
 *   `kSbEventTypePreload`
 
-    Applications should perform initialization and prepare to react to
-    subsequent events, but must not initialize any graphics resources (through
-    GL or SbBlitter). The intent of this event is to allow the application to do
-    as much work as possible ahead of time, so that when the application is
-    first brought to the foreground, it's as fast as a resume.
-
-    The `kSbEventTypeStart` event may be sent at any time, regardless of
-    initialization state. Input events will not be sent in the `PRELOADING`
-    state. This event will only be sent once for a given process launch.
-    SbEventStartData is passed as the data argument.
-
-    The system may send `kSbEventTypeSuspend` in `PRELOADING` if it wants to
-    push the app into a lower resource consumption state. Applications can also
-    call SbSystemRequestSuspend() when they are done preloading to request this.
+    The system may send `kSbEventTypePreload` in `UNSTARTED` if it wants to push
+    the app into a lower resource consumption state. Applications will also call
+    SbSystemRequestConceal() when they request this. The only events that should
+    be dispatched after a Preload event are Reveal or Freeze. No data argument.
 *   `kSbEventTypeStart`
 
     The first event that an application receives on startup when starting
-    normally (i.e. not being preloaded). Applications should perform
-    initialization, start running, and prepare to react to subsequent events.
-    Applications that wish to run and then exit must call
-    `SbSystemRequestStop()` to terminate. This event will only be sent once for
-    a given process launch. `SbEventStartData` is passed as the data argument.
-    In case of preload, the `SbEventStartData` will be the same as what was
-    passed to `kSbEventTypePreload`.
-*   `kSbEventTypePause`
+    normally. Applications should perform initialization, start running, and
+    prepare to react to subsequent events. Applications that wish to run and
+    then exit must call `SbSystemRequestStop()` to terminate. This event will
+    only be sent once for a given process launch. `SbEventStartData` is passed
+    as the data argument.
+*   `kSbEventTypeBlur`
 
     A dialog will be raised or the application will otherwise be put into a
-    background-but-visible or partially-obscured state (PAUSED). Graphics and
-    video resources will still be available, but the application should pause
+    background-but-visible or partially-obscured state (BLURRED). Graphics and
+    video resources will still be available, but the application could pause
     foreground activity like animations and video playback. Can only be received
-    after a Start event. The only events that should be dispatched after a Pause
-    event are Unpause or Suspend. No data argument.
-*   `kSbEventTypeUnpause`
+    after a Start event. The only events that should be dispatched after a Blur
+    event are Focus or Conceal. No data argument.
+*   `kSbEventTypeFocus`
 
     The application is returning to the foreground (STARTED) after having been
-    put in the PAUSED (e.g. partially-obscured) state. The application should
-    unpause foreground activity like animations and video playback. Can only be
-    received after a Pause or Resume event. No data argument.
-*   `kSbEventTypeSuspend`
+    put in the BLURRED (e.g. partially-obscured) state. The application should
+    resume foreground activity like animations and video playback. Can only be
+    received after a Blur or Reveal event. No data argument.
+*   `kSbEventTypeConceal`
 
-    The operating system will put the application into a Suspended state after
+    The operating system will put the application into the Concealed state after
+    this event is handled. The application is expected to be made invisible, but
+    background tasks can still be running, such as audio playback, or updating
+    of recommendations. Can only be received after a Blur or Reveal event. The
+    only events that should be dispatched after a Conceal event are Freeze or
+    Reveal. On some platforms, the process may also be killed after Conceal
+    without a Freeze event.
+*   `kSbEventTypeReveal`
+
+    The operating system will restore the application to the BLURRED state from
+    the CONCEALED state. This is the first event the application will receive
+    coming out of CONCEALED, and it can be received after a Conceal or Unfreeze
+    event. The application will now be in the BLURRED state. No data argument.
+*   `kSbEventTypeFreeze`
+
+    The operating system will put the application into the Frozen state after
     this event is handled. The application is expected to stop periodic
     background work, release ALL graphics and video resources, and flush any
     pending SbStorage writes. Some platforms will terminate the application if
-    work is done or resources are retained after suspension. Can only be
-    received after a Pause event. The only events that should be dispatched
-    after a Suspend event are Resume or Stop. On some platforms, the process may
-    also be killed after Suspend without a Stop event. No data argument.
-*   `kSbEventTypeResume`
+    work is done or resources are retained after freezing. Can be received after
+    a Conceal or Unfreeze event. The only events that should be dispatched after
+    a Freeze event are Unfreeze or Stop. On some platforms, the process may also
+    be killed after Freeze without a Stop event. No data argument.
+*   `kSbEventTypeUnfreeze`
 
-    The operating system has restored the application to the PAUSED state from
-    the SUSPENDED state. This is the first event the application will receive
-    coming out of SUSPENDED, and it will only be received after a Suspend event.
-    The application will now be in the PAUSED state. No data argument.
+    The operating system has restored the application to the CONCEALED state
+    from the FROZEN state. This is the first event the application will receive
+    coming out of FROZEN, and it will only be received after a Freeze event. The
+    application will now be in the CONCEALED state. NO data argument.
 *   `kSbEventTypeStop`
 
     The operating system will shut the application down entirely after this
-    event is handled. Can only be received after a Suspend event, in the
-    SUSPENDED state. No data argument.
+    event is handled. Can only be received after a Freeze event, in the FROZEN
+    state. No data argument.
 *   `kSbEventTypeInput`
 
     A user input event, including keyboard, mouse, gesture, or something else.
@@ -249,7 +252,7 @@
     response to an application call to SbEventSchedule(), and it will call the
     callback directly, so SbEventHandle should never receive this event
     directly. The data type is an internally-defined structure.
-*   `kSbEventTypeAccessiblitySettingsChanged`
+*   `kSbEventTypeAccessibilitySettingsChanged`
 
     The platform's accessibility settings have changed. The application should
     query the accessibility settings using the appropriate APIs to get the new
@@ -319,8 +322,29 @@
     ticket value kSbEventOnScreenKeyboardInvalidTicket.
 *   `kSbEventTypeAccessibilityCaptionSettingsChanged`
 
-    SB_HAS(ON_SCREEN_KEYBOARD)One or more of the fields returned by
-    SbAccessibilityGetCaptionSettings has changed.
+    One or more of the fields returned by SbAccessibilityGetCaptionSettings has
+    changed.
+*   `kSbEventTypeAccessibilityTextToSpeechSettingsChanged`
+
+    The platform's text-to-speech settings have changed.
+*   `kSbEventTypeOsNetworkDisconnected`
+
+    The platform has detected a network disconnection. There are likely to be
+    cases where the platform cannot detect the disconnection but the platform
+    should make a best effort to send an event of this type when the network
+    disconnects. This event is used to implement window.onoffline DOM event.
+*   `kSbEventTypeOsNetworkConnected`
+
+    The platform has detected a network connection. There are likely to be cases
+    where the platform cannot detect the connection but the platform should make
+    a best effort to send an event of this type when the device is just
+    connected to the internet. This event is used to implement window.ononline
+    DOM event.
+*   `kSbEventDateTimeConfigurationChanged`
+
+    The platform has detected a date and/or time configuration change (such as a
+    change in the timezone setting). This should trigger the application to re-
+    query the relevant APIs to update the date and time.
 
 ## Typedefs ##
 
@@ -363,6 +387,7 @@
 #### Members ####
 
 *   `SbEventType type`
+*   `SbTimeMonotonic timestamp`
 *   `void * data`
 
 ### SbEventStartData ###
@@ -450,4 +475,3 @@
 ```
 SbEventId SbEventSchedule(SbEventCallback callback, void *context, SbTime delay)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/export.md b/cobalt/site/docs/reference/starboard/modules/14/export.md
similarity index 100%
rename from cobalt/site/docs/reference/starboard/modules/11/export.md
rename to cobalt/site/docs/reference/starboard/modules/14/export.md
diff --git a/cobalt/site/docs/reference/starboard/modules/11/file.md b/cobalt/site/docs/reference/starboard/modules/14/file.md
similarity index 94%
rename from cobalt/site/docs/reference/starboard/modules/11/file.md
rename to cobalt/site/docs/reference/starboard/modules/14/file.md
index f910cb0..56eb415 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/file.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/file.md
@@ -136,6 +136,22 @@
 
 ## Functions ##
 
+### SbFileAtomicReplace ###
+
+Replaces the content of the file at `path` with `data`. Returns whether the
+contents of the file were replaced. The replacement of the content is an atomic
+operation. The file will either have all of the data, or none.
+
+`path`: The path to the file whose contents should be replaced. `data`: The data
+to replace the file contents with. `data_size`: The amount of `data`, in bytes,
+to be written to the file.
+
+#### Declaration ####
+
+```
+bool SbFileAtomicReplace(const char *path, const char *data, int64_t data_size)
+```
+
 ### SbFileCanOpen ###
 
 Indicates whether SbFileOpen() with the given `flags` is allowed for `path`.
@@ -386,4 +402,3 @@
 ```
 static int SbFileWriteAll(SbFile file, const char *data, int size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/gles.md b/cobalt/site/docs/reference/starboard/modules/14/gles.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/gles.md
rename to cobalt/site/docs/reference/starboard/modules/14/gles.md
index 7a9108a..163d8d7 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/gles.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/gles.md
@@ -459,4 +459,3 @@
     internalformat, SbGlSizei width, SbGlSizei height, SbGlSizei depth)`
 *   `void(*glGetInternalformativ)(SbGlEnum target, SbGlEnum internalformat,
     SbGlEnum pname, SbGlSizei bufSize, SbGlInt32 *params)`
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/image.md b/cobalt/site/docs/reference/starboard/modules/14/image.md
similarity index 94%
rename from cobalt/site/docs/reference/starboard/modules/11/image.md
rename to cobalt/site/docs/reference/starboard/modules/14/image.md
index 634dab8..3050953 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/image.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/image.md
@@ -13,8 +13,6 @@
 
 ## SbImageIsDecodeSupported and SbImageDecode Example ##
 
-Let's assume that we're on a Blitter platform.
-
 ```
 SbDecodeTargetProvider* provider = GetProviderFromSomewhere();
 void* data = GetCompressedJPEGFromSomewhere();
@@ -28,9 +26,6 @@
 
 SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size,
                                              mime_type, format);
-SbBlitterSurface surface =
-    SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
-// Do stuff with surface...
 ```
 
 ## Functions ##
@@ -79,4 +74,3 @@
 ```
 bool SbImageIsDecodeSupported(const char *mime_type, SbDecodeTargetFormat format)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/input.md b/cobalt/site/docs/reference/starboard/modules/14/input.md
similarity index 93%
rename from cobalt/site/docs/reference/starboard/modules/11/input.md
rename to cobalt/site/docs/reference/starboard/modules/14/input.md
index 3554af6..ec86793 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/input.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/input.md
@@ -94,13 +94,6 @@
 
 #### Members ####
 
-*   `SbTimeMonotonic timestamp`
-
-    The time that should be reported for this event. This is intended to
-    facilitate calculation of time-sensitive information (e.g. velocity for
-    kSbInputEventTypeMove). This may be set to 0 to have the relevant systems
-    automatically set the timestamp. However, this may happen at a later time,
-    so the relative time between events may not be preserved.
 *   `SbWindow window`
 
     The window in which the input was generated.
@@ -175,4 +168,3 @@
 
 *   `float x`
 *   `float y`
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/key.md b/cobalt/site/docs/reference/starboard/modules/14/key.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/key.md
rename to cobalt/site/docs/reference/starboard/modules/14/key.md
index 16be3c1..bf8583c 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/key.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/key.md
@@ -290,4 +290,3 @@
 *   `kSbKeyModifiersPointerButtonMiddle`
 *   `kSbKeyModifiersPointerButtonBack`
 *   `kSbKeyModifiersPointerButtonForward`
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/log.md b/cobalt/site/docs/reference/starboard/modules/14/log.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/log.md
rename to cobalt/site/docs/reference/starboard/modules/14/log.md
index 1ad6c24..7edfe19 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/log.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/log.md
@@ -134,4 +134,3 @@
 ```
 void static void void SbLogRawFormatF(const char *format,...) SB_PRINTF_FORMAT(1
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/media.md b/cobalt/site/docs/reference/starboard/modules/14/media.md
similarity index 93%
rename from cobalt/site/docs/reference/starboard/modules/11/media.md
rename to cobalt/site/docs/reference/starboard/modules/14/media.md
index 10f0c24..45f6400 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/media.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/media.md
@@ -519,12 +519,10 @@
 The media buffer will be allocated using the returned alignment. Set this to a
 larger value may increase the memory consumption of media buffers.
 
-`type`: the media type of the stream (audio or video).
-
 #### Declaration ####
 
 ```
-int SbMediaGetBufferAlignment(SbMediaType type)
+int SbMediaGetBufferAlignment()
 ```
 
 ### SbMediaGetBufferAllocationUnit ###
@@ -564,12 +562,10 @@
 be use optimally by specific instructions like SIMD. Set to 0 to remove any
 padding.
 
-`type`: the media type of the stream (audio or video).
-
 #### Declaration ####
 
 ```
-int SbMediaGetBufferPadding(SbMediaType type)
+int SbMediaGetBufferPadding()
 ```
 
 ### SbMediaGetBufferStorageType ###
@@ -604,7 +600,12 @@
 ### SbMediaGetMaxBufferCapacity ###
 
 The maximum amount of memory that will be used to store media buffers. This must
-be larger than sum of the video budget and audio budget.
+be larger than sum of the video budget and audio budget. This is a soft limit
+and the app will continue to allocate media buffers even if the accumulated
+memory used by the media buffers exceeds the maximum buffer capacity. The
+allocation of media buffers may only fail when there is not enough memory in the
+system to fulfill the request, under which case the app will be terminated as
+under other OOM situations.
 
 `codec`: the video codec associated with the buffer. `resolution_width`: the
 width of the video resolution. `resolution_height`: the height of the video
@@ -682,38 +683,6 @@
 bool SbMediaIsBufferUsingMemoryPool()
 ```
 
-### SbMediaIsOutputProtected ###
-
-Indicates whether output copy protection is currently enabled on all capable
-outputs. If `true`, then non-protection-capable outputs are expected to be
-blanked.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-bool SbMediaIsOutputProtected()
-```
-
-### SbMediaIsSupported ###
-
-Indicates whether this platform supports decoding `video_codec` and
-`audio_codec` along with decrypting using `key_system`. If `video_codec` is
-`kSbMediaVideoCodecNone` or if `audio_codec` is `kSbMediaAudioCodecNone`, this
-function should return `true` as long as `key_system` is supported on the
-platform to decode any supported input formats.
-
-`video_codec`: The `SbMediaVideoCodec` being checked for platform compatibility.
-`audio_codec`: The `SbMediaAudioCodec` being checked for platform compatibility.
-`key_system`: The key system being checked for platform compatibility.
-
-#### Declaration ####
-
-```
-bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, const char *key_system)
-```
-
 ### SbMediaSetAudioWriteDuration ###
 
 Communicate to the platform how far past `current_playback_position` the app
@@ -731,23 +700,3 @@
 ```
 void SbMediaSetAudioWriteDuration(SbTime duration)
 ```
-
-### SbMediaSetOutputProtection ###
-
-Enables or disables output copy protection on all capable outputs. If enabled,
-then non-protection-capable outputs are expected to be blanked.
-
-The return value indicates whether the operation was successful, and the
-function returns a success even if the call is redundant in that it doesn't
-change the current value.
-
-`enabled`: Indicates whether output protection is enabled (`true`) or disabled.
-
-presubmit: allow sb_export mismatch
-
-#### Declaration ####
-
-```
-bool SbMediaSetOutputProtection(bool enabled)
-```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/memory.md b/cobalt/site/docs/reference/starboard/modules/14/memory.md
similarity index 74%
rename from cobalt/site/docs/reference/starboard/modules/11/memory.md
rename to cobalt/site/docs/reference/starboard/modules/14/memory.md
index 7ea1c5b..a094a66 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/memory.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/memory.md
@@ -46,16 +46,6 @@
 
 ## Functions ##
 
-### SbMemoryAlignToPageSize ###
-
-Rounds `size` up to kSbMemoryPageSize.
-
-#### Declaration ####
-
-```
-static size_t SbMemoryAlignToPageSize(size_t size)
-```
-
 ### SbMemoryAllocate ###
 
 Allocates and returns a chunk of memory of at least `size` bytes. This function
@@ -168,46 +158,6 @@
 static void* SbMemoryCalloc(size_t count, size_t size)
 ```
 
-### SbMemoryCompare ###
-
-Compares the contents of the first `count` bytes of `buffer1` and `buffer2`.
-This function returns:
-
-*   `-1` if `buffer1` is "less-than" `buffer2`
-
-*   `0` if `buffer1` and `buffer2` are equal
-
-*   `1` if `buffer1` is "greater-than" `buffer2`.
-
-This function is meant to be a drop-in replacement for `memcmp`.
-
-`buffer1`: The first buffer to be compared. `buffer2`: The second buffer to be
-compared. `count`: The number of bytes to be compared.
-
-#### Declaration ####
-
-```
-int SbMemoryCompare(const void *buffer1, const void *buffer2, size_t count)
-```
-
-### SbMemoryCopy ###
-
-Copies `count` sequential bytes from `source` to `destination`, without support
-for the `source` and `destination` regions overlapping. This function is meant
-to be a drop-in replacement for `memcpy`.
-
-The function's behavior is undefined if `destination` or `source` are NULL, and
-the function is a no-op if `count` is 0. The return value is `destination`.
-
-`destination`: The destination of the copied memory. `source`: The source of the
-copied memory. `count`: The number of sequential bytes to be copied.
-
-#### Declaration ####
-
-```
-void* SbMemoryCopy(void *destination, const void *source, size_t count)
-```
-
 ### SbMemoryDeallocate ###
 
 Frees a previously allocated chunk of memory. If `memory` is NULL, then the
@@ -244,18 +194,6 @@
 void SbMemoryDeallocateNoReport(void *memory)
 ```
 
-### SbMemoryFindByte ###
-
-Finds the lower 8-bits of `value` in the first `count` bytes of `buffer` and
-returns either a pointer to the first found occurrence or `NULL` if the value is
-not found. This function is meant to be a drop-in replacement for `memchr`.
-
-#### Declaration ####
-
-```
-const void* SbMemoryFindByte(const void *buffer, int value, size_t count)
-```
-
 ### SbMemoryFlush ###
 
 Flushes any data in the given virtual address range that is cached locally in
@@ -308,26 +246,6 @@
 void SbMemoryGetStackBounds(void **out_high, void **out_low)
 ```
 
-### SbMemoryIsAligned ###
-
-Checks whether `memory` is aligned to `alignment` bytes.
-
-#### Declaration ####
-
-```
-static bool SbMemoryIsAligned(const void *memory, size_t alignment)
-```
-
-### SbMemoryIsZero ###
-
-Returns true if the first `count` bytes of `buffer` are set to zero.
-
-#### Declaration ####
-
-```
-static bool SbMemoryIsZero(const void *buffer, size_t count)
-```
-
 ### SbMemoryMap ###
 
 Allocates `size_bytes` worth of physical memory pages and maps them into an
@@ -349,24 +267,6 @@
 void* SbMemoryMap(int64_t size_bytes, int flags, const char *name)
 ```
 
-### SbMemoryMove ###
-
-Copies `count` sequential bytes from `source` to `destination`, with support for
-the `source` and `destination` regions overlapping. This function is meant to be
-a drop-in replacement for `memmove`.
-
-The function's behavior is undefined if `destination` or `source` are NULL, and
-the function is a no-op if `count` is 0. The return value is `destination`.
-
-`destination`: The destination of the copied memory. `source`: The source of the
-copied memory. `count`: The number of sequential bytes to be copied.
-
-#### Declaration ####
-
-```
-void* SbMemoryMove(void *destination, const void *source, size_t count)
-```
-
 ### SbMemoryProtect ###
 
 Change the protection of `size_bytes` of memory regions, starting from
@@ -430,24 +330,6 @@
 void* SbMemoryReallocateUnchecked(void *memory, size_t size)
 ```
 
-### SbMemorySet ###
-
-Fills `count` sequential bytes starting at `destination`, with the unsigned char
-coercion of `byte_value`. This function is meant to be a drop-in replacement for
-`memset`.
-
-The function's behavior is undefined if `destination` is NULL, and the function
-is a no-op if `count` is 0. The return value is `destination`.
-
-`destination`: The destination of the copied memory. `count`: The number of
-sequential bytes to be set.
-
-#### Declaration ####
-
-```
-void* SbMemorySet(void *destination, int byte_value, size_t count)
-```
-
 ### SbMemoryUnmap ###
 
 Unmap `size_bytes` of physical pages starting from `virtual_address`, returning
@@ -463,4 +345,3 @@
 ```
 bool SbMemoryUnmap(void *virtual_address, int64_t size_bytes)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/memory_reporter.md b/cobalt/site/docs/reference/starboard/modules/14/memory_reporter.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/memory_reporter.md
rename to cobalt/site/docs/reference/starboard/modules/14/memory_reporter.md
index 4b4a539..d67ef1a 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/memory_reporter.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/memory_reporter.md
@@ -101,4 +101,3 @@
 ```
 bool SbMemorySetReporter(struct SbMemoryReporter *tracker)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/microphone.md b/cobalt/site/docs/reference/starboard/modules/14/microphone.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/microphone.md
rename to cobalt/site/docs/reference/starboard/modules/14/microphone.md
index 1379f86..d760e1e 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/microphone.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/microphone.md
@@ -260,4 +260,3 @@
 ```
 int SbMicrophoneRead(SbMicrophone microphone, void *out_audio_data, int audio_data_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/mutex.md b/cobalt/site/docs/reference/starboard/modules/14/mutex.md
similarity index 84%
rename from cobalt/site/docs/reference/starboard/modules/11/mutex.md
rename to cobalt/site/docs/reference/starboard/modules/14/mutex.md
index 2081a74..05dd810 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/mutex.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/mutex.md
@@ -6,6 +6,12 @@
 Defines a mutually exclusive lock that can be used to coordinate with other
 threads.
 
+## Macros ##
+
+### SB_MUTEX_MAX_SIZE ###
+
+Max size of the SbMutex type.
+
 ## Enums ##
 
 ### SbMutexResult ###
@@ -24,6 +30,19 @@
 
     The mutex has already been destroyed.
 
+## Typedefs ##
+
+### SbMutex ###
+
+An opaque handle to a mutex type with reserved memory buffer of size
+SB_MUTEX_MAX_SIZE and aligned at void pointer type.
+
+#### Definition ####
+
+```
+typedef union SbMutex  SbMutex
+```
+
 ## Functions ##
 
 ### SbMutexAcquire ###
@@ -43,7 +62,8 @@
 ### SbMutexAcquireTry ###
 
 Acquires `mutex`, without blocking. The return value identifies the acquisition
-result. SbMutexes are not reentrant, so a recursive acquisition always fails.
+result. SbMutexes are not reentrant, so a recursive acquisition has undefined
+behavior.
 
 `mutex`: The mutex to be acquired.
 
@@ -69,7 +89,7 @@
 ### SbMutexDestroy ###
 
 Destroys a mutex. The return value indicates whether the destruction was
-successful.
+successful. Destroying a locked mutex results in undefined behavior.
 
 `mutex`: The mutex to be invalidated.
 
@@ -105,4 +125,3 @@
 ```
 bool SbMutexRelease(SbMutex *mutex)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/once.md b/cobalt/site/docs/reference/starboard/modules/14/once.md
similarity index 74%
rename from cobalt/site/docs/reference/starboard/modules/11/once.md
rename to cobalt/site/docs/reference/starboard/modules/14/once.md
index b226b4b..2ad1959 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/once.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/once.md
@@ -6,8 +6,25 @@
 Onces represent initializations that should only ever happen once per process,
 in a thread-safe way.
 
+## Macros ##
+
+### SB_ONCE_MAX_SIZE ###
+
+Max size of the SbOnceControl type.
+
 ## Typedefs ##
 
+### SbOnceControl ###
+
+An opaque handle to a once control type with reserved memory buffer of size
+SB_ONCE_MAX_SIZE and aligned at void pointer type.
+
+#### Definition ####
+
+```
+typedef union SbOnceControl  SbOnceControl
+```
+
 ### SbOnceInitRoutine ###
 
 Function pointer type for methods that can be called via the SbOnce() system.
@@ -38,4 +55,3 @@
 ```
 bool SbOnce(SbOnceControl *once_control, SbOnceInitRoutine init_routine)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/player.md b/cobalt/site/docs/reference/starboard/modules/14/player.md
similarity index 97%
rename from cobalt/site/docs/reference/starboard/modules/11/player.md
rename to cobalt/site/docs/reference/starboard/modules/14/player.md
index 8763383..7acb9ae 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/player.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/player.md
@@ -33,14 +33,6 @@
 *   `kSbPlayerDecoderStateNeedsData`
 
     The decoder is asking for one more sample.
-*   `kSbPlayerDecoderStateBufferFull`
-
-    The decoder is not ready for any more samples, so do not send them. Note
-    that this enum value has been deprecated and the SbPlayer implementation
-    should no longer use this value.
-*   `kSbPlayerDecoderStateDestroyed`
-
-    The player has been destroyed, and will send no more callbacks.
 
 ### SbPlayerSampleSideDataType ###
 
@@ -501,4 +493,3 @@
 ```
 void SbPlayerWriteSample2(SbPlayer player, SbMediaType sample_type, const SbPlayerSampleInfo *sample_infos, int number_of_sample_infos)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/socket.md b/cobalt/site/docs/reference/starboard/modules/14/socket.md
similarity index 98%
rename from cobalt/site/docs/reference/starboard/modules/11/socket.md
rename to cobalt/site/docs/reference/starboard/modules/14/socket.md
index c903af6..d30bc22 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/socket.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/socket.md
@@ -348,6 +348,16 @@
 bool SbSocketIsConnectedAndIdle(SbSocket socket)
 ```
 
+### SbSocketIsIpv6Supported ###
+
+Returns whether IPV6 is supported on the current platform.
+
+#### Declaration ####
+
+```
+bool SbSocketIsIpv6Supported()
+```
+
 ### SbSocketIsValid ###
 
 Returns whether the given socket handle is valid.
@@ -573,4 +583,3 @@
 ```
 bool SbSocketSetTcpWindowScaling(SbSocket socket, bool value)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/socket_waiter.md b/cobalt/site/docs/reference/starboard/modules/14/socket_waiter.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/socket_waiter.md
rename to cobalt/site/docs/reference/starboard/modules/14/socket_waiter.md
index 738fafb..855dcf8 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/socket_waiter.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/socket_waiter.md
@@ -235,4 +235,3 @@
 ```
 void SbSocketWaiterWakeUp(SbSocketWaiter waiter)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/speech_recognizer.md b/cobalt/site/docs/reference/starboard/modules/14/speech_recognizer.md
similarity index 97%
rename from cobalt/site/docs/reference/starboard/modules/11/speech_recognizer.md
rename to cobalt/site/docs/reference/starboard/modules/14/speech_recognizer.md
index 7755e5b..2a40f19 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/speech_recognizer.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/speech_recognizer.md
@@ -218,6 +218,16 @@
 void SbSpeechRecognizerDestroy(SbSpeechRecognizer recognizer)
 ```
 
+### SbSpeechRecognizerIsSupported ###
+
+Returns whether the platform supports SbSpeechRecognizer.
+
+#### Declaration ####
+
+```
+bool SbSpeechRecognizerIsSupported()
+```
+
 ### SbSpeechRecognizerIsValid ###
 
 Indicates whether the given speech recognizer is valid.
@@ -256,4 +266,3 @@
 ```
 void SbSpeechRecognizerStop(SbSpeechRecognizer recognizer)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/speech_synthesis.md b/cobalt/site/docs/reference/starboard/modules/14/speech_synthesis.md
similarity index 86%
rename from cobalt/site/docs/reference/starboard/modules/11/speech_synthesis.md
rename to cobalt/site/docs/reference/starboard/modules/14/speech_synthesis.md
index bb3724c..1622318 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/speech_synthesis.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/speech_synthesis.md
@@ -23,6 +23,16 @@
 void SbSpeechSynthesisCancel()
 ```
 
+### SbSpeechSynthesisIsSupported ###
+
+Returns whether the platform supports speech synthesis
+
+#### Declaration ####
+
+```
+bool SbSpeechSynthesisIsSupported()
+```
+
 ### SbSpeechSynthesisSpeak ###
 
 Enqueues `text`, a UTF-8 string, to be spoken. Returns immediately.
@@ -39,4 +49,3 @@
 ```
 void SbSpeechSynthesisSpeak(const char *text)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/storage.md b/cobalt/site/docs/reference/starboard/modules/14/storage.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/storage.md
rename to cobalt/site/docs/reference/starboard/modules/14/storage.md
index c98207b..db7e950 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/storage.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/storage.md
@@ -156,4 +156,3 @@
 ```
 bool SbStorageWriteRecord(SbStorageRecord record, const char *data, int64_t data_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/14/string.md b/cobalt/site/docs/reference/starboard/modules/14/string.md
new file mode 100644
index 0000000..685f5e8
--- /dev/null
+++ b/cobalt/site/docs/reference/starboard/modules/14/string.md
@@ -0,0 +1,174 @@
+---
+layout: doc
+title: "Starboard Module Reference: string.h"
+---
+
+Defines functions for interacting with c-style strings.
+
+## Functions ##
+
+### SbStringCompareNoCase ###
+
+Compares two strings, ignoring differences in case. The return value is:
+
+*   `< 0` if `string1` is ASCII-betically lower than `string2`.
+
+*   `0` if the two strings are equal.
+
+*   `> 0` if `string1` is ASCII-betically higher than `string2`.
+
+This function is meant to be a drop-in replacement for `strcasecmp`.
+
+`string1`: The first string to compare. `string2`: The second string to compare.
+
+#### Declaration ####
+
+```
+int SbStringCompareNoCase(const char *string1, const char *string2)
+```
+
+### SbStringCompareNoCaseN ###
+
+Compares the first `count` characters of two strings, ignoring differences in
+case. The return value is:
+
+*   `< 0` if `string1` is ASCII-betically lower than `string2`.
+
+*   `0` if the two strings are equal.
+
+*   `> 0` if `string1` is ASCII-betically higher than `string2`.
+
+This function is meant to be a drop-in replacement for `strncasecmp`.
+
+`string1`: The first string to compare. `string2`: The second string to compare.
+`count`: The number of characters to compare.
+
+#### Declaration ####
+
+```
+int SbStringCompareNoCaseN(const char *string1, const char *string2, size_t count)
+```
+
+### SbStringDuplicate ###
+
+Copies `source` into a buffer that is allocated by this function and that can be
+freed with SbMemoryDeallocate. This function is meant to be a drop-in
+replacement for `strdup`.
+
+`source`: The string to be copied.
+
+#### Declaration ####
+
+```
+char* SbStringDuplicate(const char *source)
+```
+
+### SbStringFormat ###
+
+Produces a string formatted with `format` and `arguments`, placing as much of
+the result that will fit into `out_buffer`. The return value specifies the
+number of characters that the format would produce if `buffer_size` were
+infinite.
+
+This function is meant to be a drop-in replacement for `vsnprintf`.
+
+`out_buffer`: The location where the formatted string is stored. `buffer_size`:
+The size of `out_buffer`. `format`: A string that specifies how the data should
+be formatted. `arguments`: Variable arguments used in the string.
+
+#### Declaration ####
+
+```
+int SbStringFormat(char *out_buffer, size_t buffer_size, const char *format, va_list arguments) SB_PRINTF_FORMAT(3
+```
+
+### SbStringFormatF ###
+
+An inline wrapper of SbStringFormat that converts from ellipsis to va_args. This
+function is meant to be a drop-in replacement for `snprintf`.
+
+`out_buffer`: The location where the formatted string is stored. `buffer_size`:
+The size of `out_buffer`. `format`: A string that specifies how the data should
+be formatted. `...`: Arguments used in the string.
+
+#### Declaration ####
+
+```
+int static int static int SbStringFormatF(char *out_buffer, size_t buffer_size, const char *format,...) SB_PRINTF_FORMAT(3
+```
+
+### SbStringFormatUnsafeF ###
+
+An inline wrapper of SbStringFormat that is meant to be a drop-in replacement
+for the unsafe but commonly used `sprintf`.
+
+`out_buffer`: The location where the formatted string is stored. `format`: A
+string that specifies how the data should be formatted. `...`: Arguments used in
+the string.
+
+#### Declaration ####
+
+```
+static int static int SbStringFormatUnsafeF(char *out_buffer, const char *format,...) SB_PRINTF_FORMAT(2
+```
+
+### SbStringFormatWide ###
+
+This function is identical to SbStringFormat, but is for wide characters. It is
+meant to be a drop-in replacement for `vswprintf`.
+
+`out_buffer`: The location where the formatted string is stored. `buffer_size`:
+The size of `out_buffer`. `format`: A string that specifies how the data should
+be formatted. `arguments`: Variable arguments used in the string.
+
+#### Declaration ####
+
+```
+int SbStringFormatWide(wchar_t *out_buffer, size_t buffer_size, const wchar_t *format, va_list arguments)
+```
+
+### SbStringFormatWideF ###
+
+An inline wrapper of SbStringFormatWide that converts from ellipsis to
+`va_args`.
+
+`out_buffer`: The location where the formatted string is stored. `buffer_size`:
+The size of `out_buffer`. `format`: A string that specifies how the data should
+be formatted. `...`: Arguments used in the string.
+
+#### Declaration ####
+
+```
+static int SbStringFormatWideF(wchar_t *out_buffer, size_t buffer_size, const wchar_t *format,...)
+```
+
+### SbStringScan ###
+
+Scans `buffer` for `pattern`, placing the extracted values in `arguments`. The
+return value specifies the number of successfully matched items, which may be
+`0`.
+
+This function is meant to be a drop-in replacement for `vsscanf`.
+
+`buffer`: The string to scan for the pattern. `pattern`: The string to search
+for in `buffer`. `arguments`: Values matching `pattern` that were extracted from
+`buffer`.
+
+#### Declaration ####
+
+```
+int SbStringScan(const char *buffer, const char *pattern, va_list arguments)
+```
+
+### SbStringScanF ###
+
+An inline wrapper of SbStringScan that converts from ellipsis to `va_args`. This
+function is meant to be a drop-in replacement for `sscanf`. `buffer`: The string
+to scan for the pattern. `pattern`: The string to search for in `buffer`. `...`:
+Values matching `pattern` that were extracted from `buffer`.
+
+#### Declaration ####
+
+```
+static int SbStringScanF(const char *buffer, const char *pattern,...)
+```
diff --git a/cobalt/site/docs/reference/starboard/modules/11/system.md b/cobalt/site/docs/reference/starboard/modules/14/system.md
similarity index 86%
rename from cobalt/site/docs/reference/starboard/modules/11/system.md
rename to cobalt/site/docs/reference/starboard/modules/14/system.md
index 8231497..793670f 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/system.md
@@ -25,12 +25,6 @@
     Whether this system has the ability to report on GPU memory usage. If (and
     only if) a system has this capability will SbSystemGetTotalGPUMemory() and
     SbSystemGetUsedGPUMemory() be valid to call.
-*   `kSbSystemCapabilitySetsInputTimestamp`
-
-    Whether this system sets the `timestamp` field of SbInputData . If the
-    system does not set this field, then it will automatically be set; however,
-    the relative time between input events likely will not be preserved, so
-    time-related calculations (e.g. velocity for move events) will be incorrect.
 
 ### SbSystemConnectionType ###
 
@@ -78,6 +72,9 @@
 *   `kSbSystemDeviceTypeAndroidTV`
 
     An Android TV Device.
+*   `kSbSystemDeviceTypeVideoProjector`
+
+    A wall video projector.
 *   `kSbSystemDeviceTypeUnknown`
 
     Unknown device.
@@ -112,12 +109,14 @@
 *   `kSbSystemPathTempDirectory`
 
     Path to a directory where temporary files can be written.
-*   `kSbSystemPathTestOutputDirectory`
-
-    Path to a directory where test results can be written.
 *   `kSbSystemPathExecutableFile`
 
     Full path to the executable file.
+*   `kSbSystemPathStorageDirectory`
+
+    Path to a directory for permanent file storage. Both read and write access
+    is required. This is where an app may store its persistent settings. The
+    location should be user agnostic if possible.
 
 ### SbSystemPlatformErrorResponse ###
 
@@ -153,10 +152,6 @@
 *   `kSbSystemPropertyCertificationScope`
 
     The certification scope that identifies a group of devices.
-*   `kSbSystemPropertyBase64EncodedCertificationSecret`
-
-    The HMAC-SHA256 base64 encoded symmetric key used to sign a subset of the
-    query parameters from the application startup URL.
 *   `kSbSystemPropertyChipsetModelNumber`
 
     The full model number of the main platform chipset, including any vendor-
@@ -182,11 +177,10 @@
 *   `kSbSystemPropertyModelYear`
 
     The year the device was launched, e.g. "2016".
-*   `kSbSystemPropertyOriginalDesignManufacturerName`
+*   `kSbSystemPropertySystemIntegratorName`
 
-    The corporate entity responsible for the manufacturing/assembly of the
-    device on behalf of the business entity owning the brand. This is often
-    abbreviated as ODM.
+    The corporate entity responsible for submitting the device to YouTube
+    certification and for the device maintenance/updates.
 *   `kSbSystemPropertyPlatformName`
 
     The name of the operating system and platform, suitable for inclusion in a
@@ -200,6 +194,15 @@
 *   `kSbSystemPropertyUserAgentAuxField`
 
     A field that, if available, is appended to the user agent
+*   `kSbSystemPropertyAdvertisingId`
+
+    Advertising ID or IFA, typically a 128-bit UUID Please see [https://iabtechlab.com/OTT-IFA](https://iabtechlab.com/OTT-IFA) for
+    details. Corresponds to 'ifa' field. Note: `ifa_type` ifa_type field is not
+    provided.
+*   `kSbSystemPropertyLimitAdTracking`
+
+    Limit advertising tracking, treated as boolean. Set to nonzero to indicate a
+    true value. Corresponds to 'lmt' field.
 
 ## Typedefs ##
 
@@ -248,26 +251,6 @@
 
 ## Functions ##
 
-### SbSystemBinarySearch ###
-
-Binary searches a sorted table `base` of `element_count` objects, each element
-`element_width` bytes in size for an element that `comparator` compares equal to
-`key`.
-
-This function is meant to be a drop-in replacement for `bsearch`.
-
-`key`: The key to search for in the table. `base`: The sorted table of elements
-to be searched. `element_count`: The number of elements in the table.
-`element_width`: The size, in bytes, of each element in the table. `comparator`:
-A value that indicates how the element in the table should compare to the
-specified `key`.
-
-#### Declaration ####
-
-```
-void* SbSystemBinarySearch(const void *key, const void *base, size_t element_count, size_t element_width, SbSystemComparator comparator)
-```
-
 ### SbSystemBreakIntoDebugger ###
 
 Breaks the current program into the debugger, if a debugger is attached. If a
@@ -560,6 +543,18 @@
 bool SbSystemIsDebuggerAttached()
 ```
 
+### SbSystemNetworkIsDisconnected ###
+
+Returns if the device is disconnected from network. "Disconnected" is chosen
+over connected because disconnection can be determined with more certainty than
+connection usually.
+
+#### Declaration ####
+
+```
+bool SbSystemNetworkIsDisconnected()
+```
+
 ### SbSystemRaisePlatformError ###
 
 Cobalt calls this function to notify the platform that an error has occurred in
@@ -588,20 +583,94 @@
 bool SbSystemRaisePlatformError(SbSystemPlatformErrorType type, SbSystemPlatformErrorCallback callback, void *user_data)
 ```
 
-### SbSystemRequestPause ###
+### SbSystemRequestBlur ###
 
-Requests that the application move into the Paused state at the next convenient
+Requests that the application move into the Blurred state at the next convenient
 point. This should roughly correspond to "unfocused application" in a
 traditional window manager, where the application may be partially visible.
 
-This function eventually causes a `kSbEventTypePause` event to be dispatched to
-the application. Before the `kSbEventTypePause` event is dispatched, some work
+This function eventually causes a `kSbEventTypeBlur` event to be dispatched to
+the application. Before the `kSbEventTypeBlur` event is dispatched, some work
 may continue to be done, and unrelated system events may be dispatched.
 
 #### Declaration ####
 
 ```
-void SbSystemRequestPause()
+void SbSystemRequestBlur()
+```
+
+### SbSystemRequestConceal ###
+
+Requests that the application move into the Concealed state at the next
+convenient point. This should roughly correspond to "minimization" in a
+traditional window manager, where the application is no longer visible. However,
+the background tasks can still be running.
+
+This function eventually causes a `kSbEventTypeConceal` event to be dispatched
+to the application. Before the `kSbEventTypeConceal` event is dispatched, some
+work may continue to be done, and unrelated system events may be dispatched.
+
+In the Concealed state, the application will be invisible, but probably still be
+running background tasks. The expectation is that an external system event will
+bring the application out of the Concealed state.
+
+#### Declaration ####
+
+```
+void SbSystemRequestConceal()
+```
+
+### SbSystemRequestFocus ###
+
+Requests that the application move into the Started state at the next convenient
+point. This should roughly correspond to a "focused application" in a
+traditional window manager, where the application is fully visible and the
+primary receiver of input events.
+
+This function eventually causes a `kSbEventTypeFocus` event to be dispatched to
+the application. Before `kSbEventTypeFocus` is dispatched, some work may
+continue to be done, and unrelated system events may be dispatched.
+
+#### Declaration ####
+
+```
+void SbSystemRequestFocus()
+```
+
+### SbSystemRequestFreeze ###
+
+Requests that the application move into the Frozen state at the next convenient
+point.
+
+This function eventually causes a `kSbEventTypeFreeze` event to be dispatched to
+the application. Before the `kSbEventTypeSuspend` event is dispatched, some work
+may continue to be done, and unrelated system events may be dispatched.
+
+In the Frozen state, the application will be resident, but probably not running.
+The expectation is that an external system event will bring the application out
+of the Frozen state.
+
+#### Declaration ####
+
+```
+void SbSystemRequestFreeze()
+```
+
+### SbSystemRequestReveal ###
+
+Requests that the application move into the Blurred state at the next convenient
+point. This should roughly correspond to a "focused application" in a
+traditional window manager, where the application is fully visible and the
+primary receiver of input events.
+
+This function eventually causes a `kSbEventTypeReveal` event to be dispatched to
+the application. Before the `kSbEventTypeReveal` event is dispatched, some work
+may continue to be done, and unrelated system events may be dispatched.
+
+#### Declaration ####
+
+```
+void SbSystemRequestReveal()
 ```
 
 ### SbSystemRequestStop ###
@@ -621,52 +690,11 @@
 void SbSystemRequestStop(int error_level)
 ```
 
-### SbSystemRequestSuspend ###
-
-Requests that the application move into the Suspended state at the next
-convenient point. This should roughly correspond to "minimization" in a
-traditional window manager, where the application is no longer visible.
-
-This function eventually causes a `kSbEventTypeSuspend` event to be dispatched
-to the application. Before the `kSbEventTypeSuspend` event is dispatched, some
-work may continue to be done, and unrelated system events may be dispatched.
-
-In the Suspended state, the application will be resident, but probably not
-running. The expectation is that an external system event will bring the
-application out of the Suspended state.
-
-#### Declaration ####
-
-```
-void SbSystemRequestSuspend()
-```
-
-### SbSystemRequestUnpause ###
-
-Requests that the application move into the Started state at the next convenient
-point. This should roughly correspond to a "focused application" in a
-traditional window manager, where the application is fully visible and the
-primary receiver of input events.
-
-This function eventually causes a `kSbEventTypeUnpause` event to be dispatched
-to the application. Before `kSbEventTypeUnpause` is dispatched, some work may
-continue to be done, and unrelated system events may be dispatched.
-
-#### Declaration ####
-
-```
-void SbSystemRequestUnpause()
-```
-
 ### SbSystemSignWithCertificationSecretKey ###
 
 Computes a HMAC-SHA256 digest of `message` into `digest` using the application's
 certification secret.
 
-This function may be implemented as an alternative to implementing
-SbSystemGetProperty(kSbSystemPropertyBase64EncodedCertificationSecret), however
-both should not be implemented.
-
 The output will be written into `digest`. `digest_size_in_bytes` must be 32 (or
 greater), since 32-bytes will be written into it. Returns false in the case of
 an error, or if it is not implemented. In this case the contents of `digest`
@@ -678,23 +706,6 @@
 bool SbSystemSignWithCertificationSecretKey(const uint8_t *message, size_t message_size_in_bytes, uint8_t *digest, size_t digest_size_in_bytes)
 ```
 
-### SbSystemSort ###
-
-Sorts an array of elements `base`, with `element_count` elements of
-`element_width` bytes each, using `comparator` as the comparison function.
-
-This function is meant to be a drop-in replacement for `qsort`.
-
-`base`: The array of elements to be sorted. `element_count`: The number of
-elements in the array. `element_width`: The size, in bytes, of each element in
-the array. `comparator`: A value that indicates how the array should be sorted.
-
-#### Declaration ####
-
-```
-void SbSystemSort(void *base, size_t element_count, size_t element_width, SbSystemComparator comparator)
-```
-
 ### SbSystemSupportsResume ###
 
 Returns false if the platform doesn't need resume after suspend support. In such
@@ -726,4 +737,3 @@
 ```
 bool SbSystemSymbolize(const void *address, char *out_buffer, int buffer_size)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/thread.md b/cobalt/site/docs/reference/starboard/modules/14/thread.md
similarity index 98%
rename from cobalt/site/docs/reference/starboard/modules/11/thread.md
rename to cobalt/site/docs/reference/starboard/modules/14/thread.md
index d1fa609..1191e9a 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/thread.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/thread.md
@@ -81,6 +81,16 @@
 
 ## Typedefs ##
 
+### SbThread ###
+
+An opaque handle to a thread type.
+
+#### Definition ####
+
+```
+typedef void* SbThread
+```
+
 ### SbThreadAffinity ###
 
 Type for thread core affinity. This generally will be a single cpu (or core or
@@ -521,4 +531,3 @@
 ```
 void SbThreadYield()
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/time.md b/cobalt/site/docs/reference/starboard/modules/14/time.md
similarity index 94%
rename from cobalt/site/docs/reference/starboard/modules/11/time.md
rename to cobalt/site/docs/reference/starboard/modules/14/time.md
index bd59b91..19a7e4f 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/time.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/time.md
@@ -116,6 +116,16 @@
 SbTime SbTimeGetNow()
 ```
 
+### SbTimeIsTimeThreadNowSupported ###
+
+Returns whether the current platform supports time thread now
+
+#### Declaration ####
+
+```
+bool SbTimeIsTimeThreadNowSupported()
+```
+
 ### SbTimeNarrow ###
 
 Safely narrows a number from a more precise unit to a less precise one. This
@@ -140,4 +150,3 @@
 ```
 static int64_t SbTimeToPosix(SbTime time)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/time_zone.md b/cobalt/site/docs/reference/starboard/modules/14/time_zone.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/time_zone.md
rename to cobalt/site/docs/reference/starboard/modules/14/time_zone.md
index 5768bc3..f9e3fcb 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/time_zone.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/time_zone.md
@@ -45,4 +45,3 @@
 ```
 const char* SbTimeZoneGetName()
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/types.md b/cobalt/site/docs/reference/starboard/modules/14/types.md
similarity index 100%
rename from cobalt/site/docs/reference/starboard/modules/11/types.md
rename to cobalt/site/docs/reference/starboard/modules/14/types.md
diff --git a/cobalt/site/docs/reference/starboard/modules/14/ui_navigation.md b/cobalt/site/docs/reference/starboard/modules/14/ui_navigation.md
new file mode 100644
index 0000000..d868c64
--- /dev/null
+++ b/cobalt/site/docs/reference/starboard/modules/14/ui_navigation.md
@@ -0,0 +1,311 @@
+---
+layout: doc
+title: "Starboard Module Reference: ui_navigation.h"
+---
+
+API to allow applications to take advantage of the platform's native UI engine.
+This is mainly to drive the animation of visual elements and to signal which of
+those elements have focus. The implementation should not render any visual
+elements; instead, it will be used to guide the app in where these elements
+should be drawn.
+
+When the application creates the user interface, it will create SbUiNavItems for
+interactable elements. Additionally, the app must specify the position and size
+of these navigation items. As the app's user interface changes, it will create
+and destroy navigation items as appropriate.
+
+For each render frame, the app will query the local transform for each
+SbUiNavItem in case the native UI engine moves individual items in response to
+user interaction. If the navigation item is a container, then the content offset
+will also be queried to determine the placement of its content items.
+
+## Macros ##
+
+### kSbUiNavItemInvalid ###
+
+Well-defined value for an invalid navigation item.
+
+## Enums ##
+
+### SbUiNavItemType ###
+
+Navigation items may be one of the following types. This must be specified upon
+item creation and may not change during the item's lifespan.
+
+#### Values ####
+
+*   `kSbUiNavItemTypeFocus`
+
+    This is a single focusable item.
+*   `kSbUiNavItemTypeContainer`
+
+    This is a container of navigation items which can also be containers
+    themselves or focusable items. Containers themselves cannot be focused.
+
+## Typedefs ##
+
+### SbUiNavItem ###
+
+An opaque handle to an implementation-private structure representing a
+navigation item.
+
+#### Definition ####
+
+```
+typedef struct SbUiNavItemPrivate* SbUiNavItem
+```
+
+## Structs ##
+
+### SbUiNavCallbacks ###
+
+This structure specifies all the callbacks which the platform UI engine should
+invoke for various interaction events on navigation items. These callbacks may
+be invoked from any thread at any frequency. The `callback_context` is the value
+that was passed on creation of the relevant SbUiNavItem.
+
+#### Members ####
+
+*   `void(*onblur)(SbUiNavItem item, void *callback_context)`
+
+    Invoke when an item has lost focus. This is only used with focus items.
+*   `void(*onfocus)(SbUiNavItem item, void *callback_context)`
+
+    Invoke when an item has gained focus. This is only used with focus items.
+*   `void(*onscroll)(SbUiNavItem item, void *callback_context)`
+
+    Invoke when an item's content offset is changed. This is only used with
+    container items.
+
+### SbUiNavInterface ###
+
+This structure declares the interface to the UI navigation implementation. All
+function pointers must be specified if the platform supports UI navigation.
+
+#### Members ####
+
+*   `SbUiNavItem(*create_item)(SbUiNavItemType type, const SbUiNavCallbacks
+    *callbacks, void *callback_context)`
+
+    Create a new navigation item. When the user interacts with this item the
+    appropriate SbUiNavCallbacks function will be invoked with the provided
+    `callback_context`. An item is not interactable until it is enabled.
+*   `void(*destroy_item)(SbUiNavItem item)`
+
+    Destroy the given navigation item. If this is a content of another item,
+    then it will first be unregistered. Additionally, if this item contains
+    other items, then those will be unregistered as well, but they will not be
+    automatically destroyed.
+*   `void(*set_focus)(SbUiNavItem item)`
+
+    This is used to manually force focus on a navigation item of type
+    kSbUiNavItemTypeFocus. Any previously focused navigation item should receive
+    the blur event. If the item is not transitively a content of the root item,
+    then this does nothing. Specifying kSbUiNavItemInvalid should remove focus
+    from the UI navigation system.
+*   `void(*set_item_enabled)(SbUiNavItem item, bool enabled)`
+
+    This is used to enable or disable user interaction with the specified
+    navigation item. All navigation items are disabled when created, and they
+    must be explicitly enabled to allow user interaction. If a container is
+    disabled, then all of its contents are not interactable even though they
+    remain enabled. If `enabled` is false, it must be guaranteed that once this
+    function returns, no callbacks associated with this item will be invoked
+    until the item is re-enabled.
+*   `void(*set_item_dir)(SbUiNavItem item, SbUiNavItemDir dir)`
+
+    This specifies directionality for container items. Containers within
+    containers do not inherit directionality. Directionality must be specified
+    for each container explicitly. This should work even if `item` is disabled.
+*   `void(*set_item_focus_duration)(SbUiNavItem item, float seconds)`
+
+    Set the minimum amount of time the focus item should remain focused once it
+    becomes focused. This may be used to make important focus items harder to
+    navigate over. Focus may still be moved before `seconds` has elapsed by
+    using the set_focus() function. By default, item focus duration is 0
+    seconds.
+*   `void(*set_item_size)(SbUiNavItem item, float width, float height)`
+
+    Set the interactable size of the specified navigation item. By default, an
+    item's size is (0,0).
+*   `void(*set_item_transform)(SbUiNavItem item, const SbUiNavMatrix2x3
+    *transform)`
+
+    Set the transform for the navigation item and its contents if the item is a
+    container. This specifies the placement of the item's center within its
+    container. The transform origin is the center of the item. Distance is
+    measured in pixels with the origin being the top-left of the item's
+    container. By default, an item's transform is identity.
+*   `bool(*get_item_focus_transform)(SbUiNavItem item, SbUiNavMatrix4
+    *out_transform)`
+
+    Retrieve the focus transform matrix for the navigation item. The UI engine
+    may translate, rotate, and/or tilt focus items to reflect user interaction.
+    This transform should be multiplied with the item's transform to get its
+    position inside its container. The transform origin is the center of the
+    item. Return false if the item position should not be changed (i.e. the
+    transform should be treated as identity).
+*   `bool(*get_item_focus_vector)(SbUiNavItem item, float *out_x, float *out_y)`
+
+    Retrieve a vector representing the focus location within a focused item.
+    This is used to provide feedback about user input that is too small to
+    result in a focus change. If there is no focus vector for the navigation
+    item, then return false and leave `out_x` and `out_y` unchanged. Otherwise,
+    return true and set the output values in the range of [-1, +1] with (out_x,
+    out_y) of (-1, -1) being the top-left corner of the navigation item and (0,
+    0) being the center.
+*   `void(*set_item_container_window)(SbUiNavItem item, SbWindow window)`
+
+    This attaches the given navigation item (which must be a container) to the
+    specified window. Navigation items are only interactable if they are
+    transitively attached to a window.The native UI engine should never change
+    this navigation item's content offset. It is assumed to be used as a proxy
+    for the system window.A navigation item may only have a SbUiNavItem or
+    SbWindow as its direct container. The navigation item hierarchy is
+    established using set_item_container_item() with the root container attached
+    to a SbWindow using set_item_container_window() to enable interaction with
+    all enabled items in the hierarchy.
+
+    If `item` is already registered with a different window, then this will
+    unregister it from that window then attach it to the given `window`. It is
+    an error to register more than one navigation item with any given window. If
+    `window` is kSbWindowInvalid, then this will unregister the `item` from its
+    current window if any. Upon destruction of `item` or `window`, the `item` is
+    automatically unregistered from the `window`.
+*   `void(*set_item_container_item)(SbUiNavItem item, SbUiNavItem container)`
+
+    A container navigation item may contain other navigation items. However, it
+    is an error to have circular containment or for `container` to not be of
+    type kSbUiNavItemTypeContainer. If `item` already has a different container,
+    then this first serves that connection. If `container` is
+    kSbUiNavItemInvalid, then this removes `item` from its current container.
+    Upon destruction of `item` or `container`, the `item` is automatically
+    removed from the `container`.
+
+    The position of items within a container are specified relative to the
+    container's position. The position of these content items are further
+    modified by the container's "content offset".
+
+    For example, consider item A with position (5,5) and content offset (0,0).
+    Given item B with position (10,10) is registered as a content of item A.
+
+    1) Item B should be drawn at position (15,15).
+
+    2) If item A's content offset is changed to (10,0), then item B should be
+    drawn at position (5,15).
+
+    Essentially, content items should be drawn at: [container position] +
+    [content position] - [container content offset] Content items may overlap
+    within a container. This can cause obscured items to be unfocusable. The
+    only rule that needs to be followed is that contents which are focus items
+    can obscure other contents which are containers, but not vice versa. The
+    caller must ensure that content focus items do not overlap other content
+    focus items and content container items do not overlap other content
+    container items.
+*   `void(*set_item_content_offset)(SbUiNavItem item, float content_offset_x,
+    float content_offset_y)`
+
+    Set the current content offset for the given container. This may be used to
+    force scrolling to make certain content items visible. A container item's
+    content offset helps determine where its content items should be drawn.
+    Essentially, a content item should be drawn at: [container position] +
+    [content position] - [container content offset] If `item` is not a
+    container, then this does nothing. By default, the content offset is (0,0).
+    This should update the values returned by get_item_content_offset() even if
+    the `item` is disabled.
+*   `void(*get_item_content_offset)(SbUiNavItem item, float
+    *out_content_offset_x, float *out_content_offset_y)`
+
+    Retrieve the current content offset for the navigation item. If `item` is
+    not a container, then the content offset is (0,0). The native UI engine
+    should not change the content offset of a container unless one of its
+    contents (possibly recursively) is focused. This is to allow seamlessly
+    disabling then re-enabling focus items without having their containers
+    change offsets.
+*   `void(*do_batch_update)(void(*update_function)(void *), void *context)`
+
+    Call `update_function` with `context` to perform a series of UI navigation
+    changes atomically before returning.
+
+### SbUiNavItemDir ###
+
+Navigation items of type kSbUiNavItemTypeContainer have directionality. If
+directionality is not specified for a container, it should default to left-to-
+right and top-to-bottom.
+
+```
+///   For left-to-right, content offset x = 0 shows the leftmost content.
+///                          |<--Container Size-->|
+///     +--------------------+--------------------+--------------------+
+///     | Not selectable.    | Selectable.        | Selectable.        |
+///     | Offscreen.         | Onscreen.          | Offscreen.         |
+///     | Negative position. | Positive position. | Positive position. |
+///     +--------------------+--------------------+--------------------+
+///                          ^
+///                  Content Offset X = 0.
+///
+///   For right-to-left, content offset x = 0 shows the rightmost content.
+///                          |<--Container Size-->|
+///     +--------------------+--------------------+--------------------+
+///     | Selectable.        | Selectable.        | Not selectable.    |
+///     | Offscreen.         | Onscreen.          | Offscreen.         |
+///     | Negative position. | Positive position. | Positive position. |
+///     +--------------------+--------------------+--------------------+
+///                          ^
+///                  Content Offset X = 0.
+```
+
+```
+  Top-to-bottom is similar to left-to-right, but for the Y position.
+  Bottom-to-top is similar to right-to-left, but for the Y position.
+```
+
+#### Members ####
+
+*   `bool is_left_to_right`
+*   `bool is_top_to_bottom`
+
+### SbUiNavMatrix2x3 ###
+
+This represents a 2x3 transform matrix in row-major order.
+
+```
+///   | a b tx |
+///   | c d ty |
+```
+
+#### Members ####
+
+*   `float m`
+
+### SbUiNavMatrix4 ###
+
+This represents a 4x4 transform matrix in row-major order.
+
+#### Members ####
+
+*   `float m`
+
+## Functions ##
+
+### SbUiNavGetInterface ###
+
+Retrieve the platform's UI navigation implementation. If the platform does not
+provide one, then return false without modifying `out_interface`. Otherwise,
+initialize all members of `out_interface` and return true.
+
+#### Declaration ####
+
+```
+bool SbUiNavGetInterface(SbUiNavInterface *out_interface)
+```
+
+### SbUiNavItemIsValid ###
+
+Returns whether the given navigation item handle is valid.
+
+#### Declaration ####
+
+```
+static bool SbUiNavItemIsValid(SbUiNavItem item)
+```
diff --git a/cobalt/site/docs/reference/starboard/modules/11/user.md b/cobalt/site/docs/reference/starboard/modules/14/user.md
similarity index 99%
rename from cobalt/site/docs/reference/starboard/modules/11/user.md
rename to cobalt/site/docs/reference/starboard/modules/14/user.md
index a30cc95..406e695 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/user.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/user.md
@@ -131,4 +131,3 @@
 ```
 static bool SbUserIsValid(SbUser user)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/11/window.md b/cobalt/site/docs/reference/starboard/modules/14/window.md
similarity index 97%
rename from cobalt/site/docs/reference/starboard/modules/11/window.md
rename to cobalt/site/docs/reference/starboard/modules/14/window.md
index d050f89..915e254 100644
--- a/cobalt/site/docs/reference/starboard/modules/11/window.md
+++ b/cobalt/site/docs/reference/starboard/modules/14/window.md
@@ -249,6 +249,16 @@
 static bool SbWindowIsValid(SbWindow window)
 ```
 
+### SbWindowOnScreenKeyboardIsSupported ###
+
+Return whether the current platform supports an on screen keyboard
+
+#### Declaration ####
+
+```
+bool SbWindowOnScreenKeyboardIsSupported()
+```
+
 ### SbWindowOnScreenKeyboardSuggestionsSupported ###
 
 Determine if the on screen keyboard has suggestions implemented. If this returns
@@ -318,4 +328,3 @@
 ```
 void SbWindowUpdateOnScreenKeyboardSuggestions(SbWindow window, const char *suggestions[], int num_suggestions, int ticket)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/configuration.md b/cobalt/site/docs/reference/starboard/modules/configuration.md
index 3bd7067..630f1cf 100644
--- a/cobalt/site/docs/reference/starboard/modules/configuration.md
+++ b/cobalt/site/docs/reference/starboard/modules/configuration.md
@@ -134,7 +134,11 @@
 
 ### SB_RESTRICT ###
 
-Include the platform-specific configuration.
+Include the platform-specific configuration. This macro is set by GN in
+starboard/build/config/BUILD.gn and passed in on the command line for all
+targets and all configurations.Makes a pointer-typed parameter restricted so
+that the compiler can make certain optimizations because it knows the pointers
+are unique.
 
 ### SB_SIZE_OF(DATATYPE) ###
 
diff --git a/cobalt/site/docs/reference/starboard/modules/configuration_constants.md b/cobalt/site/docs/reference/starboard/modules/configuration_constants.md
index b380f62..51212e9 100644
--- a/cobalt/site/docs/reference/starboard/modules/configuration_constants.md
+++ b/cobalt/site/docs/reference/starboard/modules/configuration_constants.md
@@ -67,6 +67,10 @@
 
 Determines the alignment that allocations should have on this platform.
 
+### kSbMaxSystemPathCacheDirectorySize ###
+
+The maximum size the cache directory is allowed to use in bytes.
+
 ### kSbMaxThreadLocalKeys ###
 
 The maximum number of thread local storage keys supported by this platform. This
diff --git a/cobalt/site/docs/reference/starboard/modules/decode_target.md b/cobalt/site/docs/reference/starboard/modules/decode_target.md
index 852af38..0e912d5 100644
--- a/cobalt/site/docs/reference/starboard/modules/decode_target.md
+++ b/cobalt/site/docs/reference/starboard/modules/decode_target.md
@@ -5,8 +5,7 @@
 
 A target for decoding image and video data into. This corresponds roughly to an
 EGLImage, but that extension may not be fully supported on all GL platforms.
-SbDecodeTarget supports multi-plane targets. The SbBlitter API is supported as
-well, and this is able to more-or-less unify the two.
+SbDecodeTarget supports multi-plane targets.
 
 An SbDecodeTarget can be passed into any function which decodes video or image
 data. This allows the application to allocate fast graphics memory, and have
@@ -117,7 +116,12 @@
 *   `kSbDecodeTargetFormat3Plane10BitYUVI420`
 
     A decoder target format consisting of 10bit Y, U, and V planes, in that
-    order.
+    order. Each pixel is stored in 16 bits.
+*   `kSbDecodeTargetFormat3Plane10BitYUVI420Compact`
+
+    A decoder target format consisting of 10bit Y, U, and V planes, in that
+    order. The plane data is stored in a compact format. Every three 10-bit
+    pixels are packed into 32 bits.
 *   `kSbDecodeTargetFormat1PlaneUYVY`
 
     A decoder target format consisting of a single plane with pixels laid out in
@@ -394,4 +398,3 @@
 ```
 static void SbDecodeTargetRunInGlesContext(SbDecodeTargetGraphicsContextProvider *provider, SbDecodeTargetGlesContextRunnerTarget target, void *target_context)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/event.md b/cobalt/site/docs/reference/starboard/modules/event.md
index 601e343..8d20c6f 100644
--- a/cobalt/site/docs/reference/starboard/modules/event.md
+++ b/cobalt/site/docs/reference/starboard/modules/event.md
@@ -127,9 +127,9 @@
 
 `PRELOADING` can only happen as the first application state. In this state, the
 application should start and run as normal, but will not receive any input, and
-should not try to initialize graphics resources (via GL or `SbBlitter`). In
-`PRELOADING`, the application can receive `Start` or `Suspend` events. `Start`
-will receive the same data that was passed into `Preload`.
+should not try to initialize graphics resources via GL. In `PRELOADING`, the
+application can receive `Start` or `Suspend` events. `Start` will receive the
+same data that was passed into `Preload`.
 
 In the `STARTED` state, the application is in the foreground and can expect to
 do all of the normal things it might want to do. Once in the `STARTED` state, it
@@ -322,8 +322,8 @@
     ticket value kSbEventOnScreenKeyboardInvalidTicket.
 *   `kSbEventTypeAccessibilityCaptionSettingsChanged`
 
-    SB_HAS(ON_SCREEN_KEYBOARD)One or more of the fields returned by
-    SbAccessibilityGetCaptionSettings has changed.
+    One or more of the fields returned by SbAccessibilityGetCaptionSettings has
+    changed.
 *   `kSbEventTypeAccessibilityTextToSpeechSettingsChanged`
 
     The platform's text-to-speech settings have changed.
@@ -475,4 +475,3 @@
 ```
 SbEventId SbEventSchedule(SbEventCallback callback, void *context, SbTime delay)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/image.md b/cobalt/site/docs/reference/starboard/modules/image.md
index 634dab8..3050953 100644
--- a/cobalt/site/docs/reference/starboard/modules/image.md
+++ b/cobalt/site/docs/reference/starboard/modules/image.md
@@ -13,8 +13,6 @@
 
 ## SbImageIsDecodeSupported and SbImageDecode Example ##
 
-Let's assume that we're on a Blitter platform.
-
 ```
 SbDecodeTargetProvider* provider = GetProviderFromSomewhere();
 void* data = GetCompressedJPEGFromSomewhere();
@@ -28,9 +26,6 @@
 
 SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size,
                                              mime_type, format);
-SbBlitterSurface surface =
-    SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA);
-// Do stuff with surface...
 ```
 
 ## Functions ##
@@ -79,4 +74,3 @@
 ```
 bool SbImageIsDecodeSupported(const char *mime_type, SbDecodeTargetFormat format)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/media.md b/cobalt/site/docs/reference/starboard/modules/media.md
index 6b46874..f7ccacd 100644
--- a/cobalt/site/docs/reference/starboard/modules/media.md
+++ b/cobalt/site/docs/reference/starboard/modules/media.md
@@ -30,6 +30,9 @@
 *   `kSbMediaAudioCodecEac3`
 *   `kSbMediaAudioCodecOpus`
 *   `kSbMediaAudioCodecVorbis`
+*   `kSbMediaAudioCodecMp3`
+*   `kSbMediaAudioCodecFlac`
+*   `kSbMediaAudioCodecPcm`
 
 ### SbMediaAudioCodingType ###
 
@@ -519,12 +522,10 @@
 The media buffer will be allocated using the returned alignment. Set this to a
 larger value may increase the memory consumption of media buffers.
 
-`type`: the media type of the stream (audio or video).
-
 #### Declaration ####
 
 ```
-int SbMediaGetBufferAlignment(SbMediaType type)
+int SbMediaGetBufferAlignment()
 ```
 
 ### SbMediaGetBufferAllocationUnit ###
@@ -564,12 +565,10 @@
 be use optimally by specific instructions like SIMD. Set to 0 to remove any
 padding.
 
-`type`: the media type of the stream (audio or video).
-
 #### Declaration ####
 
 ```
-int SbMediaGetBufferPadding(SbMediaType type)
+int SbMediaGetBufferPadding()
 ```
 
 ### SbMediaGetBufferStorageType ###
@@ -604,7 +603,12 @@
 ### SbMediaGetMaxBufferCapacity ###
 
 The maximum amount of memory that will be used to store media buffers. This must
-be larger than sum of the video budget and audio budget.
+be larger than sum of the video budget and audio budget. This is a soft limit
+and the app will continue to allocate media buffers even if the accumulated
+memory used by the media buffers exceeds the maximum buffer capacity. The
+allocation of media buffers may only fail when there is not enough memory in the
+system to fulfill the request, under which case the app will be terminated as
+under other OOM situations.
 
 `codec`: the video codec associated with the buffer. `resolution_width`: the
 width of the video resolution. `resolution_height`: the height of the video
@@ -699,4 +703,3 @@
 ```
 void SbMediaSetAudioWriteDuration(SbTime duration)
 ```
-
diff --git a/cobalt/site/docs/reference/starboard/modules/system.md b/cobalt/site/docs/reference/starboard/modules/system.md
index d6ce62e..793670f 100644
--- a/cobalt/site/docs/reference/starboard/modules/system.md
+++ b/cobalt/site/docs/reference/starboard/modules/system.md
@@ -72,6 +72,9 @@
 *   `kSbSystemDeviceTypeAndroidTV`
 
     An Android TV Device.
+*   `kSbSystemDeviceTypeVideoProjector`
+
+    A wall video projector.
 *   `kSbSystemDeviceTypeUnknown`
 
     Unknown device.
@@ -106,9 +109,6 @@
 *   `kSbSystemPathTempDirectory`
 
     Path to a directory where temporary files can be written.
-*   `kSbSystemPathTestOutputDirectory`
-
-    Path to a directory where test results can be written.
 *   `kSbSystemPathExecutableFile`
 
     Full path to the executable file.
@@ -194,6 +194,15 @@
 *   `kSbSystemPropertyUserAgentAuxField`
 
     A field that, if available, is appended to the user agent
+*   `kSbSystemPropertyAdvertisingId`
+
+    Advertising ID or IFA, typically a 128-bit UUID Please see [https://iabtechlab.com/OTT-IFA](https://iabtechlab.com/OTT-IFA) for
+    details. Corresponds to 'ifa' field. Note: `ifa_type` ifa_type field is not
+    provided.
+*   `kSbSystemPropertyLimitAdTracking`
+
+    Limit advertising tracking, treated as boolean. Set to nonzero to indicate a
+    true value. Corresponds to 'lmt' field.
 
 ## Typedefs ##
 
@@ -686,10 +695,6 @@
 Computes a HMAC-SHA256 digest of `message` into `digest` using the application's
 certification secret.
 
-This function may be implemented as an alternative to implementing
-SbSystemGetProperty(kSbSystemPropertyBase64EncodedCertificationSecret), however
-both should not be implemented.
-
 The output will be written into `digest`. `digest_size_in_bytes` must be 32 (or
 greater), since 32-bytes will be written into it. Returns false in the case of
 an error, or if it is not implemented. In this case the contents of `digest`
@@ -732,4 +737,3 @@
 ```
 bool SbSystemSymbolize(const void *address, char *out_buffer, int buffer_size)
 ```
-
diff --git a/cobalt/speech/sandbox/BUILD.gn b/cobalt/speech/sandbox/BUILD.gn
index 7853ac1..4b78a30 100644
--- a/cobalt/speech/sandbox/BUILD.gn
+++ b/cobalt/speech/sandbox/BUILD.gn
@@ -41,5 +41,5 @@
 
   deps += cobalt_platform_dependencies
 
-  content_deps = [ "//cobalt/speech:speech_testdata" ]
+  data_deps = [ "//cobalt/speech:speech_testdata" ]
 }
diff --git a/cobalt/web/BUILD.gn b/cobalt/web/BUILD.gn
index 860384c..fba82ac 100644
--- a/cobalt/web/BUILD.gn
+++ b/cobalt/web/BUILD.gn
@@ -72,6 +72,8 @@
     "location_base.h",
     "message_event.cc",
     "message_event.h",
+    "message_port.cc",
+    "message_port.h",
     "navigator_base.cc",
     "navigator_base.h",
     "navigator_ua_data.cc",
diff --git a/cobalt/worker/message_port.cc b/cobalt/web/message_port.cc
similarity index 96%
rename from cobalt/worker/message_port.cc
rename to cobalt/web/message_port.cc
index 703bde5..bc56140 100644
--- a/cobalt/worker/message_port.cc
+++ b/cobalt/web/message_port.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/worker/message_port.h"
+#include "cobalt/web/message_port.h"
 
 #include <string>
 
@@ -30,7 +30,7 @@
 #include "cobalt/web/message_event.h"
 
 namespace cobalt {
-namespace worker {
+namespace web {
 
 MessagePort::MessagePort(web::EventTarget* event_target)
     : event_target_(event_target) {}
@@ -64,5 +64,5 @@
   }
 }
 
-}  // namespace worker
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/worker/message_port.h b/cobalt/web/message_port.h
similarity index 94%
rename from cobalt/worker/message_port.h
rename to cobalt/web/message_port.h
index c1df327..86ff17b 100644
--- a/cobalt/worker/message_port.h
+++ b/cobalt/web/message_port.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_WORKER_MESSAGE_PORT_H_
-#define COBALT_WORKER_MESSAGE_PORT_H_
+#ifndef COBALT_WEB_MESSAGE_PORT_H_
+#define COBALT_WEB_MESSAGE_PORT_H_
 
 #include <string>
 
@@ -29,7 +29,7 @@
 #include "cobalt/web/message_event.h"
 
 namespace cobalt {
-namespace worker {
+namespace web {
 
 class MessagePort : public script::Wrappable,
                     public base::SupportsWeakPtr<MessagePort> {
@@ -83,7 +83,7 @@
   web::EventTarget* event_target_ = nullptr;
 };
 
-}  // namespace worker
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_WORKER_MESSAGE_PORT_H_
+#endif  // COBALT_WEB_MESSAGE_PORT_H_
diff --git a/cobalt/worker/message_port.idl b/cobalt/web/message_port.idl
similarity index 100%
rename from cobalt/worker/message_port.idl
rename to cobalt/web/message_port.idl
diff --git a/cobalt/web/navigator_ua_data.cc b/cobalt/web/navigator_ua_data.cc
index 391815f..93ec852 100644
--- a/cobalt/web/navigator_ua_data.cc
+++ b/cobalt/web/navigator_ua_data.cc
@@ -69,8 +69,6 @@
       platform_info->model_year().value_or(""));
   all_high_entropy_values_.set_device_brand(
       platform_info->brand().value_or(""));
-  all_high_entropy_values_.set_connection_type(
-      platform_info->connection_type_string());
   all_high_entropy_values_.set_aux(platform_info->aux_field());
 
   low_entropy_json_.set_brands(brands_);
@@ -141,9 +139,6 @@
     } else if ((*it).compare("deviceBrand") == 0) {
       select_high_entropy_values_.set_device_brand(
           all_high_entropy_values_.device_brand());
-    } else if ((*it).compare("connectionType") == 0) {
-      select_high_entropy_values_.set_connection_type(
-          all_high_entropy_values_.connection_type());
     } else if ((*it).compare("aux") == 0) {
       select_high_entropy_values_.set_aux(all_high_entropy_values_.aux());
     }
diff --git a/cobalt/web/navigator_ua_data.idl b/cobalt/web/navigator_ua_data.idl
index 3eacfb5..c55f6fa 100644
--- a/cobalt/web/navigator_ua_data.idl
+++ b/cobalt/web/navigator_ua_data.idl
@@ -14,9 +14,9 @@
 
 // https://wicg.github.io/ua-client-hints/#navigatoruadata
 
-[Exposed=Window]
-interface NavigatorUAData {
-  // TODO make this be FrozenArray<NavigatorUABrandVersion> when available
+[Exposed = Window] interface NavigatorUAData {
+  // TODO(b/236750294): Make this be FrozenArray<NavigatorUABrandVersion> when
+  // available.
   readonly attribute sequence<NavigatorUABrandVersion> brands;
   readonly attribute boolean mobile;
   readonly attribute DOMString platform;
diff --git a/cobalt/web/user_agent_platform_info.h b/cobalt/web/user_agent_platform_info.h
index b1300de..995051d 100644
--- a/cobalt/web/user_agent_platform_info.h
+++ b/cobalt/web/user_agent_platform_info.h
@@ -40,8 +40,6 @@
   virtual base::Optional<std::string> brand() const = 0;
   virtual base::Optional<std::string> model() const = 0;
   virtual const std::string& aux_field() const = 0;
-  virtual base::Optional<SbSystemConnectionType> connection_type() const = 0;
-  virtual const std::string& connection_type_string() const = 0;
   virtual const std::string& javascript_engine_version() const = 0;
   virtual const std::string& rasterizer_type() const = 0;
   virtual const std::string& evergreen_type() const = 0;
diff --git a/cobalt/webdriver/BUILD.gn b/cobalt/webdriver/BUILD.gn
index abb1b08..e275c91 100644
--- a/cobalt/webdriver/BUILD.gn
+++ b/cobalt/webdriver/BUILD.gn
@@ -155,6 +155,4 @@
     ":copy_webdriver_data",
     ":webdriver_copy_test_data",
   ]
-
-  content_deps = data_deps
 }
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index e7af371..8c0077a 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -26,8 +26,8 @@
     "dedicated_worker.h",
     "dedicated_worker_global_scope.cc",
     "dedicated_worker_global_scope.h",
-    "message_port.cc",
-    "message_port.h",
+    "extendable_event.h",
+    "extendable_message_event.h",
     "navigation_preload_manager.cc",
     "navigation_preload_manager.h",
     "service_worker.cc",
diff --git a/cobalt/worker/client.h b/cobalt/worker/client.h
index fbeaa82..c40ca72 100644
--- a/cobalt/worker/client.h
+++ b/cobalt/worker/client.h
@@ -19,14 +19,14 @@
 
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/client_type.h"
 #include "cobalt/worker/frame_type.h"
-#include "cobalt/worker/message_port.h"
 
 namespace cobalt {
 namespace worker {
 
-class Client : public MessagePort {
+class Client : public web::MessagePort {
  public:
   // https://w3c.github.io/ServiceWorker/#create-client-algorithm
   static Client* Create(web::EnvironmentSettings* client) {
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 6af2e1a..69b0300 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -20,7 +20,7 @@
 #include "cobalt/browser/stack_size_constants.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/web/event_target.h"
-#include "cobalt/worker/message_port.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/worker.h"
 #include "cobalt/worker/worker_options.h"
 #include "url/gurl.h"
@@ -73,7 +73,7 @@
   // 6. Let worker be a new Worker object.
   // 7. Let outside port be a new MessagePort in outside settings's Realm.
   // 8. Associate the outside port with worker.
-  outside_port_ = new MessagePort(this);
+  outside_port_ = new web::MessagePort(this);
   // 9. Run this step in parallel:
   //    1. Run a worker given worker, worker URL, outside settings, outside
   //    port, and options.
diff --git a/cobalt/worker/dedicated_worker.h b/cobalt/worker/dedicated_worker.h
index 9539741..90c9547 100644
--- a/cobalt/worker/dedicated_worker.h
+++ b/cobalt/worker/dedicated_worker.h
@@ -22,8 +22,8 @@
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/event_target_listener_info.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/abstract_worker.h"
-#include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker.h"
 #include "cobalt/worker/worker_options.h"
 
@@ -82,7 +82,7 @@
 
   const std::string script_url_;
   const WorkerOptions worker_options_;
-  scoped_refptr<MessagePort> outside_port_;
+  scoped_refptr<web::MessagePort> outside_port_;
   std::unique_ptr<Worker> worker_;
 };
 
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
new file mode 100644
index 0000000..4e5d4ff
--- /dev/null
+++ b/cobalt/worker/extendable_event.h
@@ -0,0 +1,53 @@
+// 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_WORKER_EXTENDABLE_EVENT_H_
+#define COBALT_WORKER_EXTENDABLE_EVENT_H_
+
+#include <string>
+
+#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/event.h"
+#include "cobalt/worker/extendable_event_init.h"
+
+namespace cobalt {
+namespace worker {
+
+class ExtendableEvent : public web::Event {
+ public:
+  explicit ExtendableEvent(const std::string& type) : Event(type) {}
+  explicit ExtendableEvent(base::Token type) : Event(type) {}
+  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();
+  }
+
+  DEFINE_WRAPPABLE_TYPE(ExtendableEvent);
+
+ protected:
+  ~ExtendableEvent() override {}
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_EXTENDABLE_EVENT_H_
diff --git a/cobalt/worker/extendable_event.idl b/cobalt/worker/extendable_event.idl
new file mode 100644
index 0000000..2297cbc
--- /dev/null
+++ b/cobalt/worker/extendable_event.idl
@@ -0,0 +1,22 @@
+// 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.
+
+// https://w3c.github.io/ServiceWorker/#extendableevent
+
+[
+  Exposed = ServiceWorker,
+  Constructor(DOMString type, optional ExtendableEventInit eventInitDict)
+] interface ExtendableEvent : Event {
+  [CallWith = EnvironmentSettings] void waitUntil(Promise<any> f);
+};
diff --git a/cobalt/worker/extendable_event_init.idl b/cobalt/worker/extendable_event_init.idl
new file mode 100644
index 0000000..d040bfb
--- /dev/null
+++ b/cobalt/worker/extendable_event_init.idl
@@ -0,0 +1,19 @@
+// 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.
+
+// https://w3c.github.io/ServiceWorker/#dictdef-extendableeventinit
+
+dictionary ExtendableEventInit : EventInit{
+  // Defined for the forward compatibility across the derived events
+};
diff --git a/cobalt/worker/extendable_message_event.h b/cobalt/worker/extendable_message_event.h
new file mode 100644
index 0000000..c6364a7
--- /dev/null
+++ b/cobalt/worker/extendable_message_event.h
@@ -0,0 +1,76 @@
+// 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_WORKER_EXTENDABLE_MESSAGE_EVENT_H_
+#define COBALT_WORKER_EXTENDABLE_MESSAGE_EVENT_H_
+
+#include <memory>
+#include <string>
+
+#include "base/optional.h"
+#include "cobalt/base/token.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/message_port.h"
+#include "cobalt/worker/extendable_event.h"
+#include "cobalt/worker/extendable_message_event_init.h"
+
+namespace cobalt {
+namespace worker {
+
+class ExtendableMessageEvent : public ExtendableEvent {
+ public:
+  typedef scoped_refptr<script::Wrappable> SourceType;
+
+  explicit ExtendableMessageEvent(const std::string& type)
+      : ExtendableEvent(type) {}
+  explicit ExtendableMessageEvent(base::Token type) : ExtendableEvent(type) {}
+  ExtendableMessageEvent(const std::string& type,
+                         const ExtendableMessageEventInit& init_dict)
+      : ExtendableEvent(type, init_dict) {}
+
+
+  const script::ValueHandleHolder* data() const {
+    if (!data_) {
+      return NULL;
+    }
+
+    return &(data_->referenced_value());
+  }
+
+  std::string origin() const { return origin_; }
+  std::string last_event_id() const { return last_event_id_; }
+
+  base::Optional<SourceType> source() { return source_; }
+
+  script::Sequence<scoped_refptr<MessagePort>> ports() const { return ports_; }
+
+  DEFINE_WRAPPABLE_TYPE(ExtendableMessageEvent);
+
+ protected:
+  ~ExtendableMessageEvent() override {}
+
+ private:
+  std::unique_ptr<script::ValueHandleHolder::Reference> data_;
+
+  std::string origin_ = "Origin Stub Value";
+  std::string last_event_id_ = "Last Event Id Stub Value";
+  base::Optional<SourceType> source_;
+  script::Sequence<scoped_refptr<MessagePort>> ports_;
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_EXTENDABLE_MESSAGE_EVENT_H_
diff --git a/cobalt/worker/extendable_message_event.idl b/cobalt/worker/extendable_message_event.idl
new file mode 100644
index 0000000..1e2d5da
--- /dev/null
+++ b/cobalt/worker/extendable_message_event.idl
@@ -0,0 +1,27 @@
+// 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.
+
+// https://w3c.github.io/ServiceWorker/#extendablemessageevent
+
+[
+  Exposed = ServiceWorker,
+  Constructor(DOMString type, optional ExtendableMessageEventInit eventInitDict)
+] interface ExtendableMessageEvent : ExtendableEvent {
+  readonly attribute any data;
+  readonly attribute USVString origin;
+  readonly attribute DOMString lastEventId;
+  [SameObject] readonly attribute (Client or ServiceWorker or MessagePort)? source;
+  // TODO(b/236750294): Make this be FrozenArray<MessagePort> when available.
+  readonly attribute sequence<MessagePort> ports;
+};
diff --git a/cobalt/worker/extendable_message_event_init.idl b/cobalt/worker/extendable_message_event_init.idl
new file mode 100644
index 0000000..25da4cb
--- /dev/null
+++ b/cobalt/worker/extendable_message_event_init.idl
@@ -0,0 +1,23 @@
+// 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.
+
+// https://w3c.github.io/ServiceWorker/#dictdef-extendablemessageeventinit
+
+dictionary ExtendableMessageEventInit : ExtendableEventInit {
+  any data = null;
+  USVString origin = "";
+  DOMString lastEventId = "";
+  (Client or ServiceWorker or MessagePort)? source = null;
+  sequence<MessagePort> ports;
+};
diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc
index c683464..4d6ac58 100644
--- a/cobalt/worker/service_worker.cc
+++ b/cobalt/worker/service_worker.cc
@@ -18,6 +18,7 @@
 #include <utility>
 
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/service_worker_global_scope.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
@@ -29,7 +30,7 @@
                              worker::ServiceWorkerObject* worker)
     : web::EventTarget(settings),
       worker_(worker),
-      message_port_(new MessagePort(worker->worker_global_scope())),
+      message_port_(new web::MessagePort(worker->worker_global_scope())),
       state_(kServiceWorkerStateParsed) {}
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker.h b/cobalt/worker/service_worker.h
index 5e212e1..2b68fc2 100644
--- a/cobalt/worker/service_worker.h
+++ b/cobalt/worker/service_worker.h
@@ -23,8 +23,8 @@
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/event_target_listener_info.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/abstract_worker.h"
-#include "cobalt/worker/message_port.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
 
@@ -44,6 +44,7 @@
   // Web API: ServiceWorker
   //
   void PostMessage(const std::string& message) {
+    DCHECK(message_port_);
     message_port_->PostMessage(message);
   }
 
@@ -82,7 +83,7 @@
   }
 
   ServiceWorkerObject* worker_ = nullptr;
-  scoped_refptr<MessagePort> message_port_;
+  scoped_refptr<web::MessagePort> message_port_;
   ServiceWorkerState state_;
 };
 
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index 224873e..3414b2e 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -25,8 +25,8 @@
 #include "cobalt/browser/user_agent_platform_info.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/web/error_event.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/dedicated_worker_global_scope.h"
-#include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker_global_scope.h"
 #include "cobalt/worker/worker_options.h"
 #include "cobalt/worker/worker_settings.h"
@@ -222,7 +222,7 @@
   // Done at step 8.
   // 16. Let inside port be a new MessagePort object in inside settings's Realm.
   // 17. Associate inside port with worker global scope.
-  message_port_ = new MessagePort(worker_global_scope_);
+  message_port_ = new web::MessagePort(worker_global_scope_);
   // 18. Entangle outside port and inside port.
   // TODO(b/226640425): Implement this when Message Ports can be entangled.
   // 19. Create a new WorkerLocation object and associate it with worker global
diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h
index 4476ea9..fe78f28 100644
--- a/cobalt/worker/worker.h
+++ b/cobalt/worker/worker.h
@@ -36,8 +36,8 @@
 #include "cobalt/web/agent.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/message_port.h"
 #include "cobalt/worker/dedicated_worker_global_scope.h"
-#include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker_global_scope.h"
 #include "cobalt/worker/worker_options.h"
 #include "cobalt/worker/worker_settings.h"
@@ -65,7 +65,7 @@
     //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker
     GURL url;
     web::EnvironmentSettings* outside_settings = nullptr;
-    MessagePort* outside_port = nullptr;
+    web::MessagePort* outside_port = nullptr;
     WorkerOptions options;
   };
 
@@ -76,7 +76,7 @@
 
   void Terminate();
 
-  MessagePort* message_port() const { return message_port_.get(); }
+  web::MessagePort* message_port() const { return message_port_.get(); }
 
   // The message loop this object is running on.
   base::MessageLoop* message_loop() const {
@@ -123,7 +123,7 @@
   scoped_refptr<WorkerGlobalScope> worker_global_scope_;
 
   // Inner message port.
-  scoped_refptr<MessagePort> message_port_;
+  scoped_refptr<web::MessagePort> message_port_;
 
   // The loader that is used for asynchronous loads.
   std::unique_ptr<loader::Loader> loader_;
diff --git a/cobalt/worker/worker_settings.cc b/cobalt/worker/worker_settings.cc
index 33db372..3c730e3 100644
--- a/cobalt/worker/worker_settings.cc
+++ b/cobalt/worker/worker_settings.cc
@@ -24,7 +24,7 @@
 namespace worker {
 WorkerSettings::WorkerSettings() : web::EnvironmentSettings() {}
 
-WorkerSettings::WorkerSettings(worker::MessagePort* message_port)
+WorkerSettings::WorkerSettings(web::MessagePort* message_port)
     : web::EnvironmentSettings(), message_port_(message_port) {}
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/worker_settings.h b/cobalt/worker/worker_settings.h
index 94808a1..99fae7f 100644
--- a/cobalt/worker/worker_settings.h
+++ b/cobalt/worker/worker_settings.h
@@ -18,7 +18,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/web/environment_settings.h"
-#include "cobalt/worker/message_port.h"
+#include "cobalt/web/message_port.h"
 
 namespace cobalt {
 namespace worker {
@@ -29,13 +29,13 @@
 class WorkerSettings : public web::EnvironmentSettings {
  public:
   WorkerSettings();
-  explicit WorkerSettings(worker::MessagePort* message_port);
+  explicit WorkerSettings(web::MessagePort* message_port);
 
-  worker::MessagePort* message_port() const { return message_port_; }
+  web::MessagePort* message_port() const { return message_port_; }
 
  private:
   // Outer message port.
-  worker::MessagePort* message_port_ = nullptr;
+  web::MessagePort* message_port_ = nullptr;
 };
 
 }  // namespace worker
diff --git a/docker/linux/base/Dockerfile b/docker/linux/base/Dockerfile
index d0c21b3..84368e4 100644
--- a/docker/linux/base/Dockerfile
+++ b/docker/linux/base/Dockerfile
@@ -23,10 +23,6 @@
 # === Install common dependencies
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
-        python-pip \
-        python-requests \
-        python-setuptools \
-        python-wheel \
         python3-dev \
         python3-pip \
         python3-requests \
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 258af0d..8c7de67 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -20,7 +20,6 @@
 
 config("net_public_defines") {
   defines = [
-    "HTTP_CACHE_TESTING_DISABLED",
     "ENABLE_BUILT_IN_DNS",
     "QUIC_TRACE_DISABLED",
     "COBALT_QUIC46",
@@ -4198,7 +4197,6 @@
   }
 
   defines = [
-    "HTTP_CACHE_TESTING_DISABLED",
     "GMOCK_NO_MOVE_MOCK",
   ]
 
@@ -4230,7 +4228,7 @@
   ]
 
   if (is_starboard) {
-    content_deps = [
+    data_deps = [
       ":net_unittest_files",
       ":third_party_unittest_files",
       "//third_party/icu:icudata",
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 1ecd878..0740dc8 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -3725,8 +3725,6 @@
   EXPECT_EQ(1, cache.disk_cache()->create_count());
 }
 
-// TODO: Enable test when HTTP Caching is fully functional.
-#if !defined(STARBOARD)
 // Tests that we can doom an entry with pending transactions and delete one of
 // the pending transactions before the first one completes.
 // See http://code.google.com/p/chromium/issues/detail?id=25588
@@ -3777,7 +3775,6 @@
     ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
   }
 }
-#endif
 
 TEST_F(HttpCacheTest, DoomDoesNotSetHints) {
   // Test that a doomed writer doesn't set in-memory index hints.
@@ -7859,8 +7856,6 @@
   RemoveMockTransaction(&kRangeGET_TransactionOK);
 }
 
-// TODO: Enable test when HTTP Caching is fully functional.
-#if !defined(HTTP_CACHE_TESTING_DISABLED) 
 // Tests that we handle large range values properly.
 TEST_F(HttpCacheTest, RangeGET_LargeValues) {
   // We need a real sparse cache for this test.
@@ -7892,7 +7887,6 @@
 
   RemoveMockTransaction(&kRangeGET_TransactionOK);
 }
-#endif
 
 // Tests that we don't crash with a range request if the disk cache was not
 // initialized properly.
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index 7a4afdd..fc7b92b 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -489,7 +489,6 @@
             network_delegate_.total_network_bytes_received());
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
 TEST_F(URLRequestHttpJobWithMockSocketsTest, TestSuccessfulCachedHeadRequest) {
   // Cache the response.
   {
@@ -548,7 +547,6 @@
     EXPECT_EQ(0, request->GetTotalReceivedBytes());
   }
 }
-#endif
 
 TEST_F(URLRequestHttpJobWithMockSocketsTest,
        TestContentLengthSuccessfulHttp09Request) {
diff --git a/net/url_request/url_request_quic_unittest.cc b/net/url_request/url_request_quic_unittest.cc
index 46e75a5..3bd45db 100644
--- a/net/url_request/url_request_quic_unittest.cc
+++ b/net/url_request/url_request_quic_unittest.cc
@@ -269,7 +269,7 @@
   EXPECT_EQ(kHelloBodyValue, delegate.data_received());
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
+#if !defined(STARBOARD)
 TEST_F(URLRequestQuicTest, CancelPushIfCached_SomeCached) {
   Init();
 
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 3f9f7ef..d78b9b0 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -7936,7 +7936,6 @@
   EXPECT_EQ(0, d.received_redirect_count());
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
 // Make sure redirects are cached, despite not reading their bodies.
 TEST_F(URLRequestTestHTTP, CacheRedirect) {
   ASSERT_TRUE(http_test_server()->Start());
@@ -7973,7 +7972,6 @@
     EXPECT_EQ(http_test_server()->GetURL("/echo"), req->url());
   }
 }
-#endif
 
 // Make sure a request isn't cached when a NetworkDelegate forces a redirect
 // when the headers are read, since the body won't have been read.
@@ -8398,7 +8396,6 @@
   }
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
 TEST_F(URLRequestTestHTTP, VaryHeader) {
   ASSERT_TRUE(http_test_server()->Start());
 
@@ -8497,7 +8494,6 @@
     EXPECT_TRUE(r->was_cached());
   }
 }
-#endif
 
 // Check that Set-Cookie headers in 401 responses are respected.
 // http://crbug.com/6450
@@ -8561,7 +8557,6 @@
   }
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
 // Tests that load timing works as expected with auth and the cache.
 TEST_F(URLRequestTestHTTP, BasicAuthLoadTiming) {
   ASSERT_TRUE(http_test_server()->Start());
@@ -8624,7 +8619,6 @@
     TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
   }
 }
-#endif
 
 // In this test, we do a POST which the server will 302 redirect.
 // The subsequent transaction should use GET, and should not send the
@@ -9249,7 +9243,6 @@
   EXPECT_TRUE(req->response_info().network_accessed);
 }
 
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
 TEST_F(URLRequestTestHTTP, NetworkAccessedClearOnCachedResponse) {
   ASSERT_TRUE(http_test_server()->Start());
 
@@ -9290,7 +9283,6 @@
 
   EXPECT_FALSE(req->response_info().network_accessed);
 }
-#endif
 
 // Test that a single job with a THROTTLED priority completes
 // correctly in the absence of contention.
@@ -12337,7 +12329,6 @@
     EXPECT_EQ("GET /cachetime HTTP/1.1\r\n", raw_req_headers.request_line());
     EXPECT_EQ(raw_resp_headers.get(), r->response_headers());
   }
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
   {
     std::unique_ptr<URLRequest> r(context.CreateRequest(
         url, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
@@ -12353,7 +12344,6 @@
     delegate.RunUntilComplete();
     EXPECT_TRUE(r->was_cached());
   }
-#endif
 }
 
 TEST_F(URLRequestTestHTTP, HeadersCallbacksWithRedirect) {
@@ -12484,14 +12474,12 @@
   EXPECT_FALSE(r2->is_pending());
   ASSERT_EQ(raw_req_headers.size(), 3u);
   ASSERT_EQ(raw_resp_headers.size(), 3u);
-#if !defined(HTTP_CACHE_TESTING_DISABLED)
   // Google for the "If-None-Match" request header to see its relation to
   // HTTP cache.
   EXPECT_TRUE(raw_req_headers[2]->FindHeaderForTest("If-None-Match", &value));
   EXPECT_NE(raw_resp_headers[2].get(), r2->response_headers());
   EXPECT_EQ(304, raw_resp_headers[2]->response_code());
   EXPECT_EQ("Not Modified", raw_resp_headers[2]->GetStatusText());
-#endif
 }
 
 TEST_F(URLRequestTest, HeadersCallbacksNonHTTP) {
diff --git a/requirements.txt b/requirements.txt
index 35a4036..2732a91 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,6 @@
 yapf<1
 requests==2.25.1
 jsonschema<5
+selenium==3.141.0
+Brotli==1.0.9
+six<2
diff --git a/starboard/CHANGELOG.md b/starboard/CHANGELOG.md
index 7516ca9..99eece6 100644
--- a/starboard/CHANGELOG.md
+++ b/starboard/CHANGELOG.md
@@ -15,7 +15,10 @@
 [configuration.h](configuration.h).
 
 ## Version 14
-### Add kSbSystemPropretyAdvertisingId and kSbSystemPropretyLimitAdTracking
+### Add MP3, FLAC, and PCM values to SbMediaAudioCodec.
+This makes it possible to support these codecs in the future.
+
+### Add kSbSystemPropertyAdvertisingId and kSbSystemPropertyLimitAdTracking
 Adds properties for retrieving IFA identifier and related ad tracking limiting.
 
 ### Add kSbSystemDeviceTypeVideoProjector type to Starboard devices.
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle
index 944dcbd..568d689 100644
--- a/starboard/android/apk/app/build.gradle
+++ b/starboard/android/apk/app/build.gradle
@@ -46,6 +46,11 @@
     buildToolsVersion '30.0.0'
     ndkVersion NDK_VERSION
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
     aaptOptions {
         // The following pattern matches the default aapt pattern with the
         // exception that it is missing the blanket ignore wildcard (!.*) that
@@ -178,6 +183,7 @@
     implementation 'androidx.annotation:annotation:1.1.0'
     implementation 'androidx.leanback:leanback:1.0.0'
     implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
     implementation 'com.google.android.gms:play-services-auth:18.0.0'
     implementation 'com.google.protobuf:protobuf-lite:3.0.1'
 }
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml
index c56c2b4..78b3400 100644
--- a/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -32,6 +32,9 @@
   <!-- This is needed when targeting API 28+ to use foreground services -->
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 
+  <!-- Used for go/cobalt-ifa and AdvertisingIdClient.Info.getId() -->
+  <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
+
   <application
     android:name="dev.cobalt.app.CobaltApplication"
     android:icon="@mipmap/ic_app"
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AdvertisingId.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AdvertisingId.java
new file mode 100644
index 0000000..bde7982
--- /dev/null
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AdvertisingId.java
@@ -0,0 +1,81 @@
+// 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.
+
+package dev.cobalt.coat;
+
+import static dev.cobalt.util.Log.TAG;
+
+import android.content.Context;
+import com.google.android.gms.ads.identifier.AdvertisingIdClient;
+import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
+import com.google.android.gms.common.GooglePlayServicesRepairableException;
+import dev.cobalt.util.Log;
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** A class for managing the IfA for CoAT. https://developer.android.com/training/articles/ad-id */
+public class AdvertisingId {
+  private final Context context;
+  private final ExecutorService singleThreadExecutor;
+  private long lastRefreshed;
+  private final long cacheTtlMs = 1000 * 60 * 10; // 10 minutes.
+  private AdvertisingIdClient.Info advertisingIdInfo;
+
+  public AdvertisingId(Context context) {
+    this.context = context;
+    this.lastRefreshed = 0;
+    this.singleThreadExecutor = Executors.newSingleThreadExecutor();
+    refresh();
+  }
+
+  private void refresh() {
+    if (System.currentTimeMillis() - lastRefreshed < cacheTtlMs) {
+      // Cache is up to date.
+      return;
+    }
+    singleThreadExecutor.execute(
+        () -> {
+          try {
+            advertisingIdInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
+            lastRefreshed = System.currentTimeMillis();
+            Log.i(TAG, "Successfully retrieved Advertising ID (IfA).");
+          } catch (IOException
+              | GooglePlayServicesNotAvailableException
+              | GooglePlayServicesRepairableException e) {
+            Log.e(TAG, "Failed to retrieve Advertising ID (IfA).");
+          }
+        });
+  }
+
+  public String getId() {
+    String result = "";
+    if (lastRefreshed != 0) {
+      result = advertisingIdInfo.getId();
+      refresh();
+    }
+    Log.d(TAG, "Returning IfA getId: " + result);
+    return result;
+  }
+
+  public boolean isLimitAdTrackingEnabled() {
+    boolean result = false;
+    if (lastRefreshed != 0) {
+      result = advertisingIdInfo.isLimitAdTrackingEnabled();
+      refresh();
+    }
+    Log.d(TAG, "Returning IfA LimitedAdTrackingEnabled: " + Boolean.toString(result));
+    return result;
+  }
+}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 62165b2..902ca00 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -82,6 +82,7 @@
   private KeyboardEditor keyboardEditor;
   private NetworkStatus networkStatus;
   private ResourceOverlay resourceOverlay;
+  private AdvertisingId advertisingId;
 
   static {
     // Even though NativeActivity already loads our library from C++,
@@ -140,6 +141,7 @@
     this.audioPermissionRequester = new AudioPermissionRequester(appContext, activityHolder);
     this.networkStatus = new NetworkStatus(appContext);
     this.resourceOverlay = new ResourceOverlay(appContext);
+    this.advertisingId = new AdvertisingId(appContext);
   }
 
   private native boolean nativeInitialize();
@@ -684,6 +686,20 @@
     return sb.toString();
   }
 
+  /** Returns string for kSbSystemPropertyAdvertisingId */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  protected String getAdvertisingId() {
+    return this.advertisingId.getId();
+  }
+
+  /** Returns boolean for kSbSystemPropertyLimitAdTracking */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  protected boolean getLimitAdTracking() {
+    return this.advertisingId.isLimitAdTrackingEnabled();
+  }
+
   @SuppressWarnings("unused")
   @UsedByNative
   AudioOutputManager getAudioOutputManager() {
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 0a7270e..69c2e78 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
@@ -110,6 +110,8 @@
               .setUsage(AudioAttributes.USAGE_MEDIA)
               .build();
     } else {
+      final int usage =
+          isWebAudio ? AudioAttributes.USAGE_NOTIFICATION : AudioAttributes.USAGE_MEDIA;
       // TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
       final boolean isSurround =
           sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
@@ -120,7 +122,7 @@
                   useContentTypeMovie
                       ? AudioAttributes.CONTENT_TYPE_MOVIE
                       : AudioAttributes.CONTENT_TYPE_MUSIC)
-              .setUsage(AudioAttributes.USAGE_MEDIA)
+              .setUsage(usage)
               .build();
     }
     AudioFormat format =
diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc
index f80c44e..b2f7671 100644
--- a/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/starboard/android/shared/audio_renderer_passthrough.cc
@@ -337,7 +337,7 @@
 
   SbTime updated_at;
   auto playback_head_position =
-      audio_track_bridge_->GetPlaybackHeadPosition(&updated_at);
+      audio_track_bridge_->GetAudioTimestamp(&updated_at);
   if (playback_head_position <= 0) {
     // The playback is warming up, don't adjust the media time by the monotonic
     // system time.
@@ -491,7 +491,7 @@
         audio_track_bridge_->Stop();
         stop_called_ = true;
         playback_head_position_when_stopped_ =
-            audio_track_bridge_->GetPlaybackHeadPosition(&stopped_at_);
+            audio_track_bridge_->GetAudioTimestamp(&stopped_at_);
         total_frames_written_ = total_frames_written_on_audio_track_thread_;
         decoded_audio_writing_in_progress_ = nullptr;
         SB_LOG(INFO) << "Audio track stopped at " << stopped_at_
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index 0e9d29e..45d90b0 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -188,7 +188,7 @@
 
     if (was_playing) {
       playback_head_position =
-          bridge_.GetPlaybackHeadPosition(&frames_consumed_at, env);
+          bridge_.GetAudioTimestamp(&frames_consumed_at, env);
       SB_DCHECK(playback_head_position >= last_playback_head_position_);
 
       playback_head_position =
diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc
index 05b9c9b..aad3562 100644
--- a/starboard/android/shared/audio_track_bridge.cc
+++ b/starboard/android/shared/audio_track_bridge.cc
@@ -257,7 +257,7 @@
   }
 }
 
-int64_t AudioTrackBridge::GetPlaybackHeadPosition(
+int64_t AudioTrackBridge::GetAudioTimestamp(
     SbTime* updated_at,
     JniEnvExt* env /*= JniEnvExt::Get()*/) {
   SB_DCHECK(env);
diff --git a/starboard/android/shared/audio_track_bridge.h b/starboard/android/shared/audio_track_bridge.h
index 3804b6a..9ddb4f1 100644
--- a/starboard/android/shared/audio_track_bridge.h
+++ b/starboard/android/shared/audio_track_bridge.h
@@ -77,10 +77,10 @@
 
   void SetVolume(double volume, JniEnvExt* env = JniEnvExt::Get());
 
-  // |updated_at| contains the timestamp when the playback head position is
-  // updated on return.  It can be nullptr.
-  int64_t GetPlaybackHeadPosition(SbTime* updated_at,
-                                  JniEnvExt* env = JniEnvExt::Get());
+  // |updated_at| contains the timestamp when the audio timstamp is updated on
+  // return.  It can be nullptr.
+  int64_t GetAudioTimestamp(SbTime* updated_at,
+                            JniEnvExt* env = JniEnvExt::Get());
   bool GetAndResetHasAudioDeviceChanged(JniEnvExt* env = JniEnvExt::Get());
   int GetUnderrunCount(JniEnvExt* env = JniEnvExt::Get());
 
diff --git a/starboard/android/shared/system_get_connection_type.cc b/starboard/android/shared/system_get_connection_type.cc
index 0b99415..7b3d2bf 100644
--- a/starboard/android/shared/system_get_connection_type.cc
+++ b/starboard/android/shared/system_get_connection_type.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/system.h"
 
+#if SB_API_VERSION < 14
+
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/common/log.h"
 
@@ -30,3 +32,5 @@
     return kSbSystemConnectionTypeWired;
   }
 }
+
+#endif  // SB_API_VERSION < 14
diff --git a/starboard/android/shared/system_get_property.cc b/starboard/android/shared/system_get_property.cc
index b08528f..256edc3 100644
--- a/starboard/android/shared/system_get_property.cc
+++ b/starboard/android/shared/system_get_property.cc
@@ -144,6 +144,24 @@
           CopyStringAndTestIfSuccess(out_value, value_length, utf_str.c_str());
       return success;
     }
+#if SB_API_VERSION >= 14
+    case kSbSystemPropertyAdvertisingId: {
+      JniEnvExt* env = JniEnvExt::Get();
+      ScopedLocalJavaRef<jstring> id_string(
+          env->CallStarboardObjectMethodOrAbort("getAdvertisingId",
+                                                "()Ljava/lang/String;"));
+      std::string utf_str = env->GetStringStandardUTFOrAbort(id_string.Get());
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        utf_str.c_str());
+    }
+    case kSbSystemPropertyLimitAdTracking: {
+      bool limit_ad_tracking_enabled =
+          JniEnvExt::Get()->CallStarboardBooleanMethodOrAbort(
+              "getLimitAdTracking", "()Z") == JNI_TRUE;
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        limit_ad_tracking_enabled ? "1" : "0");
+    }
+#endif
     default:
       SB_DLOG(WARNING) << __FUNCTION__
                        << ": Unrecognized property: " << property_id;
diff --git a/starboard/build/config/BUILD.gn b/starboard/build/config/BUILD.gn
index aa06b1d..f407ee5 100644
--- a/starboard/build/config/BUILD.gn
+++ b/starboard/build/config/BUILD.gn
@@ -67,11 +67,16 @@
 
       if (target_cpu == "arm" || target_cpu == "x86") {
         cflags = [ "-m32" ]
-        ldflags += [
-          "-target",
-          "i386-unknown-linux-gnu",
-          "-latomic",
-        ]
+        ldflags += [ "-latomic" ]
+
+        if (is_clang) {
+          ldflags += [
+            "-target",
+            "i386-unknown-linux-gnu",
+          ]
+        } else {
+          ldflags += [ "-m32" ]
+        }
       }
     } else if (is_win) {
       # A few flags to mute MSVC compiler errors that does not appear on Linux.
diff --git a/starboard/build/config/BUILDCONFIG.gn b/starboard/build/config/BUILDCONFIG.gn
index 41086c5..774be60 100644
--- a/starboard/build/config/BUILDCONFIG.gn
+++ b/starboard/build/config/BUILDCONFIG.gn
@@ -197,9 +197,7 @@
 # content targets.
 import("//starboard/build/config/install.gni")
 
-# Set up the method of generating the install targets as defined by the
-# platform.
-import("$install_target_path")
+# Template for copying content files to the install directory.
 template("install_content") {
   target(invoker.target_type, target_name) {
     forward_variables_from(invoker, "*", [ "install_content" ])
@@ -209,6 +207,8 @@
   }
 
   if (defined(invoker.install_content) && invoker.install_content) {
+    # We're using a custom script to copy the files here because rebase_path
+    # can't be used with {{}} expansions in the outputs of a copy target.
     action("${target_name}_install_content") {
       forward_variables_from(invoker, [ "testonly" ])
 
@@ -216,6 +216,14 @@
 
       sources = get_target_outputs(":${invoker.target_name}")
 
+      # List of files in metadata so they can be forwarded for bundling.
+      metadata = {
+        install_content = sources
+
+        # Don't inherit any metadata from upstream deps.
+        ignore_install_content = deps
+      }
+
       install_content_dir = "$sb_install_output_dir/$sb_install_content_subdir"
       outputs = []
       foreach(source, sources) {
@@ -242,6 +250,9 @@
   }
 }
 
+# Set up the method of generating the install targets as defined by the
+# platform.
+import("$install_target_path")
 template("action") {
   install_content(target_name) {
     forward_variables_from(invoker, "*")
@@ -266,68 +277,28 @@
 template("executable") {
   target_with_platform_configs(target_name) {
     target_type = "executable"
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "install_target",
-                             "content_deps",
-                           ])
+    forward_variables_from(invoker, "*", [ "install_target" ])
   }
 
   if (current_toolchain == default_toolchain &&
       (!defined(invoker.install_target) || invoker.install_target)) {
     executable_target_name = target_name
 
-    content_deps = []
-    if (defined(invoker.content_deps)) {
-      content_deps += invoker.content_deps
+    data_deps = []
+    if (defined(invoker.data_deps)) {
+      data_deps += invoker.data_deps
     }
     if (defined(platform_i18n_config_path)) {
-      content_deps += [ platform_i18n_config_path ]
+      data_deps += [ platform_i18n_config_path ]
     }
 
     if (separate_install_targets_for_bundling) {
-      bundle_deps = []
-      foreach(content_dep, content_deps) {
-        bundle_deps += [ "${content_dep}_install_content" ]
-      }
-
-      # TODO(b/220024845): Make this much better.
-      action("${executable_target_name}_bundle_content") {
+      import("//starboard/build/config/bundle_content.gni")
+      bundle_content("${executable_target_name}_bundle_content") {
         forward_variables_from(invoker, [ "testonly" ])
 
-        deps = [ ":$executable_target_name" ]
-        deps += bundle_deps
-
-        sources = []
-        foreach(bundle_dep, bundle_deps) {
-          # Split the target path and name. E.g. //path/to:target -> [ "//path/to", "target" ].
-          target_path_and_name = string_split(bundle_dep, ":")
-
-          # The content_dep target will place its file under the root gen dir.
-          content_dep_list_file = string_join("/",
-                                              [
-                                                root_gen_dir,
-                                                target_path_and_name[1],
-                                              ])
-          target_path_and_name = []
-
-          sources += [ "${content_dep_list_file}_files.tmp" ]
-        }
-
-        bundle_content_dir = "$sb_install_output_dir/$executable_target_name/$sb_install_content_subdir"
-
-        # TODO(b/220024845): We don't have the list of output files.
-        outputs = [ bundle_content_dir ]
-
-        script = "//starboard/build/copy_bundle_content.py"
-        args = [
-          "--output_dir",
-          rebase_path(bundle_content_dir, root_build_dir),
-          "--base_dir",
-          rebase_path(sb_static_contents_output_data_dir, root_build_dir),
-        ]
-        args += rebase_path(sources, root_build_dir)
+        bundle_name = executable_target_name
+        bundle_deps = data_deps
       }
     }
 
@@ -339,12 +310,12 @@
       if (defined(invoker.deps)) {
         deps += invoker.deps
       }
+      foreach(dep, data_deps) {
+        deps += [ "${dep}_install_content" ]
+      }
       if (separate_install_targets_for_bundling) {
         deps += [ ":${executable_target_name}_bundle_content" ]
       }
-      foreach(content_dep, content_deps) {
-        deps += [ "${content_dep}_install_content" ]
-      }
     }
   }
 }
@@ -352,68 +323,29 @@
 template("shared_library") {
   target_with_platform_configs(target_name) {
     target_type = "shared_library"
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "install_target",
-                             "content_deps",
-                           ])
+    forward_variables_from(invoker, "*", [ "install_target" ])
   }
 
   if (current_toolchain == default_toolchain &&
       (!defined(invoker.install_target) || invoker.install_target)) {
     shared_library_target_name = target_name
 
-    content_deps = []
-    if (defined(invoker.content_deps)) {
-      content_deps += invoker.content_deps
+    data_deps = []
+    if (defined(invoker.data_deps)) {
+      data_deps += invoker.data_deps
     }
     if (defined(platform_i18n_config_path)) {
-      content_deps += [ platform_i18n_config_path ]
+      data_deps += [ platform_i18n_config_path ]
     }
 
     if (separate_install_targets_for_bundling) {
-      bundle_deps = []
-      foreach(content_dep, content_deps) {
-        bundle_deps += [ "${content_dep}_install_content" ]
-      }
-
-      # TODO(b/220024845): Make this much better.
-      action("${shared_library_target_name}_bundle_content") {
+      import("//starboard/build/config/bundle_content.gni")
+      bundle_content("${shared_library_target_name}_bundle_content") {
         forward_variables_from(invoker, [ "testonly" ])
 
-        deps = [ ":$shared_library_target_name" ]
-        deps += bundle_deps
-
-        sources = []
-        foreach(bundle_dep, bundle_deps) {
-          # Split the target path and name. E.g. //path/to:target -> [ "//path/to", "target" ].
-          target_path_and_name = string_split(bundle_dep, ":")
-
-          # The content_dep target will place its file under the root gen dir.
-          content_dep_list_file = string_join("/",
-                                              [
-                                                root_gen_dir,
-                                                target_path_and_name[1],
-                                              ])
-          target_path_and_name = []
-
-          sources += [ "${content_dep_list_file}_files.tmp" ]
-        }
-
-        bundle_content_dir = "$sb_install_output_dir/$shared_library_target_name/$sb_install_content_subdir"
-
-        # TODO(b/220024845): We don't have the list of output files.
-        outputs = [ bundle_content_dir ]
-
-        script = "//starboard/build/copy_bundle_content.py"
-        args = [
-          "--output_dir",
-          rebase_path(bundle_content_dir, root_build_dir),
-          "--base_dir",
-          rebase_path(sb_static_contents_output_data_dir, root_build_dir),
-        ]
-        args += rebase_path(sources, root_build_dir)
+        bundle_name = shared_library_target_name
+        bundle_deps = []
+        bundle_deps = data_deps
       }
     }
 
@@ -425,12 +357,12 @@
       if (defined(invoker.deps)) {
         deps += invoker.deps
       }
+      foreach(dep, data_deps) {
+        deps += [ "${dep}_install_content" ]
+      }
       if (separate_install_targets_for_bundling) {
         deps += [ ":${shared_library_target_name}_bundle_content" ]
       }
-      foreach(content_dep, content_deps) {
-        deps += [ "${content_dep}_install_content" ]
-      }
     }
   }
 }
diff --git a/starboard/build/config/bundle_content.gni b/starboard/build/config/bundle_content.gni
new file mode 100644
index 0000000..2008c95
--- /dev/null
+++ b/starboard/build/config/bundle_content.gni
@@ -0,0 +1,69 @@
+# 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.
+
+template("bundle_content") {
+  bundle_name = invoker.bundle_name
+  bundle_deps = invoker.bundle_deps
+
+  bundle_content_list_file = "$root_gen_dir/${target_name}_files.txt"
+  file_format = "list lines"
+
+  # If the platform bundles content the list of content files can be collected
+  # from the metadata field of the generated install_content targets.
+  generated_file("list_$target_name") {
+    forward_variables_from(invoker, [ "testonly" ])
+
+    # Rebase the paths the script working directory.
+    rebase = root_build_dir
+
+    data_keys = [ "install_content" ]
+
+    walk_keys = [ "ignore_install_content" ]
+
+    deps = []
+    foreach(dep, bundle_deps) {
+      deps += [ "${dep}_install_content" ]
+    }
+
+    output_conversion = file_format
+    outputs = [ bundle_content_list_file ]
+  }
+
+  action(target_name) {
+    forward_variables_from(invoker, [ "testonly" ])
+
+    deps = [ ":list_$target_name" ]
+
+    inputs = [ bundle_content_list_file ]
+
+    bundle_content_dir =
+        "$sb_install_output_dir/$bundle_name/$sb_install_content_subdir"
+
+    # TODO(b/220024845): We don't have the list of output files. The files
+    # are listed in `bundle_content_list_file` but can't be accessed in GN
+    # as `read_file` can't reliably access files generated by
+    # `generated_file` targets.
+    outputs = [ bundle_content_dir ]
+
+    script = "//starboard/build/copy_install_content.py"
+    args = [
+      "--output_dir",
+      rebase_path(bundle_content_dir, root_build_dir),
+      "--base_dir",
+      rebase_path(sb_static_contents_output_data_dir, root_build_dir),
+      "--files_list",
+      rebase_path(bundle_content_list_file, root_build_dir),
+    ]
+  }
+}
diff --git a/starboard/build/copy_bundle_content.py b/starboard/build/copy_bundle_content.py
deleted file mode 100644
index 859dd08..0000000
--- a/starboard/build/copy_bundle_content.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# 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.
-
-# TODO(b/216341587): Refactor install_content to get rid of this file.
-
-# This file is loosely based on starboard/build/copy_data.py
-"""Copies all input files to the output directory.
-
-The folder structure of the input files is maintained in the output relative
-to the 'base_dir' parameter.
-"""
-
-import argparse
-import os
-import shutil
-
-
-class InvalidArgumentException(Exception):
-  pass
-
-
-def copy_files(files_to_copy, base_dir, output_dir):
-  if not os.path.exists(output_dir):
-    os.makedirs(output_dir)
-
-  for path in files_to_copy:
-    # In certain cases, files would fail to open on windows if relative paths
-    # were provided.  Using absolute paths fixes this.
-    filename = os.path.abspath(path)
-
-    # Get the path of the file relative to the source base_dir.
-    rel_path = os.path.relpath(path, base_dir)
-    output_dir = os.path.abspath(output_dir)
-    # Use rel_path to preserve the input folder structure in the output.
-    output_filename = os.path.abspath(os.path.join(output_dir, rel_path))
-
-    # In cases where a directory has turned into a file or vice versa, delete it
-    # before copying it below.
-    if os.path.exists(output_dir) and not os.path.isdir(output_dir):
-      os.remove(output_dir)
-    if os.path.exists(output_filename) and os.path.isdir(output_filename):
-      shutil.rmtree(output_filename)
-
-    if not os.path.exists(os.path.dirname(output_filename)):
-      os.makedirs(os.path.dirname(output_filename))
-
-    if os.path.isfile(filename):
-      shutil.copy(filename, output_filename)
-    else:
-      # TODO(b/211909342): Remove this branch once lottie files are listed.
-      shutil.copytree(filename, output_filename)
-
-
-if __name__ == '__main__':
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '--output_dir', dest='output_dir', required=True, help='output directory')
-  parser.add_argument(
-      '--base_dir',
-      dest='base_dir',
-      required=True,
-      help='source base directory')
-  parser.add_argument(
-      'input_paths',
-      metavar='path',
-      nargs='*',
-      help='path to a file containing the list of files to copy')
-  options = parser.parse_args()
-
-  # Load file names from the files containing the list of file names.
-  file_names = []
-  for input_path in options.input_paths:
-    with open(input_path.strip()) as files_list:
-      file_names.extend([line.strip() for line in files_list])
-
-  copy_files(file_names, options.base_dir, options.output_dir)
diff --git a/starboard/build/copy_install_content.py b/starboard/build/copy_install_content.py
index 767079b..1e29cbf 100644
--- a/starboard/build/copy_install_content.py
+++ b/starboard/build/copy_install_content.py
@@ -31,6 +31,9 @@
 
 
 def copy_files(files_to_copy, base_dir, output_dir):
+  if not os.path.exists(output_dir):
+    os.makedirs(output_dir)
+
   for path in files_to_copy:
     # All input paths must point at files.
     # TODO(b/211909342): Re-enable once lottie test files are listed.
@@ -53,8 +56,8 @@
     if os.path.exists(output_filename) and os.path.isdir(output_filename):
       shutil.rmtree(output_filename)
 
-    if not os.path.exists(output_dir):
-      os.makedirs(output_dir)
+    if not os.path.exists(os.path.dirname(output_filename)):
+      os.makedirs(os.path.dirname(output_filename))
 
     if os.path.isfile(filename):
       shutil.copy(filename, output_filename)
diff --git a/starboard/build/toolchain/gcc_ar_wrapper.py b/starboard/build/toolchain/gcc_ar_wrapper.py
deleted file mode 100755
index 4663f98..0000000
--- a/starboard/build/toolchain/gcc_ar_wrapper.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Runs the 'ar' command after removing its output file first.
-
-This script is invoked like:
-  python gcc_ar_wrapper.py --ar=$AR --output=$OUT $OP $INPUTS
-to do the equivalent of:
-  rm -f $OUT && $AR $OP $OUT $INPUTS
-"""
-
-import argparse
-import os
-import subprocess
-import sys
-
-import wrapper_utils
-
-
-def main():
-  parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument('--ar',
-                      required=True,
-                      help='The ar binary to run',
-                      metavar='PATH')
-  parser.add_argument('--output',
-                      required=True,
-                      help='Output archive file',
-                      metavar='ARCHIVE')
-  parser.add_argument('--plugin',
-                      help='Load plugin')
-  parser.add_argument('operation',
-                      help='Operation on the archive')
-  parser.add_argument('inputs', nargs='+',
-                      help='Input files')
-  args = parser.parse_args()
-
-  command = [args.ar, args.operation]
-  if args.plugin is not None:
-    command += ['--plugin', args.plugin]
-  command.append(args.output)
-  command += args.inputs
-
-  # Remove the output file first.
-  try:
-    os.remove(args.output)
-  except OSError as e:
-    if e.errno != os.errno.ENOENT:
-      raise
-
-  # Now just run the ar command.
-  return subprocess.call(wrapper_utils.CommandToRun(command))
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/starboard/build/toolchain/gcc_link_wrapper.py b/starboard/build/toolchain/gcc_link_wrapper.py
deleted file mode 100755
index b964347..0000000
--- a/starboard/build/toolchain/gcc_link_wrapper.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Runs a linking command and optionally a strip command.
-
-This script exists to avoid using complex shell commands in
-gcc_toolchain.gni's tool("link"), in case the host running the compiler
-does not have a POSIX-like shell (e.g. Windows).
-"""
-
-import argparse
-import os
-import subprocess
-import sys
-
-import wrapper_utils
-
-
-# When running on a Windows host and using a toolchain whose tools are
-# actually wrapper scripts (i.e. .bat files on Windows) rather than binary
-# executables, the "command" to run has to be prefixed with this magic.
-# The GN toolchain definitions take care of that for when GN/Ninja is
-# running the tool directly.  When that command is passed in to this
-# script, it appears as a unitary string but needs to be split up so that
-# just 'cmd' is the actual command given to Python's subprocess module.
-BAT_PREFIX = 'cmd /c call '
-
-
-def CommandToRun(command):
-  if command[0].startswith(BAT_PREFIX):
-    command = command[0].split(None, 3) + command[1:]
-  return command
-
-
-def main():
-  parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument('--strip',
-                      help='The strip binary to run',
-                      metavar='PATH')
-  parser.add_argument('--unstripped-file',
-                      help='Executable file produced by linking command',
-                      metavar='FILE')
-  parser.add_argument('--map-file',
-                      help=('Use --Wl,-Map to generate a map file. Will be '
-                            'gzipped if extension ends with .gz'),
-                      metavar='FILE')
-  parser.add_argument('--output',
-                      required=True,
-                      help='Final output executable file',
-                      metavar='FILE')
-  parser.add_argument('command', nargs='+',
-                      help='Linking command')
-  args = parser.parse_args()
-
-  # Work-around for gold being slow-by-default. http://crbug.com/632230
-  fast_env = dict(os.environ)
-  fast_env['LC_ALL'] = 'C'
-  result = wrapper_utils.RunLinkWithOptionalMapFile(args.command, env=fast_env,
-                                                    map_file=args.map_file)
-  if result != 0:
-    return result
-
-  # Finally, strip the linked executable (if desired).
-  if args.strip:
-    result = subprocess.call(CommandToRun([
-        args.strip, '--strip-unneeded', '-o', args.output, args.unstripped_file
-        ]))
-
-  return result
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/starboard/build/toolchain/gcc_solink_wrapper.py b/starboard/build/toolchain/gcc_solink_wrapper.py
deleted file mode 100755
index 71af5e3..0000000
--- a/starboard/build/toolchain/gcc_solink_wrapper.py
+++ /dev/null
@@ -1,145 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged.
-
-This script exists to avoid using complex shell commands in
-gcc_toolchain.gni's tool("solink"), in case the host running the compiler
-does not have a POSIX-like shell (e.g. Windows).
-"""
-
-import argparse
-import os
-import subprocess
-import sys
-
-import wrapper_utils
-
-
-def CollectSONAME(args):
-  """Replaces: readelf -d $sofile | grep SONAME.
-
-  Args:
-    args: the args to pass to the subprocess
-
-  Returns:
-    A tuple of (return code, output)
-  """
-  toc = ''
-  readelf = subprocess.Popen(wrapper_utils.CommandToRun(
-      [args.readelf, '-d', args.sofile]), stdout=subprocess.PIPE, bufsize=-1)
-  for line in readelf.stdout:
-    if 'SONAME' in line:
-      toc += line
-  return readelf.wait(), toc
-
-
-def CollectDynSym(args):
-  """Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '.
-
-  Args:
-    args: the args to pass to the subprocess
-
-  Returns:
-    A tuple of (return code, output)
-  """
-  toc = ''
-  nm = subprocess.Popen(wrapper_utils.CommandToRun([
-      args.nm, '--format=posix', '-g', '-D', args.sofile]),
-                        stdout=subprocess.PIPE, bufsize=-1)
-  for line in nm.stdout:
-    toc += ' '.join(line.split(' ', 2)[:2]) + '\n'
-  return nm.wait(), toc
-
-
-def CollectTOC(args):
-  result, toc = CollectSONAME(args)
-  if result == 0:
-    result, dynsym = CollectDynSym(args)
-    toc += dynsym
-  return result, toc
-
-
-def UpdateTOC(tocfile, toc):
-  if os.path.exists(tocfile):
-    old_toc = open(tocfile, 'r').read()
-  else:
-    old_toc = None
-  if toc != old_toc:
-    open(tocfile, 'w').write(toc)
-
-
-def main():
-  parser = argparse.ArgumentParser(description=__doc__)
-  parser.add_argument('--readelf',
-                      required=True,
-                      help='The readelf binary to run',
-                      metavar='PATH')
-  parser.add_argument('--nm',
-                      required=True,
-                      help='The nm binary to run',
-                      metavar='PATH')
-  parser.add_argument('--strip',
-                      help='The strip binary to run',
-                      metavar='PATH')
-  parser.add_argument('--sofile',
-                      required=True,
-                      help='Shared object file produced by linking command',
-                      metavar='FILE')
-  parser.add_argument('--tocfile',
-                      required=True,
-                      help='Output table-of-contents file',
-                      metavar='FILE')
-  parser.add_argument('--map-file',
-                      help=('Use --Wl,-Map to generate a map file. Will be '
-                            'gzipped if extension ends with .gz'),
-                      metavar='FILE')
-  parser.add_argument('--output',
-                      required=True,
-                      help='Final output shared object file',
-                      metavar='FILE')
-  parser.add_argument('--resource-whitelist',
-                      help='Merge all resource whitelists into a single file.',
-                      metavar='PATH')
-  parser.add_argument('command', nargs='+',
-                      help='Linking command')
-  args = parser.parse_args()
-
-  # Work-around for gold being slow-by-default. http://crbug.com/632230
-  fast_env = dict(os.environ)
-  fast_env['LC_ALL'] = 'C'
-
-  if args.resource_whitelist:
-    whitelist_candidates = wrapper_utils.ResolveRspLinks(args.command)
-    wrapper_utils.CombineResourceWhitelists(
-        whitelist_candidates, args.resource_whitelist)
-
-  # First, run the actual link.
-  command = wrapper_utils.CommandToRun(args.command)
-  result = wrapper_utils.RunLinkWithOptionalMapFile(command, env=fast_env,
-                                                    map_file=args.map_file)
-
-  if result != 0:
-    return result
-
-  # Next, generate the contents of the TOC file.
-  result, toc = CollectTOC(args)
-  if result != 0:
-    return result
-
-  # If there is an existing TOC file with identical contents, leave it alone.
-  # Otherwise, write out the TOC file.
-  UpdateTOC(args.tocfile, toc)
-
-  # Finally, strip the linked shared object file (if desired).
-  if args.strip:
-    result = subprocess.call(wrapper_utils.CommandToRun(
-        [args.strip, '--strip-unneeded', '-o', args.output, args.sofile]))
-
-  return result
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/starboard/build/toolchain/get_clang_base_path.py b/starboard/build/toolchain/get_clang_base_path.py
deleted file mode 100755
index 092ba5d..0000000
--- a/starboard/build/toolchain/get_clang_base_path.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 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.
-"""Finds and prints the Clang base path.
-
-If Clang is not present, it will be downloaded.
-"""
-
-from starboard.build import clang
-from starboard.tools import build
-
-
-def main():
-  print(build.EnsureClangAvailable(clang.GetClangSpecification()))
-
-
-if __name__ == '__main__':
-  main()
diff --git a/starboard/build/toolchain/get_concurrent_links.py b/starboard/build/toolchain/get_concurrent_links.py
deleted file mode 100644
index be5640a..0000000
--- a/starboard/build/toolchain/get_concurrent_links.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Computes and prints the default number of concurrent links.
-
-Computes the number of concurrent links to be run in the build, as a function
-of machine spec. Based on GetDefaultConcurrentLinks in GYP.
-"""
-
-import ctypes
-import optparse
-import os
-import re
-import subprocess
-import sys
-
-
-def _GetTotalMemoryInBytes():
-  """Gets the total amount of virtual memory in bytes.
-
-  Returns:
-    See above
-  """
-  if sys.platform in ('win32', 'cygwin'):
-
-    class MEMORYSTATUSEX(ctypes.Structure):
-      _fields_ = [
-          ('dwLength', ctypes.c_ulong),
-          ('dwMemoryLoad', ctypes.c_ulong),
-          ('ullTotalPhys', ctypes.c_ulonglong),
-          ('ullAvailPhys', ctypes.c_ulonglong),
-          ('ullTotalPageFile', ctypes.c_ulonglong),
-          ('ullAvailPageFile', ctypes.c_ulonglong),
-          ('ullTotalVirtual', ctypes.c_ulonglong),
-          ('ullAvailVirtual', ctypes.c_ulonglong),
-          ('sullAvailExtendedVirtual', ctypes.c_ulonglong),
-      ]
-
-    stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX))
-    ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
-    return stat.ullTotalPhys
-  elif sys.platform.startswith('linux'):
-    if os.path.exists('/proc/meminfo'):
-      with open('/proc/meminfo') as meminfo:
-        memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
-        for line in meminfo:
-          match = memtotal_re.match(line)
-          if not match:
-            continue
-          return float(match.group(1)) * 2**10
-  elif sys.platform == 'darwin':
-    try:
-      return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
-    except subprocess.CalledProcessError:
-      return 0
-  # TODO: Implement this for other platforms.
-  return 0
-
-
-def _GetDefaultConcurrentLinks(mem_per_link_gb, reserve_mem_gb):
-  # Inherit the legacy environment variable for people that have set it in GYP.
-  pool_size = int(os.getenv('GYP_LINK_CONCURRENCY', 0))
-  if pool_size:
-    return pool_size
-
-  mem_total_bytes = _GetTotalMemoryInBytes()
-  mem_total_bytes = max(0, mem_total_bytes - reserve_mem_gb * 2**30)
-  num_concurrent_links = int(max(1, mem_total_bytes / mem_per_link_gb / 2**30))
-  hard_cap = max(1, int(os.getenv('GYP_LINK_CONCURRENCY_MAX', 2**32)))
-  return min(num_concurrent_links, hard_cap)
-
-
-def main():
-  parser = optparse.OptionParser()
-  parser.add_option('--mem_per_link_gb', action='store', type='int', default=6)
-  parser.add_option('--reserve_mem_gb', action='store', type='int', default=0)
-  parser.disable_interspersed_args()
-  options, _ = parser.parse_args()
-
-  print _GetDefaultConcurrentLinks(options.mem_per_link_gb,
-                                   options.reserve_mem_gb)
-  return 0
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/starboard/build/toolchain/wrapper_utils.py b/starboard/build/toolchain/wrapper_utils.py
deleted file mode 100644
index d01933d..0000000
--- a/starboard/build/toolchain/wrapper_utils.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright (c) 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Helper functions for gcc_toolchain.gni wrappers."""
-
-import gzip
-import os
-import re
-import shlex
-import shutil
-import subprocess
-import threading
-
-_BAT_PREFIX = 'cmd /c call '
-_WHITELIST_RE = re.compile('whitelisted_resource_(?P<resource_id>[0-9]+)')
-
-
-def _GzipThenDelete(src_path, dest_path):
-  # Results for Android map file with GCC on a z620:
-  # Uncompressed: 207MB
-  # gzip -9: 16.4MB, takes 8.7 seconds.
-  # gzip -1: 21.8MB, takes 2.0 seconds.
-  # Piping directly from the linker via -print-map (or via -Map with a fifo)
-  # adds a whopping 30-45 seconds!
-  with open(src_path, 'rb') as f_in, gzip.GzipFile(dest_path, 'wb', 1) as f_out:
-    shutil.copyfileobj(f_in, f_out)
-  os.unlink(src_path)
-
-
-def CommandToRun(command):
-  """Generates commands compatible with Windows.
-
-  When running on a Windows host and using a toolchain whose tools are
-  actually wrapper scripts (i.e. .bat files on Windows) rather than binary
-  executables, the |command| to run has to be prefixed with this magic.
-  The GN toolchain definitions take care of that for when GN/Ninja is
-  running the tool directly.  When that command is passed in to this
-  script, it appears as a unitary string but needs to be split up so that
-  just 'cmd' is the actual command given to Python's subprocess module.
-
-  Args:
-    command: List containing the UNIX style |command|.
-
-  Returns:
-    A list containing the Windows version of the |command|.
-  """
-  if command[0].startswith(_BAT_PREFIX):
-    command = command[0].split(None, 3) + command[1:]
-  return command
-
-
-def RunLinkWithOptionalMapFile(command, env=None, map_file=None):
-  """Runs the given command, adding in -Wl,-Map when |map_file| is given.
-
-  Also takes care of gzipping when |map_file| ends with .gz.
-
-  Args:
-    command: List of arguments comprising the command.
-    env: Environment variables.
-    map_file: Path to output map_file.
-
-  Returns:
-    The exit code of running |command|.
-  """
-  tmp_map_path = None
-  if map_file and map_file.endswith('.gz'):
-    tmp_map_path = map_file + '.tmp'
-    command.append('-Wl,-Map,' + tmp_map_path)
-  elif map_file:
-    command.append('-Wl,-Map,' + map_file)
-
-  result = subprocess.call(command, env=env)
-
-  if tmp_map_path and result == 0:
-    threading.Thread(
-        target=lambda: _GzipThenDelete(tmp_map_path, map_file)).start()
-  elif tmp_map_path and os.path.exists(tmp_map_path):
-    os.unlink(tmp_map_path)
-
-  return result
-
-
-def ResolveRspLinks(inputs):
-  """Return a list of files contained in a response file.
-
-  Args:
-    inputs: A command containing rsp files.
-
-  Returns:
-    A set containing the rsp file content.
-  """
-  rspfiles = [a[1:] for a in inputs if a.startswith('@')]
-  resolved = set()
-  for rspfile in rspfiles:
-    with open(rspfile, 'r') as f:
-      resolved.update(shlex.split(f.read()))
-
-  return resolved
-
-
-def CombineResourceWhitelists(whitelist_candidates, outfile):
-  """Combines all whitelists for a resource file into a single whitelist.
-
-  Args:
-    whitelist_candidates: List of paths to rsp files containing all targets.
-    outfile: Path to save the combined whitelist.
-  """
-  whitelists = ('%s.whitelist' % candidate for candidate in whitelist_candidates
-                if os.path.exists('%s.whitelist' % candidate))
-
-  resources = set()
-  for whitelist in whitelists:
-    with open(whitelist, 'r') as f:
-      resources.update(f.readlines())
-
-  with open(outfile, 'w') as f:
-    f.writelines(resources)
-
-
-def ExtractResourceIdsFromPragmaWarnings(text):
-  """Returns set of resource IDs that are inside unknown pragma warnings.
-
-  Args:
-    text: The text that will be scanned for unknown pragma warnings.
-
-  Returns:
-    A set containing integers representing resource IDs.
-  """
-  used_resources = set()
-  lines = text.splitlines()
-  for ln in lines:
-    match = _WHITELIST_RE.search(ln)
-    if match:
-      resource_id = int(match.group('resource_id'))
-      used_resources.add(resource_id)
-
-  return used_resources
-
-
-def CaptureCommandStderr(command, env=None):
-  """Returns the stderr of a command.
-
-  Args:
-    command: A list containing the command and arguments.
-    env: Environment variables for the new process.
-  """
-  child = subprocess.Popen(command, stderr=subprocess.PIPE, env=env)
-  _, stderr = child.communicate()
-  return child.returncode, stderr
diff --git a/starboard/client_porting/eztime/BUILD.gn b/starboard/client_porting/eztime/BUILD.gn
index 5611ee1..df579de 100644
--- a/starboard/client_porting/eztime/BUILD.gn
+++ b/starboard/client_porting/eztime/BUILD.gn
@@ -42,5 +42,5 @@
     "//testing/gtest",
   ]
 
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
 }
diff --git a/starboard/common/media.cc b/starboard/common/media.cc
index 283d0ff..eb74ac8 100644
--- a/starboard/common/media.cc
+++ b/starboard/common/media.cc
@@ -601,6 +601,14 @@
       return "opus";
     case kSbMediaAudioCodecVorbis:
       return "vorbis";
+#if SB_API_VERSION >= 14
+    case kSbMediaAudioCodecMp3:
+      return "mp3";
+    case kSbMediaAudioCodecFlac:
+      return "flac";
+    case kSbMediaAudioCodecPcm:
+      return "pcm";
+#endif  // SB_API_VERSION >= 14
   }
   SB_NOTREACHED();
   return "invalid";
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn
index ec20753..4a9f9a6 100644
--- a/starboard/elf_loader/BUILD.gn
+++ b/starboard/elf_loader/BUILD.gn
@@ -87,11 +87,11 @@
 }
 
 target(final_executable_type, "elf_loader_sandbox") {
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
   if (cobalt_font_package == "empty") {
-    content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+    data_deps += [ "//cobalt/content/fonts:copy_font_data" ]
   } else {
-    content_deps += [
+    data_deps += [
       "//cobalt/content/fonts:copy_fonts",
       "//cobalt/content/fonts:fonts_xml",
     ]
@@ -176,7 +176,7 @@
     # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions.
     deps += cobalt_platform_dependencies
 
-    content_deps = [ ":copy_elf_loader_testdata" ]
+    data_deps = [ ":copy_elf_loader_testdata" ]
   }
 }
 
diff --git a/starboard/elf_loader/exported_symbols.cc b/starboard/elf_loader/exported_symbols.cc
index 915039b..f5282ef 100644
--- a/starboard/elf_loader/exported_symbols.cc
+++ b/starboard/elf_loader/exported_symbols.cc
@@ -250,7 +250,9 @@
 #endif
   REGISTER_SYMBOL(SbSystemBreakIntoDebugger);
   REGISTER_SYMBOL(SbSystemClearLastError);
+#if SB_API_VERSION < 14
   REGISTER_SYMBOL(SbSystemGetConnectionType);
+#endif
   REGISTER_SYMBOL(SbSystemGetDeviceType);
   REGISTER_SYMBOL(SbSystemGetErrorString);
   REGISTER_SYMBOL(SbSystemGetLastError);
diff --git a/starboard/examples/window/BUILD.gn b/starboard/examples/window/BUILD.gn
index beb0040..93c3aae 100644
--- a/starboard/examples/window/BUILD.gn
+++ b/starboard/examples/window/BUILD.gn
@@ -17,5 +17,5 @@
 
   sources = [ "main.cc" ]
   public_deps = [ "//starboard" ]
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
 }
diff --git a/starboard/linux/shared/system_get_connection_type.cc b/starboard/linux/shared/system_get_connection_type.cc
index 7795c20..8042f3d 100644
--- a/starboard/linux/shared/system_get_connection_type.cc
+++ b/starboard/linux/shared/system_get_connection_type.cc
@@ -14,6 +14,8 @@
 
 #include "starboard/system.h"
 
+#if SB_API_VERSION < 14
+
 #include "starboard/linux/shared/routes.h"
 
 using starboard::shared::Routes;
@@ -42,3 +44,5 @@
                        : kSbSystemConnectionTypeWired;
   return kSbSystemConnectionTypeUnknown;
 }
+
+#endif  // SB_API_VERSION < 14
diff --git a/starboard/linux/shared/system_get_extensions.cc b/starboard/linux/shared/system_get_extensions.cc
index 3c44ffd..fba44bd 100644
--- a/starboard/linux/shared/system_get_extensions.cc
+++ b/starboard/linux/shared/system_get_extensions.cc
@@ -16,13 +16,16 @@
 
 #include "cobalt/extension/configuration.h"
 #include "cobalt/extension/crash_handler.h"
+#include "cobalt/extension/demuxer.h"
 #include "cobalt/extension/free_space.h"
 #include "cobalt/extension/memory_mapped_file.h"
 #include "cobalt/extension/platform_service.h"
 #include "starboard/common/string.h"
 #include "starboard/linux/shared/soft_mic_platform_service.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
 #include "starboard/shared/posix/free_space.h"
 #include "starboard/shared/posix/memory_mapped_file.h"
+#include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/crash_handler.h"
 #if SB_IS(EVERGREEN_COMPATIBLE)
 #include "starboard/elf_loader/evergreen_config.h"
@@ -56,5 +59,13 @@
   if (strcmp(name, kCobaltExtensionFreeSpaceName) == 0) {
     return starboard::shared::posix::GetFreeSpaceApi();
   }
+  if (strcmp(name, kCobaltExtensionDemuxerApi) == 0) {
+    auto command_line =
+        starboard::shared::starboard::Application::Get()->GetCommandLine();
+    const bool use_ffmpeg_demuxer =
+        command_line->HasSwitch("enable_demuxer_extension");
+    return use_ffmpeg_demuxer ? starboard::shared::ffmpeg::GetFFmpegDemuxerApi()
+                              : NULL;
+  }
   return NULL;
 }
diff --git a/starboard/linux/x64x11/system_get_property_impl.cc b/starboard/linux/x64x11/system_get_property_impl.cc
index 96008e1..aa33def 100644
--- a/starboard/linux/x64x11/system_get_property_impl.cc
+++ b/starboard/linux/x64x11/system_get_property_impl.cc
@@ -88,11 +88,11 @@
       return false;
 #if SB_API_VERSION >= 14
     // Implementation provided for testing purposes only
-    case kSbSystemPropretyAdvertisingId:
+    case kSbSystemPropertyAdvertisingId:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           GetEnvironment("COBALT_ADVERTISING_ID").c_str());
-    case kSbSystemPropretyLimitAdTracking:
+    case kSbSystemPropertyLimitAdTracking:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str());
diff --git a/starboard/media.h b/starboard/media.h
index d7c39f9..17d637d 100644
--- a/starboard/media.h
+++ b/starboard/media.h
@@ -63,6 +63,11 @@
   kSbMediaAudioCodecEac3,
   kSbMediaAudioCodecOpus,
   kSbMediaAudioCodecVorbis,
+#if SB_API_VERSION >= 14
+  kSbMediaAudioCodecMp3,
+  kSbMediaAudioCodecFlac,
+  kSbMediaAudioCodecPcm,
+#endif  // SB_API_VERSION >= 14
 } SbMediaAudioCodec;
 
 // Indicates how confident the device is that it can play media resources of the
diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn
index c96b0d5..7b44620 100644
--- a/starboard/nplb/BUILD.gn
+++ b/starboard/nplb/BUILD.gn
@@ -291,11 +291,6 @@
   data_deps = [
     "//starboard/nplb/testdata/file_tests:nplb_file_tests_data",
     "//starboard/shared/starboard/player:player_download_test_data",
-  ]
-
-  content_deps = [
-    "//starboard/nplb/testdata/file_tests:nplb_file_tests_data",
-    "//starboard/shared/starboard/player:player_download_test_data",
     "//third_party/icu:icudata",
   ]
 }
diff --git a/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn b/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
index 705025b..f22104c 100644
--- a/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
+++ b/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
@@ -37,5 +37,5 @@
     "//testing/gmock",
   ]
 
-  content_deps = [ "//third_party/icu:icudata" ]
+  data_deps = [ "//third_party/icu:icudata" ]
 }
diff --git a/starboard/nplb/player_create_test.cc b/starboard/nplb/player_create_test.cc
index f3038b1..061a323 100644
--- a/starboard/nplb/player_create_test.cc
+++ b/starboard/nplb/player_create_test.cc
@@ -191,10 +191,18 @@
       kSbPlayerOutputModeDecodeToTexture, kSbPlayerOutputModePunchOut};
 
   constexpr SbMediaAudioCodec kAudioCodecs[] = {
-      kSbMediaAudioCodecNone,
+    kSbMediaAudioCodecNone,
 
-      kSbMediaAudioCodecAac,  kSbMediaAudioCodecAc3,    kSbMediaAudioCodecEac3,
-      kSbMediaAudioCodecOpus, kSbMediaAudioCodecVorbis,
+    kSbMediaAudioCodecAac,
+    kSbMediaAudioCodecAc3,
+    kSbMediaAudioCodecEac3,
+    kSbMediaAudioCodecOpus,
+    kSbMediaAudioCodecVorbis,
+#if SB_API_VERSION >= 14
+    kSbMediaAudioCodecMp3,
+    kSbMediaAudioCodecFlac,
+    kSbMediaAudioCodecPcm,
+#endif  // SB_API_VERSION >= 14
   };
 
   // TODO: turn this into a macro.
@@ -209,6 +217,11 @@
     case kAudioCodecs[3]:
     case kAudioCodecs[4]:
     case kAudioCodecs[5]:
+#if SB_API_VERSION >= 14
+    case kAudioCodecs[6]:
+    case kAudioCodecs[7]:
+    case kAudioCodecs[8]:
+#endif
       break;
   }
 
diff --git a/starboard/nplb/player_creation_param_helpers.cc b/starboard/nplb/player_creation_param_helpers.cc
index 65eddde..a2f2090 100644
--- a/starboard/nplb/player_creation_param_helpers.cc
+++ b/starboard/nplb/player_creation_param_helpers.cc
@@ -94,6 +94,50 @@
           audio_sample_info.bits_per_sample / 8;
       break;
     }
+#if SB_API_VERSION >= 14
+    case kSbMediaAudioCodecMp3: {
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 44100;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 16;
+      audio_sample_info.audio_specific_config = nullptr;
+      audio_sample_info.audio_specific_config_size = 0;
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+    case kSbMediaAudioCodecFlac: {
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 44100;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 16;
+      audio_sample_info.audio_specific_config = nullptr;
+      audio_sample_info.audio_specific_config_size = 0;
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+    case kSbMediaAudioCodecPcm: {
+      audio_sample_info.format_tag = 0x01;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 44100;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 32;
+      audio_sample_info.audio_specific_config = nullptr;
+      audio_sample_info.audio_specific_config_size = 0;
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+#endif  // SB_API_VERSION >= 14
   }
   return audio_sample_info;
 }
diff --git a/starboard/nplb/player_get_preferred_output_mode_test.cc b/starboard/nplb/player_get_preferred_output_mode_test.cc
index 40f68f5..a007352 100644
--- a/starboard/nplb/player_get_preferred_output_mode_test.cc
+++ b/starboard/nplb/player_get_preferred_output_mode_test.cc
@@ -45,8 +45,17 @@
 
 TEST(SbPlayerGetPreferredOutputModeTest, AllCodecs) {
   const SbMediaAudioCodec kAudioCodecs[] = {
-      kSbMediaAudioCodecNone, kSbMediaAudioCodecAac,  kSbMediaAudioCodecAc3,
-      kSbMediaAudioCodecEac3, kSbMediaAudioCodecOpus, kSbMediaAudioCodecVorbis,
+    kSbMediaAudioCodecNone,
+    kSbMediaAudioCodecAac,
+    kSbMediaAudioCodecAc3,
+    kSbMediaAudioCodecEac3,
+    kSbMediaAudioCodecOpus,
+    kSbMediaAudioCodecVorbis,
+#if SB_API_VERSION >= 14
+    kSbMediaAudioCodecMp3,
+    kSbMediaAudioCodecFlac,
+    kSbMediaAudioCodecPcm,
+#endif  // SB_API_VERSION >= 14
   };
   const SbMediaVideoCodec kVideoCodecs[] = {
       kSbMediaVideoCodecNone,  kSbMediaVideoCodecH264,   kSbMediaVideoCodecH265,
diff --git a/starboard/nplb/system_get_connection_type_test.cc b/starboard/nplb/system_get_connection_type_test.cc
index 4c21b30..3677b10 100644
--- a/starboard/nplb/system_get_connection_type_test.cc
+++ b/starboard/nplb/system_get_connection_type_test.cc
@@ -20,6 +20,8 @@
 #include "starboard/system.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if SB_API_VERSION < 14
+
 namespace starboard {
 namespace nplb {
 namespace {
@@ -42,3 +44,5 @@
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
+
+#endif  // SB_API_VERSION < 14
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py
index 4076997..99fa3d2 100644
--- a/starboard/raspi/shared/launcher.py
+++ b/starboard/raspi/shared/launcher.py
@@ -130,8 +130,7 @@
     options = '-avzLhc'
     source = test_dir + '/'
     destination = '{}:~/{}/'.format(raspi_user_hostname, raspi_test_dir)
-    self.rsync_command = 'rsync ' + options + ' ' + source + ' ' + \
-        destination + ';sync'
+    self.rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
 
     # ssh command setup
     self.ssh_command = 'ssh -t ' + raspi_user_hostname + ' TERM=dumb bash -l'
diff --git a/starboard/raspi/shared/system_get_property.cc b/starboard/raspi/shared/system_get_property.cc
index 8fd7d07..e437f17 100644
--- a/starboard/raspi/shared/system_get_property.cc
+++ b/starboard/raspi/shared/system_get_property.cc
@@ -147,11 +147,11 @@
 
 #if SB_API_VERSION >= 14
     // Implementation provided for testing purposes only
-    case kSbSystemPropretyAdvertisingId:
+    case kSbSystemPropertyAdvertisingId:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           starboard::GetEnvironment("COBALT_ADVERTISING_ID").c_str());
-    case kSbSystemPropretyLimitAdTracking:
+    case kSbSystemPropertyLimitAdTracking:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           starboard::GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str());
diff --git a/starboard/shared/ffmpeg/BUILD.gn b/starboard/shared/ffmpeg/BUILD.gn
index e6bc469..b55636d 100644
--- a/starboard/shared/ffmpeg/BUILD.gn
+++ b/starboard/shared/ffmpeg/BUILD.gn
@@ -16,6 +16,8 @@
   "ffmpeg_audio_decoder_impl.cc",
   "ffmpeg_audio_decoder_impl.h",
   "ffmpeg_common.h",
+  "ffmpeg_demuxer_impl.cc",
+  "ffmpeg_demuxer_impl.h",
   "ffmpeg_video_decoder_impl.cc",
   "ffmpeg_video_decoder_impl.h",
 ]
@@ -23,6 +25,7 @@
 static_library("ffmpeg_dynamic_load") {
   sources = [
     "ffmpeg_dynamic_load_audio_decoder_impl.cc",
+    "ffmpeg_dynamic_load_demuxer_impl.cc",
     "ffmpeg_dynamic_load_dispatch_impl.cc",
     "ffmpeg_dynamic_load_video_decoder_impl.cc",
   ]
@@ -43,6 +46,7 @@
     check_includes = false
     sources = ffmpeg_specialization_sources + [
                 "ffmpeg_linked_audio_decoder_impl.cc",
+                "ffmpeg_linked_demuxer_impl.cc",
                 "ffmpeg_linked_dispatch_impl.cc",
                 "ffmpeg_linked_video_decoder_impl.cc",
               ]
@@ -83,6 +87,8 @@
   check_includes = false
   sources = [
     "ffmpeg_audio_decoder.h",
+    "ffmpeg_demuxer.cc",
+    "ffmpeg_demuxer.h",
     "ffmpeg_dispatch.cc",
     "ffmpeg_dispatch.h",
     "ffmpeg_video_decoder.h",
@@ -90,3 +96,28 @@
 
   public_configs = [ "//starboard/build/config:starboard_implementation" ]
 }
+
+target(gtest_target_type, "ffmpeg_demuxer_test") {
+  testonly = true
+  configs += [ "//starboard/build/config:starboard_implementation" ]
+  sources = ffmpeg_specialization_sources + [
+              "ffmpeg_demuxer.h",
+              "ffmpeg_demuxer.cc",
+              "ffmpeg_demuxer_test.cc",
+            ]
+
+  # Build only against one specified version of the ffmpeg includes. That means
+  # that this binary will only work well when run on a machine with the given
+  # version of ffmpeg installed. This test binary actually should have
+  # specializations for all supported ffmpeg versions, or it should only test
+  # the behavior of the abstraction layer without testing implementation
+  # details.
+  include_dirs = [ "//third_party/ffmpeg_includes/ffmpeg.58.35.100" ]
+  deps = [
+    "//cobalt/test:run_all_unittests",
+    "//starboard",
+    "//starboard/common",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/starboard/shared/ffmpeg/ffmpeg_common.h b/starboard/shared/ffmpeg/ffmpeg_common.h
index 61d562e..c10bbbd 100644
--- a/starboard/shared/ffmpeg/ffmpeg_common.h
+++ b/starboard/shared/ffmpeg/ffmpeg_common.h
@@ -20,7 +20,9 @@
 #include <libavcodec/avcodec.h>
 #include <libavformat/avformat.h>
 #include <libavutil/avutil.h>
+#include <libavutil/channel_layout.h>
 #include <libavutil/imgutils.h>
+#include <libavutil/mathematics.h>
 #include <libavutil/opt.h>
 }  // extern "C"
 
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer.cc
new file mode 100644
index 0000000..d359e32
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer.cc
@@ -0,0 +1,143 @@
+// 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/ffmpeg/ffmpeg_demuxer.h"
+
+#include <memory>
+
+#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+namespace {
+
+CobaltExtensionDemuxerStatus FFmpegDemuxer_Initialize(void* user_data) {
+  return static_cast<FFmpegDemuxer*>(user_data)->Initialize();
+}
+
+CobaltExtensionDemuxerStatus FFmpegDemuxer_Seek(int64_t seek_time_us,
+                                                void* user_data) {
+  return static_cast<FFmpegDemuxer*>(user_data)->Seek(seek_time_us);
+}
+
+SbTime FFmpegDemuxer_GetStartTime(void* user_data) {
+  return static_cast<FFmpegDemuxer*>(user_data)->GetStartTime();
+}
+
+SbTime FFmpegDemuxer_GetTimelineOffset(void* user_data) {
+  return static_cast<FFmpegDemuxer*>(user_data)->GetTimelineOffset();
+}
+
+void FFmpegDemuxer_Read(CobaltExtensionDemuxerStreamType type,
+                        CobaltExtensionDemuxerReadCB read_cb,
+                        void* read_cb_user_data,
+                        void* user_data) {
+  static_cast<FFmpegDemuxer*>(user_data)->Read(type, read_cb,
+                                               read_cb_user_data);
+}
+
+bool FFmpegDemuxer_GetAudioConfig(
+    CobaltExtensionDemuxerAudioDecoderConfig* config,
+    void* user_data) {
+  auto* ffmpeg_demuxer = static_cast<FFmpegDemuxer*>(user_data);
+  if (!ffmpeg_demuxer->HasAudioStream()) {
+    return false;
+  }
+  *config = ffmpeg_demuxer->GetAudioConfig();
+  return true;
+}
+
+bool FFmpegDemuxer_GetVideoConfig(
+    CobaltExtensionDemuxerVideoDecoderConfig* config,
+    void* user_data) {
+  auto* ffmpeg_demuxer = static_cast<FFmpegDemuxer*>(user_data);
+  if (!ffmpeg_demuxer->HasVideoStream()) {
+    return false;
+  }
+  *config = ffmpeg_demuxer->GetVideoConfig();
+  return true;
+}
+
+SbTime FFmpegDemuxer_GetDuration(void* user_data) {
+  return static_cast<FFmpegDemuxer*>(user_data)->GetDuration();
+}
+
+CobaltExtensionDemuxer* CreateFFmpegDemuxer(
+    CobaltExtensionDemuxerDataSource* data_source,
+    CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
+    int64_t supported_audio_codecs_size,
+    CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
+    int64_t supported_video_codecs_size) {
+  // TODO(b/231632632): utilize supported_audio_codecs and
+  // supported_video_codecs. They should ultimately be passed to FFmpegDemuxer's
+  // ctor (as vectors), and the demuxer should fail fast for unsupported codecs.
+  return new CobaltExtensionDemuxer{
+      &FFmpegDemuxer_Initialize,
+      &FFmpegDemuxer_Seek,
+      &FFmpegDemuxer_GetStartTime,
+      &FFmpegDemuxer_GetTimelineOffset,
+      &FFmpegDemuxer_Read,
+      &FFmpegDemuxer_GetAudioConfig,
+      &FFmpegDemuxer_GetVideoConfig,
+      &FFmpegDemuxer_GetDuration,
+      FFmpegDemuxer::Create(data_source).release()};
+}
+
+void DestroyFFmpegDemuxer(CobaltExtensionDemuxer* demuxer) {
+  auto* ffmpeg_demuxer = static_cast<FFmpegDemuxer*>(demuxer->user_data);
+  delete ffmpeg_demuxer;
+  delete demuxer;
+}
+
+const CobaltExtensionDemuxerApi kDemuxerApi = {
+    /*name=*/kCobaltExtensionDemuxerApi,
+    /*version=*/1,
+    /*CreateDemuxer=*/&CreateFFmpegDemuxer,
+    /*DestroyDemuxer=*/&DestroyFFmpegDemuxer};
+
+FFMPEGDispatch* g_test_dispatch = nullptr;
+
+}  // namespace
+
+FFmpegDemuxer::FFmpegDemuxer() = default;
+
+FFmpegDemuxer::~FFmpegDemuxer() = default;
+
+// static
+const FFMPEGDispatch* FFmpegDemuxer::GetDispatch() {
+  if (g_test_dispatch) {
+    return g_test_dispatch;
+  }
+
+  static const auto* const dispatch = FFMPEGDispatch::GetInstance();
+  return dispatch;
+}
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+// static
+void FFmpegDemuxer::TestOnlySetFFmpegDispatch(FFMPEGDispatch* ffmpeg) {
+  g_test_dispatch = ffmpeg;
+}
+#endif
+
+const CobaltExtensionDemuxerApi* GetFFmpegDemuxerApi() {
+  return &kDemuxerApi;
+}
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer.h b/starboard/shared/ffmpeg/ffmpeg_demuxer.h
new file mode 100644
index 0000000..1a132bc3
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer.h
@@ -0,0 +1,72 @@
+// 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_FFMPEG_FFMPEG_DEMUXER_H_
+#define STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_H_
+
+#include <memory>
+
+#include "cobalt/extension/demuxer.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+class FFMPEGDispatch;
+
+// Returns a demuxer implementation based on FFmpeg.
+const CobaltExtensionDemuxerApi* GetFFmpegDemuxerApi();
+
+// The API of this class mirrors that of CobaltExtensionDemuxer; those calls get
+// forwarded to an instance of this class. See demuxer.h for details about the
+// API.
+class FFmpegDemuxer {
+ public:
+  // Creates an FFmpegDemuxer for the currently-loaded FFmpeg library.
+  static std::unique_ptr<FFmpegDemuxer> Create(
+      CobaltExtensionDemuxerDataSource* data_source);
+
+  FFmpegDemuxer();
+  virtual ~FFmpegDemuxer();
+
+  virtual CobaltExtensionDemuxerStatus Initialize() = 0;
+  virtual bool HasAudioStream() const = 0;
+  virtual bool HasVideoStream() const = 0;
+  virtual const CobaltExtensionDemuxerAudioDecoderConfig& GetAudioConfig()
+      const = 0;
+  virtual const CobaltExtensionDemuxerVideoDecoderConfig& GetVideoConfig()
+      const = 0;
+  virtual SbTime GetDuration() const = 0;
+  virtual SbTime GetStartTime() const = 0;
+  virtual SbTime GetTimelineOffset() const = 0;
+  virtual void Read(CobaltExtensionDemuxerStreamType type,
+                    CobaltExtensionDemuxerReadCB read_cb,
+                    void* read_cb_user_data) = 0;
+  virtual CobaltExtensionDemuxerStatus Seek(int64_t seek_time_us) = 0;
+
+  // All child classes should call this function to access FFmpeg.
+  static const FFMPEGDispatch* GetDispatch();
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  // For testing purposes, the FFMPEGDispatch -- through which all FFmpeg calls
+  // go -- can be overridden. This should never be called in production code.
+  static void TestOnlySetFFmpegDispatch(FFMPEGDispatch* ffmpeg);
+#endif
+};
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_H_
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
new file mode 100644
index 0000000..6b8455e
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
@@ -0,0 +1,978 @@
+// 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/ffmpeg/ffmpeg_demuxer_impl.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <deque>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "starboard/common/log.h"
+#include "starboard/shared/ffmpeg/ffmpeg_common.h"
+#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+namespace {
+
+constexpr int64_t kNoFFmpegTimestamp = static_cast<int64_t>(AV_NOPTS_VALUE);
+constexpr int kAvioBufferSize = 4 * 1024;  // 4KB.
+
+CobaltExtensionDemuxerAudioCodec AvCodecIdToAudioCodec(AVCodecID codec) {
+  switch (codec) {
+    case AV_CODEC_ID_AAC:
+      return kCobaltExtensionDemuxerCodecAAC;
+    case AV_CODEC_ID_MP3:
+      return kCobaltExtensionDemuxerCodecMP3;
+    case AV_CODEC_ID_PCM_U8:
+    case AV_CODEC_ID_PCM_S16LE:
+    case AV_CODEC_ID_PCM_S24LE:
+    case AV_CODEC_ID_PCM_S32LE:
+    case AV_CODEC_ID_PCM_F32LE:
+      return kCobaltExtensionDemuxerCodecPCM;
+    case AV_CODEC_ID_VORBIS:
+      return kCobaltExtensionDemuxerCodecVorbis;
+    case AV_CODEC_ID_FLAC:
+      return kCobaltExtensionDemuxerCodecFLAC;
+    case AV_CODEC_ID_AMR_NB:
+      return kCobaltExtensionDemuxerCodecAMR_NB;
+    case AV_CODEC_ID_AMR_WB:
+      return kCobaltExtensionDemuxerCodecAMR_WB;
+    case AV_CODEC_ID_PCM_MULAW:
+      return kCobaltExtensionDemuxerCodecPCM_MULAW;
+    case AV_CODEC_ID_PCM_S16BE:
+      return kCobaltExtensionDemuxerCodecPCM_S16BE;
+    case AV_CODEC_ID_PCM_S24BE:
+      return kCobaltExtensionDemuxerCodecPCM_S24BE;
+    case AV_CODEC_ID_OPUS:
+      return kCobaltExtensionDemuxerCodecOpus;
+    case AV_CODEC_ID_EAC3:
+      return kCobaltExtensionDemuxerCodecEAC3;
+    case AV_CODEC_ID_PCM_ALAW:
+      return kCobaltExtensionDemuxerCodecPCM_ALAW;
+    case AV_CODEC_ID_ALAC:
+      return kCobaltExtensionDemuxerCodecALAC;
+    case AV_CODEC_ID_AC3:
+      return kCobaltExtensionDemuxerCodecAC3;
+    default:
+      return kCobaltExtensionDemuxerCodecUnknownAudio;
+  }
+}
+
+CobaltExtensionDemuxerVideoCodec AvCodecIdToVideoCodec(AVCodecID codec) {
+  switch (codec) {
+    case AV_CODEC_ID_H264:
+      return kCobaltExtensionDemuxerCodecH264;
+    case AV_CODEC_ID_VC1:
+      return kCobaltExtensionDemuxerCodecVC1;
+    case AV_CODEC_ID_MPEG2VIDEO:
+      return kCobaltExtensionDemuxerCodecMPEG2;
+    case AV_CODEC_ID_MPEG4:
+      return kCobaltExtensionDemuxerCodecMPEG4;
+    case AV_CODEC_ID_THEORA:
+      return kCobaltExtensionDemuxerCodecTheora;
+    case AV_CODEC_ID_VP8:
+      return kCobaltExtensionDemuxerCodecVP8;
+#if FFMPEG >= 560
+    case AV_CODEC_ID_VP9:
+      return kCobaltExtensionDemuxerCodecVP9;
+    case AV_CODEC_ID_HEVC:
+      return kCobaltExtensionDemuxerCodecHEVC;
+#endif  // FFMPEG >= 560
+    default:
+      return kCobaltExtensionDemuxerCodecUnknownVideo;
+  }
+}
+
+CobaltExtensionDemuxerSampleFormat AvSampleFormatToSampleFormat(
+    AVSampleFormat sample_format) {
+  switch (sample_format) {
+    case AV_SAMPLE_FMT_U8:
+      return kCobaltExtensionDemuxerSampleFormatU8;
+    case AV_SAMPLE_FMT_S16:
+      return kCobaltExtensionDemuxerSampleFormatS16;
+    case AV_SAMPLE_FMT_S32:
+      return kCobaltExtensionDemuxerSampleFormatS32;
+    case AV_SAMPLE_FMT_FLT:
+      return kCobaltExtensionDemuxerSampleFormatF32;
+    case AV_SAMPLE_FMT_S16P:
+      return kCobaltExtensionDemuxerSampleFormatPlanarS16;
+    case AV_SAMPLE_FMT_S32P:
+      return kCobaltExtensionDemuxerSampleFormatPlanarS32;
+    case AV_SAMPLE_FMT_FLTP:
+      return kCobaltExtensionDemuxerSampleFormatPlanarF32;
+    default:
+      return kCobaltExtensionDemuxerSampleFormatUnknown;
+  }
+}
+
+CobaltExtensionDemuxerChannelLayout GuessChannelLayout(int channels) {
+  switch (channels) {
+    case 1:
+      return kCobaltExtensionDemuxerChannelLayoutMono;
+    case 2:
+      return kCobaltExtensionDemuxerChannelLayoutStereo;
+    case 3:
+      return kCobaltExtensionDemuxerChannelLayoutSurround;
+    case 4:
+      return kCobaltExtensionDemuxerChannelLayoutQuad;
+    case 5:
+      return kCobaltExtensionDemuxerChannelLayout5_0;
+    case 6:
+      return kCobaltExtensionDemuxerChannelLayout5_1;
+    case 7:
+      return kCobaltExtensionDemuxerChannelLayout6_1;
+    case 8:
+      return kCobaltExtensionDemuxerChannelLayout7_1;
+    default:
+      SB_LOG(ERROR) << "Unsupported channel count: " << channels;
+  }
+  return kCobaltExtensionDemuxerChannelLayoutUnsupported;
+}
+
+CobaltExtensionDemuxerChannelLayout AvChannelLayoutToChannelLayout(
+    uint64_t channel_layout,
+    int num_channels) {
+  if (num_channels > 8) {
+    return kCobaltExtensionDemuxerChannelLayoutDiscrete;
+  }
+
+  switch (channel_layout) {
+    case AV_CH_LAYOUT_MONO:
+      return kCobaltExtensionDemuxerChannelLayoutMono;
+    case AV_CH_LAYOUT_STEREO:
+      return kCobaltExtensionDemuxerChannelLayoutStereo;
+    case AV_CH_LAYOUT_2_1:
+      return kCobaltExtensionDemuxerChannelLayout2_1;
+    case AV_CH_LAYOUT_SURROUND:
+      return kCobaltExtensionDemuxerChannelLayoutSurround;
+    case AV_CH_LAYOUT_4POINT0:
+      return kCobaltExtensionDemuxerChannelLayout4_0;
+    case AV_CH_LAYOUT_2_2:
+      return kCobaltExtensionDemuxerChannelLayout2_2;
+    case AV_CH_LAYOUT_QUAD:
+      return kCobaltExtensionDemuxerChannelLayoutQuad;
+    case AV_CH_LAYOUT_5POINT0:
+      return kCobaltExtensionDemuxerChannelLayout5_0;
+    case AV_CH_LAYOUT_5POINT1:
+      return kCobaltExtensionDemuxerChannelLayout5_1;
+    case AV_CH_LAYOUT_5POINT0_BACK:
+      return kCobaltExtensionDemuxerChannelLayout5_0Back;
+    case AV_CH_LAYOUT_5POINT1_BACK:
+      return kCobaltExtensionDemuxerChannelLayout5_1Back;
+    case AV_CH_LAYOUT_7POINT0:
+      return kCobaltExtensionDemuxerChannelLayout7_0;
+    case AV_CH_LAYOUT_7POINT1:
+      return kCobaltExtensionDemuxerChannelLayout7_1;
+    case AV_CH_LAYOUT_7POINT1_WIDE:
+      return kCobaltExtensionDemuxerChannelLayout7_1Wide;
+    case AV_CH_LAYOUT_STEREO_DOWNMIX:
+      return kCobaltExtensionDemuxerChannelLayoutStereoDownmix;
+    case AV_CH_LAYOUT_2POINT1:
+      return kCobaltExtensionDemuxerChannelLayout2point1;
+    case AV_CH_LAYOUT_3POINT1:
+      return kCobaltExtensionDemuxerChannelLayout3_1;
+    case AV_CH_LAYOUT_4POINT1:
+      return kCobaltExtensionDemuxerChannelLayout4_1;
+    case AV_CH_LAYOUT_6POINT0:
+      return kCobaltExtensionDemuxerChannelLayout6_0;
+    case AV_CH_LAYOUT_6POINT0_FRONT:
+      return kCobaltExtensionDemuxerChannelLayout6_0Front;
+    case AV_CH_LAYOUT_HEXAGONAL:
+      return kCobaltExtensionDemuxerChannelLayoutHexagonal;
+    case AV_CH_LAYOUT_6POINT1:
+      return kCobaltExtensionDemuxerChannelLayout6_1;
+    case AV_CH_LAYOUT_6POINT1_BACK:
+      return kCobaltExtensionDemuxerChannelLayout6_1Back;
+    case AV_CH_LAYOUT_6POINT1_FRONT:
+      return kCobaltExtensionDemuxerChannelLayout6_1Front;
+    case AV_CH_LAYOUT_7POINT0_FRONT:
+      return kCobaltExtensionDemuxerChannelLayout7_0Front;
+    case AV_CH_LAYOUT_7POINT1_WIDE_BACK:
+      return kCobaltExtensionDemuxerChannelLayout7_1WideBack;
+    case AV_CH_LAYOUT_OCTAGONAL:
+      return kCobaltExtensionDemuxerChannelLayoutOctagonal;
+    default:
+      return GuessChannelLayout(num_channels);
+  }
+}
+
+CobaltExtensionDemuxerVideoCodecProfile ProfileIDToVideoCodecProfile(
+    int profile) {
+  // Clear out the CONSTRAINED & INTRA flags which are strict subsets of the
+  // corresponding profiles with which they're used.
+  profile &= ~FF_PROFILE_H264_CONSTRAINED;
+  profile &= ~FF_PROFILE_H264_INTRA;
+  switch (profile) {
+    case FF_PROFILE_H264_BASELINE:
+      return kCobaltExtensionDemuxerH264ProfileBaseline;
+    case FF_PROFILE_H264_MAIN:
+      return kCobaltExtensionDemuxerH264ProfileMain;
+    case FF_PROFILE_H264_EXTENDED:
+      return kCobaltExtensionDemuxerH264ProfileExtended;
+    case FF_PROFILE_H264_HIGH:
+      return kCobaltExtensionDemuxerH264ProfileHigh;
+    case FF_PROFILE_H264_HIGH_10:
+      return kCobaltExtensionDemuxerH264ProfileHigh10Profile;
+    case FF_PROFILE_H264_HIGH_422:
+      return kCobaltExtensionDemuxerH264ProfileHigh422Profile;
+    case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+      return kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile;
+    default:
+      SB_LOG(ERROR) << "Unknown profile id: " << profile;
+      return kCobaltExtensionDemuxerVideoCodecProfileUnknown;
+  }
+}
+
+int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) {
+  auto* data_source = static_cast<CobaltExtensionDemuxerDataSource*>(opaque);
+  const int bytes_read =
+      data_source->BlockingRead(buf, buf_size, data_source->user_data);
+
+  if (bytes_read == 0) {
+    return AVERROR_EOF;
+  } else if (bytes_read < 0) {
+    return AVERROR(EIO);
+  } else {
+    return bytes_read;
+  }
+}
+
+int64_t AVIOSeekOperation(void* opaque, int64_t offset, int whence) {
+  auto* data_source = static_cast<CobaltExtensionDemuxerDataSource*>(opaque);
+  switch (whence) {
+    case SEEK_SET: {
+      data_source->SeekTo(offset, data_source->user_data);
+      break;
+    }
+    case SEEK_CUR: {
+      const int64_t current_position =
+          data_source->GetPosition(data_source->user_data);
+      data_source->SeekTo(current_position + offset, data_source->user_data);
+      break;
+    }
+    case SEEK_END: {
+      const int64_t size = data_source->GetSize(data_source->user_data);
+      data_source->SeekTo(size + offset, data_source->user_data);
+      break;
+    }
+    case AVSEEK_SIZE: {
+      return data_source->GetSize(data_source->user_data);
+    }
+    default: {
+      SB_LOG(ERROR) << "Invalid whence: " << whence;
+      return AVERROR(EIO);
+    }
+  }
+
+  // In the case where we did a real seek, return the new position.
+  return data_source->GetPosition(data_source->user_data);
+}
+
+int64_t ConvertFromTimeBaseToMicros(AVRational time_base, int64_t timestamp) {
+  return FFmpegDemuxer::GetDispatch()->av_rescale_rnd(
+      timestamp, time_base.num * kSbTimeSecond, time_base.den,
+      static_cast<int>(AV_ROUND_NEAR_INF));
+}
+
+int64_t ConvertMicrosToTimeBase(AVRational time_base, int64_t timestamp_us) {
+  return FFmpegDemuxer::GetDispatch()->av_rescale_rnd(
+      timestamp_us, time_base.den, time_base.num * kSbTimeSecond,
+      static_cast<int>(AV_ROUND_NEAR_INF));
+}
+
+CobaltExtensionDemuxerEncryptionScheme GetEncryptionScheme(
+    const AVStream& stream) {
+  return FFmpegDemuxer::GetDispatch()->av_dict_get(
+             stream.metadata, "enc_key_id", nullptr, 0) == nullptr
+             ? kCobaltExtensionDemuxerEncryptionSchemeUnencrypted
+             : kCobaltExtensionDemuxerEncryptionSchemeCenc;
+}
+
+int64_t ExtractStartTime(AVStream* stream) {
+  int64_t start_time = 0;
+  if (stream->start_time != kNoFFmpegTimestamp) {
+    start_time =
+        ConvertFromTimeBaseToMicros(stream->time_base, stream->start_time);
+  }
+
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+  const int32_t codec_id = stream->codecpar->codec_id;
+#else
+  const int32_t codec_id = stream->codec->codec_id;
+#endif
+
+  if (stream->first_dts != kNoFFmpegTimestamp
+#if FFMPEG >= 560
+      && codec_id != AV_CODEC_ID_HEVC
+#endif  // FFMPEG >= 560
+      && codec_id != AV_CODEC_ID_H264 && codec_id != AV_CODEC_ID_MPEG4) {
+    const int64_t first_pts =
+        ConvertFromTimeBaseToMicros(stream->time_base, stream->first_dts);
+    start_time = std::min(first_pts, start_time);
+  }
+
+  return start_time;
+}
+
+// Recursively splits |s| around |delimiter| characters.
+std::vector<std::string> Split(const std::string& s, char delimiter) {
+  // Work from right to left, since it's faster to append to the end of a
+  // vector.
+  const size_t pos = s.rfind(delimiter);
+  if (pos == std::string::npos) {
+    // Base case.
+    return {s};
+  }
+
+  // Recursive case.
+  std::vector<std::string> previous_splits = Split(s.substr(0, pos), delimiter);
+  previous_splits.push_back(s.substr(pos + 1));
+  return previous_splits;
+}
+
+int64_t ExtractTimelineOffset(AVFormatContext* format_context) {
+  const std::vector<std::string> input_formats =
+      Split(format_context->iformat->name, ',');
+
+  // The name for ff_matroska_demuxer contains "webm" in its comma-separated
+  // list.
+  const bool is_webm = std::any_of(
+      input_formats.cbegin(), input_formats.cend(),
+      +[](const std::string& format) -> bool { return format == "webm"; });
+
+  if (is_webm) {
+    const AVDictionaryEntry* entry = FFmpegDemuxer::GetDispatch()->av_dict_get(
+        format_context->metadata, "creation_time", nullptr, 0);
+
+    // TODO(b/231634260): properly implement this if necessary. We need to
+    // return microseconds since epoch for the given date string in UTC, which
+    // is harder than it sounds in pure C++.
+    return 0;
+  }
+  return 0;
+}
+
+const bool g_registered =
+    FFMPEGDispatch::RegisterSpecialization(FFMPEG,
+                                           LIBAVCODEC_VERSION_MAJOR,
+                                           LIBAVFORMAT_VERSION_MAJOR,
+                                           LIBAVUTIL_VERSION_MAJOR);
+}  // namespace
+
+#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFreeContext::operator()(
+    void* ptr) const {
+  if (!ptr) {
+    return;
+  }
+  auto* codec_context = static_cast<AVCodecContext*>(ptr);
+  GetDispatch()->avcodec_free_context(&codec_context);
+}
+#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+
+void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFree::operator()(void* ptr) const {
+  if (!ptr) {
+    return;
+  }
+  GetDispatch()->av_free(ptr);
+}
+
+void FFmpegDemuxerImpl<FFMPEG>::ScopedPtrAVFreePacket::operator()(
+    void* ptr) const {
+  if (!ptr) {
+    return;
+  }
+  auto* packet = static_cast<AVPacket*>(ptr);
+  GetDispatch()->av_packet_free(&packet);
+}
+
+FFmpegDemuxerImpl<FFMPEG>::FFmpegDemuxerImpl(
+    CobaltExtensionDemuxerDataSource* data_source)
+    : data_source_(data_source) {
+  SB_DCHECK(data_source_);
+  SB_DCHECK(g_registered) << "Demuxer specialization registration failed.";
+}
+
+FFmpegDemuxerImpl<FFMPEG>::~FFmpegDemuxerImpl() {
+  if (format_context_) {
+    GetDispatch()->avformat_close_input(&format_context_);
+  }
+}
+
+CobaltExtensionDemuxerStatus FFmpegDemuxerImpl<FFMPEG>::Initialize() {
+  SB_DCHECK(format_context_ == nullptr);
+
+  if (initialized_) {
+    SB_LOG(ERROR)
+        << "Multiple calls to FFmpegDemuxerImpl::Initialize are not allowed.";
+    return kCobaltExtensionDemuxerErrorInitializationFailed;
+  }
+  initialized_ = true;
+
+  avio_context_.reset(GetDispatch()->avio_alloc_context(
+      static_cast<unsigned char*>(GetDispatch()->av_malloc(kAvioBufferSize)),
+      kAvioBufferSize, 0,
+      /*opaque=*/data_source_, &AVIOReadOperation, nullptr,
+      &AVIOSeekOperation));
+  avio_context_->seekable =
+      data_source_->is_streaming ? 0 : AVIO_SEEKABLE_NORMAL;
+  avio_context_->write_flag = 0;
+
+  format_context_ = GetDispatch()->avformat_alloc_context();
+  format_context_->flags |= AVFMT_FLAG_CUSTOM_IO;
+#ifdef AVFMT_FLAG_FAST_SEEK
+  format_context_->flags |= AVFMT_FLAG_FAST_SEEK;
+#endif  // AVFMT_FLAG_FAST_SEEK
+#ifdef AVFMT_FLAG_KEEP_SIDE_DATA
+  format_context_->flags |= AVFMT_FLAG_KEEP_SIDE_DATA;
+#endif  // AVFMT_FLAG_KEEP_SIDE_DATA
+  format_context_->error_recognition |= AV_EF_EXPLODE;
+  format_context_->pb = avio_context_.get();
+
+  if (GetDispatch()->avformat_open_input(&format_context_, nullptr, nullptr,
+                                         nullptr) < 0) {
+    SB_LOG(ERROR) << "avformat_open_input failed.";
+    return kCobaltExtensionDemuxerErrorCouldNotOpen;
+  }
+  if (GetDispatch()->avformat_find_stream_info(format_context_, nullptr) < 0) {
+    SB_LOG(ERROR) << "avformat_find_stream_info failed.";
+    return kCobaltExtensionDemuxerErrorCouldNotParse;
+  }
+
+  // Find the first audio stream and video stream, if present.
+  // TODO(b/231632632): pick a stream based on supported codecs, not the first
+  // stream present.
+  for (int i = 0; i < format_context_->nb_streams; ++i) {
+    AVStream* stream = format_context_->streams[i];
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+    const AVCodecParameters* codec_parameters = stream->codecpar;
+    const AVMediaType codec_type = codec_parameters->codec_type;
+    const AVCodecID codec_id = codec_parameters->codec_id;
+#else
+    const AVCodecContext* codec = stream->codec;
+    const AVMediaType codec_type = codec->codec_type;
+    const AVCodecID codec_id = codec->codec_id;
+#endif
+    // Skip streams which are not properly detected.
+    if (codec_id == AV_CODEC_ID_NONE) {
+      stream->discard = AVDISCARD_ALL;
+      continue;
+    }
+
+    if (codec_type == AVMEDIA_TYPE_AUDIO) {
+      if (audio_stream_) {
+        continue;
+      }
+      audio_stream_ = stream;
+    } else if (codec_type == AVMEDIA_TYPE_VIDEO) {
+      if (video_stream_)
+        continue;
+      video_stream_ = stream;
+    }
+  }
+
+  if (!audio_stream_ && !video_stream_) {
+    SB_LOG(ERROR) << "No audio or video stream was present.";
+    return kCobaltExtensionDemuxerErrorNoSupportedStreams;
+  }
+
+  if (audio_stream_ && !ParseAudioConfig(audio_stream_, &audio_config_)) {
+    return kCobaltExtensionDemuxerErrorInitializationFailed;
+  }
+  if (video_stream_ && !ParseVideoConfig(video_stream_, &video_config_)) {
+    return kCobaltExtensionDemuxerErrorInitializationFailed;
+  }
+
+  if (format_context_->duration != kNoFFmpegTimestamp) {
+    duration_us_ = ConvertFromTimeBaseToMicros(
+        /*time_base=*/{1, AV_TIME_BASE}, format_context_->duration);
+  }
+
+  start_time_ = std::min(audio_stream_ ? ExtractStartTime(audio_stream_)
+                                       : std::numeric_limits<int64_t>::max(),
+                         video_stream_ ? ExtractStartTime(video_stream_)
+                                       : std::numeric_limits<int64_t>::max());
+
+  timeline_offset_us_ = ExtractTimelineOffset(format_context_);
+
+  return kCobaltExtensionDemuxerOk;
+}
+
+bool FFmpegDemuxerImpl<FFMPEG>::HasAudioStream() const {
+  return audio_stream_ != nullptr;
+}
+
+const CobaltExtensionDemuxerAudioDecoderConfig&
+FFmpegDemuxerImpl<FFMPEG>::GetAudioConfig() const {
+  return audio_config_;
+}
+
+bool FFmpegDemuxerImpl<FFMPEG>::HasVideoStream() const {
+  return video_stream_ != nullptr;
+}
+
+const CobaltExtensionDemuxerVideoDecoderConfig&
+FFmpegDemuxerImpl<FFMPEG>::GetVideoConfig() const {
+  return video_config_;
+}
+
+SbTime FFmpegDemuxerImpl<FFMPEG>::GetDuration() const {
+  return duration_us_;
+}
+
+SbTime FFmpegDemuxerImpl<FFMPEG>::GetStartTime() const {
+  return start_time_;
+}
+
+SbTime FFmpegDemuxerImpl<FFMPEG>::GetTimelineOffset() const {
+  return timeline_offset_us_;
+}
+
+void FFmpegDemuxerImpl<FFMPEG>::Read(CobaltExtensionDemuxerStreamType type,
+                                     CobaltExtensionDemuxerReadCB read_cb,
+                                     void* read_cb_user_data) {
+  SB_DCHECK(type == kCobaltExtensionDemuxerStreamTypeAudio ||
+            type == kCobaltExtensionDemuxerStreamTypeVideo);
+
+  if (type == kCobaltExtensionDemuxerStreamTypeAudio) {
+    SB_DCHECK(audio_stream_);
+  } else {
+    SB_DCHECK(video_stream_);
+  }
+
+  const AVRational time_base = type == kCobaltExtensionDemuxerStreamTypeAudio
+                                   ? audio_stream_->time_base
+                                   : video_stream_->time_base;
+
+  CobaltExtensionDemuxerBuffer buffer = {};
+  ScopedAVPacket packet = GetNextPacket(type);
+  if (!packet) {
+    // Either an error occurred or we reached EOS. Treat as EOS.
+    buffer.end_of_stream = true;
+    read_cb(&buffer, read_cb_user_data);
+    return;
+  }
+
+  // NOTE: subtracting start_time_ is necessary because the rest of the cobalt
+  // pipeline never calls the demuxer's GetStartTime() to handle the offset
+  // (it assumes 0 offset).
+  //
+  // TODO(b/231634475): don't subtract start_time_ here if the rest of the
+  // pipeline is updated to handle nonzero start times.
+  buffer.pts =
+      ConvertFromTimeBaseToMicros(time_base, packet->pts) - start_time_;
+  buffer.duration = ConvertFromTimeBaseToMicros(time_base, packet->duration);
+  buffer.is_keyframe = packet->flags & AV_PKT_FLAG_KEY;
+  buffer.end_of_stream = false;
+  buffer.data = packet->data;
+  buffer.data_size = packet->size;
+
+// The supported libav libraries don't define
+// AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL.
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+  std::vector<CobaltExtensionDemuxerSideData> side_data;
+  for (int i = 0; i < packet->side_data_elems; ++i) {
+    const AVPacketSideData& packet_side_data = packet->side_data[i];
+    if (packet_side_data.type == AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL) {
+      CobaltExtensionDemuxerSideData extension_side_data = {};
+      extension_side_data.data = packet_side_data.data;
+      extension_side_data.data_size = packet_side_data.size;
+      extension_side_data.type = kCobaltExtensionDemuxerMatroskaBlockAdditional;
+      side_data.push_back(std::move(extension_side_data));
+    }
+    // TODO(b/231635220): support other types of side data, if necessary.
+  }
+
+  if (side_data.empty()) {
+    buffer.side_data = nullptr;
+    buffer.side_data_elements = 0;
+  } else {
+    buffer.side_data = side_data.data();
+    buffer.side_data_elements = side_data.size();
+  }
+#endif  // LIBAVCODEC_LIBRARY_IS_FFMPEG
+
+  read_cb(&buffer, read_cb_user_data);
+}
+
+CobaltExtensionDemuxerStatus FFmpegDemuxerImpl<FFMPEG>::Seek(
+    int64_t seek_time_us) {
+  // Clear any buffered packets and seek via FFmpeg.
+  video_packets_.clear();
+  audio_packets_.clear();
+
+  AVStream* const stream = video_stream_ ? video_stream_ : audio_stream_;
+  GetDispatch()->av_seek_frame(
+      format_context_, stream->index,
+      ConvertMicrosToTimeBase(stream->time_base, seek_time_us),
+      AVSEEK_FLAG_BACKWARD);
+
+  return kCobaltExtensionDemuxerOk;
+}
+
+// Returns the next packet of type |type|, or nullptr if EoS has been reached
+// or an error was encountered.
+FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket FFmpegDemuxerImpl<
+    FFMPEG>::GetNextPacket(CobaltExtensionDemuxerStreamType type) {
+  // Handle the simple case: if we already have a packet buffered, just return
+  // it.
+  ScopedAVPacket packet = GetBufferedPacket(type);
+  if (packet)
+    return packet;
+
+  // Read another packet from FFmpeg. We may have to discard a packet if it's
+  // not from the right stream. Additionally, if we hit end-of-file or an
+  // error, we need to return null.
+  packet.reset(GetDispatch()->av_packet_alloc());
+  while (true) {
+    int result = GetDispatch()->av_read_frame(format_context_, packet.get());
+    if (result < 0) {
+      // The packet will be unref-ed when ScopedAVPacket's destructor runs.
+      return nullptr;
+    }
+
+    // Determine whether to drop the packet. In that case, we need to manually
+    // unref the packet, since new data will be written to it.
+    if (video_stream_ && packet->stream_index == video_stream_->index) {
+      if (type == kCobaltExtensionDemuxerStreamTypeVideo) {
+        // We found the packet that the caller was looking for.
+        return packet;
+      }
+
+      // The caller doesn't need a video packet; just buffer it and allocate a
+      // new packet.
+      BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeVideo);
+      packet.reset(GetDispatch()->av_packet_alloc());
+      continue;
+    } else if (audio_stream_ && packet->stream_index == audio_stream_->index) {
+      if (type == kCobaltExtensionDemuxerStreamTypeAudio) {
+        // We found the packet that the caller was looking for.
+        return packet;
+      }
+
+      // The caller doesn't need an audio packet; just buffer it and allocate
+      // a new packet.
+      BufferPacket(std::move(packet), kCobaltExtensionDemuxerStreamTypeAudio);
+      packet.reset(GetDispatch()->av_packet_alloc());
+      continue;
+    }
+
+    // This is a packet for a stream we don't care about. Unref it and keep
+    // searching.
+    GetDispatch()->av_packet_unref(packet.get());
+  }
+
+  SB_NOTREACHED();
+  return nullptr;
+}
+
+// Returns a buffered packet of type |type|, or nullptr if no buffered packet
+// is available.
+FFmpegDemuxerImpl<FFMPEG>::ScopedAVPacket FFmpegDemuxerImpl<
+    FFMPEG>::GetBufferedPacket(CobaltExtensionDemuxerStreamType type) {
+  if (type == kCobaltExtensionDemuxerStreamTypeVideo) {
+    if (video_packets_.empty()) {
+      return nullptr;
+    }
+    ScopedAVPacket packet = std::move(video_packets_.front());
+    video_packets_.pop_front();
+    return packet;
+  } else {
+    if (audio_packets_.empty()) {
+      return nullptr;
+    }
+    ScopedAVPacket packet = std::move(audio_packets_.front());
+    audio_packets_.pop_front();
+    return packet;
+  }
+}
+
+// Pushes |packet| into the queue specified by |type|.
+void FFmpegDemuxerImpl<FFMPEG>::BufferPacket(
+    ScopedAVPacket packet,
+    CobaltExtensionDemuxerStreamType type) {
+  if (type == kCobaltExtensionDemuxerStreamTypeVideo) {
+    video_packets_.push_back(std::move(packet));
+  } else {
+    audio_packets_.push_back(std::move(packet));
+  }
+}
+
+bool FFmpegDemuxerImpl<FFMPEG>::ParseAudioConfig(
+    AVStream* audio_stream,
+    CobaltExtensionDemuxerAudioDecoderConfig* config) {
+  if (!config) {
+    return false;
+  }
+
+  config->encryption_scheme = GetEncryptionScheme(*audio_stream);
+
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+  std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context(
+      GetDispatch()->avcodec_alloc_context3(nullptr));
+  if (!codec_context) {
+    SB_LOG(ERROR) << "Could not allocate codec context.";
+    return false;
+  }
+  if (GetDispatch()->avcodec_parameters_to_context(
+          codec_context.get(), audio_stream->codecpar) < 0) {
+    return false;
+  }
+#else   // LIBAVCODEC_LIBRARY_IS_FFMPEG
+  AVCodecContext* codec_context = audio_stream->codec;
+#endif  // LIBAVCODEC_LIBRARY_IS_FFMPEG
+
+  config->codec = AvCodecIdToAudioCodec(codec_context->codec_id);
+  config->sample_format =
+      AvSampleFormatToSampleFormat(codec_context->sample_fmt);
+  config->channel_layout = AvChannelLayoutToChannelLayout(
+      codec_context->channel_layout, codec_context->channels);
+  config->samples_per_second = codec_context->sample_rate;
+
+  // Catch a potential FFmpeg bug. See http://crbug.com/517163 for more info.
+  if ((codec_context->extradata_size == 0) !=
+      (codec_context->extradata == nullptr)) {
+    SB_LOG(ERROR) << (codec_context->extradata == nullptr ? " NULL"
+                                                          : " Non-NULL")
+                  << " extra data cannot have size of "
+                  << codec_context->extradata_size;
+    return false;
+  }
+
+  if (codec_context->extradata_size > 0) {
+    extra_audio_data_.assign(
+        codec_context->extradata,
+        codec_context->extradata + codec_context->extradata_size);
+    config->extra_data = extra_audio_data_.data();
+    config->extra_data_size = extra_audio_data_.size();
+  } else {
+    config->extra_data = nullptr;
+    config->extra_data_size = 0;
+  }
+
+  // The spec for AC3/EAC3 audio is ETSI TS 102 366. According to sections
+  // F.3.1 and F.5.1 in that spec, the sample format must be 16 bits.
+  if (config->codec == kCobaltExtensionDemuxerCodecAC3 ||
+      config->codec == kCobaltExtensionDemuxerCodecEAC3) {
+    config->sample_format = kCobaltExtensionDemuxerSampleFormatS16;
+  }
+
+  // TODO(b/231637692): If we need to support MPEG-H, the channel layout and
+  // sample format need to be set here.
+  return true;
+}
+
+bool FFmpegDemuxerImpl<FFMPEG>::ParseVideoConfig(
+    AVStream* video_stream,
+    CobaltExtensionDemuxerVideoDecoderConfig* config) {
+#if LIBAVCODEC_LIBRARY_IS_FFMPEG
+  std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> codec_context(
+      GetDispatch()->avcodec_alloc_context3(nullptr));
+
+  if (!codec_context) {
+    SB_LOG(ERROR) << "Could not allocate codec context.";
+    return false;
+  }
+  if (GetDispatch()->avcodec_parameters_to_context(
+          codec_context.get(), video_stream->codecpar) < 0) {
+    return false;
+  }
+#else
+  AVCodecContext* codec_context = video_stream->codec;
+#endif
+
+  config->visible_rect_x = 0;
+  config->visible_rect_y = 0;
+  config->visible_rect_width = codec_context->width;
+  config->visible_rect_height = codec_context->height;
+
+  config->coded_width = codec_context->width;
+  config->coded_height = codec_context->height;
+
+  auto get_aspect_ratio = +[](AVRational rational) -> double {
+    return rational.den == 0 ? 0.0
+                             : static_cast<double>(rational.num) / rational.den;
+  };
+
+  const double aspect_ratio =
+      video_stream->sample_aspect_ratio.num
+          ? get_aspect_ratio(video_stream->sample_aspect_ratio)
+          : codec_context->sample_aspect_ratio.num
+                ? get_aspect_ratio(codec_context->sample_aspect_ratio)
+                : 0.0;
+  {
+    double width = config->visible_rect_width;
+    double height = config->visible_rect_height;
+    if (aspect_ratio >= 1) {
+      // Wide pixels; grow width.
+      width = width * aspect_ratio;
+    } else {
+      // Narrow pixels; grow height.
+      height = height / aspect_ratio;
+    }
+
+    width = std::round(width);
+    height = std::round(height);
+    if (width < 1.0 || width > std::numeric_limits<int>::max() ||
+        height < 1.0 || height > std::numeric_limits<int>::max()) {
+      // Invalid width and height. Just use the visible width and height.
+      config->natural_width = config->visible_rect_width;
+      config->natural_height = config->visible_rect_height;
+    } else {
+      config->natural_width = static_cast<int>(width);
+      config->natural_height = static_cast<int>(height);
+    }
+  }
+  config->codec = AvCodecIdToVideoCodec(codec_context->codec_id);
+
+  // Without the ffmpeg decoder configured, libavformat is unable to get the
+  // profile, format, or coded size. So choose sensible defaults and let
+  // decoders fail later if the configuration is actually unsupported.
+  config->profile = kCobaltExtensionDemuxerVideoCodecProfileUnknown;
+
+  switch (config->codec) {
+    case kCobaltExtensionDemuxerCodecH264: {
+      config->profile = ProfileIDToVideoCodecProfile(codec_context->profile);
+      if (config->profile == kCobaltExtensionDemuxerVideoCodecProfileUnknown &&
+          codec_context->extradata && codec_context->extradata_size) {
+        // TODO(b/231631898): handle the extra data here, if necessary.
+        SB_LOG(ERROR) << "Extra data is not currently handled.";
+      }
+      break;
+    }
+#ifdef FF_PROFILE_HEVC_MAIN
+    case kCobaltExtensionDemuxerCodecHEVC: {
+      int hevc_profile = FF_PROFILE_UNKNOWN;
+      if ((codec_context->profile < FF_PROFILE_HEVC_MAIN ||
+           codec_context->profile >
+#ifdef FF_PROFILE_HEVC_REXT
+               FF_PROFILE_HEVC_REXT
+#else  // FF_PROFILE_HEVC_REXT
+               FF_PROFILE_HEVC_MAIN_STILL_PICTURE
+#endif  // FF_PROFILE_HEVC_REXT
+           ) &&
+          codec_context->extradata && codec_context->extradata_size) {
+        // TODO(b/231631898): handle the extra data here, if necessary.
+        SB_LOG(ERROR) << "Extra data is not currently handled.";
+      } else {
+        hevc_profile = codec_context->profile;
+      }
+      switch (hevc_profile) {
+        case FF_PROFILE_HEVC_MAIN:
+          config->profile = kCobaltExtensionDemuxerHevcProfileMain;
+          break;
+        case FF_PROFILE_HEVC_MAIN_10:
+          config->profile = kCobaltExtensionDemuxerHevcProfileMain10;
+          break;
+        case FF_PROFILE_HEVC_MAIN_STILL_PICTURE:
+          config->profile = kCobaltExtensionDemuxerHevcProfileMainStillPicture;
+          break;
+        default:
+          // Always assign a default if all heuristics fail.
+          config->profile = kCobaltExtensionDemuxerHevcProfileMain;
+          break;
+      }
+      break;
+    }
+#endif  // FF_PROFILE_HEVC_MAIN
+    case kCobaltExtensionDemuxerCodecVP8:
+      config->profile = kCobaltExtensionDemuxerVp8ProfileAny;
+      break;
+#ifdef FF_PROFILE_VP9_0
+    case kCobaltExtensionDemuxerCodecVP9:
+      switch (codec_context->profile) {
+        case FF_PROFILE_VP9_0:
+          config->profile = kCobaltExtensionDemuxerVp9ProfileProfile0;
+          break;
+        case FF_PROFILE_VP9_1:
+          config->profile = kCobaltExtensionDemuxerVp9ProfileProfile1;
+          break;
+        case FF_PROFILE_VP9_2:
+          config->profile = kCobaltExtensionDemuxerVp9ProfileProfile2;
+          break;
+        case FF_PROFILE_VP9_3:
+          config->profile = kCobaltExtensionDemuxerVp9ProfileProfile3;
+          break;
+        default:
+          config->profile = kCobaltExtensionDemuxerVp9ProfileMin;
+          break;
+      }
+      break;
+#endif  // FF_PROFILE_VP9_0
+    case kCobaltExtensionDemuxerCodecAV1:
+      config->profile = kCobaltExtensionDemuxerAv1ProfileProfileMain;
+      break;
+    case kCobaltExtensionDemuxerCodecTheora:
+      config->profile = kCobaltExtensionDemuxerTheoraProfileAny;
+      break;
+    default:
+      config->profile = ProfileIDToVideoCodecProfile(codec_context->profile);
+  }
+
+  config->color_space_primaries = codec_context->color_primaries;
+  config->color_space_transfer = codec_context->color_trc;
+  config->color_space_matrix = codec_context->colorspace;
+  config->color_space_range_id =
+      codec_context->color_range == AVCOL_RANGE_JPEG
+          ? kCobaltExtensionDemuxerColorSpaceRangeIdFull
+          : kCobaltExtensionDemuxerColorSpaceRangeIdLimited;
+
+  // Catch a potential FFmpeg bug.
+  if ((codec_context->extradata_size == 0) !=
+      (codec_context->extradata == nullptr)) {
+    SB_LOG(ERROR) << (codec_context->extradata == nullptr ? " NULL"
+                                                          : " Non-NULL")
+                  << " extra data cannot have size of "
+                  << codec_context->extradata_size;
+    return false;
+  }
+
+  if (codec_context->extradata_size > 0) {
+    extra_video_data_.assign(
+        codec_context->extradata,
+        codec_context->extradata + codec_context->extradata_size);
+    config->extra_data = extra_video_data_.data();
+    config->extra_data_size = extra_video_data_.size();
+  } else {
+    config->extra_data = nullptr;
+    config->extra_data_size = 0;
+  }
+
+  config->encryption_scheme = GetEncryptionScheme(*video_stream);
+
+  return true;
+}
+
+// static
+std::unique_ptr<FFmpegDemuxer> FFmpegDemuxerImpl<FFMPEG>::Create(
+    CobaltExtensionDemuxerDataSource* data_source) {
+  return std::unique_ptr<FFmpegDemuxerImpl<FFMPEG>>(
+      new FFmpegDemuxerImpl<FFMPEG>(data_source));
+}
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h
new file mode 100644
index 0000000..25f7308
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.h
@@ -0,0 +1,130 @@
+// 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_FFMPEG_FFMPEG_DEMUXER_IMPL_H_
+#define STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_IMPL_H_
+
+#include <deque>
+#include <memory>
+#include <vector>
+
+#include "cobalt/extension/demuxer.h"
+#include "starboard/shared/ffmpeg/ffmpeg_common.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+class FFMPEGDispatch;
+
+// Forward class declaration of the explicit specialization with value FFMPEG.
+template <>
+class FFmpegDemuxerImpl<FFMPEG>;
+
+// Declare the explicit specialization of the class with value FFMPEG.
+template <>
+class FFmpegDemuxerImpl<FFMPEG> : public FFmpegDemuxer {
+ public:
+  // Creates an FFmpegDemuxer instance for a specific version of FFmpeg. Returns
+  // null if the version of FFmpeg that is present is not supported.
+  static std::unique_ptr<FFmpegDemuxer> Create(
+      CobaltExtensionDemuxerDataSource* data_source);
+
+  ~FFmpegDemuxerImpl() override;
+
+  // FFmpegDemuxer implementation:
+  CobaltExtensionDemuxerStatus Initialize() override;
+  bool HasAudioStream() const override;
+  bool HasVideoStream() const override;
+  const CobaltExtensionDemuxerAudioDecoderConfig& GetAudioConfig()
+      const override;
+  const CobaltExtensionDemuxerVideoDecoderConfig& GetVideoConfig()
+      const override;
+  SbTime GetDuration() const override;
+  SbTime GetStartTime() const override;
+  SbTime GetTimelineOffset() const override;
+  void Read(CobaltExtensionDemuxerStreamType type,
+            CobaltExtensionDemuxerReadCB read_cb,
+            void* read_cb_user_data) override;
+  CobaltExtensionDemuxerStatus Seek(int64_t seek_time_us) override;
+
+ private:
+#if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+  struct ScopedPtrAVFreeContext {
+    void operator()(void* ptr) const;
+  };
+#endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
+
+  struct ScopedPtrAVFree {
+    void operator()(void* ptr) const;
+  };
+
+  struct ScopedPtrAVFreePacket {
+    void operator()(void* ptr) const;
+  };
+
+  using ScopedAVPacket = std::unique_ptr<AVPacket, ScopedPtrAVFreePacket>;
+
+  explicit FFmpegDemuxerImpl(CobaltExtensionDemuxerDataSource* data_source);
+
+  // Returns the next packet of type |type|, or nullptr if EoS has been reached
+  // or an error was encountered.
+  ScopedAVPacket GetNextPacket(CobaltExtensionDemuxerStreamType type);
+
+  // Returns a buffered packet of type |type|, or nullptr if no buffered packet
+  // is available.
+  ScopedAVPacket GetBufferedPacket(CobaltExtensionDemuxerStreamType type);
+
+  // Pushes |packet| into the queue specified by |type|.
+  void BufferPacket(ScopedAVPacket packet,
+                    CobaltExtensionDemuxerStreamType type);
+
+  bool ParseAudioConfig(AVStream* audio_stream,
+                        CobaltExtensionDemuxerAudioDecoderConfig* config);
+
+  bool ParseVideoConfig(AVStream* video_stream,
+                        CobaltExtensionDemuxerVideoDecoderConfig* config);
+
+  CobaltExtensionDemuxerDataSource* data_source_ = nullptr;
+  AVStream* video_stream_ = nullptr;
+  AVStream* audio_stream_ = nullptr;
+  std::deque<ScopedAVPacket> video_packets_;
+  std::deque<ScopedAVPacket> audio_packets_;
+
+  std::vector<uint8_t> extra_audio_data_;
+  std::vector<uint8_t> extra_video_data_;
+
+  // These will only be properly populated if the corresponding AVStream is not
+  // null.
+  CobaltExtensionDemuxerAudioDecoderConfig audio_config_ = {};
+  CobaltExtensionDemuxerVideoDecoderConfig video_config_ = {};
+
+  bool initialized_ = false;
+  int64_t start_time_ = 0L;
+  int64_t duration_us_ = 0L;
+  int64_t timeline_offset_us_ = 0L;
+
+  // FFmpeg-related structs.
+  std::unique_ptr<AVIOContext, ScopedPtrAVFree> avio_context_;
+  AVFormatContext* format_context_ = nullptr;
+};
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_IMPL_H_
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h
new file mode 100644
index 0000000..901edc6
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h
@@ -0,0 +1,40 @@
+// 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_FFMPEG_FFMPEG_DEMUXER_IMPL_INTERFACE_H_
+#define STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_IMPL_INTERFACE_H_
+
+#include <memory>
+
+#include "cobalt/extension/demuxer.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+// For each version V that is supported, there will be an implementation of an
+// explicit specialization of the FFmpegDemuxer class.
+template <int V>
+class FFmpegDemuxerImpl : public FFmpegDemuxer {
+ public:
+  static std::unique_ptr<FFmpegDemuxer> Create(
+      CobaltExtensionDemuxerDataSource* data_source);
+};
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_SHARED_FFMPEG_FFMPEG_DEMUXER_IMPL_INTERFACE_H_
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc
new file mode 100644
index 0000000..927adcb
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_test.cc
@@ -0,0 +1,763 @@
+// 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/ffmpeg/ffmpeg_demuxer.h"
+
+#include <cstdint>
+#include <cstdlib>
+#include <memory>
+#include <tuple>
+#include <vector>
+
+#include "starboard/shared/ffmpeg/ffmpeg_common.h"
+#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+namespace {
+
+using ::testing::_;
+using ::testing::AllArgs;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::ExplainMatchResult;
+using ::testing::Invoke;
+using ::testing::IsNull;
+using ::testing::MockFunction;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::Pointwise;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::WithArg;
+
+// Compares two CobaltExtensionDemuxerSideData structs. Deep equality is
+// checked; in other words, the actual side data is inspected (not just the ptr
+// addresses).
+MATCHER_P(SideDataEq, expected, "") {
+  return arg.type == expected.type && arg.data_size == expected.data_size &&
+         ExplainMatchResult(
+             ElementsAreArray(expected.data,
+                              static_cast<size_t>(expected.data_size)),
+             std::tuple<uint8_t*, size_t>{arg.data, arg.data_size},
+             result_listener);
+}
+
+// Compares two CobaltExtensionDemuxerBuffers. Deep equality is checked; the
+// side data and data are checked element-by-element, rather than simply
+// checking ptr addresses.
+MATCHER_P(BufferMatches, expected_buffer, "") {
+  if (expected_buffer.end_of_stream) {
+    // For EoS buffers, we don't care about the other values.
+    return arg.end_of_stream;
+  }
+
+  if (arg.data_size != expected_buffer.data_size) {
+    return false;
+  }
+  if (!ExplainMatchResult(
+          ElementsAreArray(expected_buffer.data,
+                           static_cast<size_t>(expected_buffer.data_size)),
+          std::tuple<uint8_t*, size_t>{arg.data, arg.data_size},
+          result_listener)) {
+    return false;
+  }
+  if (arg.side_data_elements != expected_buffer.side_data_elements) {
+    return false;
+  }
+  // Note: ::testing::Pointwise doesn't support pointer/count as a
+  // representation of an array, so we manually check each side data element.
+  for (int i = 0; i < expected_buffer.side_data_elements; ++i) {
+    if (!ExplainMatchResult(SideDataEq(expected_buffer.side_data[i]),
+                            arg.side_data[i], result_listener)) {
+      return false;
+    }
+  }
+  return (arg.pts == expected_buffer.pts &&
+          arg.duration == expected_buffer.duration &&
+          arg.is_keyframe == expected_buffer.is_keyframe &&
+          arg.end_of_stream == expected_buffer.end_of_stream);
+}
+
+// Streaming is not supported.
+constexpr bool kIsStreaming = false;
+
+// Used to convert a MockFn to a pure C function.
+template <typename T, typename U>
+void CallMockCB(U* u, void* user_data) {
+  static_cast<T*>(user_data)->AsStdFunction()(u);
+}
+
+// A mock class for receiving FFmpeg calls. The API mimics the relevant parts of
+// the real FFmpeg API.
+class MockFFmpegImpl {
+ public:
+  MOCK_METHOD1(AVCodecFreeContext, void(AVCodecContext** avctx));
+  MOCK_METHOD1(AVFree, void(void* ptr));
+  MOCK_METHOD4(AVRescaleRnd, int64_t(int64_t a, int64_t b, int64_t c, int rnd));
+  MOCK_METHOD4(AVDictGet,
+               AVDictionaryEntry*(const AVDictionary* m,
+                                  const char* key,
+                                  const AVDictionaryEntry* prev,
+                                  int flags));
+  MOCK_METHOD4(AVFormatOpenInput,
+               int(AVFormatContext** ps,
+                   const char* filename,
+                   AVInputFormat* fmt,
+                   AVDictionary** options));
+  MOCK_METHOD1(AVFormatCloseInput, void(AVFormatContext** s));
+  MOCK_METHOD7(
+      AVIOAllocContext,
+      AVIOContext*(
+          unsigned char* buffer,
+          int buffer_size,
+          int write_flag,
+          void* opaque,
+          int (*read_packet)(void* opaque, uint8_t* buf, int buf_size),
+          int (*write_packet)(void* opaque, uint8_t* buf, int buf_size),
+          int64_t (*seek)(void* opaque, int64_t offset, int whence)));
+  MOCK_METHOD1(AVMalloc, void*(size_t size));
+  MOCK_METHOD0(AVFormatAllocContext, AVFormatContext*());
+  MOCK_METHOD2(AVFormatFindStreamInfo,
+               int(AVFormatContext* ic, AVDictionary** options));
+  MOCK_METHOD4(
+      AVSeekFrame,
+      int(AVFormatContext* s, int stream_index, int64_t timestamp, int flags));
+  MOCK_METHOD0(AVPacketAlloc, AVPacket*());
+  MOCK_METHOD1(AVPacketFree, void(AVPacket** pkt));
+  MOCK_METHOD1(AVPacketUnref, void(AVPacket* pkt));
+  MOCK_METHOD2(AVReadFrame, int(AVFormatContext* s, AVPacket* pkt));
+  MOCK_METHOD1(AVCodecAllocContext3, AVCodecContext*(const AVCodec* codec));
+  MOCK_METHOD2(AVCodecParametersToContext,
+               int(AVCodecContext* codec, const AVCodecParameters* par));
+};
+
+// Returns a MockFFmpegImpl instance. It should not be deleted by the caller.
+MockFFmpegImpl* GetMockFFmpegImpl() {
+  static auto* const ffmpeg_wrapper = []() {
+    auto* wrapper = new MockFFmpegImpl;
+    // This mock won't be destructed.
+    testing::Mock::AllowLeak(wrapper);
+    return wrapper;
+  }();
+  return ffmpeg_wrapper;
+}
+
+// Pure C functions that call the static mock.
+void mock_avcodec_free_context(AVCodecContext** avctx) {
+  GetMockFFmpegImpl()->AVCodecFreeContext(avctx);
+}
+
+void mock_av_free(void* ptr) {
+  GetMockFFmpegImpl()->AVFree(ptr);
+}
+
+int64_t mock_av_rescale_rnd(int64_t a, int64_t b, int64_t c, int rnd) {
+  return GetMockFFmpegImpl()->AVRescaleRnd(a, b, c, rnd);
+}
+
+AVDictionaryEntry* mock_av_dict_get(const AVDictionary* m,
+                                    const char* key,
+                                    const AVDictionaryEntry* prev,
+                                    int flags) {
+  return GetMockFFmpegImpl()->AVDictGet(m, key, prev, flags);
+}
+
+int mock_avformat_open_input(AVFormatContext** ps,
+                             const char* filename,
+                             AVInputFormat* fmt,
+                             AVDictionary** options) {
+  return GetMockFFmpegImpl()->AVFormatOpenInput(ps, filename, fmt, options);
+}
+
+void mock_avformat_close_input(AVFormatContext** s) {
+  GetMockFFmpegImpl()->AVFormatCloseInput(s);
+}
+
+AVIOContext* mock_avio_alloc_context(
+    unsigned char* buffer,
+    int buffer_size,
+    int write_flag,
+    void* opaque,
+    int (*read_packet)(void* opaque, uint8_t* buf, int buf_size),
+    int (*write_packet)(void* opaque, uint8_t* buf, int buf_size),
+    int64_t (*seek)(void* opaque, int64_t offset, int whence)) {
+  return GetMockFFmpegImpl()->AVIOAllocContext(
+      buffer, buffer_size, write_flag, opaque, read_packet, write_packet, seek);
+}
+
+void* mock_av_malloc(size_t size) {
+  return GetMockFFmpegImpl()->AVMalloc(size);
+}
+
+AVFormatContext* mock_avformat_alloc_context() {
+  return GetMockFFmpegImpl()->AVFormatAllocContext();
+}
+
+int mock_avformat_find_stream_info(AVFormatContext* ic,
+                                   AVDictionary** options) {
+  return GetMockFFmpegImpl()->AVFormatFindStreamInfo(ic, options);
+}
+
+int mock_av_seek_frame(AVFormatContext* s,
+                       int stream_index,
+                       int64_t timestamp,
+                       int flags) {
+  return GetMockFFmpegImpl()->AVSeekFrame(s, stream_index, timestamp, flags);
+}
+
+AVPacket* mock_av_packet_alloc() {
+  return GetMockFFmpegImpl()->AVPacketAlloc();
+}
+
+void mock_av_packet_free(AVPacket** pkt) {
+  GetMockFFmpegImpl()->AVPacketFree(pkt);
+}
+
+void mock_av_packet_unref(AVPacket* pkt) {
+  GetMockFFmpegImpl()->AVPacketUnref(pkt);
+}
+
+int mock_av_read_frame(AVFormatContext* s, AVPacket* pkt) {
+  return GetMockFFmpegImpl()->AVReadFrame(s, pkt);
+}
+
+AVCodecContext* mock_avcodec_alloc_context3(const AVCodec* codec) {
+  return GetMockFFmpegImpl()->AVCodecAllocContext3(codec);
+}
+
+int mock_avcodec_parameters_to_context(AVCodecContext* codec,
+                                       const AVCodecParameters* par) {
+  return GetMockFFmpegImpl()->AVCodecParametersToContext(codec, par);
+}
+
+// Returns an FFMPEGDispatch instance that forwards calls to the mock stored in
+// GetMockFFmpegImpl() above. The returned FFMPEGDispatch should not be
+// deleted; it has static storage duration.
+FFMPEGDispatch* GetFFMPEGDispatch() {
+  static auto* const ffmpeg_dispatch = []() -> FFMPEGDispatch* {
+    auto* dispatch = new FFMPEGDispatch;
+    dispatch->avcodec_free_context = &mock_avcodec_free_context;
+    dispatch->av_free = &mock_av_free;
+    dispatch->av_rescale_rnd = &mock_av_rescale_rnd;
+    dispatch->av_dict_get = &mock_av_dict_get;
+    dispatch->avformat_open_input = &mock_avformat_open_input;
+    dispatch->avformat_close_input = &mock_avformat_close_input;
+    dispatch->avio_alloc_context = &mock_avio_alloc_context;
+    dispatch->av_malloc = &mock_av_malloc;
+    dispatch->avformat_alloc_context = &mock_avformat_alloc_context;
+    dispatch->avformat_find_stream_info = &mock_avformat_find_stream_info;
+    dispatch->av_seek_frame = &mock_av_seek_frame;
+    dispatch->av_packet_alloc = &mock_av_packet_alloc;
+    dispatch->av_packet_free = &mock_av_packet_free;
+    dispatch->av_packet_unref = &mock_av_packet_unref;
+    dispatch->av_read_frame = &mock_av_read_frame;
+    dispatch->avcodec_alloc_context3 = &mock_avcodec_alloc_context3;
+    dispatch->avcodec_parameters_to_context =
+        &mock_avcodec_parameters_to_context;
+    return dispatch;
+  }();
+
+  return ffmpeg_dispatch;
+}
+
+// A mock class representing a data source passed to the cobalt extension
+// demuxer.
+class MockDataSource {
+ public:
+  MOCK_METHOD2(BlockingRead, int(uint8_t* data, int bytes_requested));
+  MOCK_METHOD1(SeekTo, void(int position));
+  MOCK_METHOD0(GetPosition, int64_t());
+  MOCK_METHOD0(GetSize, int64_t());
+};
+
+// These functions forward calls to a MockDataSource.
+int MockBlockingRead(uint8_t* data, int bytes_requested, void* user_data) {
+  return static_cast<MockDataSource*>(user_data)->BlockingRead(data,
+                                                               bytes_requested);
+}
+
+void MockSeekTo(int position, void* user_data) {
+  static_cast<MockDataSource*>(user_data)->SeekTo(position);
+}
+
+int64_t MockGetPosition(void* user_data) {
+  return static_cast<MockDataSource*>(user_data)->GetPosition();
+}
+
+int64_t MockGetSize(void* user_data) {
+  return static_cast<MockDataSource*>(user_data)->GetSize();
+}
+
+// A test fixture is used to ensure that the (static) mock is checked and reset
+// between tests.
+class FFmpegDemuxerTest : public ::testing::Test {
+ public:
+  FFmpegDemuxerTest() {
+    FFmpegDemuxer::TestOnlySetFFmpegDispatch(GetFFMPEGDispatch());
+  }
+
+  ~FFmpegDemuxerTest() override {
+    testing::Mock::VerifyAndClearExpectations(GetMockFFmpegImpl());
+  }
+};
+
+TEST_F(FFmpegDemuxerTest, InitializeAllocatesContextAndOpensInput) {
+  auto* const mock_ffmpeg_wrapper = GetMockFFmpegImpl();
+
+  AVFormatContext format_context = {};
+  AVInputFormat iformat = {};
+  iformat.name = "mp4";
+  format_context.iformat = &iformat;
+
+  std::vector<AVStream> streams = {AVStream{}};
+  std::vector<AVStream*> stream_ptrs = {&streams[0]};
+  std::vector<AVCodecParameters> stream_params = {AVCodecParameters{}};
+  stream_params[0].codec_type = AVMEDIA_TYPE_AUDIO;
+  stream_params[0].codec_id = AV_CODEC_ID_AAC;
+
+  AVIOContext avio_context = {};
+  AVCodecContext codec_context = {};
+
+  // Sanity checks; if any of these fail, the test has a bug.
+  SB_CHECK(streams.size() == stream_ptrs.size());
+  SB_CHECK(streams.size() == stream_params.size());
+
+  for (int i = 0; i < streams.size(); ++i) {
+    streams[i].codecpar = &stream_params[i];
+    streams[i].time_base.num = 1;
+    streams[i].time_base.den = 1000000;
+    streams[i].start_time = 0;
+  }
+
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatAllocContext())
+      .WillOnce(Return(&format_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatCloseInput(Pointee(Eq(&format_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVMalloc(_)).Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVIOAllocContext(_, _, _, _, _, _, _))
+      .WillOnce(Return(&avio_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatOpenInput(Pointee(Eq(&format_context)), _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatFindStreamInfo(&format_context, _))
+      .WillOnce(WithArg<0>(Invoke([&stream_ptrs](AVFormatContext* context) {
+        context->nb_streams = stream_ptrs.size();
+        context->streams = stream_ptrs.data();
+        context->duration = 120 * AV_TIME_BASE;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVCodecAllocContext3(_))
+      .WillOnce(Return(&codec_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecFreeContext(Pointee(Eq(&codec_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecParametersToContext(&codec_context, streams[0].codecpar))
+      .WillOnce(WithArg<0>(Invoke([](AVCodecContext* context) {
+        context->codec_id = AV_CODEC_ID_AAC;
+        context->sample_fmt = AV_SAMPLE_FMT_FLT;
+        context->channel_layout = AV_CH_LAYOUT_STEREO;
+        context->channels = 2;
+        context->sample_rate = 44100;
+        return 0;
+      })));
+
+  const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
+  MockDataSource data_source;
+  CobaltExtensionDemuxerDataSource c_data_source{
+      &MockBlockingRead, &MockSeekTo,  &MockGetPosition,
+      &MockGetSize,      kIsStreaming, &data_source};
+  std::vector<CobaltExtensionDemuxerAudioCodec> supported_audio_codecs;
+  std::vector<CobaltExtensionDemuxerVideoCodec> supported_video_codecs;
+
+  CobaltExtensionDemuxer* demuxer = api->CreateDemuxer(
+      &c_data_source, supported_audio_codecs.data(),
+      supported_audio_codecs.size(), supported_video_codecs.data(),
+      supported_video_codecs.size());
+
+  ASSERT_THAT(api, NotNull());
+  EXPECT_EQ(demuxer->Initialize(demuxer->user_data), kCobaltExtensionDemuxerOk);
+
+  api->DestroyDemuxer(demuxer);
+}
+
+TEST_F(FFmpegDemuxerTest, ReadsDataFromDataSource) {
+  auto* const mock_ffmpeg_wrapper = GetMockFFmpegImpl();
+  constexpr size_t kReadSize = 5;
+
+  AVFormatContext format_context = {};
+  AVInputFormat iformat = {};
+  iformat.name = "mp4";
+  format_context.iformat = &iformat;
+
+  std::vector<AVStream> streams = {AVStream{}};
+  std::vector<AVStream*> stream_ptrs = {&streams[0]};
+  std::vector<AVCodecParameters> stream_params = {AVCodecParameters{}};
+  stream_params[0].codec_type = AVMEDIA_TYPE_AUDIO;
+  stream_params[0].codec_id = AV_CODEC_ID_AAC;
+
+  AVIOContext avio_context = {};
+  AVCodecContext codec_context = {};
+
+  // Sanity checks; if any of these fail, the test has a bug.
+  SB_CHECK(streams.size() == stream_ptrs.size());
+  SB_CHECK(streams.size() == stream_params.size());
+
+  for (int i = 0; i < streams.size(); ++i) {
+    streams[i].codecpar = &stream_params[i];
+    streams[i].time_base.num = 1;
+    streams[i].time_base.den = 1000000;
+    streams[i].start_time = 0;
+    streams[i].index = i;
+  }
+
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatAllocContext())
+      .WillOnce(Return(&format_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatCloseInput(Pointee(Eq(&format_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVMalloc(_)).Times(1);
+
+  // We will capture the AVIO read operation passed to FFmpeg, so that we can
+  // simulate FFmpeg reading data from the data source.
+  int (*read_packet)(void*, uint8_t*, int) = nullptr;
+  // Data blob that will be passed to read_packet.
+  void* opaque_read_packet = nullptr;
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVIOAllocContext(_, _, _, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<3>(&opaque_read_packet), SaveArg<4>(&read_packet),
+                      Return(&avio_context)));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatOpenInput(Pointee(Eq(&format_context)), _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatFindStreamInfo(&format_context, _))
+      .WillOnce(WithArg<0>(Invoke([&stream_ptrs](AVFormatContext* context) {
+        context->nb_streams = stream_ptrs.size();
+        context->streams = stream_ptrs.data();
+        context->duration = 120 * AV_TIME_BASE;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVCodecAllocContext3(_))
+      .WillOnce(Return(&codec_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecFreeContext(Pointee(Eq(&codec_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecParametersToContext(&codec_context, streams[0].codecpar))
+      .WillOnce(WithArg<0>(Invoke([](AVCodecContext* context) {
+        context->codec_id = AV_CODEC_ID_AAC;
+        context->sample_fmt = AV_SAMPLE_FMT_FLT;
+        context->channel_layout = AV_CH_LAYOUT_STEREO;
+        context->channels = 2;
+        context->sample_rate = 44100;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVReadFrame(&format_context, _))
+      .WillOnce(WithArg<1>(Invoke([&opaque_read_packet, &read_packet,
+                                   kReadSize](AVPacket* packet) {
+        SB_CHECK(read_packet != nullptr)
+            << "FFmpeg's read operation should be set via avio_alloc_context "
+               "before av_read_frame is called.";
+        // This will be freed when av_packet_free is called (which eventually
+        // calls AVPacketFree).
+        packet->data =
+            static_cast<uint8_t*>(malloc(kReadSize * sizeof(uint8_t)));
+        packet->size = kReadSize;
+        read_packet(opaque_read_packet, packet->data, kReadSize);
+        return 0;
+      })));
+
+  const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
+  ASSERT_THAT(api, NotNull());
+
+  std::vector<uint8_t> expected_data = {0, 1, 2, 3, 4};
+  SB_CHECK(expected_data.size() == kReadSize);
+
+  MockDataSource data_source;
+  EXPECT_CALL(data_source, BlockingRead(_, kReadSize))
+      .WillOnce(WithArg<0>(Invoke([expected_data](uint8_t* buffer) {
+        for (int i = 0; i < expected_data.size(); ++i) {
+          buffer[i] = expected_data[i];
+        }
+        return kReadSize;
+      })));
+  CobaltExtensionDemuxerDataSource c_data_source{
+      &MockBlockingRead, &MockSeekTo,  &MockGetPosition,
+      &MockGetSize,      kIsStreaming, &data_source};
+  std::vector<CobaltExtensionDemuxerAudioCodec> supported_audio_codecs;
+  std::vector<CobaltExtensionDemuxerVideoCodec> supported_video_codecs;
+
+  CobaltExtensionDemuxer* demuxer = api->CreateDemuxer(
+      &c_data_source, supported_audio_codecs.data(),
+      supported_audio_codecs.size(), supported_video_codecs.data(),
+      supported_video_codecs.size());
+
+  EXPECT_EQ(demuxer->Initialize(demuxer->user_data), kCobaltExtensionDemuxerOk);
+
+  const CobaltExtensionDemuxerBuffer expected_buffer = {
+      expected_data.data(),
+      static_cast<int64_t>(expected_data.size()),
+      nullptr,
+      0,
+      0,
+      0,
+      false,
+      false};
+
+  MockFunction<void(CobaltExtensionDemuxerBuffer*)> read_cb;
+  AVPacket av_packet = {};
+
+  // This is the main check: we ensure that the expected buffer is passed to us
+  // via the read callback.
+  EXPECT_CALL(read_cb, Call(Pointee(BufferMatches(expected_buffer)))).Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVPacketAlloc())
+      .WillOnce(Return(&av_packet));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVPacketFree(Pointee(Eq(&av_packet))))
+      .WillOnce(Invoke([](AVPacket** av_packet) { free((*av_packet)->data); }));
+
+  demuxer->Read(kCobaltExtensionDemuxerStreamTypeAudio,
+                &CallMockCB<decltype(read_cb), CobaltExtensionDemuxerBuffer>,
+                &read_cb, demuxer->user_data);
+
+  api->DestroyDemuxer(demuxer);
+}
+
+TEST_F(FFmpegDemuxerTest, ReturnsAudioConfig) {
+  auto* const mock_ffmpeg_wrapper = GetMockFFmpegImpl();
+
+  AVFormatContext format_context = {};
+  AVInputFormat iformat = {};
+  iformat.name = "mp4";
+  format_context.iformat = &iformat;
+
+  std::vector<AVStream> streams = {AVStream{}};
+  std::vector<AVStream*> stream_ptrs = {&streams[0]};
+  std::vector<AVCodecParameters> stream_params = {AVCodecParameters{}};
+  stream_params[0].codec_type = AVMEDIA_TYPE_AUDIO;
+  stream_params[0].codec_id = AV_CODEC_ID_AAC;
+
+  AVIOContext avio_context = {};
+  AVCodecContext codec_context = {};
+
+  // Sanity checks; if any of these fail, the test has a bug.
+  SB_CHECK(streams.size() == stream_ptrs.size());
+  SB_CHECK(streams.size() == stream_params.size());
+
+  for (int i = 0; i < streams.size(); ++i) {
+    streams[i].codecpar = &stream_params[i];
+    streams[i].time_base.num = 1;
+    streams[i].time_base.den = 1000000;
+    streams[i].start_time = 0;
+  }
+
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatAllocContext())
+      .WillOnce(Return(&format_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatCloseInput(Pointee(Eq(&format_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVMalloc(_)).Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVIOAllocContext(_, _, _, _, _, _, _))
+      .WillOnce(Return(&avio_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatOpenInput(Pointee(Eq(&format_context)), _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatFindStreamInfo(&format_context, _))
+      .WillOnce(WithArg<0>(Invoke([&stream_ptrs](AVFormatContext* context) {
+        context->nb_streams = stream_ptrs.size();
+        context->streams = stream_ptrs.data();
+        context->duration = 120 * AV_TIME_BASE;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVCodecAllocContext3(_))
+      .WillOnce(Return(&codec_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecFreeContext(Pointee(Eq(&codec_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecParametersToContext(&codec_context, streams[0].codecpar))
+      .WillOnce(WithArg<0>(Invoke([](AVCodecContext* context) {
+        context->codec_id = AV_CODEC_ID_AAC;
+        context->sample_fmt = AV_SAMPLE_FMT_FLT;
+        context->channel_layout = AV_CH_LAYOUT_STEREO;
+        context->channels = 2;
+        context->sample_rate = 44100;
+        return 0;
+      })));
+
+  const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
+  MockDataSource data_source;
+  CobaltExtensionDemuxerDataSource c_data_source{
+      &MockBlockingRead, &MockSeekTo,  &MockGetPosition,
+      &MockGetSize,      kIsStreaming, &data_source};
+  std::vector<CobaltExtensionDemuxerAudioCodec> supported_audio_codecs;
+  std::vector<CobaltExtensionDemuxerVideoCodec> supported_video_codecs;
+
+  CobaltExtensionDemuxer* demuxer = api->CreateDemuxer(
+      &c_data_source, supported_audio_codecs.data(),
+      supported_audio_codecs.size(), supported_video_codecs.data(),
+      supported_video_codecs.size());
+
+  ASSERT_THAT(api, NotNull());
+  EXPECT_EQ(demuxer->Initialize(demuxer->user_data), kCobaltExtensionDemuxerOk);
+
+  CobaltExtensionDemuxerAudioDecoderConfig actual_audio_config = {};
+  demuxer->GetAudioConfig(&actual_audio_config, demuxer->user_data);
+
+  // These values are derived from those set via AVCodecParametersToContext.
+  EXPECT_EQ(actual_audio_config.codec, kCobaltExtensionDemuxerCodecAAC);
+  EXPECT_EQ(actual_audio_config.sample_format,
+            kCobaltExtensionDemuxerSampleFormatF32);
+  EXPECT_EQ(actual_audio_config.channel_layout,
+            kCobaltExtensionDemuxerChannelLayoutStereo);
+  EXPECT_EQ(actual_audio_config.encryption_scheme,
+            kCobaltExtensionDemuxerEncryptionSchemeUnencrypted);
+  EXPECT_EQ(actual_audio_config.samples_per_second, 44100);
+  EXPECT_THAT(actual_audio_config.extra_data, IsNull());
+  EXPECT_EQ(actual_audio_config.extra_data_size, 0);
+
+  api->DestroyDemuxer(demuxer);
+}
+
+TEST_F(FFmpegDemuxerTest, ReturnsVideoConfig) {
+  auto* const mock_ffmpeg_wrapper = GetMockFFmpegImpl();
+
+  AVFormatContext format_context = {};
+  AVInputFormat iformat = {};
+  iformat.name = "mp4";
+  format_context.iformat = &iformat;
+
+  // In this test we simulate both an audio stream and a video stream being
+  // present.
+  std::vector<AVStream> streams = {AVStream{}, AVStream{}};
+  std::vector<AVStream*> stream_ptrs = {&streams[0], &streams[1]};
+  std::vector<AVCodecParameters> stream_params = {AVCodecParameters{},
+                                                  AVCodecParameters{}};
+  stream_params[0].codec_type = AVMEDIA_TYPE_AUDIO;
+  stream_params[0].codec_id = AV_CODEC_ID_AAC;
+  stream_params[1].codec_type = AVMEDIA_TYPE_VIDEO;
+  stream_params[1].codec_id = AV_CODEC_ID_H264;
+
+  AVIOContext avio_context = {};
+  AVCodecContext codec_context_1 = {};
+  AVCodecContext codec_context_2 = {};
+
+  // Sanity checks; if any of these fail, the test has a bug.
+  SB_CHECK(streams.size() == stream_ptrs.size());
+  SB_CHECK(streams.size() == stream_params.size());
+
+  for (int i = 0; i < streams.size(); ++i) {
+    streams[i].codecpar = &stream_params[i];
+    streams[i].time_base.num = 1;
+    streams[i].time_base.den = 1000000;
+    streams[i].start_time = 0;
+  }
+
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatAllocContext())
+      .WillOnce(Return(&format_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatCloseInput(Pointee(Eq(&format_context))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVMalloc(_)).Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVIOAllocContext(_, _, _, _, _, _, _))
+      .WillOnce(Return(&avio_context));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVFormatOpenInput(Pointee(Eq(&format_context)), _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVFormatFindStreamInfo(&format_context, _))
+      .WillOnce(WithArg<0>(Invoke([&stream_ptrs](AVFormatContext* context) {
+        context->nb_streams = stream_ptrs.size();
+        context->streams = stream_ptrs.data();
+        context->duration = 120 * AV_TIME_BASE;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper, AVCodecAllocContext3(_))
+      .WillOnce(Return(&codec_context_1))
+      .WillOnce(Return(&codec_context_2));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecFreeContext(Pointee(Eq(&codec_context_1))))
+      .Times(1);
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecFreeContext(Pointee(Eq(&codec_context_2))))
+      .Times(1);
+
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecParametersToContext(_, streams[0].codecpar))
+      .WillOnce(WithArg<0>(Invoke([](AVCodecContext* context) {
+        context->codec_id = AV_CODEC_ID_AAC;
+        context->sample_fmt = AV_SAMPLE_FMT_FLT;
+        context->channel_layout = AV_CH_LAYOUT_STEREO;
+        context->channels = 2;
+        context->sample_rate = 44100;
+        return 0;
+      })));
+  EXPECT_CALL(*mock_ffmpeg_wrapper,
+              AVCodecParametersToContext(_, streams[1].codecpar))
+      .WillOnce(WithArg<0>(Invoke([](AVCodecContext* context) {
+        context->codec_id = AV_CODEC_ID_H264;
+        context->width = 1920;
+        context->height = 1080;
+        context->sample_aspect_ratio.num = 1;
+        context->sample_aspect_ratio.den = 1;
+        context->profile = FF_PROFILE_H264_BASELINE;
+        context->pix_fmt = AV_PIX_FMT_YUVJ420P;
+        context->colorspace = AVCOL_SPC_BT709;
+        context->color_range = AVCOL_RANGE_MPEG;
+        return 0;
+      })));
+
+  const CobaltExtensionDemuxerApi* api = GetFFmpegDemuxerApi();
+  MockDataSource data_source;
+  CobaltExtensionDemuxerDataSource c_data_source{
+      &MockBlockingRead, &MockSeekTo,  &MockGetPosition,
+      &MockGetSize,      kIsStreaming, &data_source};
+  std::vector<CobaltExtensionDemuxerAudioCodec> supported_audio_codecs;
+  std::vector<CobaltExtensionDemuxerVideoCodec> supported_video_codecs;
+
+  CobaltExtensionDemuxer* demuxer = api->CreateDemuxer(
+      &c_data_source, supported_audio_codecs.data(),
+      supported_audio_codecs.size(), supported_video_codecs.data(),
+      supported_video_codecs.size());
+
+  ASSERT_THAT(api, NotNull());
+  EXPECT_EQ(demuxer->Initialize(demuxer->user_data), kCobaltExtensionDemuxerOk);
+
+  CobaltExtensionDemuxerVideoDecoderConfig actual_video_config = {};
+  demuxer->GetVideoConfig(&actual_video_config, demuxer->user_data);
+
+  // These values are derived from those set via AVCodecParametersToContext.
+  EXPECT_EQ(actual_video_config.codec, kCobaltExtensionDemuxerCodecH264);
+  EXPECT_EQ(actual_video_config.profile,
+            kCobaltExtensionDemuxerH264ProfileBaseline);
+  EXPECT_EQ(actual_video_config.coded_width, 1920);
+  EXPECT_EQ(actual_video_config.coded_height, 1080);
+  EXPECT_EQ(actual_video_config.visible_rect_x, 0);
+  EXPECT_EQ(actual_video_config.visible_rect_y, 0);
+  EXPECT_EQ(actual_video_config.visible_rect_width, 1920);
+  EXPECT_EQ(actual_video_config.visible_rect_height, 1080);
+  EXPECT_EQ(actual_video_config.natural_width, 1920);
+  EXPECT_EQ(actual_video_config.natural_height, 1080);
+  EXPECT_THAT(actual_video_config.extra_data, IsNull());
+  EXPECT_EQ(actual_video_config.extra_data_size, 0);
+
+  api->DestroyDemuxer(demuxer);
+}
+
+}  // namespace
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/ffmpeg/ffmpeg_dispatch.h b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
index 4d70b66..1b46ff6 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dispatch.h
+++ b/starboard/shared/ffmpeg/ffmpeg_dispatch.h
@@ -23,8 +23,13 @@
 
 struct AVCodec;
 struct AVCodecContext;
+struct AVCodecParameters;
 struct AVDictionary;
+struct AVDictionaryEntry;
+struct AVFormatContext;
 struct AVFrame;
+struct AVInputFormat;
+struct AVIOContext;
 struct AVPacket;
 
 namespace starboard {
@@ -99,12 +104,46 @@
   AVFrame* (*avcodec_alloc_frame)(void);
   void (*avcodec_get_frame_defaults)(AVFrame* frame);
   void (*avcodec_align_dimensions2)(AVCodecContext* avctx,
-                                    int* width, int* height,
+                                    int* width,
+                                    int* height,
                                     int linesize_align[]);
 
   unsigned (*avformat_version)(void);
   void (*av_register_all)(void);
 
+  void (*av_free)(void* ptr);
+  AVPacket* (*av_packet_alloc)(void);
+  void (*av_packet_free)(AVPacket** pkt);
+  AVDictionaryEntry* (*av_dict_get)(const AVDictionary* m,
+                                    const char* key,
+                                    const AVDictionaryEntry* prev,
+                                    int flags);
+  // Note: |rnd| represents type enum AVRounding.
+  int64_t (*av_rescale_rnd)(int64_t a, int64_t b, int64_t c, int rnd);
+  int (*av_seek_frame)(AVFormatContext* s,
+                       int stream_index,
+                       int64_t timestamp,
+                       int flags);
+  int (*av_read_frame)(AVFormatContext* s, AVPacket* pkt);
+  void (*av_packet_unref)(AVPacket* pkt);
+  int (*avformat_open_input)(AVFormatContext** ps,
+                             const char* filename,
+                             AVInputFormat* fmt,
+                             AVDictionary** options);
+  void (*avformat_close_input)(AVFormatContext** s);
+  AVFormatContext* (*avformat_alloc_context)(void);
+  int (*avformat_find_stream_info)(AVFormatContext* ic, AVDictionary** options);
+  AVIOContext* (*avio_alloc_context)(
+      unsigned char* buffer,
+      int buffer_size,
+      int write_flag,
+      void* opaque,
+      int (*read_packet)(void* opaque, uint8_t* buf, int buf_size),
+      int (*write_packet)(void* opaque, uint8_t* buf, int buf_size),
+      int64_t (*seek)(void* opaque, int64_t offset, int whence));
+  int (*avcodec_parameters_to_context)(AVCodecContext* codec,
+                                       const AVCodecParameters* par);
+
   int specialization_version() const;
 
   // In Ffmpeg, the calls to avcodec_open2() and avcodec_close() are not
diff --git a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_demuxer_impl.cc
new file mode 100644
index 0000000..842fd52
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_demuxer_impl.cc
@@ -0,0 +1,51 @@
+// 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 <memory>
+
+#include "starboard/common/log.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h"
+#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+// static
+std::unique_ptr<FFmpegDemuxer> FFmpegDemuxer::Create(
+    CobaltExtensionDemuxerDataSource* data_source) {
+  FFMPEGDispatch* ffmpeg = FFMPEGDispatch::GetInstance();
+  if (!ffmpeg || !ffmpeg->is_valid()) {
+    return nullptr;
+  }
+  switch (ffmpeg->specialization_version()) {
+    case 540:
+      return FFmpegDemuxerImpl<540>::Create(data_source);
+    case 550:
+    case 560:
+      return FFmpegDemuxerImpl<560>::Create(data_source);
+    case 571:
+    case 581:
+      return FFmpegDemuxerImpl<581>::Create(data_source);
+    default:
+      SB_LOG(WARNING) << "Unsupported FFMPEG specialization "
+                      << ffmpeg->specialization_version();
+      return nullptr;
+  }
+}
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
index 4f8aec4..748d897 100644
--- a/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_dynamic_load_dispatch_impl.cc
@@ -261,6 +261,9 @@
   INITSYMBOL(avutil_, av_malloc);
   INITSYMBOL(avutil_, av_freep);
   INITSYMBOL(avutil_, av_frame_alloc);
+  INITSYMBOL(avutil_, av_free);
+  INITSYMBOL(avutil_, av_dict_get);
+  INITSYMBOL(avutil_, av_rescale_rnd);
 #if LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
   INITSYMBOL(avutil_, av_frame_free);
 #endif  // LIBAVUTIL_VERSION_INT >= LIBAVUTIL_VERSION_52_8
@@ -287,12 +290,23 @@
   INITSYMBOL(avcodec_, avcodec_alloc_frame);
   INITSYMBOL(avcodec_, avcodec_get_frame_defaults);
   INITSYMBOL(avcodec_, avcodec_align_dimensions2);
+  INITSYMBOL(avcodec_, av_packet_alloc);
+  INITSYMBOL(avcodec_, av_packet_free);
+  INITSYMBOL(avcodec_, av_packet_unref);
+  INITSYMBOL(avcodec_, avcodec_parameters_to_context);
 
   // Load symbols from the avformat shared library.
   INITSYMBOL(avformat_, avformat_version);
   SB_DCHECK(ffmpeg_->avformat_version);
   INITSYMBOL(avformat_, av_register_all);
   SB_DCHECK(ffmpeg_->av_register_all);
+  INITSYMBOL(avformat_, av_read_frame);
+  INITSYMBOL(avformat_, av_seek_frame);
+  INITSYMBOL(avformat_, avformat_open_input);
+  INITSYMBOL(avformat_, avformat_close_input);
+  INITSYMBOL(avformat_, avformat_alloc_context);
+  INITSYMBOL(avformat_, avformat_find_stream_info);
+  INITSYMBOL(avformat_, avio_alloc_context);
 
 #undef INITSYMBOL
 }
diff --git a/starboard/shared/ffmpeg/ffmpeg_linked_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_linked_demuxer_impl.cc
new file mode 100644
index 0000000..e898dbb
--- /dev/null
+++ b/starboard/shared/ffmpeg/ffmpeg_linked_demuxer_impl.cc
@@ -0,0 +1,38 @@
+// 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 <memory>
+
+#include "starboard/common/log.h"
+#include "starboard/shared/ffmpeg/ffmpeg_common.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
+#include "starboard/shared/ffmpeg/ffmpeg_demuxer_impl_interface.h"
+#include "starboard/shared/ffmpeg/ffmpeg_dispatch.h"
+
+namespace starboard {
+namespace shared {
+namespace ffmpeg {
+
+std::unique_ptr<FFmpegDemuxer> FFmpegDemuxer::Create(
+    CobaltExtensionDemuxerDataSource* data_source) {
+  FFMPEGDispatch* ffmpeg = FFMPEGDispatch::GetInstance();
+  SB_DCHECK(ffmpeg && ffmpeg->is_valid());
+  SB_DCHECK(FFMPEG == ffmpeg->specialization_version());
+
+  return FFmpegDemuxerImpl<FFMPEG>::Create(data_source);
+}
+
+}  // namespace ffmpeg
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/media/codec_util.cc b/starboard/shared/starboard/media/codec_util.cc
index cb331b4..fa1c5bf 100644
--- a/starboard/shared/starboard/media/codec_util.cc
+++ b/starboard/shared/starboard/media/codec_util.cc
@@ -92,6 +92,15 @@
   if (strcmp(codec, "vorbis") == 0) {
     return kSbMediaAudioCodecVorbis;
   }
+#if SB_API_VERSION >= 14
+  if (strcmp(codec, "mp3") == 0 || strcmp(codec, "mp4a.69") == 0 ||
+      strcmp(codec, "mp4a.6B") == 0) {
+    return kSbMediaAudioCodecMp3;
+  }
+  if (strcmp(codec, "flac") == 0) {
+    return kSbMediaAudioCodecFlac;
+  }
+#endif  // SB_API_VERSION >= 14
   return kSbMediaAudioCodecNone;
 }
 
diff --git a/starboard/shared/starboard/media/media_util.cc b/starboard/shared/starboard/media/media_util.cc
index 40fe9b1..a5a135c 100644
--- a/starboard/shared/starboard/media/media_util.cc
+++ b/starboard/shared/starboard/media/media_util.cc
@@ -89,6 +89,17 @@
     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();
diff --git a/starboard/shared/starboard/player/filter/testing/BUILD.gn b/starboard/shared/starboard/player/filter/testing/BUILD.gn
index 15ca8b9..a126ef0 100644
--- a/starboard/shared/starboard/player/filter/testing/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/testing/BUILD.gn
@@ -41,7 +41,7 @@
 
   deps = cobalt_platform_dependencies
 
-  content_deps =
+  data_deps =
       [ "//starboard/shared/starboard/player:player_download_test_data" ]
 }
 
diff --git a/starboard/shared/starboard/player/filter/tools/BUILD.gn b/starboard/shared/starboard/player/filter/tools/BUILD.gn
index 698d480..4e59d2f 100644
--- a/starboard/shared/starboard/player/filter/tools/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/tools/BUILD.gn
@@ -27,7 +27,7 @@
     "//starboard",
     "//starboard/shared/starboard/player:player_download_test_data",
   ]
-  content_deps = [
+  data_deps = [
     "//starboard/shared/starboard/player:player_download_test_data",
     "//third_party/icu:icudata",
   ]
diff --git a/starboard/shared/stub/system_get_connection_type.cc b/starboard/shared/stub/system_get_connection_type.cc
index 4f547eb..49de2c4 100644
--- a/starboard/shared/stub/system_get_connection_type.cc
+++ b/starboard/shared/stub/system_get_connection_type.cc
@@ -14,6 +14,10 @@
 
 #include "starboard/system.h"
 
+#if SB_API_VERSION < 14
+
 SbSystemConnectionType SbSystemGetConnectionType() {
   return kSbSystemConnectionTypeUnknown;
 }
+
+#endif  // SB_API_VERSION < 14
diff --git a/starboard/shared/win32/system_get_connection_type.cc b/starboard/shared/win32/system_get_connection_type.cc
deleted file mode 100644
index e474949..0000000
--- a/starboard/shared/win32/system_get_connection_type.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2017 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/system.h"
-
-#include <winsock2.h>
-
-#include <ifdef.h>
-#include <iphlpapi.h>
-
-#include "starboard/shared/win32/adapter_utils.h"
-
-namespace sbwin32 = ::starboard::shared::win32;
-
-namespace {
-// Return the connection type of the first "up" ethernet interface,
-// or unknown if none found.
-SbSystemConnectionType FindConnectionType(PIP_ADAPTER_ADDRESSES adapter) {
-  for (; adapter != nullptr; adapter = adapter->Next) {
-    if ((adapter->OperStatus != IfOperStatusUp) ||
-        !sbwin32::IsIfTypeEthernet(adapter->IfType)) {
-      continue;
-    }
-    // Some devices do not report IfType correctly.
-    // So, an extra attempt at determining if an interface is wireless is made.
-    if (adapter->IfType == IF_TYPE_IEEE80211 ||
-      (wcsstr(adapter->Description, L"WiFi") != nullptr)) {
-      return kSbSystemConnectionTypeWireless;
-    }
-    return kSbSystemConnectionTypeWired;
-  }
-  return kSbSystemConnectionTypeUnknown;
-}
-}  // namespace
-
-SbSystemConnectionType SbSystemGetConnectionType() {
-  std::unique_ptr<char[]> buffer;
-  if (!sbwin32::GetAdapters(kSbSocketAddressTypeIpv4, &buffer)) {
-      return kSbSystemConnectionTypeUnknown;
-  }
-  SbSystemConnectionType result = FindConnectionType(
-      reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.get()));
-  if (result != kSbSystemConnectionTypeUnknown) {
-    return result;
-  }
-  if (!sbwin32::GetAdapters(kSbSocketAddressTypeIpv6, &buffer)) {
-      return kSbSystemConnectionTypeUnknown;
-  }
-  return FindConnectionType(
-      reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.get()));
-}
diff --git a/starboard/shared/win32/system_get_property.cc b/starboard/shared/win32/system_get_property.cc
index f9f744b..718d65e 100644
--- a/starboard/shared/win32/system_get_property.cc
+++ b/starboard/shared/win32/system_get_property.cc
@@ -58,11 +58,11 @@
 
 #if SB_API_VERSION >= 14
     // Implementation provided for testing purposes only
-    case kSbSystemPropretyAdvertisingId:
+    case kSbSystemPropertyAdvertisingId:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           starboard::GetEnvironment("COBALT_ADVERTISING_ID").c_str());
-    case kSbSystemPropretyLimitAdTracking:
+    case kSbSystemPropertyLimitAdTracking:
       return CopyStringAndTestIfSuccess(
           out_value, value_length,
           starboard::GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str());
diff --git a/starboard/system.h b/starboard/system.h
index 6a97870..0263241 100644
--- a/starboard/system.h
+++ b/starboard/system.h
@@ -130,11 +130,11 @@
   // Advertising ID or IFA, typically a 128-bit UUID
   // Please see https://iabtechlab.com/OTT-IFA for details.
   // Corresponds to 'ifa' field. Note: `ifa_type` field is not provided.
-  kSbSystemPropretyAdvertisingId,
+  kSbSystemPropertyAdvertisingId,
 
   // Limit advertising tracking, treated as boolean. Set to nonzero to indicate
   // a true value. Corresponds to 'lmt' field.
-  kSbSystemPropretyLimitAdTracking,
+  kSbSystemPropertyLimitAdTracking,
 #endif
 } SbSystemPropertyId;
 
@@ -173,6 +173,7 @@
   kSbSystemDeviceTypeUnknown,
 } SbSystemDeviceType;
 
+#if SB_API_VERSION < 14
 // Enumeration of network connection types.
 typedef enum SbSystemConnectionType {
   // The system is on a wired connection.
@@ -184,6 +185,7 @@
   // The system connection type is unknown.
   kSbSystemConnectionTypeUnknown,
 } SbSystemConnectionType;
+#endif  // SB_API_VERSION < 14
 
 // Runtime capabilities are boolean properties of a platform that can't be
 // determined at compile-time. They may vary from device to device, but they
@@ -314,8 +316,10 @@
 // Returns the type of the device.
 SB_EXPORT SbSystemDeviceType SbSystemGetDeviceType();
 
+#if SB_API_VERSION < 14
 // Returns the device's current network connection type.
 SB_EXPORT SbSystemConnectionType SbSystemGetConnectionType();
+#endif  // SB_API_VERSION < 14
 
 #if SB_API_VERSION >= 13
 // Returns if the device is disconnected from network. "Disconnected" is chosen
diff --git a/starboard/tools/toolchain/__init__.py b/starboard/tools/toolchain/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/starboard/tools/toolchain/__init__.py
+++ /dev/null
diff --git a/starboard/tools/toolchain/abstract.py b/starboard/tools/toolchain/abstract.py
deleted file mode 100644
index 430b168..0000000
--- a/starboard/tools/toolchain/abstract.py
+++ /dev/null
@@ -1,547 +0,0 @@
-# Copyright 2017 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.
-"""Defines abstract build tools."""
-
-import abc
-
-
-class Tool(object):
-  """A base class for all build tools.
-
-  Build tools are used to generate Ninja files.
-  """
-
-  __metaclass__ = abc.ABCMeta
-
-  @abc.abstractmethod
-  def IsPlatformAgnostic(self):
-    """Whether an output of a tool depends on a platform.
-
-    Platform-agnostic tools are only processed once, even if they are provided
-    by both target and host toolchains.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetRuleName(self):
-    """Returns a name of a Ninja rule.
-
-    Used as a base name for several Ninja variables, such as path, flags, etc.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule.
-    """
-    pass
-
-  # TODO: Inline path in a command and get rid of this method.
-  @abc.abstractmethod
-  def GetPath(self):
-    """Returns a full path to a tool.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule.
-    """
-    pass
-
-  # TODO: Inline extra flags in a command and get rid of this method.
-  @abc.abstractmethod
-  def GetExtraFlags(self):
-    """Returns tool flags specific to a platform.
-
-    These flags are common for all tool invocations.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetMaxConcurrentProcesses(self):
-    """Restricts an amount of concurrently running tool instances.
-
-    See 'depth' at https://ninja-build.org/manual.html#ref_pool for details.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule or doesn't restrict an
-      amount of concurrently running instances.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetCommand(self, path, extra_flags, flags, shell):
-    """Returns a command that invokes a tool.
-
-    See 'command' at https://ninja-build.org/manual.html#ref_rule for details.
-
-    Args:
-      path: A variable that contains a path to a tool.
-      extra_flags: A variable that contains tool flags specific to a platform.
-      flags: A variable that contains tool flags specific to a target.
-      shell: The shell that will be used to interpret the command.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetDescription(self):
-    """Returns a human-readable description of an action performed by a tool.
-
-    See 'description' at https://ninja-build.org/manual.html#ref_rule for
-    details.
-
-    Returns:
-      None if a tool doesn't have a corresponding rule.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetHeaderDependenciesFilePath(self):
-    """Returns a path to a Makefile that contains implicit dependencies.
-
-    See 'depfile' at https://ninja-build.org/manual.html#ref_rule for details.
-
-    Returns:
-      None if a tool doesn't generate implicit dependencies.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetHeaderDependenciesFormat(self):
-    """Specifies special dependency processing by Ninja.
-
-    See 'deps' at https://ninja-build.org/manual.html#ref_rule for details.
-
-    Returns:
-      None if a tool doesn't generate implicit dependencies.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetRspFilePath(self):
-    """Returns a full path to a response file.
-
-    See 'rspfile' at https://ninja-build.org/manual.html#ref_rule for details.
-
-    Returns:
-      None if a response file is not used.
-    """
-    pass
-
-  @abc.abstractmethod
-  def GetRspFileContent(self):
-    """Returns a content of a response file.
-
-    See 'rspfile_content' at https://ninja-build.org/manual.html#ref_rule for
-    details.
-
-    Returns:
-      None if a response file is not used.
-    """
-    pass
-
-  def GetRestat(self):
-    """Returns a boolean controlling use of restat.
-
-    See 'restat' at https://ninja-build.org/manual.html#restat for
-    details.
-
-    Returns:
-      False if restat use is not specified.
-    """
-    return False
-
-
-class CCompiler(Tool):
-  """Compiles C sources."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'compile_c'
-
-  @abc.abstractmethod
-  def GetFlags(self, defines, include_dirs, cflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      defines: A list of preprocessor defines in "NAME=VALUE" format.
-      include_dirs: A list of header search directories.
-      cflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class CxxCompiler(Tool):
-  """Compiles C++ sources."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'compile_cxx'
-
-  @abc.abstractmethod
-  def GetFlags(self, defines, include_dirs, cflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      defines: A list of preprocessor defines in "NAME=VALUE" format.
-      include_dirs: A list of header search directories.
-      cflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class ObjectiveCxxCompiler(Tool):
-  """Compiles Objective-C++ sources."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'compile_objective_cxx'
-
-  @abc.abstractmethod
-  def GetFlags(self, defines, include_dirs, cflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      defines: A list of preprocessor defines in "NAME=VALUE" format.
-      include_dirs: A list of header search directories.
-      cflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class AssemblerWithCPreprocessor(Tool):
-  """Compiles assembler sources that contain C preprocessor directives."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'assemble'
-
-  @abc.abstractmethod
-  def GetFlags(self, defines, include_dirs, cflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      defines: A list of preprocessor defines in "NAME=VALUE" format.
-      include_dirs: A list of header search directories.
-      cflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class StaticLinker(Tool):
-  """Creates self-contained archives."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'link_static'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetFlags(self):
-    """Returns tool flags specific to a target.
-
-    Returns:
-      A list of flags.
-    """
-    return []
-
-
-class StaticThinLinker(Tool):
-  """Creates thin archives using GNU ar."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'link_static_thin'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetFlags(self):
-    """Returns tool flags specific to a target.
-
-    Returns:
-      A list of flags.
-    """
-    return []
-
-class ExecutableLinker(Tool):
-  """Links executables."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'link_executable'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-  @abc.abstractmethod
-  def GetFlags(self, ldflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      ldflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class SharedLibraryLinker(Tool):
-  """Links shared libraries."""
-
-  def IsPlatformAgnostic(self):
-    return False
-
-  def GetRuleName(self):
-    return 'link_shared'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-  @abc.abstractmethod
-  def GetFlags(self, ldflags):
-    """Returns tool flags specific to a target.
-
-    This method translates platform-agnostic concepts into a command line
-    arguments understood by a tool.
-
-    Args:
-      ldflags: A list of GCC-style command-line flags. See
-        https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options for
-        details.
-
-    Returns:
-      A list of unquoted strings, one for each flag. It is a responsibility of a
-      caller to quote flags that contain special characters (as determined by a
-      shell) before passing to a tool.
-    """
-    pass
-
-
-class Stamp(Tool):
-  """Updates the access and modification times of a file to the current time."""
-
-  def IsPlatformAgnostic(self):
-    return True
-
-  def GetRuleName(self):
-    return 'stamp'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-
-class Copy(Tool):
-  """Copies individual files."""
-
-  def IsPlatformAgnostic(self):
-    return True
-
-  def GetRuleName(self):
-    return 'copy'
-
-  def GetHeaderDependenciesFilePath(self):
-    # Only applicable to C family compilers.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Only applicable to C family compilers.
-    return None
-
-
-class Shell(Tool):
-  """Constructs command lines."""
-
-  def IsPlatformAgnostic(self):
-    return True
-
-  def GetRuleName(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetPath(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetExtraFlags(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetMaxConcurrentProcesses(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetDescription(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetHeaderDependenciesFilePath(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetHeaderDependenciesFormat(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetRspFilePath(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  def GetRspFileContent(self):
-    # Shell should not be used by Ninja, only by other tools.
-    return None
-
-  @abc.abstractmethod
-  def MaybeQuoteArgument(self, argument):
-    """Quotes a string so that shell could interpret it as a single argument.
-
-    Args:
-      argument: A string that can contain arbitrary characters.
-
-    Returns:
-      A quoted and appropriately escaped string. Returns the original string
-      if no processing is necessary.
-    """
-    pass
-
-  @abc.abstractmethod
-  def Join(self, command):
-    """Joins a sequence of unquoted command line terms into a string.
-
-    Args:
-      command: An unquoted, unescaped sequence of command line terms.
-
-    Returns:
-      A quoted and appropriately escaped string for the same command line.
-    """
-    pass
-
-  @abc.abstractmethod
-  def And(self, *commands):
-    """Joins a sequence of command lines with the AND shell operator.
-
-    Args:
-      *commands: A sequence of either quoted strings or unquoted sequences of
-                 command line terms that make up the command lines to be ANDed.
-
-    Returns:
-      A quoted and appropriately escaped string for the ANDed command line.
-    """
-    pass
-
-  @abc.abstractmethod
-  def Or(self, *commands):
-    """Joins a sequence of command lines with the OR shell operator.
-
-    Args:
-      *commands: A sequence of either quoted strings or unquoted sequences of
-                 command line terms that make up the command lines to be ORed.
-
-    Returns:
-      A quoted and appropriately escaped string for the ORed command line.
-    """
-    pass
diff --git a/starboard/tools/toolchain/ar.py b/starboard/tools/toolchain/ar.py
deleted file mode 100644
index 73b13e4..0000000
--- a/starboard/tools/toolchain/ar.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use GNU ar as a static linker."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class StaticLinkerBase(object):
-  """A base class for GNU ar-based static linkers."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('ar', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetDescription(self):
-    return 'AR $out'
-
-  def GetRspFilePath(self):
-    return '$out.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline'
-
-
-class StaticLinker(StaticLinkerBase, abstract.StaticLinker):
-  """Creates self-contained archives using GNU ar."""
-
-  def __init__(self, **kwargs):
-    super(StaticLinker, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags  # Not used.
-    return shell.And('rm -f $out',
-                     '{0} rcs {1} $out @$rspfile'.format(path, extra_flags))
-
-
-class StaticThinLinker(StaticLinkerBase, abstract.StaticThinLinker):
-  """Creates thin archives using GNU ar."""
-
-  def __init__(self, **kwargs):
-    super(StaticThinLinker, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags  # Not used.
-    return shell.And('rm -f $out',
-                     '{0} rcsT {1} $out @$rspfile'.format(path, extra_flags))
diff --git a/starboard/tools/toolchain/bash.py b/starboard/tools/toolchain/bash.py
deleted file mode 100644
index 8d583c1..0000000
--- a/starboard/tools/toolchain/bash.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use Bash as a shell."""
-
-import re
-
-from starboard.tools.toolchain import abstract
-
-
-def _MaybeJoin(shell, command):
-  if isinstance(command, basestring):
-    return command
-  return shell.Join(command)
-
-
-class Shell(abstract.Shell):
-  """Constructs command lines using Bash syntax."""
-
-  def __init__(self, quote=True):
-    # Toggle whether or not to quote command line arguments.
-    self.quote = quote
-
-  def MaybeQuoteArgument(self, argument):
-    # Rather than attempting to enumerate the bad shell characters, just
-    # whitelist common OK ones and quote anything else.
-    if re.match(r'^[a-zA-Z0-9_=.,\\/+-]+$', argument):
-      return argument  # No quoting necessary.
-    if re.match(r'^--gcc-toolchain', argument):
-      return argument.replace("'", "'\"'\"'")  # Do not surround with quotes
-    return "'" + argument.replace("'", "'\"'\"'") + "'"
-
-  def Join(self, command):
-    assert not isinstance(command, basestring)
-    if self.quote:
-      return ' '.join(self.MaybeQuoteArgument(argument) for argument in command)
-    else:
-      return ' '.join(command)
-
-  def And(self, *commands):
-    return ' && '.join(_MaybeJoin(self, command) for command in commands)
-
-  def Or(self, *commands):
-    return ' || '.join(_MaybeJoin(self, command) for command in commands)
diff --git a/starboard/tools/toolchain/clang.py b/starboard/tools/toolchain/clang.py
deleted file mode 100644
index e24c7ff..0000000
--- a/starboard/tools/toolchain/clang.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use Clang as a compiler and assembler."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class CompilerBase(object):
-  """A base class for Clang-based compilers and assemblers."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('clang', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-    self._defines = kwargs.get('defines', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetHeaderDependenciesFilePath(self):
-    return '$out.d'
-
-  def GetHeaderDependenciesFormat(self):
-    return 'gcc'
-
-  def GetRspFilePath(self):
-    # A command line only contains one input and one output file.
-    pass
-
-  def GetRspFileContent(self):
-    # A command line only contains one input and one output file.
-    pass
-
-
-def _GetClangCommand(path, language, extra_flags, flags, shell):
-  del shell  # Not used.
-  return ('{path} -x {language} -MMD -MF $out.d {extra_flags} {flags} -c $in '
-          '-o $out'.format(path=path, language=language,
-                           extra_flags=extra_flags, flags=flags))
-
-
-class CCompiler(CompilerBase, abstract.CCompiler):
-  """Compiles C sources using Clang."""
-
-  def __init__(self, **kwargs):
-    # pylint:disable=useless-super-delegation
-    super(CCompiler, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    return _GetClangCommand(path, 'c', extra_flags, flags, shell)
-
-  def GetDescription(self):
-    return 'CC $out'
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    define_flags = ['-D{0}'.format(define) for define in defines]
-    include_dir_flags = [
-        '-I{0}'.format(include_dir) for include_dir in include_dirs
-    ]
-    return define_flags + include_dir_flags + cflags
-
-
-class CxxCompiler(CompilerBase, abstract.CxxCompiler):
-  """Compiles C++ sources using Clang."""
-
-  def __init__(self, **kwargs):
-    # pylint:disable=useless-super-delegation
-    super(CxxCompiler, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    return _GetClangCommand(path, 'c++', extra_flags, flags, shell)
-
-  def GetDescription(self):
-    return 'CXX $out'
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    define_flags = [
-        '-D{0}'.format(define) for define in self._defines + defines
-    ]
-    include_dir_flags = [
-        '-I{0}'.format(include_dir) for include_dir in include_dirs
-    ]
-    return define_flags + include_dir_flags + cflags
-
-
-class ObjectiveCxxCompiler(CompilerBase, abstract.ObjectiveCxxCompiler):
-  """Compiles Objective-C++ sources using Clang."""
-
-  def __init__(self, **kwargs):
-    # pylint:disable=useless-super-delegation
-    super(ObjectiveCxxCompiler, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    return _GetClangCommand(path, 'objective-c++', extra_flags, flags, shell)
-
-  def GetDescription(self):
-    return 'OBJCXX $out'
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    define_flags = [
-        '-D{0}'.format(define) for define in self._defines + defines
-    ]
-    include_dir_flags = [
-        '-I{0}'.format(include_dir) for include_dir in include_dirs
-    ]
-    return define_flags + include_dir_flags + cflags
-
-
-class AssemblerWithCPreprocessor(CompilerBase,
-                                 abstract.AssemblerWithCPreprocessor):
-  """Compiles assembler sources that contain C preprocessor directives."""
-
-  def __init__(self, **kwargs):
-    # pylint:disable=useless-super-delegation
-    super(AssemblerWithCPreprocessor, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    return _GetClangCommand(path, 'assembler-with-cpp', extra_flags, flags,
-                            shell)
-
-  def GetDescription(self):
-    return 'ASM $out'
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    define_flags = [
-        '-D{0}'.format(define) for define in self._defines + defines
-    ]
-    include_dir_flags = [
-        '-I{0}'.format(include_dir) for include_dir in include_dirs
-    ]
-    return define_flags + include_dir_flags + cflags
diff --git a/starboard/tools/toolchain/clangxx.py b/starboard/tools/toolchain/clangxx.py
deleted file mode 100644
index 581d38e..0000000
--- a/starboard/tools/toolchain/clangxx.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use Clang (invoked as clang++) as a dynamic linker."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class DynamicLinkerBase(object):
-  """A base class for Clang based linking of executables and shared libraries.
-
-  Invoked as clang++.
-  """
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('clang++', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-    self._max_concurrent_processes = kwargs.get(
-        'max_concurrent_processes', common.EstimateMaxConcurrentLinkers())
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    return self._max_concurrent_processes
-
-  def GetDescription(self):
-    return 'LINK $out'
-
-  def GetRspFilePath(self):
-    return '$out.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline'
-
-  def GetFlags(self, ldflags):
-    return ldflags
-
-
-class ExecutableLinker(DynamicLinkerBase, abstract.ExecutableLinker):
-  """Links executables using Clang (invoked as clang++)."""
-
-  def __init__(self, **kwargs):
-    super(ExecutableLinker, self).__init__(**kwargs)
-    # Groups archives to be searched until all references are resolved.
-    self._write_group = kwargs.get('write_group', False)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del shell  # Not used.
-    if self._write_group:
-      return ('{0} {1} -Wl,--start-group @$rspfile -Wl,--end-group -o $out {2}'
-              .format(path, extra_flags, flags))
-    else:
-      return '{0} {1} @$rspfile {2} -o $out'.format(path, extra_flags, flags)
-
-
-class SharedLibraryLinker(DynamicLinkerBase, abstract.SharedLibraryLinker):
-  """Links shared libraries using Clang (invoked as clang++)."""
-
-  def __init__(self, **kwargs):
-    super(SharedLibraryLinker, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del shell  # Not used.
-    return ('{0} -shared {1} -o $out {2} -Wl,-soname=$soname '
-            '-Wl,--whole-archive @$rspfile -Wl,--no-whole-archive').format(
-                path, extra_flags, flags)
diff --git a/starboard/tools/toolchain/cmd.py b/starboard/tools/toolchain/cmd.py
deleted file mode 100644
index cb30844..0000000
--- a/starboard/tools/toolchain/cmd.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright 2018 Google Inc. 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.
-"""Allows to use cmd as a shell."""
-
-import re
-
-from starboard.tools.toolchain import abstract
-
-
-def _MaybeJoin(shell, command):
-  if isinstance(command, basestring):
-    return command
-  return shell.Join(command)
-
-
-class Shell(abstract.Shell):
-  """Constructs command lines using Cmd syntax."""
-
-  def __init__(self, quote=True):
-    # Toggle whether or not to quote command line arguments.
-    self.quote = quote
-
-  def MaybeQuoteArgument(self, arg):
-    # Rather than attempting to enumerate the bad shell characters, just
-    # whitelist common OK ones and quote anything else.
-    if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
-      return arg  # No quoting necessary.
-    return self.QuoteForRspFile(arg)
-
-  def QuoteForRspFile(self, arg):
-    """Quote a command line argument.
-
-    Quote the argument so that it appears as one argument when
-    processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
-    Windows programs).
-
-    Args:
-      arg: The argument to quote.
-
-    Returns:
-      The quoted argument.
-    """
-
-    # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment
-    # threads. This is actually the quoting rules for CommandLineToArgvW, not
-    # for the shell, because the shell doesn't do anything in Windows. This
-    # works more or less because most programs (including the compiler, etc.)
-    # use that function to handle command line arguments.
-
-    # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
-    # preceding it, and results in n backslashes + the quote. So we substitute
-    # in 2* what we match, +1 more, plus the quote.
-    windows_quoter_regex = re.compile(r'(\\*)"')
-    arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg)
-
-    # %'s also need to be doubled otherwise they're interpreted as batch
-    # positional arguments. Also make sure to escape the % so that they're
-    # passed literally through escaping so they can be singled to just the
-    # original %. Otherwise, trying to pass the literal representation that
-    # looks like an environment variable to the shell (e.g. %PATH%) would fail.
-    arg = arg.replace('%', '%%')
-
-    # These commands are used in rsp files, so no escaping for the shell (via ^)
-    # is necessary.
-
-    # Finally, wrap the whole thing in quotes so that the above quote rule
-    # applies and whitespace isn't a word break.
-    return '"' + arg + '"'
-
-  def Join(self, command):
-    assert not isinstance(command, basestring)
-    if self.quote:
-      return ' '.join(self.MaybeQuoteArgument(argument) for argument in command)
-    else:
-      return ' '.join(command)
-
-  def And(self, *commands):
-    return ' && '.join(_MaybeJoin(self, command) for command in commands)
-
-  def Or(self, *commands):
-    return ' || '.join(_MaybeJoin(self, command) for command in commands)
diff --git a/starboard/tools/toolchain/common.py b/starboard/tools/toolchain/common.py
deleted file mode 100644
index fcbe86a..0000000
--- a/starboard/tools/toolchain/common.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2017 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.
-"""Provides functionality common for all tools."""
-
-import ctypes
-import os
-import re
-import subprocess
-import sys
-
-
-def GetPath(name, **kwargs):
-  """Computes a path to a tool.
-
-  All tools understand the same 4 path-related arguments: 'path', 'dir',
-  'prefix', and 'name'. When the 'path' argument is provided, it overrides the
-  path to a tool. Otherwise, 'dir', 'prefix', and 'name' arguments, all of which
-  are optional, are joined as {dir}/{prefix}{name} to compute the path.
-
-  Args:
-    name: A default name of a tool.
-    **kwargs: A dictionary that optionally contains 'path', 'dir', 'prefix', and
-        'name' arguments.
-
-  Returns:
-    The computed path.
-  """
-  if 'path' in kwargs:
-    return kwargs['path']
-
-  path = kwargs.get('prefix', '') + kwargs.get('name', name)
-  if 'dir' in kwargs:
-    path = os.path.join(kwargs['dir'], path)
-  return path
-
-
-def GetRuleName(rule_name_base, toolset):
-  """Computes a Ninja name for target and host rules."""
-  suffix = '' if toolset == 'target' else '_{0}'.format(toolset)
-  return rule_name_base + suffix
-
-
-def EstimateMaxConcurrentLinkers():
-  """Estimates a number of dynamic linkers to run concurrently.
-
-  The estimate takes into account available RAM and conservatively assumes that
-  Chromium is being built.
-
-  Returns:
-    An estimated number of processes.
-  """
-  # TODO: Introduce _TryGetPhysicalMemoryInBytes().
-  if sys.platform in ('win32', 'cygwin'):
-
-    class MEMORYSTATUSEX(ctypes.Structure):
-      _fields_ = [
-          ('dwLength', ctypes.c_ulong),
-          ('dwMemoryLoad', ctypes.c_ulong),
-          ('ullTotalPhys', ctypes.c_ulonglong),
-          ('ullAvailPhys', ctypes.c_ulonglong),
-          ('ullTotalPageFile', ctypes.c_ulonglong),
-          ('ullAvailPageFile', ctypes.c_ulonglong),
-          ('ullTotalVirtual', ctypes.c_ulonglong),
-          ('ullAvailVirtual', ctypes.c_ulonglong),
-          ('sullAvailExtendedVirtual', ctypes.c_ulonglong),
-      ]  # pylint: disable=invalid-name
-
-    stat = MEMORYSTATUSEX()
-    stat.dwLength = ctypes.sizeof(stat)
-    ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
-
-    # VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
-    # on a 64 GB machine.
-    return max(1, stat.ullTotalPhys / (5 * (2**30)))  # total / 5GB
-  elif sys.platform.startswith('linux'):
-    if os.path.exists('/proc/meminfo'):
-      with open('/proc/meminfo') as meminfo:
-        memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
-        for line in meminfo:
-          match = memtotal_re.match(line)
-          if not match:
-            continue
-          # Allow 6Gb per link on Linux because Gold is quite memory hungry
-          return max(1, int(match.group(1)) / (6 * (2**20)))
-    return 1
-  elif sys.platform == 'darwin':
-    try:
-      avail_bytes = int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
-      # A static library debug build of Chromium's unit_tests takes ~2.7GB, so
-      # 4GB per ld process allows for some more bloat.
-      return max(1, avail_bytes / (4 * (2**30)))  # total / 4GB
-    except subprocess.CalledProcessError:
-      return 1
-  else:
-    return 1
diff --git a/starboard/tools/toolchain/cp.py b/starboard/tools/toolchain/cp.py
deleted file mode 100644
index 69d3be0..0000000
--- a/starboard/tools/toolchain/cp.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use GNU cp as a copy tool."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class Copy(abstract.Copy):
-  """Copies individual files using GNU cp."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('cp', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags, shell  # Not used.
-    return '{0} -f -p {1} $in $out'.format(path, extra_flags)
-
-  def GetDescription(self):
-    return 'COPY $in $out'
-
-  def GetRspFilePath(self):
-    # A command line only contains one input and one output file.
-    pass
-
-  def GetRspFileContent(self):
-    # A command line only contains one input and one output file.
-    pass
diff --git a/starboard/tools/toolchain/evergreen_linker.py b/starboard/tools/toolchain/evergreen_linker.py
deleted file mode 100644
index fe9bba9..0000000
--- a/starboard/tools/toolchain/evergreen_linker.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2018 Google Inc. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import clangxx
-
-
-class SharedLibraryLinker(clangxx.DynamicLinkerBase,
-                          abstract.SharedLibraryLinker):
-  """Links shared libraries using the LLVM Project's lld."""
-
-  def __init__(self, **kwargs):  # pylint: disable=useless-super-delegation
-    super(SharedLibraryLinker, self).__init__(**kwargs)
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    lld_path = '{0}/bin/ld.lld'.format(self.GetPath())
-
-    return shell.And('{0} '
-                     '--build-id '
-                     '-gc-sections '
-                     '-X '
-                     '-v '
-                     '--eh-frame-hdr '
-                     '--fini=__cxa_finalize '
-                     '{2} '
-                     '-shared '
-                     '-o $out '
-                     '-L{1} '
-                     '-L/usr/lib '
-                     '-L/lib '
-                     '-soname=$soname '
-                     '-nostdlib '
-                     '--whole-archive '
-                     '--no-whole-archive '
-                     '-u GetEvergreenSabiString '
-                     '@$rspfile'.format(lld_path, self.GetPath(), *extra_flags))
diff --git a/starboard/tools/toolchain/libtool_darwin.py b/starboard/tools/toolchain/libtool_darwin.py
deleted file mode 100644
index a953d14..0000000
--- a/starboard/tools/toolchain/libtool_darwin.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use Darwin (not GNU!) libtool as a static linker."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class StaticLinker(abstract.StaticLinker):
-  """Creates self-contained archives using Darwin (not GNU!) libtool."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('libtool', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags, shell  # Not used.
-    return '{0} -static {1} -filelist $rspfile -o $out'.format(
-        path, extra_flags)
-
-  def GetDescription(self):
-    return 'LIBTOOL $out'
-
-  def GetRspFilePath(self):
-    return '$out.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline'
diff --git a/starboard/tools/toolchain/msvc.py b/starboard/tools/toolchain/msvc.py
deleted file mode 100644
index c304a55..0000000
--- a/starboard/tools/toolchain/msvc.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# Copyright 2020 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.
-"""Allows use of MSVC build tools."""
-
-import sys
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-def QuoteArguments(args):
-  return ['\"{0}\"'.format(arg.replace('"', '\\"')) for arg in args]
-
-
-class CompilerBase(object):
-  """A base class for MSVC-based compilers."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('msvc', **kwargs)
-    self._gyp_defines = kwargs.get('gyp_defines', [])
-    self._gyp_include_dirs = kwargs.get('gyp_include_dirs', [])
-    self._gyp_cflags = kwargs.get('gyp_cflags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    # Not used.
-    return []
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as many concurrent processes as possible.
-    return None
-
-  def GetHeaderDependenciesFilePath(self):
-    pass
-
-  def GetHeaderDependenciesFormat(self):
-    return 'msvc'
-
-  def GetRspFilePath(self):
-    return None
-
-  def GetRspFileContent(self):
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del extra_flags  # Not used.
-    del shell  # Not used.
-    return ('{path} /nologo /showIncludes {flags} /c $in /Fo$out /Fd$out.pdb'
-            .format(path=path, flags=flags))
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    defines = defines + self._gyp_defines
-    quoted_defines = QuoteArguments(defines)
-    define_flags = ['/D{0}'.format(define) for define in quoted_defines]
-    include_dirs = include_dirs + self._gyp_include_dirs
-    quoted_include_dirs = QuoteArguments(include_dirs)
-    include_dir_flags = [
-        '/I{0}'.format(include_dir) for include_dir in quoted_include_dirs
-    ]
-    if self._gyp_cflags:
-      cflags = self._gyp_cflags
-    return define_flags + include_dir_flags + cflags
-
-
-class CCompiler(CompilerBase, abstract.CCompiler):
-  """Compiles C sources using MSVC."""
-
-  def GetDescription(self):
-    return 'CC $out'
-
-
-class CxxCompiler(CompilerBase, abstract.CxxCompiler):
-  """Compiles C++ sources using MSVC."""
-
-  def GetDescription(self):
-    return 'CXX $out'
-
-
-class AssemblerWithCPreprocessor(abstract.AssemblerWithCPreprocessor):
-  """Compiles assembler sources that contain C preprocessor directives."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('masm', **kwargs)
-    self._arch = kwargs.get('arch', 'environment.x64')
-    self._gyp_defines = kwargs.get('gyp_defines', [])
-    self._gyp_include_dirs = kwargs.get('gyp_include_dirs', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    # Not used.
-    return []
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetHeaderDependenciesFilePath(self):
-    pass
-
-  def GetHeaderDependenciesFormat(self):
-    pass
-
-  def GetRspFilePath(self):
-    pass
-
-  def GetRspFileContent(self):
-    pass
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del extra_flags  # Not used.
-    del shell  # Not used.
-    return ('{python} gyp-win-tool asm-wrapper {arch} {path} {flags} /c /Fo '
-            '$out $in'.format(
-                python=sys.executable, arch=self._arch, path=path, flags=flags))
-
-  def GetDescription(self):
-    return 'ASM $in'
-
-  def GetFlags(self, defines, include_dirs, cflags):
-    return []
-
-
-class StaticLinkerBase(object):
-  """A base class for MSVC-based static linkers."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('ar', **kwargs)
-    self._arch = kwargs.get('arch', 'environment.x64')
-    self._gyp_libflags = kwargs.get('gyp_libflags', [])
-    self._max_concurrent_processes = kwargs.get('max_concurrent_processes',
-                                                None)
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    # Not used.
-    return []
-
-  def GetMaxConcurrentProcesses(self):
-    return self._max_concurrent_processes
-
-  def GetDescription(self):
-    return 'LIB $out'
-
-  def GetRspFilePath(self):
-    return '$out.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline ' + self._command_flags
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del extra_flags  # Not used.
-    del shell  # Not used.
-    self._command_flags = flags
-    return ('{python} gyp-win-tool link-wrapper {arch} {path} /nologo '
-            '/ignore:4221 /OUT:$out @$out.rsp'.format(
-                python=sys.executable, arch=self._arch, path=path))
-
-  def GetFlags(self):
-    return self._gyp_libflags
-
-
-class StaticLinker(StaticLinkerBase, abstract.StaticLinker):
-  """Creates self-contained archives using LIB.exe."""
-
-  def __init__(self, **kwargs):
-    super(StaticLinker, self).__init__(**kwargs)
-
-
-class StaticThinLinker(StaticLinkerBase, abstract.StaticThinLinker):
-  """Creates thin archives using LIB.exe."""
-
-  def __init__(self, **kwargs):
-    super(StaticThinLinker, self).__init__(**kwargs)
-
-
-class DynamicLinkerBase(object):
-  """A base class for MSVC-based executable and shared library linkers."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('ld', **kwargs)
-    self._max_concurrent_processes = kwargs.get(
-        'max_concurrent_processes', common.EstimateMaxConcurrentLinkers())
-    self._arch = kwargs.get('arch', 'environment.x64')
-    self._gyp_ldflags = kwargs.get('gyp_ldflags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    # Not used.
-    return []
-
-  def GetMaxConcurrentProcesses(self):
-    return self._max_concurrent_processes
-
-  def GetFlags(self, ldflags):
-    del ldflags  # Not used.
-    return self._gyp_ldflags
-
-
-class ExecutableLinker(DynamicLinkerBase, abstract.ExecutableLinker):
-  """Links executables using LINK.exe."""
-
-  def __init__(self, **kwargs):
-    super(ExecutableLinker, self).__init__(**kwargs)
-
-  def GetDescription(self):
-    return 'LINK $out'
-
-  def GetRspFilePath(self):
-    return '$out.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline ' + self._command_flags
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del extra_flags  # Not used.
-    del shell  # Not used.
-    self._command_flags = flags
-    return ('{python} gyp-win-tool link-wrapper {arch} {path} /nologo '
-            '/OUT:$out /PDB:$out.pdb @$out.rsp'.format(
-                python=sys.executable, arch=self._arch, path=path))
-
-
-class SharedLibraryLinker(DynamicLinkerBase, abstract.SharedLibraryLinker):
-  """Links shared libraries using LINK.exe."""
-
-  def __init__(self, **kwargs):
-    super(SharedLibraryLinker, self).__init__(**kwargs)
-
-  def GetDescription(self):
-    return 'LINK(DLL) $dll'
-
-  def GetRspFilePath(self):
-    return '$dll.rsp'
-
-  def GetRspFileContent(self):
-    return '$in_newline ' + self._command_flags
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del extra_flags  # Not used.
-    del shell  # Not used.
-    self._command_flags = flags
-    return ('{python} gyp-win-tool link-wrapper {arch} {path} /nologo '
-            '$implibflag /DLL /OUT:$dll /PDB:$dll.pdb @$dll.rsp'.format(
-                python=sys.executable, arch=self._arch, path=path))
-
-  def GetRestat(self):
-    return True
diff --git a/starboard/tools/toolchain/python.py b/starboard/tools/toolchain/python.py
deleted file mode 100644
index 0f32d88..0000000
--- a/starboard/tools/toolchain/python.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2018 Google Inc. 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.
-"""Allow use of gyp-win-tool via python as copy and stamp tools."""
-
-import sys
-from starboard.tools.toolchain import abstract
-
-
-class Copy(abstract.Copy):
-  """Copies individual files."""
-
-  def __init__(self, **kwargs):
-    self._path = kwargs.get('path', sys.executable)
-    self._extra_flags = kwargs.get('extra_flags',
-                                   ['gyp-win-tool', 'recursive-mirror'])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags, shell  # Not used.
-    return '{0} {1} $in $out'.format(path, extra_flags)
-
-  def GetDescription(self):
-    return 'COPY $in $out'
-
-  def GetRspFilePath(self):
-    # A command line only contains one input and one output file.
-    pass
-
-  def GetRspFileContent(self):
-    # A command line only contains one input and one output file.
-    pass
-
-
-class Stamp(abstract.Stamp):
-  """Updates the access and modification times of a file to the current time."""
-
-  def __init__(self, **kwargs):
-    self._path = kwargs.get('path', sys.executable)
-    self._extra_flags = kwargs.get('extra_flags', ['gyp-win-tool', 'stamp'])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags, shell  # Not used.
-    return '{0} {1} $out'.format(path, extra_flags)
-
-  def GetDescription(self):
-    return 'STAMP $out'
-
-  def GetRspFilePath(self):
-    # A command line only contains one input file.
-    pass
-
-  def GetRspFileContent(self):
-    # A command line only contains one input file.
-    pass
diff --git a/starboard/tools/toolchain/touch.py b/starboard/tools/toolchain/touch.py
deleted file mode 100644
index a166140..0000000
--- a/starboard/tools/toolchain/touch.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2017 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.
-"""Allows to use POSIX touch as a stamp tool."""
-
-from starboard.tools.toolchain import abstract
-from starboard.tools.toolchain import common
-
-
-class Stamp(abstract.Stamp):
-  """Updates the access and modification times of a file to the current time."""
-
-  def __init__(self, **kwargs):
-    self._path = common.GetPath('touch', **kwargs)
-    self._extra_flags = kwargs.get('extra_flags', [])
-
-  def GetPath(self):
-    return self._path
-
-  def GetExtraFlags(self):
-    return self._extra_flags
-
-  def GetMaxConcurrentProcesses(self):
-    # Run as much concurrent processes as possible.
-    return None
-
-  def GetCommand(self, path, extra_flags, flags, shell):
-    del flags, shell  # Not used.
-    return '{0} {1} $out'.format(path, extra_flags)
-
-  def GetDescription(self):
-    return 'STAMP $out'
-
-  def GetRspFilePath(self):
-    # A command line only contains one input file.
-    pass
-
-  def GetRspFileContent(self):
-    # A command line only contains one input file.
-    pass
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn
index 0a7e0f7..5f20368 100644
--- a/starboard/win/shared/BUILD.gn
+++ b/starboard/win/shared/BUILD.gn
@@ -312,7 +312,6 @@
     "//starboard/shared/win32/string_format_wide.cc",
     "//starboard/shared/win32/system_break_into_debugger.cc",
     "//starboard/shared/win32/system_clear_last_error.cc",
-    "//starboard/shared/win32/system_get_connection_type.cc",
     "//starboard/shared/win32/system_get_error_string.cc",
     "//starboard/shared/win32/system_get_last_error.cc",
     "//starboard/shared/win32/system_get_locale_id.cc",
diff --git a/third_party/chromium/media/base/starboard_utils.cc b/third_party/chromium/media/base/starboard_utils.cc
index 658bb2c..687e5af 100644
--- a/third_party/chromium/media/base/starboard_utils.cc
+++ b/third_party/chromium/media/base/starboard_utils.cc
@@ -19,10 +19,10 @@
 #include "base/logging.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "media/base/decrypt_config.h"
 #include "starboard/common/media.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
-#include "media/base/decrypt_config.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -40,13 +40,8 @@
   SbMediaTransferId transfer_id;
   SbMediaMatrixId matrix_id;
 
-  if (starboard::ParseVideoCodec(codecs.c_str(),
-                                 &codec,
-                                 &profile,
-                                 &level,
-                                 &bit_depth,
-                                 &primary_id,
-                                 &transfer_id,
+  if (starboard::ParseVideoCodec(codecs.c_str(), &codec, &profile, &level,
+                                 &bit_depth, &primary_id, &transfer_id,
                                  &matrix_id)) {
     return bit_depth;
   }
@@ -79,6 +74,14 @@
       return kSbMediaAudioCodecVorbis;
     case AudioCodec::kOpus:
       return kSbMediaAudioCodecOpus;
+#if SB_API_VERSION >= 14
+    case AudioCodec::kMP3:
+      return kSbMediaAudioCodecMp3;
+    case AudioCodec::kFLAC:
+      return kSbMediaAudioCodecFlac;
+    case AudioCodec::kPCM:
+      return kSbMediaAudioCodecPcm;
+#endif  // SB_API_VERSION >= 14
     default:
       // Cobalt only supports a subset of audio codecs defined by Chromium.
       DLOG(ERROR) << "Unsupported audio codec " << GetCodecName(codec);
@@ -208,9 +211,8 @@
   drm_info->subsample_count = config->subsamples().size();
 
   if (drm_info->subsample_count > 0) {
-    COMPILE_ASSERT(
-        sizeof(SbDrmSubSampleMapping) == sizeof(SubsampleEntry),
-        SubSampleEntrySizesMatch);
+    COMPILE_ASSERT(sizeof(SbDrmSubSampleMapping) == sizeof(SubsampleEntry),
+                   SubSampleEntrySizesMatch);
     drm_info->subsample_mapping =
         reinterpret_cast<const SbDrmSubSampleMapping*>(
             &config->subsamples()[0]);
diff --git a/third_party/crashpad/handler/BUILD.gn b/third_party/crashpad/handler/BUILD.gn
index af93709..9442c21 100644
--- a/third_party/crashpad/handler/BUILD.gn
+++ b/third_party/crashpad/handler/BUILD.gn
@@ -171,7 +171,7 @@
   crashpad_executable("crashpad_handler") {
     if (crashpad_is_in_starboard) {
       check_includes = false
-      content_deps = [ "//third_party/icu:icudata" ]
+      data_deps = [ "//third_party/icu:icudata" ]
     }
 
     sources = [ "main.cc" ]
diff --git a/third_party/devtools/scripts/build/modular_build.py b/third_party/devtools/scripts/build/modular_build.py
index 9671fa2..249fc75 100755
--- a/third_party/devtools/scripts/build/modular_build.py
+++ b/third_party/devtools/scripts/build/modular_build.py
@@ -114,7 +114,7 @@
             return self._cached_sorted_modules
 
         result = []
-        unvisited_modules = set(self.modules)
+        unvisited_modules = sorted(set(self.modules))
         temp_modules = set()
 
         def visit(parent, name):
@@ -127,7 +127,7 @@
             temp_modules.add(name)
             deps = self.modules[name].get('dependencies')
             if deps:
-                for dep_name in deps:
+                for dep_name in sorted(deps):
                     bad_dep = visit(name, dep_name)
                     if bad_dep:
                         return bad_dep
diff --git a/third_party/libvpx/platforms/linux-x64/libvpx.a b/third_party/libvpx/platforms/linux-x64/libvpx.a
new file mode 100644
index 0000000..c8f685f
--- /dev/null
+++ b/third_party/libvpx/platforms/linux-x64/libvpx.a
Binary files differ
diff --git a/third_party/opus/starboard/config.h b/third_party/opus/starboard/config.h
new file mode 100644
index 0000000..7c67f29
--- /dev/null
+++ b/third_party/opus/starboard/config.h
@@ -0,0 +1,64 @@
+/***********************************************************************
+Copyright (c) 2011, Skype Limited. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+- Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+- Neither the name of Internet Society, IETF or IETF Trust, nor the
+names of specific contributors, may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************/
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#define USE_ALLOCA            0
+
+/* Comment out the next line for floating-point code */
+/*#define FIXED_POINT           1 */
+
+#define OPUS_BUILD            1
+
+#if defined(_M_IX86) || defined(_M_X64)
+/* Can always compile SSE intrinsics (no special compiler flags necessary) */
+#define OPUS_X86_MAY_HAVE_SSE
+#define OPUS_X86_MAY_HAVE_SSE2
+#define OPUS_X86_MAY_HAVE_SSE4_1
+
+/* Presume SSE functions, if compiled to use SSE/SSE2/AVX (note that AMD64 implies SSE2, and AVX
+   implies SSE4.1) */
+#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) || defined(__AVX__)
+#define OPUS_X86_PRESUME_SSE 1
+#endif
+#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 2)) || defined(__AVX__)
+#define OPUS_X86_PRESUME_SSE2 1
+#endif
+#if defined(__AVX__)
+#define OPUS_X86_PRESUME_SSE4_1 1
+#endif
+
+#if !defined(OPUS_X86_PRESUME_SSE4_1) || !defined(OPUS_X86_PRESUME_SSE2) || !defined(OPUS_X86_PRESUME_SSE)
+#define OPUS_HAVE_RTCD 1
+#endif
+
+#endif
+
+#include "version.h"
+
+#endif /* CONFIG_H */
diff --git a/third_party/opus/starboard/version.h b/third_party/opus/starboard/version.h
new file mode 100644
index 0000000..d95510d
--- /dev/null
+++ b/third_party/opus/starboard/version.h
@@ -0,0 +1 @@
+#define OPUS_VERSION "1.3-rc-2-gc1c247d-dirty"

diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn
index 67b9d7b..132c555 100644
--- a/third_party/zlib/BUILD.gn
+++ b/third_party/zlib/BUILD.gn
@@ -542,6 +542,6 @@
     configs -= [ "//starboard/build/config:size" ]
     configs += [ "//starboard/build/config:speed" ]
 
-    content_deps = [ "//third_party/zlib/google/test:zip_unittest_files" ]
+    data_deps = [ "//third_party/zlib/google/test:zip_unittest_files" ]
   }
 }