Import Cobalt 24.lts.20.1032766
diff --git a/.github/config/raspi-2-skia.json b/.github/config/raspi-2-skia.json
index f99fb73..e83ea1f 100644
--- a/.github/config/raspi-2-skia.json
+++ b/.github/config/raspi-2-skia.json
@@ -9,7 +9,7 @@
       "platform":"raspi-2-skia",
       "target_platform":"raspi-2-skia",
       "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments": "is_clang=false"
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false"
     }
   ]
 }
diff --git a/.github/config/raspi-2.json b/.github/config/raspi-2.json
index ab2a1e6..9008437 100644
--- a/.github/config/raspi-2.json
+++ b/.github/config/raspi-2.json
@@ -14,8 +14,6 @@
   },
   "platforms": [
     "raspi-2",
-    "raspi-2-sbversion-13",
-    "raspi-2-sbversion-14",
     "raspi-2-sbversion-15"
   ],
   "includes": [
@@ -24,25 +22,7 @@
       "platform":"raspi-2",
       "target_platform":"raspi-2",
       "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments": "is_clang=false",
-      "dimension": "release_version=regex:10.*"
-    },
-    {
-      "name":"sbversion-13",
-      "platform":"raspi-2-sbversion-13",
-      "target_platform":"raspi-2",
-      "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments":"is_clang=false",
-      "sb_api_version": "sb_api_version=13",
-      "dimension": "release_version=regex:10.*"
-    },
-    {
-      "name":"sbversion-14",
-      "platform":"raspi-2-sbversion-14",
-      "target_platform":"raspi-2",
-      "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments":"is_clang=false",
-      "sb_api_version": "sb_api_version=14",
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false",
       "dimension": "release_version=regex:10.*"
     },
     {
@@ -50,7 +30,7 @@
       "platform":"raspi-2-sbversion-15",
       "target_platform":"raspi-2",
       "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments":"is_clang=false",
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false",
       "sb_api_version": "sb_api_version=15",
       "dimension": "release_version=regex:10.*"
     }
diff --git a/base/android/jni_generator/AndroidManifest.xml b/base/android/jni_generator/AndroidManifest.xml
index 1555a81..aa0b7c5 100644
--- a/base/android/jni_generator/AndroidManifest.xml
+++ b/base/android/jni_generator/AndroidManifest.xml
@@ -7,7 +7,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jni.generator">
 
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" />
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" />
     <application></application>
 
 </manifest>
diff --git a/base/logging.cc b/base/logging.cc
index 809e141..5a159ff 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -478,9 +478,15 @@
     case LOG_ERROR:
       return kSbLogPriorityError;
     case LOG_FATAL:
-    case LOG_VERBOSE:
       return kSbLogPriorityFatal;
+    case LOG_VERBOSE:
     default:
+      if (level <= LOG_VERBOSE) {
+        // Verbose level can be any negative integer, sanity check its range to
+        // filter out potential errors.
+        DCHECK_GE(level, -256);
+        return kSbLogPriorityInfo;
+      }
       NOTREACHED() << "Unrecognized log level.";
       return kSbLogPriorityInfo;
   }
diff --git a/cobalt/audio/audio_device.cc b/cobalt/audio/audio_device.cc
index 7aae3b4..874d1ba 100644
--- a/cobalt/audio/audio_device.cc
+++ b/cobalt/audio/audio_device.cc
@@ -32,6 +32,11 @@
 namespace {
 const int kRenderBufferSizeFrames = 1024;
 const int kDefaultFramesPerChannel = 8 * kRenderBufferSizeFrames;
+
+int AlignUp(int value, int alignment) {
+  int decremented_value = value - 1;
+  return decremented_value + alignment - (decremented_value % alignment);
+}
 }  // namespace
 
 class AudioDevice::Impl {
@@ -85,11 +90,13 @@
     : number_of_channels_(number_of_channels),
       output_sample_type_(GetPreferredOutputStarboardSampleType()),
       render_callback_(callback),
-      frames_per_channel_(std::max(SbAudioSinkGetMinBufferSizeInFrames(
-                                       number_of_channels, output_sample_type_,
-                                       kStandardOutputSampleRate) +
-                                       kRenderBufferSizeFrames * 2,
-                                   kDefaultFramesPerChannel)),
+      frames_per_channel_(
+          std::max(AlignUp(SbAudioSinkGetMinBufferSizeInFrames(
+                               number_of_channels, output_sample_type_,
+                               kStandardOutputSampleRate) +
+                               kRenderBufferSizeFrames * 2,
+                           kRenderBufferSizeFrames),
+                   kDefaultFramesPerChannel)),
       input_audio_bus_(static_cast<size_t>(number_of_channels),
                        static_cast<size_t>(kRenderBufferSizeFrames),
                        GetPreferredOutputSampleType(), AudioBus::kPlanar),
@@ -99,6 +106,7 @@
   DCHECK(number_of_channels_ == 1 || number_of_channels_ == 2)
       << "Invalid number of channels: " << number_of_channels_;
   DCHECK(render_callback_);
+  DCHECK(frames_per_channel_ % kRenderBufferSizeFrames == 0);
   DCHECK(SbAudioSinkIsAudioFrameStorageTypeSupported(
       kSbMediaAudioFrameStorageTypeInterleaved))
       << "Only interleaved frame storage is supported.";
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn
index a619003..f2652cc 100644
--- a/cobalt/base/BUILD.gn
+++ b/cobalt/base/BUILD.gn
@@ -60,6 +60,7 @@
     "log_message_handler.cc",
     "log_message_handler.h",
     "message_queue.h",
+    "on_metric_upload_event.h",
     "on_screen_keyboard_hidden_event.h",
     "on_screen_keyboard_shown_event.h",
     "path_provider.cc",
diff --git a/cobalt/base/on_metric_upload_event.h b/cobalt/base/on_metric_upload_event.h
new file mode 100644
index 0000000..d011f0a
--- /dev/null
+++ b/cobalt/base/on_metric_upload_event.h
@@ -0,0 +1,61 @@
+// Copyright 2023 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_BASE_ON_METRIC_UPLOAD_EVENT_H_
+#define COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/h5vcc/h5vcc_metric_type.h"
+
+namespace base {
+
+// Event sent when a metric payload is ready for upload.
+class OnMetricUploadEvent : public Event {
+ public:
+  OnMetricUploadEvent(const cobalt::h5vcc::H5vccMetricType& metric_type,
+                      const std::string& serialized_proto)
+      : metric_type_(metric_type), serialized_proto_(serialized_proto) {}
+
+  explicit OnMetricUploadEvent(const Event* event) {
+    CHECK(event != nullptr);
+    const base::OnMetricUploadEvent* on_metric_upload_event =
+        base::polymorphic_downcast<const base::OnMetricUploadEvent*>(event);
+    metric_type_ = on_metric_upload_event->metric_type();
+    serialized_proto_ = on_metric_upload_event->serialized_proto();
+  }
+
+  const cobalt::h5vcc::H5vccMetricType& metric_type() const {
+    return metric_type_;
+  }
+
+  const std::string& serialized_proto() const { return serialized_proto_; }
+
+
+  BASE_EVENT_SUBCLASS(OnMetricUploadEvent);
+
+ private:
+  cobalt::h5vcc::H5vccMetricType metric_type_;
+  std::string serialized_proto_;
+};
+
+}  // namespace base
+
+#endif  // COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 25e387a..9b7b12c 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -118,7 +118,6 @@
     "client_hint_headers.h",
     "device_authentication.cc",
     "device_authentication.h",
-    "lifecycle_observer.h",
     "on_screen_keyboard_starboard_bridge.cc",
     "on_screen_keyboard_starboard_bridge.h",
     "render_tree_combiner.cc",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index 31cb00d..ebf95f7 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -460,7 +460,12 @@
 }
 
 int StringToLogLevel(const std::string& log_level) {
-  if (log_level == "info") {
+  if (log_level == "verbose") {
+    // The lower the verbose level is, the more messages are logged.  Set it to
+    // a lower enough value to ensure that all known verbose messages are
+    // logged.
+    return logging::LOG_VERBOSE - 15;
+  } else if (log_level == "info") {
     return logging::LOG_INFO;
   } else if (log_level == "warning") {
     return logging::LOG_WARNING;
@@ -698,9 +703,6 @@
                        base::kApplicationStateStarted, kWatchdogTimeInterval,
                        kWatchdogTimeWait, watchdog::NONE);
 
-  cobalt::cache::Cache::GetInstance()->set_persistent_settings(
-      persistent_settings_.get());
-
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   base::Optional<cssom::ViewportSize> requested_viewport_size =
       GetRequestedViewportSize(command_line);
@@ -1025,6 +1027,8 @@
         base::TimeDelta::FromSeconds(duration_in_seconds));
   }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+  AddCrashLogApplicationState(base::kApplicationStateStarted);
 }
 
 Application::~Application() {
@@ -1524,6 +1528,7 @@
       metrics::kMetricEnabledSettingName, false);
   auto metric_event_interval = persistent_settings_->GetPersistentSettingAsInt(
       metrics::kMetricEventIntervalSettingName, 300);
+  metrics_services_manager_->SetEventDispatcher(&event_dispatcher_);
   metrics_services_manager_->SetUploadInterval(metric_event_interval);
   metrics_services_manager_->ToggleMetricsEnabled(is_metrics_enabled);
   // Metric recording state initialization _must_ happen before we bootstrap
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index c6f7f18..f44b2b2 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -52,6 +52,7 @@
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/overlay_info/overlay_info_registry.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
+#include "cobalt/trace_event/scoped_trace_to_file.h"
 #include "cobalt/ui_navigation/scroll_engine/scroll_engine.h"
 #include "cobalt/web/csp_delegate_factory.h"
 #include "cobalt/web/navigator_ua_data.h"
@@ -161,6 +162,13 @@
     "is useful when trying to target testing to certain codecs, since other "
     "codecs will get picked as a fallback as a result.";
 
+const char kNavigateTimedTrace[] = "navigate_timed_trace";
+const char kNavigateTimedTraceShortHelp[] =
+    "Request a timed trace from the next navigation.";
+const char kNavigateTimedTraceLongHelp[] =
+    "When this is called, a timed trace will start at the next navigation "
+    "and run for the given number of seconds.";
+
 void ScreenshotCompleteCallback(const base::FilePath& output_path) {
   DLOG(INFO) << "Screenshot written to " << output_path.value();
 }
@@ -268,6 +276,11 @@
                      base::Unretained(this)),
           kDisableMediaCodecsCommandShortHelp,
           kDisableMediaCodecsCommandLongHelp)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(navigate_timed_trace_command_handler_(
+          kNavigateTimedTrace,
+          base::Bind(&BrowserModule::OnNavigateTimedTrace,
+                     base::Unretained(this)),
+          kNavigateTimedTraceShortHelp, kNavigateTimedTraceLongHelp)),
 #endif  // defined(ENABLE_DEBUGGER)
       has_resumed_(base::WaitableEvent::ResetPolicy::MANUAL,
                    base::WaitableEvent::InitialState::NOT_SIGNALED),
@@ -289,7 +302,7 @@
 
   platform_info_.reset(new browser::UserAgentPlatformInfo());
   service_worker_registry_.reset(new ServiceWorkerRegistry(
-      &web_settings_, network_module, platform_info_.get(), url));
+      &web_settings_, network_module, platform_info_.get()));
 
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
@@ -462,7 +475,9 @@
   }
   debug_console_.reset();
 #endif
+
   DestroySplashScreen();
+  DestroyScrollEngine();
   // Make sure the WebModule is destroyed before the ServiceWorkerRegistry
   if (web_module_) {
     lifecycle_observers_.RemoveObserver(web_module_.get());
@@ -478,6 +493,7 @@
   DLOG(INFO) << "In BrowserModule::Navigate " << url;
   TRACE_EVENT1("cobalt::browser", "BrowserModule::Navigate()", "url",
                url.spec());
+
   // Reset the waitable event regardless of the thread. This ensures that the
   // webdriver won't incorrectly believe that the webmodule has finished loading
   // when it calls Navigate() and waits for the |web_module_loaded_| signal.
@@ -552,6 +568,17 @@
   current_main_web_module_timeline_id_ = next_timeline_id_++;
 
   main_web_module_layer_->Reset();
+
+#if defined(ENABLE_DEBUGGER)
+  // Check to see if a timed_trace has been set, indicating that we should
+  // begin a timed trace upon startup.
+  if (navigate_timed_trace_duration_ != base::TimeDelta()) {
+    trace_event::TraceToFileForDuration(
+        base::FilePath(FILE_PATH_LITERAL("timed_trace.json")),
+        navigate_timed_trace_duration_);
+    navigate_timed_trace_duration_ = base::TimeDelta();
+  }
+#endif  // defined(ENABLE_DEBUGGER)
 }
 
 void BrowserModule::NavigateResetErrorHandling() {
@@ -625,7 +652,9 @@
 }
 
 void BrowserModule::NavigateSetupScrollEngine() {
+  DestroyScrollEngine();
   scroll_engine_.reset(new ui_navigation::scroll_engine::ScrollEngine());
+  lifecycle_observers_.AddObserver(scroll_engine_.get());
   scroll_engine_->thread()->Start();
 }
 
@@ -662,8 +691,8 @@
   }
 
   options.provide_screenshot_function =
-      base::Bind(&ScreenShotWriter::RequestScreenshotToMemoryUnencoded,
-                 base::Unretained(screen_shot_writer_.get()));
+      base::Bind(&BrowserModule::RequestScreenshotToMemoryUnencoded,
+                 base::Unretained(this));
 
 #if defined(ENABLE_DEBUGGER)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -777,6 +806,14 @@
   return web_module_loaded_.TimedWait(timeout);
 }
 
+void BrowserModule::EnsureScreenShotWriter() {
+  if (!screen_shot_writer_ && renderer_module_) {
+    screen_shot_writer_.reset(
+        new ScreenShotWriter(renderer_module_->pipeline()));
+  }
+}
+
+#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
 void BrowserModule::RequestScreenshotToFile(
     const base::FilePath& path,
     loader::image::EncodedStaticImage::ImageFormat image_format,
@@ -784,7 +821,9 @@
     const base::Closure& done_callback) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToFile()");
   DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
+  EnsureScreenShotWriter();
   DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
 
   scoped_refptr<render_tree::Node> render_tree;
   web_module_->DoSynchronousLayoutAndGetRenderTree(&render_tree);
@@ -802,7 +841,9 @@
     const base::Optional<math::Rect>& clip_rect,
     const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToMemory()");
+  EnsureScreenShotWriter();
   DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
   // Note: This does not have to be called from self_message_loop_.
 
   scoped_refptr<render_tree::Node> render_tree;
@@ -815,6 +856,19 @@
   screen_shot_writer_->RequestScreenshotToMemory(image_format, render_tree,
                                                  clip_rect, screenshot_ready);
 }
+#endif  // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
+
+// Request a screenshot to memory without compressing the image.
+void BrowserModule::RequestScreenshotToMemoryUnencoded(
+    const scoped_refptr<render_tree::Node>& render_tree_root,
+    const base::Optional<math::Rect>& clip_rect,
+    const renderer::Pipeline::RasterizationCompleteCallback& callback) {
+  EnsureScreenShotWriter();
+  DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
+  screen_shot_writer_->RequestScreenshotToMemoryUnencoded(render_tree_root,
+                                                          clip_rect, callback);
+}
 
 void BrowserModule::ProcessRenderTreeSubmissionQueue() {
   TRACE_EVENT0("cobalt::browser",
@@ -1137,6 +1191,13 @@
   SubmitCurrentRenderTreeToRenderer();
 }
 
+void BrowserModule::OnNavigateTimedTrace(const std::string& time) {
+  double duration_in_seconds = 0;
+  base::StringToDouble(time, &duration_in_seconds);
+  navigate_timed_trace_duration_ =
+      base::TimeDelta::FromMilliseconds(static_cast<int64_t>(
+          duration_in_seconds * base::Time::kMillisecondsPerSecond));
+}
 #endif  // defined(ENABLE_DEBUGGER)
 
 void BrowserModule::OnOnScreenKeyboardInputEventProduced(
@@ -1437,6 +1498,20 @@
   }
 }
 
+void BrowserModule::DestroyScrollEngine() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroyScrollEngine()");
+  if (base::MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->task_runner()->PostTask(
+        FROM_HERE, base::Bind(&BrowserModule::DestroyScrollEngine, weak_this_));
+    return;
+  }
+  if (scroll_engine_ &&
+      lifecycle_observers_.HasObserver(scroll_engine_.get())) {
+    lifecycle_observers_.RemoveObserver(scroll_engine_.get());
+  }
+  scroll_engine_.reset();
+}
+
 #if defined(ENABLE_WEBDRIVER)
 std::unique_ptr<webdriver::SessionDriver> BrowserModule::CreateSessionDriver(
     const webdriver::protocol::SessionId& session_id) {
@@ -1744,7 +1819,7 @@
       system_window_.get(),
       RendererModuleWithCameraOptions(options_.renderer_module_options,
                                       input_device_manager_->camera_3d())));
-  screen_shot_writer_.reset(new ScreenShotWriter(renderer_module_->pipeline()));
+  screen_shot_writer_.reset();
 }
 
 void BrowserModule::DestroyRendererModule() {
@@ -2184,11 +2259,7 @@
   auto url_request_context = network_module_->url_request_context();
   auto http_cache = url_request_context->http_transaction_factory()->GetCache();
   if (!http_cache) return;
-  auto cache_backend = static_cast<disk_cache::CobaltBackendImpl*>(
-      http_cache->GetCurrentBackend());
-  if (cache_backend) {
-    cache_backend->ValidatePersistentSettings();
-  }
+  network_module_->url_request_context()->ValidateCachePersistentSettings();
 }
 
 }  // namespace browser
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 2011577..abf05fd 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -25,6 +25,7 @@
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "cobalt/base/accessibility_caption_settings_changed_event.h"
 #include "cobalt/base/application_state.h"
@@ -151,6 +152,10 @@
   void AddURLHandler(const URLHandler::URLHandlerCallback& callback);
   void RemoveURLHandler(const URLHandler::URLHandlerCallback& callback);
 
+  // Start the ScreenShotWriter if it's not already running.
+  void EnsureScreenShotWriter();
+
+#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
   // Request a screenshot to be written to the specified path. Callback will
   // be fired after the screenshot has been written to disk.
   void RequestScreenshotToFile(
@@ -164,6 +169,13 @@
       loader::image::EncodedStaticImage::ImageFormat image_format,
       const base::Optional<math::Rect>& clip_rect,
       const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready);
+#endif  // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
+
+  // Request a screenshot to memory without compressing the image.
+  void RequestScreenshotToMemoryUnencoded(
+      const scoped_refptr<render_tree::Node>& render_tree_root,
+      const base::Optional<math::Rect>& clip_rect,
+      const renderer::Pipeline::RasterizationCompleteCallback& callback);
 
 #if defined(ENABLE_WEBDRIVER)
   std::unique_ptr<webdriver::SessionDriver> CreateSessionDriver(
@@ -361,6 +373,8 @@
   // Destroys the splash screen, if currently displayed.
   void DestroySplashScreen(base::TimeDelta close_time = base::TimeDelta());
 
+  void DestroyScrollEngine();
+
   // Called when web module has received window.close().
   void OnWindowClose(base::TimeDelta close_time);
 
@@ -383,6 +397,8 @@
       const browser::WebModule::LayoutResults& layout_results);
   void OnDebugConsoleRenderTreeProduced(
       const browser::WebModule::LayoutResults& layout_results);
+
+  void OnNavigateTimedTrace(const std::string& time);
 #endif  // defined(ENABLE_DEBUGGER)
 
 #if defined(ENABLE_WEBDRIVER)
@@ -658,6 +674,12 @@
 
   // Saves the previous debugger state to be restored in the new WebModule.
   std::unique_ptr<debug::backend::DebuggerState> debugger_state_;
+
+  // Amount of time to run a Timed Trace after Navigate
+  base::TimeDelta navigate_timed_trace_duration_;
+
+  debug::console::ConsoleCommandManager::CommandHandler
+      navigate_timed_trace_command_handler_;
 #endif  // defined(ENABLE_DEBUGGER)
 
   // The splash screen. The pointer wrapped here should be non-NULL iff
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index 4663d09..41539c3 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -171,6 +171,7 @@
   "//cobalt/h5vcc/h5vcc_screen.idl",
   "//cobalt/h5vcc/h5vcc_system.idl",
   "//cobalt/h5vcc/h5vcc_trace_event.idl",
+  "//cobalt/h5vcc/h5vcc_net_log.idl",
   "//cobalt/h5vcc/h5vcc_updater.idl",
 
   "//cobalt/media_capture/blob_event.idl",
diff --git a/cobalt/browser/metrics/BUILD.gn b/cobalt/browser/metrics/BUILD.gn
index 62c85fa..414d1b5 100644
--- a/cobalt/browser/metrics/BUILD.gn
+++ b/cobalt/browser/metrics/BUILD.gn
@@ -24,11 +24,11 @@
     "cobalt_metrics_services_manager.h",
     "cobalt_metrics_services_manager_client.cc",
     "cobalt_metrics_services_manager_client.h",
-    "cobalt_metrics_uploader_callback.h",
   ]
 
   deps = [
     "//base",
+    "//cobalt/base",
     "//cobalt/browser:generated_types",
     "//cobalt/h5vcc:metric_event_handler_wrapper",
     "//components/metrics",
@@ -51,8 +51,9 @@
   deps = [
     ":metrics",
     "//base",
-    "//cobalt//browser:test_dependencies_on_browser",
+    "//cobalt/base",
     "//cobalt/browser:generated_types",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/h5vcc",
     "//cobalt/h5vcc:metric_event_handler_wrapper",
     "//cobalt/test:run_all_unittests",
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
index 49f1c81..955ef04 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
@@ -14,9 +14,12 @@
 
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
 
+#include <memory>
+
 #include "base/base64url.h"
 #include "base/logging.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "components/metrics/log_decoder.h"
 #include "components/metrics/metrics_log_uploader.h"
@@ -49,7 +52,7 @@
     const std::string& compressed_log_data, const std::string& log_hash,
     const ::metrics::ReportingInfo& reporting_info) {
   if (service_type_ == ::metrics::MetricsLogUploader::UMA) {
-    if (upload_handler_ != nullptr) {
+    if (event_dispatcher_ != nullptr) {
       std::string uncompressed_serialized_proto;
       ::metrics::DecodeLogData(compressed_log_data,
                                &uncompressed_serialized_proto);
@@ -67,8 +70,11 @@
       base::Base64UrlEncode(cobalt_uma_event.SerializeAsString(),
                             base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                             &base64_encoded_proto);
-      upload_handler_->Run(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma,
-                           base64_encoded_proto);
+
+      event_dispatcher_->DispatchEvent(
+          std::unique_ptr<base::Event>(new base::OnMetricUploadEvent(
+              h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma,
+              base64_encoded_proto)));
     }
   }
 
@@ -79,9 +85,9 @@
                           /*was_https*/ true);
 }
 
-void CobaltMetricsLogUploader::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* upload_handler) {
-  upload_handler_ = upload_handler;
+void CobaltMetricsLogUploader::SetEventDispatcher(
+    const base::EventDispatcher* event_dispatcher) {
+  event_dispatcher_ = event_dispatcher;
 }
 
 }  // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
index fa71662..8b5b73d 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
@@ -20,7 +20,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/h5vcc/metric_event_handler_wrapper.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "third_party/metrics_proto/reporting_info.pb.h"
@@ -43,6 +43,9 @@
 
   virtual ~CobaltMetricsLogUploader() {}
 
+  // Set event dispatcher to be used to publish any metrics events (eg upload).
+  void SetEventDispatcher(const base::EventDispatcher* event_dispatcher);
+
   // Uploads a log with the specified |compressed_log_data| and |log_hash|.
   // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data
   // before compression.
@@ -50,15 +53,10 @@
                  const std::string& log_hash,
                  const ::metrics::ReportingInfo& reporting_info);
 
-  // Sets the event handler wrapper to be called when metrics are ready for
-  // upload. This should be the JavaScript H5vcc callback implementation.
-  void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* metric_event_handler);
-
  private:
   const ::metrics::MetricsLogUploader::MetricServiceType service_type_;
   const ::metrics::MetricsLogUploader::UploadCallback on_upload_complete_;
-  const CobaltMetricsUploaderCallback* upload_handler_ = nullptr;
+  const base::EventDispatcher* event_dispatcher_ = nullptr;
 };
 
 }  // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
index 78e7d18..f6927a8 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
@@ -18,7 +18,10 @@
 
 #include "base/base64url.h"
 #include "base/test/mock_callback.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
+#include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "cobalt/h5vcc/h5vcc_metrics.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -26,7 +29,6 @@
 #include "third_party/metrics_proto/cobalt_uma_event.pb.h"
 #include "third_party/metrics_proto/reporting_info.pb.h"
 #include "third_party/zlib/google/compression_utils.h"
-
 namespace cobalt {
 namespace browser {
 namespace metrics {
@@ -43,6 +45,12 @@
 class CobaltMetricsLogUploaderTest : public ::testing::Test {
  public:
   void SetUp() override {
+    dispatcher_ = std::make_unique<base::EventDispatcher>();
+    dispatcher_->AddEventCallback(
+        base::OnMetricUploadEvent::TypeId(),
+        base::Bind(&CobaltMetricsLogUploaderTest::OnMetricUploadEventHandler,
+                   base::Unretained(this)));
+
     uploader_ = std::make_unique<CobaltMetricsLogUploader>(
         ::metrics::MetricsLogUploader::MetricServiceType::UMA,
         base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback,
@@ -51,6 +59,13 @@
 
   void TearDown() override {}
 
+  void OnMetricUploadEventHandler(const base::Event* event) {
+    std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event(
+        new base::OnMetricUploadEvent(event));
+    last_metric_type_ = on_metric_upload_event->metric_type();
+    last_serialized_proto_ = on_metric_upload_event->serialized_proto();
+  }
+
   void UploadCompleteCallback(int response_code, int error_code,
                               bool was_https) {
     callback_count_++;
@@ -58,13 +73,14 @@
 
  protected:
   std::unique_ptr<CobaltMetricsLogUploader> uploader_;
+  std::unique_ptr<base::EventDispatcher> dispatcher_;
   int callback_count_ = 0;
+  cobalt::h5vcc::H5vccMetricType last_metric_type_;
+  std::string last_serialized_proto_ = "";
 };
 
 TEST_F(CobaltMetricsLogUploaderTest, TriggersUploadHandler) {
-  base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler;
-  const auto cb = mock_upload_handler.Get();
-  uploader_->SetOnUploadHandler(&cb);
+  uploader_->SetEventDispatcher(dispatcher_.get());
   ::metrics::ReportingInfo dummy_reporting_info;
   dummy_reporting_info.set_attempt_count(33);
   ::metrics::ChromeUserMetricsExtension uma_log;
@@ -87,12 +103,11 @@
   base::Base64UrlEncode(cobalt_event.SerializeAsString(),
                         base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                         &base64_encoded_proto);
-  EXPECT_CALL(mock_upload_handler,
-              Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma),
-                  StrEq(base64_encoded_proto)))
-      .Times(1);
   uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info);
   ASSERT_EQ(callback_count_, 1);
+  ASSERT_EQ(last_metric_type_,
+            cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma);
+  ASSERT_EQ(last_serialized_proto_, base64_encoded_proto);
 
   ::metrics::ChromeUserMetricsExtension uma_log2;
   uma_log2.set_session_id(456);
@@ -108,11 +123,10 @@
   base::Base64UrlEncode(cobalt_event2.SerializeAsString(),
                         base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                         &base64_encoded_proto2);
-  EXPECT_CALL(mock_upload_handler,
-              Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma),
-                  StrEq(base64_encoded_proto2)))
-      .Times(1);
   uploader_->UploadLog(compressed_message2, "fake_hash", dummy_reporting_info);
+  ASSERT_EQ(last_metric_type_,
+            cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma);
+  ASSERT_EQ(last_serialized_proto_, base64_encoded_proto2);
   ASSERT_EQ(callback_count_, 2);
 }
 
@@ -121,17 +135,15 @@
       ::metrics::MetricsLogUploader::MetricServiceType::UKM,
       base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback,
                  base::Unretained(this))));
-  base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler;
-  const auto cb = mock_upload_handler.Get();
-  uploader_->SetOnUploadHandler(&cb);
+  uploader_->SetEventDispatcher(dispatcher_.get());
   ::metrics::ReportingInfo dummy_reporting_info;
   ::metrics::ChromeUserMetricsExtension uma_log;
   uma_log.set_session_id(1234);
   uma_log.set_client_id(1234);
   std::string compressed_message;
   compression::GzipCompress(uma_log.SerializeAsString(), &compressed_message);
-  EXPECT_CALL(mock_upload_handler, Run(_, _)).Times(0);
   uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info);
+  ASSERT_EQ(last_serialized_proto_, "");
   // Even though we don't upload this log, we still need to trigger the complete
   // callback so the metric code can keep running.
   ASSERT_EQ(callback_count_, 1);
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.cc b/cobalt/browser/metrics/cobalt_metrics_service_client.cc
index 2b4d21a..c3408a9 100644
--- a/cobalt/browser/metrics/cobalt_metrics_service_client.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_service_client.cc
@@ -24,9 +24,9 @@
 #include "base/memory/singleton.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h"
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics/enabled_state_provider.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "components/metrics/metrics_pref_names.h"
@@ -50,11 +50,11 @@
 // Upload Handler.
 const int kStandardUploadIntervalSeconds = 5 * 60;  // 5 minutes.
 
-void CobaltMetricsServiceClient::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
-  upload_handler_ = uploader_callback;
+void CobaltMetricsServiceClient::SetEventDispatcher(
+    const base::EventDispatcher* event_dispatcher) {
+  event_dispatcher_ = event_dispatcher;
   if (log_uploader_) {
-    log_uploader_->SetOnUploadHandler(upload_handler_);
+    log_uploader_->SetEventDispatcher(event_dispatcher);
   }
 }
 
@@ -77,7 +77,8 @@
 
 void CobaltMetricsServiceClient::SetMetricsClientId(
     const std::string& client_id) {
-  // TODO(b/286066035): What to do with client id here?
+  // ClientId is unnecessary within Cobalt. We expect the web client responsible
+  // for uploading these to have its own concept of device/client identifiers.
 }
 
 // TODO(b/286884542): Audit all stub implementations in this class and reaffirm
@@ -153,8 +154,8 @@
   auto uploader = std::make_unique<CobaltMetricsLogUploader>(
       service_type, on_upload_complete);
   log_uploader_ = uploader.get();
-  if (upload_handler_ != nullptr) {
-    log_uploader_->SetOnUploadHandler(upload_handler_);
+  if (event_dispatcher_ != nullptr) {
+    log_uploader_->SetEventDispatcher(event_dispatcher_);
   }
   return uploader;
 }
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.h b/cobalt/browser/metrics/cobalt_metrics_service_client.h
index c7860f0..c8efa8f 100644
--- a/cobalt/browser/metrics/cobalt_metrics_service_client.h
+++ b/cobalt/browser/metrics/cobalt_metrics_service_client.h
@@ -23,9 +23,9 @@
 #include "base/callback.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h"
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "components/metrics/metrics_reporting_default_state.h"
 #include "components/metrics/metrics_service.h"
@@ -48,10 +48,8 @@
  public:
   ~CobaltMetricsServiceClient() override{};
 
-  // Sets the uploader handler to be called when metrics are ready for
-  // upload.
-  void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  // Set event dispatcher to be used to publish any metrics events (eg upload).
+  void SetEventDispatcher(const base::EventDispatcher* event_dispatcher);
 
   // Returns the MetricsService instance that this client is associated with.
   // With the exception of testing contexts, the returned instance must be valid
@@ -165,10 +163,10 @@
 
   CobaltMetricsLogUploader* log_uploader_ = nullptr;
 
-  const CobaltMetricsUploaderCallback* upload_handler_ = nullptr;
-
   uint32_t custom_upload_interval_ = UINT32_MAX;
 
+  const base::EventDispatcher* event_dispatcher_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(CobaltMetricsServiceClient);
 };
 
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
index acb5d8b..f816858 100644
--- a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
@@ -17,6 +17,7 @@
 #include <memory>
 
 #include "base/logging.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_metrics_service_client.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
@@ -41,23 +42,27 @@
   return instance_;
 }
 
-void CobaltMetricsServicesManager::DeleteInstance() { delete instance_; }
-
-void CobaltMetricsServicesManager::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
-  instance_->task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&CobaltMetricsServicesManager::SetOnUploadHandlerInternal,
-                 base::Unretained(instance_), uploader_callback));
+void CobaltMetricsServicesManager::DeleteInstance() {
+  delete instance_;
+  instance_ = nullptr;
 }
 
-void CobaltMetricsServicesManager::SetOnUploadHandlerInternal(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
+void CobaltMetricsServicesManager::SetEventDispatcher(
+    base::EventDispatcher* event_dispatcher) {
+  if (instance_ != nullptr) {
+    instance_->task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&CobaltMetricsServicesManager::SetEventDispatcherInternal,
+                   base::Unretained(instance_), event_dispatcher));
+  }
+}
+
+void CobaltMetricsServicesManager::SetEventDispatcherInternal(
+    base::EventDispatcher* event_dispatcher) {
   CobaltMetricsServiceClient* client =
       static_cast<CobaltMetricsServiceClient*>(GetMetricsServiceClient());
   DCHECK(client);
-  client->SetOnUploadHandler(uploader_callback);
-  LOG(INFO) << "New Cobalt Telemetry metric upload handler bound.";
+  client->SetEventDispatcher(event_dispatcher);
 }
 
 void CobaltMetricsServicesManager::ToggleMetricsEnabled(bool is_enabled) {
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.h b/cobalt/browser/metrics/cobalt_metrics_services_manager.h
index da31294..20a4345 100644
--- a/cobalt/browser/metrics/cobalt_metrics_services_manager.h
+++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.h
@@ -20,8 +20,8 @@
 
 #include "base//memory/scoped_refptr.h"
 #include "base/single_thread_task_runner.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
 #include "components/metrics_services_manager/metrics_services_manager_client.h"
 
@@ -54,10 +54,9 @@
   // Destructs the static instance of CobaltMetricsServicesManager.
   static void DeleteInstance();
 
-  // Sets the upload handler onto the current static instance of
-  // CobaltMetricsServicesManager.
-  static void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  // Sets an event dispatcher to call when metric events happen (e.g., on
+  // upload).
+  static void SetEventDispatcher(base::EventDispatcher* event_dispatcher);
 
   // Toggles whether metric reporting is enabled via
   // CobaltMetricsServicesManager.
@@ -68,8 +67,7 @@
   static void SetUploadInterval(uint32_t interval_seconds);
 
  private:
-  void SetOnUploadHandlerInternal(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  void SetEventDispatcherInternal(base::EventDispatcher* event_dispatcher);
 
   void ToggleMetricsEnabledInternal(bool is_enabled);
 
diff --git a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h b/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h
deleted file mode 100644
index db980c5..0000000
--- a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 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_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
-#define COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "cobalt/h5vcc/h5vcc_metric_type.h"
-
-namespace cobalt {
-namespace browser {
-namespace metrics {
-
-typedef base::RepeatingCallback<void(
-    const cobalt::h5vcc::H5vccMetricType& metric_type,
-    const std::string& serialized_proto)>
-    CobaltMetricsUploaderCallback;
-
-}  // namespace metrics
-}  // namespace browser
-}  // namespace cobalt
-
-#endif  // COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index 89b9395..2a62dac 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -49,7 +49,7 @@
 
 ServiceWorkerRegistry::ServiceWorkerRegistry(
     web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, const GURL& url)
+    web::UserAgentPlatformInfo* platform_info)
     : thread_("ServiceWorkerRegistry") {
   if (!thread_.Start()) return;
   DCHECK(message_loop());
@@ -73,7 +73,7 @@
   message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&ServiceWorkerRegistry::Initialize, base::Unretained(this),
-                 web_settings, network_module, platform_info, url));
+                 web_settings, network_module, platform_info));
 
   // Register as a destruction observer to shut down the Web Agent once all
   // pending tasks have been executed and the message loop is about to be
@@ -145,11 +145,11 @@
 
 void ServiceWorkerRegistry::Initialize(
     web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, const GURL& url) {
+    web::UserAgentPlatformInfo* platform_info) {
   TRACE_EVENT0("cobalt::browser", "ServiceWorkerRegistry::Initialize()");
   DCHECK_EQ(base::MessageLoop::current(), message_loop());
   service_worker_context_.reset(new worker::ServiceWorkerContext(
-      web_settings, network_module, platform_info, message_loop(), url));
+      web_settings, network_module, platform_info, message_loop()));
 }
 
 }  // namespace browser
diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h
index f0e934e..de31979 100644
--- a/cobalt/browser/service_worker_registry.h
+++ b/cobalt/browser/service_worker_registry.h
@@ -35,8 +35,7 @@
  public:
   ServiceWorkerRegistry(web::WebSettings* web_settings,
                         network::NetworkModule* network_module,
-                        web::UserAgentPlatformInfo* platform_info,
-                        const GURL& url);
+                        web::UserAgentPlatformInfo* platform_info);
   ~ServiceWorkerRegistry();
 
   // The message loop this object is running on.
@@ -58,7 +57,7 @@
   // the dedicated thread.
   void Initialize(web::WebSettings* web_settings,
                   network::NetworkModule* network_module,
-                  web::UserAgentPlatformInfo* platform_info, const GURL& url);
+                  web::UserAgentPlatformInfo* platform_info);
 
   void PingWatchdog();
 
diff --git a/cobalt/browser/splash_screen.h b/cobalt/browser/splash_screen.h
index f50c2e4..3fc1202 100644
--- a/cobalt/browser/splash_screen.h
+++ b/cobalt/browser/splash_screen.h
@@ -57,7 +57,6 @@
   }
 
   // LifecycleObserver implementation.
-  // LifecycleObserver implementation.
   void Blur(SbTimeMonotonic timestamp) override { web_module_->Blur(0); }
   void Conceal(render_tree::ResourceProvider* resource_provider,
                SbTimeMonotonic timestamp) override {
diff --git a/cobalt/browser/switches.cc b/cobalt/browser/switches.cc
index 70bb756..11c7db6 100644
--- a/cobalt/browser/switches.cc
+++ b/cobalt/browser/switches.cc
@@ -229,7 +229,7 @@
 
 const char kMinLogLevel[] = "min_log_level";
 const char kMinLogLevelHelp[] =
-    "Set the minimum logging level: info|warning|error|fatal.";
+    "Set the minimum logging level: verbose|info|warning|error|fatal.";
 const char kDisableJavaScriptJit[] = "disable_javascript_jit";
 const char kDisableJavaScriptJitHelp[] =
     "Specifies that javascript jit should be disabled.";
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py
index e1320ab..ca1c943 100644
--- a/cobalt/build/cobalt_configuration.py
+++ b/cobalt/build/cobalt_configuration.py
@@ -133,8 +133,7 @@
         'media_capture_test',
         'media_session_test',
         'media_stream_test',
-        # TODO(b/292030213): Crashes on evergreen
-        # 'media_test',
+        'media_test',
         'memory_store_test',
         'metrics_test',
         'nb_test',
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc
index d88a6ec..42eee8e 100644
--- a/cobalt/cache/cache.cc
+++ b/cobalt/cache/cache.cc
@@ -25,7 +25,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "cobalt/configuration/configuration.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/extension/javascript_cache.h"
@@ -201,19 +200,6 @@
   return nullptr;
 }
 
-void Cache::set_enabled(bool enabled) { enabled_ = enabled; }
-
-void Cache::set_persistent_settings(
-    persistent_storage::PersistentSettings* persistent_settings) {
-  persistent_settings_ = persistent_settings;
-
-  // Guaranteed to be called before any calls to Retrieve()
-  // since set_persistent_settings() is called from the Application()
-  // constructor before the NetworkModule is initialized.
-  set_enabled(persistent_settings_->GetPersistentSettingAsBool(
-      disk_cache::kCacheEnabledPersistentSettingsKey, true));
-}
-
 MemoryCappedDirectory* Cache::GetMemoryCappedDirectory(
     disk_cache::ResourceType resource_type) {
   base::AutoLock auto_lock(lock_);
@@ -222,16 +208,6 @@
     return it->second.get();
   }
 
-  // Read in size from persistent storage.
-  auto metadata = disk_cache::kTypeMetadata[resource_type];
-  if (persistent_settings_) {
-    uint32_t bucket_size = static_cast<uint32_t>(
-        persistent_settings_->GetPersistentSettingAsDouble(
-            metadata.directory, metadata.max_size_bytes));
-    disk_cache::kTypeMetadata[resource_type] = {metadata.directory,
-                                                bucket_size};
-  }
-
   auto cache_directory = GetCacheDirectory(resource_type);
   auto max_size = GetMaxCacheStorageInBytes(resource_type);
   if (!cache_directory || !max_size) {
@@ -248,16 +224,9 @@
 void Cache::Resize(disk_cache::ResourceType resource_type, uint32_t bytes) {
   if (resource_type != disk_cache::ResourceType::kCacheApi &&
       resource_type != disk_cache::ResourceType::kCompiledScript &&
-      resource_type != disk_cache::ResourceType::kServiceWorkerScript)
+      resource_type != disk_cache::ResourceType::kServiceWorkerScript) {
     return;
-  if (bytes == disk_cache::kTypeMetadata[resource_type].max_size_bytes) return;
-
-  if (persistent_settings_) {
-    persistent_settings_->SetPersistentSetting(
-        disk_cache::kTypeMetadata[resource_type].directory,
-        std::make_unique<base::Value>(static_cast<double>(bytes)));
   }
-  disk_cache::kTypeMetadata[resource_type].max_size_bytes = bytes;
   auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
   if (memory_capped_directory) {
     memory_capped_directory->Resize(bytes);
@@ -273,7 +242,7 @@
     case disk_cache::ResourceType::kCacheApi:
     case disk_cache::ResourceType::kCompiledScript:
     case disk_cache::ResourceType::kServiceWorkerScript:
-      return disk_cache::kTypeMetadata[resource_type].max_size_bytes;
+      return disk_cache::settings::GetQuota(resource_type);
     default:
       return base::nullopt;
   }
@@ -334,7 +303,7 @@
       resource_type == disk_cache::ResourceType::kCacheApi) {
     return true;
   }
-  if (!enabled_) {
+  if (!disk_cache::settings::GetCacheEnabled()) {
     return false;
   }
   if (resource_type == disk_cache::ResourceType::kCompiledScript) {
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h
index 40bd225..e98f335 100644
--- a/cobalt/cache/cache.h
+++ b/cobalt/cache/cache.h
@@ -26,12 +26,12 @@
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/values.h"
 #include "cobalt/cache/memory_capped_directory.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/resource_type.h"
 
 namespace base {
@@ -67,14 +67,9 @@
   base::Optional<uint32_t> GetMaxCacheStorageInBytes(
       disk_cache::ResourceType resource_type);
 
-  void set_enabled(bool enabled);
-
-  void set_persistent_settings(
-      persistent_storage::PersistentSettings* persistent_settings);
-
  private:
   friend struct base::DefaultSingletonTraits<Cache>;
-  Cache() {}
+  Cache() = default;
 
   MemoryCappedDirectory* GetMemoryCappedDirectory(
       disk_cache::ResourceType resource_type);
@@ -91,9 +86,6 @@
   std::map<disk_cache::ResourceType,
            std::map<uint32_t, std::vector<base::WaitableEvent*>>>
       pending_;
-  bool enabled_ = true;
-
-  persistent_storage::PersistentSettings* persistent_settings_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(Cache);
 };  // class Cache
diff --git a/cobalt/demos/content/BUILD.gn b/cobalt/demos/content/BUILD.gn
index 1d6ec9a..9d60175 100644
--- a/cobalt/demos/content/BUILD.gn
+++ b/cobalt/demos/content/BUILD.gn
@@ -32,6 +32,7 @@
     "deviceorientation-demo/deviceorientation-demo.html",
     "disable-jit/index.html",
     "dom-gc-demo/dom-gc-demo.html",
+    "draw/index.html",
     "dual-playback-demo/bear.mp4",
     "dual-playback-demo/dual-playback-demo.html",
     "eme-demo/eme-demo.html",
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html
new file mode 100644
index 0000000..3c40603
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Configure Source Buffer Memory</title>
+  <style>
+    body {
+      background-color: #0f0;
+    }
+    video {
+      height: 240px;
+      width: 426px;
+    }
+  </style>
+</head>
+<body>
+  <script type="text/javascript" src="configure-source-buffer-memory.js"></script>
+  <video id="video"></video><br>
+  <div id="status"></div>
+</body>
+</html>
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js
new file mode 100644
index 0000000..3f2c675
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js
@@ -0,0 +1,142 @@
+// Copyright 2023 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.
+
+"use strict";
+
+const QUOTA_EXCEEDED_ERROR_CODE = 22;
+const mimeCodec = 'video/webm; codecs="vp9"';
+const assetURL = 'vp9-720p.webm';
+
+const assetDuration = 15;
+const assetSize = 344064;
+
+let status_div;
+let video;
+
+
+function fetchAB(url, cb) {
+  console.log("Fetching.. ", url);
+  const xhr = new XMLHttpRequest();
+  xhr.open("get", url);
+  xhr.responseType = "arraybuffer";
+  xhr.onload = () => {
+    console.log("onLoad - calling Callback");
+    cb(xhr.response);
+  };
+  console.log('Sending request for media segment ...');
+  xhr.send();
+}
+
+function testAppendToBuffer(media_source, mem_limit) {
+  const mediaSource = media_source;
+  const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
+  if (mem_limit > 0) {
+    status_div.innerHTML += "Test SourceBuffer, setting memoryLimit to " + mem_limit + "<br>";
+    sourceBuffer.memoryLimit = mem_limit;
+  } else {
+    status_div.innerHTML += "Test SourceBuffer, leaving memoryLimit at default<br>";
+  }
+
+  let MIN_SIZE = 12 * 1024 * 1024;
+  let ESTIMATED_MIN_TIME = 12;
+
+  fetchAB(assetURL, (buf) => {
+    let expectedTime = 0;
+    let expectedSize = 0;
+    let appendCount = 0;
+
+
+    let onBufferFull = function(buffer_was_full) {
+      console.log("OnBufferFull! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " expectedTime:" + expectedTime);
+      status_div.innerHTML += "Finished! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " appended " + appendCount * assetSize + "<br>";
+    }
+
+    sourceBuffer.addEventListener("updateend", function onupdateend() {
+      console.log("Update end. State is " + sourceBuffer.updating);
+      appendCount++;
+      console.log("Append Count" + appendCount);
+      if (sourceBuffer.buffered.start(0) > 0 || expectedTime > sourceBuffer.buffered.end(0)) {
+         sourceBuffer.removeEventListener('updatedend', onupdateend);
+         onBufferFull(false);
+      } else {
+        expectedTime +=  assetDuration;
+        expectedSize += assetSize;
+        if (expectedSize > (10 * MIN_SIZE)) {
+          sourceBuffer.removeEventListener('updateend', onupdateend);
+          onBufferFull(false);
+          return;
+        }
+
+        try {
+          sourceBuffer.timestampOffset = expectedTime;
+        } catch(e) {
+          console.log("Unexpected error: " + e);
+        }
+
+        try {
+          sourceBuffer.appendBuffer(buf);
+        } catch(e) {
+          console.log("Wuff! QUOTA_EXCEEDED_ERROR!");
+          status_div.innerHTML += "Wuff! QUOTA_EXCEEDED<br>";
+          if (e.code == QUOTA_EXCEEDED_ERROR_CODE) {
+            sourceBuffer.removeEventListener('updateend', onupdateend);
+            onBufferFull(true);
+          } else {
+            console.log("Unexpected error: " + e);
+          }
+        }
+      }
+    });
+
+    console.log("First Append!");
+    sourceBuffer.appendBuffer(buf);
+    status_div.innerHTML += "First append. MemoryLimit is:" + sourceBuffer.memoryLimit + ".<br>";
+  });
+}
+
+function onSourceOpen() {
+  console.log("Source Open. This state:", this.readyState); // open
+  status_div.innerHTML += "Source Open. This state:" + this.readyState + "<br>";
+  status_div.innerHTML += "Lets test first source_buffer, defaults..<br>";
+  testAppendToBuffer(this, 0);
+
+  let new_mem_limit = 400 * 1024 * 1024;
+  status_div.innerHTML += "<br><br>Lets test second source_buffer, setting memory to:" + new_mem_limit + "<br>";
+  testAppendToBuffer(this, new_mem_limit);
+  video.play();
+}
+
+
+function createMediaSource() {
+
+  console.log('Video Get Element By Id...');
+  video = document.getElementById('video');
+  status_div = document.getElementById('status');
+  status_div.innerHTML += 'Video Get Element By Id...<br>';
+
+  console.log('Create Media Source...');
+  status_div.innerHTML += 'Create Media Source...<br>';
+  var mediaSource = new MediaSource;
+
+  console.log('Attaching MediaSource to video element ...');
+  status_div.innerHTML += 'Attaching MediaSource to video element ...<br>';
+  video.src = window.URL.createObjectURL(mediaSource);
+
+  console.log('Add event listener..');
+  status_div.innerHTML += 'Add event listener..<br>';
+  mediaSource.addEventListener('sourceopen', onSourceOpen);
+
+}
+
+addEventListener('load', createMediaSource);
diff --git a/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm
new file mode 100644
index 0000000..08a670e
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm
Binary files differ
diff --git a/cobalt/demos/content/draw/index.html b/cobalt/demos/content/draw/index.html
new file mode 100644
index 0000000..6e596c6
--- /dev/null
+++ b/cobalt/demos/content/draw/index.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <style>
+        .container {
+            width: 500px;
+            height: 500px;
+            position: relative;
+            background-color: white;
+        }
+
+        .pixel {
+            width: 5px;
+            height: 5px;
+            position: absolute;
+            background-color: red;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container" id="container"></div>
+</body>
+
+<script>
+    window.addEventListener('load', () => registerHandlers());
+
+    function registerHandlers() {
+        const container = document.getElementById('container');
+        let isDrawing = false;
+
+        container.addEventListener('pointerdown', (e) => {
+            isDrawing = true;
+            colorPixel(e);
+        });
+        document.addEventListener('pointerup', () => { isDrawing = false; });
+        container.addEventListener('pointermove', colorPixel);
+
+        function colorPixel(e) {
+            if (!isDrawing) return;
+
+            const x = Math.floor((e.clientX - container.offsetLeft) / 5) * 5;
+            const y = Math.floor((e.clientY - container.offsetTop) / 5) * 5;
+
+            const pixel = document.createElement('div');
+            pixel.classList.add('pixel');
+            pixel.style.left = `${x}px`;
+            pixel.style.top = `${y}px`;
+
+            container.appendChild(pixel);
+        }
+    }
+</script>
+
+</html>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
index 36e504f..8e3297a 100644
--- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
+++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
@@ -23,7 +23,7 @@
           margin: 0;
         }
 
-        #player-layer {
+        #primary-player-layer {
           width: 100%;
           height: 100%;
         }
@@ -33,13 +33,14 @@
           height: 100%;
         }
 
-        #ui-layer {
+        #secondary-player-layer {
           position: absolute;
-          top: 15%;
-          height: 85%;
+          top: 60%;
+          height: 40%;
           width: 100%;
-          background-color: rgba(33, 33, 33, .75);
           padding: 24px;
+          display: flex;
+          justify-content: center;
         }
 
         .item {
@@ -48,22 +49,23 @@
           display: inline-block;
           margin: 24px;
           vertical-align: middle;
+          background-color: rgba(33, 33, 33, .75);
         }
     </style>
   </head>
   <body>
-    <div id="player-layer">
-      <video class="primary" id="primary-video" muted="1" autoplay="1"></video>
+    <div id="primary-player-layer">
+      <video id="primary-video" muted="1" autoplay="1"></video>
     </div>
-    <div id="ui-layer">
-      <div class="item" style="background-color: #D44">
-        <video class="secondary" id="secondary-video-1" muted="1" autoplay="1"></video>
+    <div id="secondary-player-layer">
+      <div class="item">
+        <video id="secondary-video-1" muted="1" autoplay="1"></video>
       </div>
-      <div class="item" style="background-color: #4D4">
-        <video class="secondary" id="secondary-video-2" muted="1" autoplay="1"></video>
+      <div class="item">
+        <video id="secondary-video-2" muted="1" autoplay="1"></video>
       </div>
-      <div class="item" style="background-color: #44D">
-        <video class="secondary" id="secondary-video-3" muted="1" autoplay="1"></video>
+      <div class="item">
+        <video id="secondary-video-3" muted="1" autoplay="1"></video>
       </div>
     </div>
     <script src="multi-encrypted-video.js"></script>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
index 028d11c..27dcddf 100644
--- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
+++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
@@ -12,6 +12,58 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+
+// Dictionary mapping string descriptions to media file descriptions in the
+// form of [contentType, url, maxVideoCapabilities (for videos only), licenseUrl]
+const MEDIA_FILES = {
+  'av1_720p_60fps_drm': {
+    contentType: 'video/mp4; codecs="av01.0.05M.08"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/av1-senc/sdr_720p60.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=ik0&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=6508f99557a8385f&signature=5153900DAC410803EC269D252DAAA82BA6D8B825.495E631E406584A8EFCB4E9C9F3D45F6488B94E4',
+  },
+  // 40 MB
+  'h264_720p_24fps_drm': {
+    contentType: 'video/mp4; codecs="avc1.640028"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 38 MB
+  'h264_720p_60fps_drm': {
+    contentType: 'video/mp4; codecs="avc1.640028"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_h264_720p_60fps_cenc.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 32 MB
+  'vp9_720p_60fps_drm': {
+    contentType: 'video/webm; codecs="vp9"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_vp9_720p_60fps_enc.webm',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=f320151fa3f061b2&signature=81E7B33929F9F35922F7D2E96A5E7AC36F3218B2.673F553EE51A48438AE5E707AEC87A071B4FEF65'
+  },
+  // 1 MB
+  // Mono won't work with tunnel mode on many devices.
+  'aac_mono_drm': {
+    contentType: 'audio/mp4; codecs="mp4a.40.2"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 2.8 MB
+  'aac_clear': {
+    contentType: 'audio/mp4; codecs="mp4a.40.2"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-8c.mp4',
+  },
+  // 1.7 MB
+  'opus_clear': {
+    contentType: 'audio/webm; codecs="opus"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/car_opus_med.webm',
+  },
+};
+
+mediaCache = {}
+
 function fetchArrayBuffer(method, url, body, callback) {
   var xhr = new XMLHttpRequest();
   xhr.responseType = 'arraybuffer';
@@ -22,6 +74,16 @@
   xhr.send(body);
 }
 
+async function fetchMediaData(mediaFileId) {
+  if (mediaFileId in mediaCache) {
+    return mediaCache[mediaFileId];
+  }
+
+  const response = await fetch(MEDIA_FILES[mediaFileId].url);
+  mediaCache[mediaFileId] = await response.arrayBuffer();
+  return mediaCache[mediaFileId];
+}
+
 function extractLicense(licenseArrayBuffer) {
   var licenseArray = new Uint8Array(licenseArrayBuffer);
   var licenseStartIndex = licenseArray.length - 2;
@@ -37,80 +99,156 @@
   return licenseArray.subarray(licenseStartIndex);
 }
 
-var videoContentType = 'video/mp4; codecs="avc1.640028"';
-var audioContentType = 'audio/mp4; codecs="mp4a.40.2"';
-
-function play(videoElementId, keySystem) {
-  navigator.requestMediaKeySystemAccess(keySystem, [{
-    'initDataTypes': ['cenc'],
-    'videoCapabilities': [{'contentType': videoContentType}],
-    'audioCapabilities': [{'contentType': audioContentType}]
-  }]).then(function(mediaKeySystemAccess) {
-    return mediaKeySystemAccess.createMediaKeys();
-  }).then(function(mediaKeys) {
-    var videoElement = document.getElementById(videoElementId);
-
-    if (videoElementId != 'primary-video') {
-      videoElement.setMaxVideoCapabilities('width=1280; height=720');
+async function createMediaKeySystem(isPrimaryVideo, audioContentType, videoContentType) {
+  const keySystems = isPrimaryVideo ? ['com.widevine.alpha'] : ['com.youtube.widevine.l3', 'com.widevine.alpha'];
+  for (keySystem of keySystems) {
+    try {
+      mediaKeySystemAccess = await navigator.requestMediaKeySystemAccess(keySystem, [{
+        'initDataTypes': ['cenc', 'webm'],
+        'audioCapabilities': [{'contentType': audioContentType}],
+        'videoCapabilities': [{'contentType': videoContentType}]}]);
+      return mediaKeySystemAccess.createMediaKeys();
+    } catch {
+      console.log('create keySystem ' + keySystem + ' failed.')
+      continue;
     }
-
-    if (mediaKeys.getMetrics) {
-      console.log('Found getMetrics(), calling it ...');
-      try {
-        mediaKeys.getMetrics();
-        console.log('Calling getMetrics() succeeded.');
-      } catch(e) {
-        console.log('Calling getMetrics() failed.');
-      }
-    }
-
-    videoElement.setMediaKeys(mediaKeys);
-
-    mediaKeySession = mediaKeys.createSession();
-    mediaKeySession.addEventListener('message', function(messageEvent) {
-      var licenseServerUrl = 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD';
-      fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message,
-          function(licenseArrayBuffer) {
-            mediaKeySession.update(extractLicense(licenseArrayBuffer));
-          });
-    });
-
-    videoElement.addEventListener('encrypted', function(encryptedEvent) {
-      mediaKeySession.generateRequest(
-          encryptedEvent.initDataType, encryptedEvent.initData);
-    });
-
-    var mediaSource = new MediaSource();
-    mediaSource.addEventListener('sourceopen', function() {
-      var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType);
-      fetchArrayBuffer('GET',
-                      'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4',
-                      null,
-                      function(videoArrayBuffer) {
-        videoSourceBuffer.appendBuffer(videoArrayBuffer);
-      });
-
-      var audioSourceBuffer = mediaSource.addSourceBuffer(audioContentType);
-      fetchArrayBuffer('GET',
-                      'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4',
-                      null,
-                      function(audioArrayBuffer) {
-        audioSourceBuffer.appendBuffer(audioArrayBuffer);
-      });
-    });
-
-    videoElement.src = URL.createObjectURL(mediaSource);
-    videoElement.play();
-  });
+  }
 }
 
-play('primary-video', 'com.widevine.alpha');
-window.setTimeout(function() {
-  play('secondary-video-1', 'com.youtube.widevine.l3');
-}, 10000);
-window.setTimeout(function() {
-  play('secondary-video-2', 'com.youtube.widevine.l3');
-}, 20000);
-window.setTimeout(function() {
-  play('secondary-video-3', 'com.youtube.widevine.l3');
-}, 30000);
+function createTunnelModeContentType(videoContentType, tunnelModeAttributeValue) {
+  return videoContentType + '; tunnelmode=' + tunnelModeAttributeValue;
+}
+
+function isTunnelModeSupported(videoContentType) {
+  if (!MediaSource.isTypeSupported(videoContentType)) {
+    // If the content type isn't supported at all, it won't be supported in
+    // tunnel mode.
+    return false;
+  }
+  if (MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'invalid'))) {
+    // The implementation doesn't understand the `tunnelmode` attribute.
+    return false;
+  }
+  return MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'true'));
+}
+
+async function play(videoElementId, videoFileId, optionalAudioFileId) {
+  const isPrimaryVideo = videoElementId == 'primary-video';
+
+  videoContentType = MEDIA_FILES[videoFileId].contentType;
+  if (isTunnelModeSupported(videoContentType)) {
+    videoContentType = createTunnelModeContentType(videoContentType, 'true');
+  }
+
+  var mediaKeys = await createMediaKeySystem(isPrimaryVideo, optionalAudioFileId ? MEDIA_FILES[optionalAudioFileId].contentType : MEDIA_FILES['opus_clear'].contentType, videoContentType);
+  var videoElement = document.getElementById(videoElementId);
+
+  if (!isPrimaryVideo && videoElement.setMaxVideoCapabilities) {
+    videoElement.setMaxVideoCapabilities(MEDIA_FILES[videoFileId].maxVideoCapabilities);
+  }
+
+  videoElement.setMediaKeys(mediaKeys);
+
+  mediaKeySession = mediaKeys.createSession();
+  var licenseServerUrl = MEDIA_FILES[videoFileId].licenseUrl;
+  mediaKeySession.addEventListener('message', function(messageEvent) {
+    fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message,
+      function(licenseArrayBuffer) {
+        mediaKeySession.update(extractLicense(licenseArrayBuffer));
+      });
+  });
+
+  videoElement.addEventListener('encrypted', function(encryptedEvent) {
+    mediaKeySession.generateRequest(
+        encryptedEvent.initDataType, encryptedEvent.initData);
+  });
+
+  var mediaSource = new MediaSource();
+  mediaSource.addEventListener('sourceopen', async function() {
+    var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType);
+    var audioSourceBuffer;
+
+    if (optionalAudioFileId) {
+      audioSourceBuffer = mediaSource.addSourceBuffer(MEDIA_FILES[optionalAudioFileId].contentType);
+    }
+
+    var videoArrayBuffer = await fetchMediaData(videoFileId);
+    videoSourceBuffer.appendBuffer(videoArrayBuffer);
+
+    if (audioSourceBuffer) {
+      var audioArrayBuffer = await fetchMediaData(optionalAudioFileId);
+      audioSourceBuffer.appendBuffer(audioArrayBuffer);
+    }
+  });
+
+  videoElement.src = URL.createObjectURL(mediaSource);
+  videoElement.play();
+}
+
+function getGetParameters() {
+  var parsedParameters = {};
+
+  const urlComponents = window.location.href.split('?');
+  if (urlComponents.length < 2) {
+    return parsedParameters;
+  }
+
+  const query = urlComponents[1];
+  const parameters = query.split('&');
+
+  for (parameter of parameters) {
+    const split = parameter.split('=');
+    if (split.length == 0) {
+      continue;
+    }
+    if (split.length == 1) {
+      parsedParameters[split[0]] = '';
+    } else {
+      parsedParameters[split[0]] = split[1];
+    }
+  }
+
+  return parsedParameters;
+}
+
+function populateMediaFileIds() {
+  var mediaFileIds = [];
+  const getParameters = getGetParameters();
+
+  mediaFileIds['video0'] = getParameters['video0'] ?? 'vp9_720p_60fps_drm';
+  mediaFileIds['video1'] = getParameters['video1'] ?? 'h264_720p_24fps_drm';
+  mediaFileIds['video2'] = getParameters['video2'] ?? 'vp9_720p_60fps_drm';
+  mediaFileIds['video3'] = getParameters['video3'] ?? 'h264_720p_24fps_drm';
+  mediaFileIds['audio'] = getParameters['audio'] ?? 'opus_clear';
+
+  return mediaFileIds;
+}
+
+async function prefetchMediaData(mediaFileIds) {
+  for (mediaFileId of Object.keys(mediaFileIds)) {
+    await fetchMediaData(mediaFileIds[mediaFileId]);
+  }
+}
+
+async function main() {
+  if (window.h5vcc && window.h5vcc.settings) {
+    h5vcc.settings.set('MediaSource.EnableAvoidCopyingArrayBuffer', 1);
+  }
+
+  const mediaFileIds = populateMediaFileIds();
+  await prefetchMediaData(mediaFileIds);
+
+  play('primary-video', mediaFileIds['video0'], mediaFileIds['audio']);
+  window.setTimeout(function() {
+    play('secondary-video-1', mediaFileIds['video1']);
+  }, 10000);
+  window.setTimeout(function() {
+    play('secondary-video-2', mediaFileIds['video2']);
+  }, 20000);
+  window.setTimeout(function() {
+    play('secondary-video-3', mediaFileIds['video3']);
+  }, 30000);
+}
+
+
+main();
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index ef21c62..9ddbdcb 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -161,6 +161,22 @@
 base::LazyInstance<NonTrivialStaticFields>::DestructorAtExit
     non_trivial_static_fields = LAZY_INSTANCE_INITIALIZER;
 
+void InvalidateScrollAreaCacheOfAncestors(Node* node) {
+  for (Node* ancestor_node = node; ancestor_node;
+       ancestor_node = ancestor_node->parent_node()) {
+    Element* ancestor_element = ancestor_node->AsElement();
+    if (!ancestor_element) {
+      continue;
+    }
+    HTMLElement* ancestor_html_element = ancestor_element->AsHTMLElement();
+    if (!ancestor_html_element) {
+      continue;
+    }
+    if (ancestor_html_element->layout_boxes())
+      ancestor_html_element->layout_boxes()->scroll_area_cache().reset();
+  }
+}
+
 }  // namespace
 
 void HTMLElement::RuleMatchingState::Clear() {
@@ -1167,6 +1183,7 @@
 }
 
 void HTMLElement::InvalidateLayoutBoxSizes() {
+  InvalidateScrollAreaCacheOfAncestors(parent_node());
   if (layout_boxes_) {
     layout_boxes_->InvalidateSizes();
 
@@ -1232,6 +1249,9 @@
 
 void HTMLElement::OnUiNavScroll(SbTimeMonotonic /* time */) {
   Document* document = node_document();
+  if (document->hidden()) {
+    return;
+  }
   scoped_refptr<Window> window(document ? document->window() : nullptr);
   DispatchEvent(new UIEvent(base::Tokens::scroll(), web::Event::kNotBubbles,
                             web::Event::kNotCancelable, window));
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index c3376ea..da10b2e 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -1755,6 +1755,10 @@
                              exception_state);
     return;
   }
+
+  LOG(INFO) << "max video capabilities is changed from \""
+            << max_video_capabilities_ << "\" to \"" << max_video_capabilities
+            << "\"";
   max_video_capabilities_ = max_video_capabilities;
 }
 
diff --git a/cobalt/dom/layout_boxes.h b/cobalt/dom/layout_boxes.h
index 440e982..b81a446 100644
--- a/cobalt/dom/layout_boxes.h
+++ b/cobalt/dom/layout_boxes.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_DOM_LAYOUT_BOXES_H_
 #define COBALT_DOM_LAYOUT_BOXES_H_
 
+#include <utility>
+
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/directionality.h"
 #include "cobalt/dom/dom_rect_list.h"
@@ -89,6 +91,9 @@
   // Invalidate the layout box's render tree nodes.
   virtual void InvalidateRenderTreeNodes() = 0;
 
+  virtual base::Optional<std::pair<dom::Directionality, math::RectF>>&
+  scroll_area_cache() = 0;
+
   // Update the navigation item associated with the layout boxes.
   virtual void SetUiNavItem(
       const scoped_refptr<ui_navigation::NavItem>& item) = 0;
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 039303f..1ad5a59 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -521,6 +521,39 @@
   tracer->Trace(video_tracks_);
 }
 
+size_t SourceBuffer::memory_limit(
+    script::ExceptionState* exception_state) const {
+  if (!chunk_demuxer_) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return 0;
+  }
+
+  size_t memory_limit = chunk_demuxer_->GetSourceBufferStreamMemoryLimit(id_);
+
+  if (memory_limit == 0) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+  }
+  return memory_limit;
+}
+
+void SourceBuffer::set_memory_limit(size_t memory_limit,
+                                    script::ExceptionState* exception_state) {
+  if (!chunk_demuxer_) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return;
+  }
+
+  if (memory_limit == 0) {
+    web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                             exception_state);
+    return;
+  }
+  chunk_demuxer_->SetSourceBufferStreamMemoryLimit(id_, memory_limit);
+}
+
 void SourceBuffer::OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
   if (!first_initialization_segment_received_) {
     media_source_->SetSourceBufferActive(this, true);
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index 5869760..0da8e81 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -144,6 +144,9 @@
   DEFINE_WRAPPABLE_TYPE(SourceBuffer);
   void TraceMembers(script::Tracer* tracer) override;
 
+  size_t memory_limit(script::ExceptionState* exception_state) const;
+  void set_memory_limit(size_t limit, script::ExceptionState* exception_state);
+
  private:
   typedef ::media::MediaTracks MediaTracks;
   typedef script::ArrayBuffer ArrayBuffer;
diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl
index 0d81b03..a963802 100644
--- a/cobalt/dom/source_buffer.idl
+++ b/cobalt/dom/source_buffer.idl
@@ -39,4 +39,10 @@
   // `InvalidStateError` if the SourceBuffer object has been removed from the
   // MediaSource object.
   [RaisesException] readonly attribute double writeHead;
+
+  // Non standard stream memory limit modifier. This will override the default
+  // stream memory limit which is tied to the resolution of the video.
+  // This will be passed down to the SourceBufferStream associated with this
+  // instance.
+  [RaisesException] attribute unsigned long long memoryLimit;
 };
diff --git a/cobalt/dom/testing/mock_layout_boxes.h b/cobalt/dom/testing/mock_layout_boxes.h
index 7065808..3d00913 100644
--- a/cobalt/dom/testing/mock_layout_boxes.h
+++ b/cobalt/dom/testing/mock_layout_boxes.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_
 #define COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_
 
+#include <utility>
+
 #include "cobalt/dom/layout_boxes.h"
 
 namespace cobalt {
@@ -57,6 +59,8 @@
   MOCK_METHOD0(InvalidateSizes, void());
   MOCK_METHOD0(InvalidateCrossReferences, void());
   MOCK_METHOD0(InvalidateRenderTreeNodes, void());
+  MOCK_METHOD0(scroll_area_cache,
+               base::Optional<std::pair<dom::Directionality, math::RectF>>&());
   MOCK_METHOD1(SetUiNavItem,
                void(const scoped_refptr<ui_navigation::NavItem>& item));
 };
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index 901f668..1de4356 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -45,6 +45,8 @@
     "h5vcc_event_listener_container.h",
     "h5vcc_metrics.cc",
     "h5vcc_metrics.h",
+    "h5vcc_net_log.cc",
+    "h5vcc_net_log.h",
     "h5vcc_platform_service.cc",
     "h5vcc_platform_service.h",
     "h5vcc_runtime.cc",
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc
index 8d957b8..ebf7b56 100644
--- a/cobalt/h5vcc/h5vcc.cc
+++ b/cobalt/h5vcc/h5vcc.cc
@@ -31,7 +31,8 @@
   audio_config_array_ = new H5vccAudioConfigArray();
   c_val_ = new dom::CValView();
   crash_log_ = new H5vccCrashLog();
-  metrics_ = new H5vccMetrics(settings.persistent_settings);
+  metrics_ =
+      new H5vccMetrics(settings.persistent_settings, settings.event_dispatcher);
   runtime_ = new H5vccRuntime(settings.event_dispatcher);
   settings_ =
       new H5vccSettings(settings.set_web_setting_func, settings.media_module,
@@ -44,6 +45,7 @@
   storage_ =
       new H5vccStorage(settings.network_module, settings.persistent_settings);
   trace_event_ = new H5vccTraceEvent();
+  net_log_ = new H5vccNetLog(settings.network_module);
 #if SB_IS(EVERGREEN)
   updater_ = new H5vccUpdater(settings.updater_module);
   system_ = new H5vccSystem(updater_);
@@ -81,6 +83,7 @@
   tracer->Trace(storage_);
   tracer->Trace(system_);
   tracer->Trace(trace_event_);
+  tracer->Trace(net_log_);
 #if SB_IS(EVERGREEN)
   tracer->Trace(updater_);
 #endif
diff --git a/cobalt/h5vcc/h5vcc.h b/cobalt/h5vcc/h5vcc.h
index 0d60a7c..0852eaa 100644
--- a/cobalt/h5vcc/h5vcc.h
+++ b/cobalt/h5vcc/h5vcc.h
@@ -26,6 +26,7 @@
 #include "cobalt/h5vcc/h5vcc_audio_config_array.h"
 #include "cobalt/h5vcc/h5vcc_crash_log.h"
 #include "cobalt/h5vcc/h5vcc_metrics.h"
+#include "cobalt/h5vcc/h5vcc_net_log.h"
 #include "cobalt/h5vcc/h5vcc_runtime.h"
 #include "cobalt/h5vcc/h5vcc_settings.h"
 #include "cobalt/h5vcc/h5vcc_storage.h"
@@ -89,6 +90,7 @@
   const scoped_refptr<H5vccTraceEvent>& trace_event() const {
     return trace_event_;
   }
+  const scoped_refptr<H5vccNetLog>& net_log() const { return net_log_; }
 #if SB_IS(EVERGREEN)
   const scoped_refptr<H5vccUpdater>& updater() const { return updater_; }
 #endif
@@ -108,6 +110,7 @@
   scoped_refptr<H5vccStorage> storage_;
   scoped_refptr<H5vccSystem> system_;
   scoped_refptr<H5vccTraceEvent> trace_event_;
+  scoped_refptr<H5vccNetLog> net_log_;
 #if SB_IS(EVERGREEN)
   scoped_refptr<H5vccUpdater> updater_;
 #endif
diff --git a/cobalt/h5vcc/h5vcc.idl b/cobalt/h5vcc/h5vcc.idl
index 188e66b..2beb5dc 100644
--- a/cobalt/h5vcc/h5vcc.idl
+++ b/cobalt/h5vcc/h5vcc.idl
@@ -42,6 +42,7 @@
   readonly attribute H5vccStorage storage;
   readonly attribute H5vccSystem system;
   readonly attribute H5vccTraceEvent traceEvent;
+  readonly attribute H5vccNetLog netLog;
   [Conditional=SB_IS_EVERGREEN]
       readonly attribute H5vccUpdater updater;
 };
diff --git a/cobalt/h5vcc/h5vcc_metrics.cc b/cobalt/h5vcc/h5vcc_metrics.cc
index 0c9062a..583eafd 100644
--- a/cobalt/h5vcc/h5vcc_metrics.cc
+++ b/cobalt/h5vcc/h5vcc_metrics.cc
@@ -17,6 +17,9 @@
 #include <string>
 
 #include "base/values.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
 #include "cobalt/browser/metrics/cobalt_metrics_service_client.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
@@ -25,17 +28,34 @@
 namespace cobalt {
 namespace h5vcc {
 
+
+H5vccMetrics::H5vccMetrics(
+    persistent_storage::PersistentSettings* persistent_settings,
+    base::EventDispatcher* event_dispatcher)
+    : task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      persistent_settings_(persistent_settings),
+      event_dispatcher_(event_dispatcher) {
+  DCHECK(event_dispatcher_);
+  on_metric_upload_event_callback_ =
+      base::Bind(&H5vccMetrics::OnMetricUploadEvent, base::Unretained(this));
+  event_dispatcher_->AddEventCallback(base::OnMetricUploadEvent::TypeId(),
+                                      on_metric_upload_event_callback_);
+}
+
+H5vccMetrics::~H5vccMetrics() {
+  event_dispatcher_->RemoveEventCallback(base::OnMetricUploadEvent::TypeId(),
+                                         on_metric_upload_event_callback_);
+}
+
+void H5vccMetrics::OnMetricUploadEvent(const base::Event* event) {
+  std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event(
+      new base::OnMetricUploadEvent(event));
+  RunEventHandler(on_metric_upload_event->metric_type(),
+                  on_metric_upload_event->serialized_proto());
+}
+
 void H5vccMetrics::OnMetricEvent(
     const h5vcc::MetricEventHandlerWrapper::ScriptValue& event_handler) {
-  if (!uploader_callback_) {
-    run_event_handler_callback_ = std::make_unique<
-        cobalt::browser::metrics::CobaltMetricsUploaderCallback>(
-        base::BindRepeating(&H5vccMetrics::RunEventHandler,
-                            base::Unretained(this)));
-    browser::metrics::CobaltMetricsServicesManager::GetInstance()
-        ->SetOnUploadHandler(run_event_handler_callback_.get());
-  }
-
   uploader_callback_ =
       new h5vcc::MetricEventHandlerWrapper(this, event_handler);
 }
@@ -43,16 +63,20 @@
 void H5vccMetrics::RunEventHandler(
     const cobalt::h5vcc::H5vccMetricType& metric_type,
     const std::string& serialized_proto) {
-  task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&H5vccMetrics::RunEventHandlerInternal, base::Unretained(this),
-                 metric_type, serialized_proto));
+  if (task_runner_ && task_runner_->HasAtLeastOneRef()) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&H5vccMetrics::RunEventHandlerInternal,
+                   base::Unretained(this), metric_type, serialized_proto));
+  }
 }
 
 void H5vccMetrics::RunEventHandlerInternal(
     const cobalt::h5vcc::H5vccMetricType& metric_type,
     const std::string& serialized_proto) {
-  uploader_callback_->callback.value().Run(metric_type, serialized_proto);
+  if (uploader_callback_ != nullptr && uploader_callback_->HasAtLeastOneRef()) {
+    uploader_callback_->callback.value().Run(metric_type, serialized_proto);
+  }
 }
 
 void H5vccMetrics::Enable() { ToggleMetricsEnabled(true); }
diff --git a/cobalt/h5vcc/h5vcc_metrics.h b/cobalt/h5vcc/h5vcc_metrics.h
index a8f69ea..93a444a 100644
--- a/cobalt/h5vcc/h5vcc_metrics.h
+++ b/cobalt/h5vcc/h5vcc_metrics.h
@@ -20,7 +20,8 @@
 
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "cobalt/h5vcc/metric_event_handler_wrapper.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
@@ -43,14 +44,15 @@
   typedef MetricEventHandler H5vccMetricEventHandler;
 
   explicit H5vccMetrics(
-      persistent_storage::PersistentSettings* persistent_settings)
-      : task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        persistent_settings_(persistent_settings) {}
+      persistent_storage::PersistentSettings* persistent_settings,
+      base::EventDispatcher* event_dispatcher);
+
+  ~H5vccMetrics();
 
   H5vccMetrics(const H5vccMetrics&) = delete;
   H5vccMetrics& operator=(const H5vccMetrics&) = delete;
 
-  // Binds an event handler that will be invoked every time Cobalt wants to
+  // Binds a JS event handler that will be invoked every time Cobalt wants to
   // upload a metrics payload.
   void OnMetricEvent(
       const MetricEventHandlerWrapper::ScriptValue& event_handler);
@@ -81,14 +83,20 @@
       const cobalt::h5vcc::H5vccMetricType& metric_type,
       const std::string& serialized_proto);
 
-  scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_;
+  // Handler method triggered when EventDispatcher sends OnMetricUploadEvents.
+  void OnMetricUploadEvent(const base::Event* event);
 
-  std::unique_ptr<cobalt::browser::metrics::CobaltMetricsUploaderCallback>
-      run_event_handler_callback_;
+  scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_;
 
   scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
 
   persistent_storage::PersistentSettings* persistent_settings_;
+
+  // Non-owned reference used to receive application event callbacks, namely
+  // metric log upload events.
+  base::EventDispatcher* event_dispatcher_;
+
+  base::EventCallback on_metric_upload_event_callback_;
 };
 
 }  // namespace h5vcc
diff --git a/cobalt/h5vcc/h5vcc_net_log.cc b/cobalt/h5vcc/h5vcc_net_log.cc
new file mode 100644
index 0000000..8a5b3a2
--- /dev/null
+++ b/cobalt/h5vcc/h5vcc_net_log.cc
@@ -0,0 +1,43 @@
+// Copyright 2023 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/h5vcc/h5vcc_net_log.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "cobalt/base/cobalt_paths.h"
+#include "cobalt/network/cobalt_net_log.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+H5vccNetLog::H5vccNetLog(cobalt::network::NetworkModule* network_module)
+    : network_module_{network_module} {}
+
+void H5vccNetLog::Start() { network_module_->StartNetLog(); }
+
+void H5vccNetLog::Stop() { network_module_->StopNetLog(); }
+
+std::string H5vccNetLog::StopAndRead() {
+  base::FilePath netlog_path = network_module_->StopNetLog();
+  std::string netlog_output{};
+  if (!netlog_path.empty()) {
+    ReadFileToString(netlog_path, &netlog_output);
+  }
+  return netlog_output;
+}
+
+
+}  // namespace h5vcc
+}  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_net_log.h b/cobalt/h5vcc/h5vcc_net_log.h
new file mode 100644
index 0000000..801839b
--- /dev/null
+++ b/cobalt/h5vcc/h5vcc_net_log.h
@@ -0,0 +1,45 @@
+// Copyright 2023 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_H5VCC_H5VCC_NET_LOG_H_
+#define COBALT_H5VCC_H5VCC_NET_LOG_H_
+
+#include <string>
+
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+class H5vccNetLog : public script::Wrappable {
+ public:
+  explicit H5vccNetLog(cobalt::network::NetworkModule* network_module);
+
+  void Start();
+  void Stop();
+  std::string StopAndRead();
+
+  DEFINE_WRAPPABLE_TYPE(H5vccNetLog);
+
+ private:
+  cobalt::network::NetworkModule* network_module_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(H5vccNetLog);
+};
+
+}  // namespace h5vcc
+}  // namespace cobalt
+
+#endif  // COBALT_H5VCC_H5VCC_NET_LOG_H_
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/cobalt/h5vcc/h5vcc_net_log.idl
similarity index 60%
copy from starboard/shared/posix/time_zone_get_name.cc
copy to cobalt/h5vcc/h5vcc_net_log.idl
index 5a1e013..2c60792 100644
--- a/starboard/shared/posix/time_zone_get_name.cc
+++ b/cobalt/h5vcc/h5vcc_net_log.idl
@@ -1,4 +1,4 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+// Copyright 2023 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.
@@ -12,13 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/time_zone.h"
-
-#include <time.h>
-
-const char* SbTimeZoneGetName() {
-  // TODO: Using tzname assumes that tzset() has been called at some
-  // point. That should happen as part of Starboard's main loop initialization,
-  // but that doesn't exist yet.
-  return tzname[0];
-}
+interface H5vccNetLog {
+  void start();
+  void stop();
+  DOMString stopAndRead();
+};
diff --git a/cobalt/h5vcc/h5vcc_runtime.cc b/cobalt/h5vcc/h5vcc_runtime.cc
index 15f682b..1ec7fcc 100644
--- a/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/cobalt/h5vcc/h5vcc_runtime.cc
@@ -24,7 +24,8 @@
 namespace h5vcc {
 H5vccRuntime::H5vccRuntime(base::EventDispatcher* event_dispatcher)
     : event_dispatcher_(event_dispatcher),
-      message_loop_(base::MessageLoop::current()) {
+      task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+  DCHECK(task_runner_);
   on_deep_link_ = new H5vccDeepLinkEventTarget(
       base::Bind(&H5vccRuntime::GetUnconsumedDeepLink, base::Unretained(this)));
   on_pause_ = new H5vccRuntimeEventTarget;
@@ -87,8 +88,9 @@
 void H5vccRuntime::OnEventForDeepLink(const base::Event* event) {
   std::unique_ptr<base::DeepLinkEvent> deep_link_event(
       new base::DeepLinkEvent(event));
-  if (base::MessageLoop::current() != message_loop_) {
-    message_loop_->task_runner()->PostTask(
+  if (!task_runner_) return;
+  if (!task_runner_->RunsTasksInCurrentSequence()) {
+    task_runner_->PostTask(
         FROM_HERE,
         base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this),
                    base::Passed(std::move(deep_link_event))));
diff --git a/cobalt/h5vcc/h5vcc_runtime.h b/cobalt/h5vcc/h5vcc_runtime.h
index 560543f..6c0ebc1 100644
--- a/cobalt/h5vcc/h5vcc_runtime.h
+++ b/cobalt/h5vcc/h5vcc_runtime.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/base/deep_link_event.h"
 #include "cobalt/base/event_dispatcher.h"
@@ -63,9 +64,9 @@
   base::EventCallback deep_link_event_callback_;
   base::OnceClosure consumed_callback_;
 
-  // Track the message loop that created this object so deep link events are
-  // handled from the same thread.
-  base::MessageLoop* message_loop_;
+  // Track the task runner from where this object is created so deep link events
+  // are handled from the same task runner.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   // Thread checker ensures all calls to DOM element are made from the same
   // thread that it is created in.
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc
index e6c7b26..4c47039 100644
--- a/cobalt/h5vcc/h5vcc_settings.cc
+++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -23,16 +23,6 @@
 namespace cobalt {
 namespace h5vcc {
 
-namespace {
-// Only including needed video combinations for the moment.
-// option 0 disables all video codecs except h264
-// option 1 disables all video codecs except av1
-// option 2 disables all video codecs except vp9
-constexpr std::array<const char*, 3> kDisableCodecCombinations{
-    {"av01;hev1;hvc1;vp09;vp8.vp9", "avc1;avc3;hev1;hvc1;vp09;vp8;vp9",
-     "av01;avc1;avc3;hev1;hvc1;vp8"}};
-};  // namespace
-
 H5vccSettings::H5vccSettings(
     const SetSettingFunc& set_web_setting_func,
     cobalt::media::MediaModule* media_module,
@@ -56,62 +46,47 @@
       persistent_settings_(persistent_settings) {
 }
 
-bool H5vccSettings::Set(const std::string& name, int32 value) const {
+bool H5vccSettings::Set(const std::string& name, SetValueType value) const {
   const char kMediaPrefix[] = "Media.";
-  const char kDisableMediaCodec[] = "DisableMediaCodec";
+  const char kMediaCodecBlockList[] = "MediaCodecBlockList";
   const char kNavigatorUAData[] = "NavigatorUAData";
-  const char kClientHintHeaders[] = "ClientHintHeaders";
   const char kQUIC[] = "QUIC";
 
 #if SB_IS(EVERGREEN)
   const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes";
 #endif
 
-  if (name == kDisableMediaCodec &&
-      value < static_cast<int32>(kDisableCodecCombinations.size())) {
-    can_play_type_handler_->SetDisabledMediaCodecs(
-        kDisableCodecCombinations[value]);
+  if (name == kMediaCodecBlockList && value.IsType<std::string>() &&
+      value.AsType<std::string>().size() < 256) {
+    can_play_type_handler_->SetDisabledMediaCodecs(value.AsType<std::string>());
     return true;
   }
 
-  if (set_web_setting_func_ && set_web_setting_func_.Run(name, value)) {
+  if (set_web_setting_func_ && value.IsType<int32>() &&
+      set_web_setting_func_.Run(name, value.AsType<int32>())) {
     return true;
   }
 
-  if (name.rfind(kMediaPrefix, 0) == 0) {
-    return media_module_ ? media_module_->SetConfiguration(
-                               name.substr(strlen(kMediaPrefix)), value)
-                         : false;
+  if (name.rfind(kMediaPrefix, 0) == 0 && value.IsType<int32>()) {
+    return media_module_
+               ? media_module_->SetConfiguration(
+                     name.substr(strlen(kMediaPrefix)), value.AsType<int32>())
+               : false;
   }
 
-  if (name.compare(kNavigatorUAData) == 0 && value == 1) {
+  if (name.compare(kNavigatorUAData) == 0 && value.IsType<int32>() &&
+      value.AsType<int32>() == 1) {
     global_environment_->BindTo("userAgentData", user_agent_data_, "navigator");
     return true;
   }
 
-  if (name.compare(kClientHintHeaders) == 0) {
-    if (!persistent_settings_) {
-      return false;
-    } else {
-      persistent_settings_->SetPersistentSetting(
-          network::kClientHintHeadersEnabledPersistentSettingsKey,
-          std::make_unique<base::Value>(value));
-      // Tell NetworkModule (if exists) to re-query persistent settings.
-      if (network_module_) {
-        network_module_
-            ->SetEnableClientHintHeadersFlagsFromPersistentSettings();
-      }
-      return true;
-    }
-  }
-
-  if (name.compare(kQUIC) == 0) {
+  if (name.compare(kQUIC) == 0 && value.IsType<int32>()) {
     if (!persistent_settings_) {
       return false;
     } else {
       persistent_settings_->SetPersistentSetting(
           network::kQuicEnabledPersistentSettingsKey,
-          std::make_unique<base::Value>(value != 0));
+          std::make_unique<base::Value>(value.AsType<int32>() != 0));
       // Tell NetworkModule (if exists) to re-query persistent settings.
       if (network_module_) {
         network_module_->SetEnableQuicFromPersistentSettings();
@@ -121,8 +96,8 @@
   }
 
 #if SB_IS(EVERGREEN)
-  if (name.compare(kUpdaterMinFreeSpaceBytes) == 0) {
-    updater_module_->SetMinFreeSpaceBytes(value);
+  if (name.compare(kUpdaterMinFreeSpaceBytes) == 0 && value.IsType<int32>()) {
+    updater_module_->SetMinFreeSpaceBytes(value.AsType<int32>());
     return true;
   }
 #endif
diff --git a/cobalt/h5vcc/h5vcc_settings.h b/cobalt/h5vcc/h5vcc_settings.h
index 34e7a44..f7e10cf 100644
--- a/cobalt/h5vcc/h5vcc_settings.h
+++ b/cobalt/h5vcc/h5vcc_settings.h
@@ -21,6 +21,7 @@
 #include "cobalt/network/network_module.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/script/union_type.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/navigator_ua_data.h"
 
@@ -39,6 +40,8 @@
   typedef base::Callback<bool(const std::string& name, int value)>
       SetSettingFunc;
 
+  typedef script::UnionType2<int32, std::string> SetValueType;
+
   H5vccSettings(const SetSettingFunc& set_web_setting_func,
                 cobalt::media::MediaModule* media_module,
                 cobalt::media::CanPlayTypeHandler* can_play_type_handler,
@@ -53,7 +56,7 @@
   // Returns true when the setting is set successfully or if the setting has
   // already been set to the expected value.  Returns false when the setting is
   // invalid or not set to the expected value.
-  bool Set(const std::string& name, int32 value) const;
+  bool Set(const std::string& name, SetValueType value) const;
 
   DEFINE_WRAPPABLE_TYPE(H5vccSettings);
 
diff --git a/cobalt/h5vcc/h5vcc_settings.idl b/cobalt/h5vcc/h5vcc_settings.idl
index fe250c8..34650a9 100644
--- a/cobalt/h5vcc/h5vcc_settings.idl
+++ b/cobalt/h5vcc/h5vcc_settings.idl
@@ -13,5 +13,5 @@
 // limitations under the License.
 
 interface H5vccSettings {
-  boolean set(DOMString name, long value);
+  boolean set(DOMString name, (long or DOMString) value);
 };
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index dac5b5c..346c104 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -74,13 +74,12 @@
 }
 
 void DeleteCacheResourceTypeDirectory(disk_cache::ResourceType type) {
-  auto metadata = disk_cache::kTypeMetadata[type];
+  std::string directory = disk_cache::defaults::GetSubdirectory(type);
   std::vector<char> cache_dir(kSbFileMaxPath + 1, 0);
   SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(),
                   kSbFileMaxPath);
   base::FilePath cache_type_dir =
-      base::FilePath(cache_dir.data())
-          .Append(FILE_PATH_LITERAL(metadata.directory));
+      base::FilePath(cache_dir.data()).Append(FILE_PATH_LITERAL(directory));
   ClearDirectory(cache_type_dir);
 }
 
@@ -313,6 +312,11 @@
 
 void H5vccStorage::SetAndSaveQuotaForBackend(disk_cache::ResourceType type,
                                              uint32_t bytes) {
+  if (disk_cache::settings::GetQuota(type) == bytes) {
+    return;
+  }
+  disk_cache::settings::SetQuota(type, bytes);
+  network_module_->url_request_context()->UpdateCacheSizeSetting(type, bytes);
   if (cache_backend_) {
     cache_backend_->UpdateSizes(type, bytes);
 
@@ -332,21 +336,19 @@
     return quota;
   }
 
-  quota.set_other(cache_backend_->GetQuota(disk_cache::kOther));
-  quota.set_html(cache_backend_->GetQuota(disk_cache::kHTML));
-  quota.set_css(cache_backend_->GetQuota(disk_cache::kCSS));
-  quota.set_image(cache_backend_->GetQuota(disk_cache::kImage));
-  quota.set_font(cache_backend_->GetQuota(disk_cache::kFont));
-  quota.set_splash(cache_backend_->GetQuota(disk_cache::kSplashScreen));
+  quota.set_other(disk_cache::settings::GetQuota(disk_cache::kOther));
+  quota.set_html(disk_cache::settings::GetQuota(disk_cache::kHTML));
+  quota.set_css(disk_cache::settings::GetQuota(disk_cache::kCSS));
+  quota.set_image(disk_cache::settings::GetQuota(disk_cache::kImage));
+  quota.set_font(disk_cache::settings::GetQuota(disk_cache::kFont));
+  quota.set_splash(disk_cache::settings::GetQuota(disk_cache::kSplashScreen));
   quota.set_uncompiled_js(
-      cache_backend_->GetQuota(disk_cache::kUncompiledScript));
+      disk_cache::settings::GetQuota(disk_cache::kUncompiledScript));
   quota.set_compiled_js(
-      cobalt::cache::Cache::GetInstance()
-          ->GetMaxCacheStorageInBytes(disk_cache::kCompiledScript)
-          .value());
-  quota.set_cache_api(cache_backend_->GetQuota(disk_cache::kCacheApi));
+      disk_cache::settings::GetQuota(disk_cache::kCompiledScript));
+  quota.set_cache_api(disk_cache::settings::GetQuota(disk_cache::kCacheApi));
   quota.set_service_worker_js(
-      cache_backend_->GetQuota(disk_cache::kServiceWorkerScript));
+      disk_cache::settings::GetQuota(disk_cache::kServiceWorkerScript));
 
   uint32_t max_quota_size = 24 * 1024 * 1024;
 #if SB_API_VERSION >= 14
@@ -366,7 +368,7 @@
       disk_cache::kCacheEnabledPersistentSettingsKey,
       std::make_unique<base::Value>(true));
 
-  cobalt::cache::Cache::GetInstance()->set_enabled(true);
+  disk_cache::settings::SetCacheEnabled(true);
 
   if (http_cache_) {
     http_cache_->set_mode(net::HttpCache::Mode::NORMAL);
@@ -378,7 +380,7 @@
       disk_cache::kCacheEnabledPersistentSettingsKey,
       std::make_unique<base::Value>(false));
 
-  cobalt::cache::Cache::GetInstance()->set_enabled(false);
+  disk_cache::settings::SetCacheEnabled(false);
 
   if (http_cache_) {
     http_cache_->set_mode(net::HttpCache::Mode::DISABLE);
diff --git a/cobalt/h5vcc/h5vcc_system.cc b/cobalt/h5vcc/h5vcc_system.cc
index 89c9d9a..addb717 100644
--- a/cobalt/h5vcc/h5vcc_system.cc
+++ b/cobalt/h5vcc/h5vcc_system.cc
@@ -21,6 +21,10 @@
 #include "starboard/common/system_property.h"
 #include "starboard/system.h"
 
+#if SB_API_VERSION < 14
+#include "starboard/extension/ifa.h"
+#endif  // SB_API_VERSION < 14
+
 using starboard::kSystemPropertyMaxLength;
 
 namespace cobalt {
@@ -57,27 +61,56 @@
 
 std::string H5vccSystem::advertising_id() const {
   std::string result;
-#if SB_API_VERSION >= 14
   char property[kSystemPropertyMaxLength] = {0};
+#if SB_API_VERSION >= 14
   if (!SbSystemGetProperty(kSbSystemPropertyAdvertisingId, property,
                            SB_ARRAY_SIZE_INT(property))) {
     DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
   } else {
     result = property;
   }
+#else
+  static auto const* ifa_extension =
+      static_cast<const StarboardExtensionIfaApi*>(
+          SbSystemGetExtension(kStarboardExtensionIfaName));
+  if (ifa_extension &&
+      strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 &&
+      ifa_extension->version >= 1) {
+    if (!ifa_extension->GetAdvertisingId(property,
+                                         SB_ARRAY_SIZE_INT(property))) {
+      DLOG(FATAL) << "Failed to get AdvertisingId from IFA extension.";
+    } else {
+      result = property;
+    }
+  }
 #endif
   return result;
 }
 bool H5vccSystem::limit_ad_tracking() const {
   bool result = false;
-#if SB_API_VERSION >= 14
   char property[kSystemPropertyMaxLength] = {0};
+#if SB_API_VERSION >= 14
   if (!SbSystemGetProperty(kSbSystemPropertyLimitAdTracking, property,
                            SB_ARRAY_SIZE_INT(property))) {
     DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
   } else {
     result = std::atoi(property);
   }
+#else
+  static auto const* ifa_extension =
+      static_cast<const StarboardExtensionIfaApi*>(
+          SbSystemGetExtension(kStarboardExtensionIfaName));
+
+  if (ifa_extension &&
+      strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 &&
+      ifa_extension->version >= 1) {
+    if (!ifa_extension->GetLimitAdTracking(property,
+                                           SB_ARRAY_SIZE_INT(property))) {
+      DLOG(FATAL) << "Failed to get LimitAdTracking from IFA extension.";
+    } else {
+      result = std::atoi(property);
+    }
+  }
 #endif
   return result;
 }
diff --git a/cobalt/layout/layout_boxes.h b/cobalt/layout/layout_boxes.h
index 42ee844..919e57e 100644
--- a/cobalt/layout/layout_boxes.h
+++ b/cobalt/layout/layout_boxes.h
@@ -67,6 +67,11 @@
   //
   const Boxes& boxes() { return boxes_; }
 
+  base::Optional<std::pair<dom::Directionality, math::RectF>>&
+  scroll_area_cache() override {
+    return scroll_area_cache_;
+  }
+
  private:
   // Returns the bounding rectangle of the border edges of the boxes.
   math::RectF GetBoundingBorderRectangle() const;
diff --git a/cobalt/layout/topmost_event_target.cc b/cobalt/layout/topmost_event_target.cc
index 3981de4..a67dd1ad 100644
--- a/cobalt/layout/topmost_event_target.cc
+++ b/cobalt/layout/topmost_event_target.cc
@@ -142,6 +142,15 @@
   return possible_scroll_targets;
 }
 
+bool HasAnyScrollTarget(
+    const dom::PossibleScrollTargets* possible_scroll_targets) {
+  if (!possible_scroll_targets) {
+    return false;
+  }
+  return possible_scroll_targets->left || possible_scroll_targets->right ||
+         possible_scroll_targets->up || possible_scroll_targets->down;
+}
+
 scoped_refptr<dom::HTMLElement> FindFirstElementWithScrollType(
     dom::PossibleScrollTargets* possible_scroll_targets,
     ui_navigation::scroll_engine::ScrollType major_scroll_axis,
@@ -280,15 +289,6 @@
   }
 }
 
-void SendStateChangeLeaveEvents(
-    bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element,
-    dom::PointerEventInit* event_init) {
-  scoped_refptr<dom::HTMLElement> target_element = nullptr;
-  scoped_refptr<dom::Element> nearest_common_ancestor = nullptr;
-  SendStateChangeLeaveEvents(is_pointer_event, previous_element, target_element,
-                             nearest_common_ancestor, event_init);
-}
-
 void SendStateChangeEnterEvents(
     bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element,
     scoped_refptr<dom::HTMLElement> target_element,
@@ -420,7 +420,10 @@
       new dom::PointerEvent(base::Tokens::pointercancel(), web::Event::kBubbles,
                             web::Event::kNotCancelable, view, *event_init));
   bool is_pointer_event = true;
-  SendStateChangeLeaveEvents(is_pointer_event, element, event_init);
+  scoped_refptr<dom::HTMLElement> target_element = nullptr;
+  scoped_refptr<dom::Element> nearest_common_ancestor = nullptr;
+  SendStateChangeLeaveEvents(is_pointer_event, element, target_element,
+                             nearest_common_ancestor, event_init);
 }
 
 math::Matrix3F GetCompleteTransformMatrix(dom::Element* element) {
@@ -563,6 +566,10 @@
         FindPossibleScrollTargets(target_element);
     pointer_state->SetPossibleScrollTargets(
         pointer_id, std::move(initial_possible_scroll_targets));
+    if (HasAnyScrollTarget(initial_possible_scroll_targets.get())) {
+      pointer_state->SetPendingPointerCaptureTargetOverride(pointer_id,
+                                                            target_element);
+    }
 
     auto transform_matrix = GetCompleteTransformMatrix(target_element.get());
     pointer_state->SetClientTransformMatrix(pointer_id, transform_matrix);
@@ -598,8 +605,11 @@
         return;
       }
 
-      DispatchPointerEventsForScrollStart(target_element, event_init);
+      scoped_refptr<dom::HTMLElement> previous_html_element(
+          previous_html_element_weak_);
+      DispatchPointerEventsForScrollStart(previous_html_element, event_init);
       pointer_state->SetWasCancelled(pointer_id);
+      pointer_state->ClearPendingPointerCaptureTargetOverride(pointer_id);
 
       should_clear_pointer_state = true;
       scroll_engine_->thread()->message_loop()->task_runner()->PostTask(
@@ -694,6 +704,12 @@
                       &event_init);
   }
 
+  bool event_was_cancelled = pointer_event && pointer_state->GetWasCancelled(
+                                                  pointer_event->pointer_id());
+  if (pointer_event && pointer_event->type() == base::Tokens::pointerup()) {
+    pointer_state->ClearWasCancelled(pointer_event->pointer_id());
+  }
+
   scoped_refptr<dom::HTMLElement> previous_html_element(
       previous_html_element_weak_);
 
@@ -702,14 +718,10 @@
   scoped_refptr<dom::Element> nearest_common_ancestor(
       GetNearestCommonAncestor(previous_html_element, target_element));
 
-  SendStateChangeLeaveEvents(pointer_event, previous_html_element,
-                             target_element, nearest_common_ancestor,
-                             &event_init);
-
-  bool event_was_cancelled = pointer_event && pointer_state->GetWasCancelled(
-                                                  pointer_event->pointer_id());
-  if (pointer_event && pointer_event->type() == base::Tokens::pointerup()) {
-    pointer_state->ClearWasCancelled(pointer_event->pointer_id());
+  if (!event_was_cancelled) {
+    SendStateChangeLeaveEvents(pointer_event, previous_html_element,
+                               target_element, nearest_common_ancestor,
+                               &event_init);
   }
 
   if (target_element) {
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index 455c37e..01039d3 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -219,7 +219,7 @@
       new browser::UserAgentPlatformInfo());
   std::unique_ptr<browser::ServiceWorkerRegistry> service_worker_registry(
       new browser::ServiceWorkerRegistry(&web_settings, &network_module,
-                                         platform_info.get(), url));
+                                         platform_info.get()));
 
   browser::WebModule::Options web_module_options;
   // Use test runner mode to allow the content itself to dictate when it is
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index 0e7c06d..bde4beb 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -43,6 +43,8 @@
     "base/format_support_query_metrics.h",
     "base/interleaved_sinc_resampler.cc",
     "base/interleaved_sinc_resampler.h",
+    "base/metrics_provider.cc",
+    "base/metrics_provider.h",
     "base/playback_statistics.cc",
     "base/playback_statistics.h",
     "base/sbplayer_bridge.cc",
diff --git a/cobalt/media/base/cval_stats_test.cc b/cobalt/media/base/cval_stats_test.cc
index 673e20c..ceba320 100644
--- a/cobalt/media/base/cval_stats_test.cc
+++ b/cobalt/media/base/cval_stats_test.cc
@@ -12,16 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifdef _WIN32
-#include <Windows.h>
-#else
-#include <unistd.h>
-#endif
+#include "cobalt/media/base/cval_stats.h"
 
 #include <set>
 #include <string>
 
-#include "cobalt/media/base/cval_stats.h"
+#include "starboard/thread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 
@@ -33,21 +29,11 @@
 
 const char kPipelineIdentifier[] = "test_pipeline";
 
-constexpr int kSleepTimeMs = 50;
+constexpr SbTime kSleepTime = 50;  // 50 microseconds
 
 namespace cobalt {
 namespace media {
 
-namespace {
-void sleep_ms(int ms) {
-#ifdef _WIN32
-  Sleep(ms);
-#else
-  usleep(ms);
-#endif
-}
-}  // namespace
-
 TEST(MediaCValStatsTest, InitiallyEmpty) {
   base::CValManager* cvm = base::CValManager::GetInstance();
   EXPECT_TRUE(cvm);
@@ -71,7 +57,8 @@
   CValStats cval_stats_;
 
   cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
-  sleep_ms(kSleepTimeMs);
+  SbThreadSleep(kSleepTime);
+
   cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
 
   base::Optional<std::string> result =
@@ -88,7 +75,7 @@
   cval_stats_.Enable(true);
 
   cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
-  sleep_ms(kSleepTimeMs);
+  SbThreadSleep(kSleepTime);
   cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
 
   base::Optional<std::string> result =
@@ -114,7 +101,7 @@
 
   for (int i = 0; i < kMediaDefaultMaxSamplesBeforeCalculation - 1; i++) {
     cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
-    sleep_ms(kSleepTimeMs);
+    SbThreadSleep(kSleepTime);
     cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
   }
 
@@ -141,7 +128,7 @@
 
   for (int i = 0; i < kMediaDefaultMaxSamplesBeforeCalculation; i++) {
     cval_stats_.StartTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
-    sleep_ms(kSleepTimeMs);
+    SbThreadSleep(kSleepTime);
     cval_stats_.StopTimer(MediaTiming::SbPlayerCreate, kPipelineIdentifier);
   }
 
diff --git a/cobalt/media/base/metrics_provider.cc b/cobalt/media/base/metrics_provider.cc
new file mode 100644
index 0000000..510d5a8
--- /dev/null
+++ b/cobalt/media/base/metrics_provider.cc
@@ -0,0 +1,126 @@
+// Copyright 2023 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/base/metrics_provider.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+
+namespace cobalt {
+namespace media {
+
+using starboard::ScopedLock;
+
+MediaMetricsProvider::~MediaMetricsProvider() {
+  if (!IsInitialized()) return;
+  ReportPipelineUMA();
+}
+
+void MediaMetricsProvider::Initialize(bool is_mse) {
+  if (IsInitialized()) {
+    return;
+  }
+
+  ScopedLock scoped_lock(mutex_);
+  media_info_.emplace(MediaInfo(is_mse));
+}
+
+void MediaMetricsProvider::OnError(const ::media::PipelineStatus status) {
+  DCHECK(IsInitialized());
+  ScopedLock scoped_lock(mutex_);
+  uma_info_.last_pipeline_status = status;
+}
+
+void MediaMetricsProvider::SetHasAudio(AudioCodec audio_codec) {
+  ScopedLock scoped_lock(mutex_);
+  uma_info_.audio_codec = audio_codec;
+  uma_info_.has_audio = true;
+}
+
+void MediaMetricsProvider::SetHasVideo(VideoCodec video_codec) {
+  ScopedLock scoped_lock(mutex_);
+  uma_info_.video_codec = video_codec;
+  uma_info_.has_video = true;
+}
+
+void MediaMetricsProvider::SetHasPlayed() {
+  ScopedLock scoped_lock(mutex_);
+  uma_info_.has_ever_played = true;
+}
+
+void MediaMetricsProvider::SetHaveEnough() {
+  ScopedLock scoped_lock(mutex_);
+  uma_info_.has_reached_have_enough = true;
+}
+
+void MediaMetricsProvider::SetIsEME() {
+  ScopedLock scoped_lock(mutex_);
+  // This may be called before Initialize().
+  uma_info_.is_eme = true;
+}
+
+void MediaMetricsProvider::ReportPipelineUMA() {
+  ScopedLock scoped_lock(mutex_);
+  if (uma_info_.has_video && uma_info_.has_audio) {
+    base::UmaHistogramExactLinear(GetUMANameForAVStream(uma_info_),
+                                  uma_info_.last_pipeline_status,
+                                  PipelineStatus::PIPELINE_STATUS_MAX + 1);
+  } else if (uma_info_.has_audio) {
+    base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.AudioOnly",
+                                  uma_info_.last_pipeline_status,
+                                  PipelineStatus::PIPELINE_STATUS_MAX + 1);
+  } else if (uma_info_.has_video) {
+    base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.VideoOnly",
+                                  uma_info_.last_pipeline_status,
+                                  PipelineStatus::PIPELINE_STATUS_MAX + 1);
+  } else {
+    // Note: This metric can be recorded as a result of normal operation with
+    // Media Source Extensions. If a site creates a MediaSource object but never
+    // creates a source buffer or appends data, PIPELINE_OK will be recorded.
+    base::UmaHistogramExactLinear("Cobalt.Media.PipelineStatus.Unsupported",
+                                  uma_info_.last_pipeline_status,
+                                  PipelineStatus::PIPELINE_STATUS_MAX + 1);
+  }
+
+  // Report whether this player ever saw a playback event. Used to measure the
+  // effectiveness of efforts to reduce loaded-but-never-used players.
+  if (uma_info_.has_reached_have_enough)
+    base::UmaHistogramBoolean("Cobalt.Media.HasEverPlayed",
+                              uma_info_.has_ever_played);
+}
+
+std::string MediaMetricsProvider::GetUMANameForAVStream(
+    const PipelineInfo& player_info) const {
+  constexpr char kPipelineUmaPrefix[] =
+      "Cobalt.Media.PipelineStatus.AudioVideo.";
+  std::string uma_name = kPipelineUmaPrefix;
+  if (player_info.video_codec == VideoCodec::kVP9)
+    uma_name += "VP9";
+  else if (player_info.video_codec == VideoCodec::kH264)
+    uma_name += "H264";
+  else if (player_info.video_codec == VideoCodec::kAV1)
+    uma_name += "AV1";
+  else
+    uma_name += "Other";
+
+  return uma_name;
+}
+
+bool MediaMetricsProvider::IsInitialized() const {
+  ScopedLock scoped_lock(mutex_);
+  return media_info_.has_value();
+}
+
+}  // namespace media
+}  // namespace cobalt
diff --git a/cobalt/media/base/metrics_provider.h b/cobalt/media/base/metrics_provider.h
new file mode 100644
index 0000000..dff1df7
--- /dev/null
+++ b/cobalt/media/base/metrics_provider.h
@@ -0,0 +1,91 @@
+// Copyright 2023 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_MEDIA_BASE_METRICS_PROVIDER_H_
+#define COBALT_MEDIA_BASE_METRICS_PROVIDER_H_
+
+#include <string>
+
+#include "starboard/common/mutex.h"
+#include "third_party/chromium/media/base/audio_codecs.h"
+#include "third_party/chromium/media/base/container_names.h"
+#include "third_party/chromium/media/base/pipeline_status.h"
+#include "third_party/chromium/media/base/timestamp_constants.h"
+#include "third_party/chromium/media/base/video_codecs.h"
+
+namespace cobalt {
+namespace media {
+
+using AudioCodec = ::media::AudioCodec;
+using VideoCodec = ::media::VideoCodec;
+using PipelineStatus = ::media::PipelineStatus;
+using VideoDecoderType = ::media::VideoDecoderType;
+
+class MediaMetricsProvider {
+ public:
+  MediaMetricsProvider() = default;
+  ~MediaMetricsProvider();
+
+ private:
+  struct PipelineInfo {
+    PipelineInfo() = default;
+    ~PipelineInfo() = default;
+    bool has_ever_played = false;
+    bool has_reached_have_enough = false;
+    bool has_audio = false;
+    bool has_video = false;
+    bool is_eme = false;
+    bool video_decoder_changed = false;
+    ::media::AudioCodec audio_codec;
+    ::media::VideoCodec video_codec;
+    ::media::PipelineStatus last_pipeline_status =
+        ::media::PipelineStatus::PIPELINE_OK;
+  };
+
+  struct MediaInfo {
+    explicit MediaInfo(bool is_mse) : is_mse{is_mse} {};
+    const bool is_mse;
+  };
+
+
+ public:
+  // based on mojom::MediaMetricsProvider
+  void Initialize(bool is_mse);
+  void OnError(const ::media::PipelineStatus status);
+  void SetHasAudio(::media::AudioCodec audio_codec);
+  void SetHasVideo(::media::VideoCodec video_codec);
+  void SetHasPlayed();
+  void SetHaveEnough();
+  void SetIsEME();
+
+  void ReportPipelineUMA();
+
+ private:
+  std::string GetUMANameForAVStream(const PipelineInfo& player_info) const;
+  bool IsInitialized() const;
+
+ private:
+  // UMA pipeline packaged data
+  PipelineInfo uma_info_;
+
+  // The values below are only set if `Initialize` has been called.
+  absl::optional<MediaInfo> media_info_;
+
+  starboard::Mutex mutex_;
+};
+
+}  // namespace media
+}  // namespace cobalt
+
+#endif  // COBALT_MEDIA_BASE_METRICS_PROVIDER_H_
diff --git a/cobalt/media/base/sbplayer_bridge.cc b/cobalt/media/base/sbplayer_bridge.cc
index 92dc4da..0f3ec4c 100644
--- a/cobalt/media/base/sbplayer_bridge.cc
+++ b/cobalt/media/base/sbplayer_bridge.cc
@@ -39,6 +39,7 @@
 
 namespace {
 
+using starboard::FormatString;
 using starboard::GetPlayerOutputModeName;
 
 class StatisticsWrapper {
@@ -707,9 +708,9 @@
     // a method of querying that texture.
     decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
         &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this)));
-    SB_LOG(INFO) << "Playing in decode-to-texture mode.";
+    LOG(INFO) << "Playing in decode-to-texture mode.";
   } else {
-    SB_LOG(INFO) << "Playing in punch-out mode.";
+    LOG(INFO) << "Playing in punch-out mode.";
   }
 
   decode_target_provider_->SetOutputMode(
@@ -777,9 +778,9 @@
     // a method of querying that texture.
     decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind(
         &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this)));
-    SB_LOG(INFO) << "Playing in decode-to-texture mode.";
+    LOG(INFO) << "Playing in decode-to-texture mode.";
   } else {
-    SB_LOG(INFO) << "Playing in punch-out mode.";
+    LOG(INFO) << "Playing in punch-out mode.";
   }
 
   decode_target_provider_->SetOutputMode(
@@ -1237,8 +1238,8 @@
 
   if (default_output_mode != kSbPlayerOutputModeDecodeToTexture &&
       video_stream_info_.codec != kSbMediaVideoCodecNone) {
-    SB_DCHECK(video_stream_info_.mime);
-    SB_DCHECK(video_stream_info_.max_video_capabilities);
+    DCHECK(video_stream_info_.mime);
+    DCHECK(video_stream_info_.max_video_capabilities);
 
     // Set the `default_output_mode` to `kSbPlayerOutputModeDecodeToTexture` if
     // any of the mime associated with it has `decode-to-texture=true` set.
@@ -1250,13 +1251,12 @@
                "decode-to-texture=true");
 
     if (is_decode_to_texture_preferred) {
-      SB_LOG(INFO) << "Setting `default_output_mode` from \""
-                   << GetPlayerOutputModeName(default_output_mode) << "\" to \""
-                   << GetPlayerOutputModeName(
-                          kSbPlayerOutputModeDecodeToTexture)
-                   << "\" because mime is set to \"" << video_stream_info_.mime
-                   << "\", and max_video_capabilities is set to \""
-                   << video_stream_info_.max_video_capabilities << "\"";
+      LOG(INFO) << "Setting `default_output_mode` from \""
+                << GetPlayerOutputModeName(default_output_mode) << "\" to \""
+                << GetPlayerOutputModeName(kSbPlayerOutputModeDecodeToTexture)
+                << "\" because mime is set to \"" << video_stream_info_.mime
+                << "\", and max_video_capabilities is set to \""
+                << video_stream_info_.max_video_capabilities << "\"";
       default_output_mode = kSbPlayerOutputModeDecodeToTexture;
     }
   }
@@ -1265,6 +1265,9 @@
 
   auto output_mode =
       sbplayer_interface_->GetPreferredOutputMode(&creation_param);
+
+  LOG(INFO) << "Output mode is set to " << GetPlayerOutputModeName(output_mode);
+
   CHECK_NE(kSbPlayerOutputModeInvalid, output_mode);
   return output_mode;
 }
@@ -1272,15 +1275,14 @@
 void SbPlayerBridge::LogStartupLatency() const {
   std::string first_events_str;
   if (set_drm_system_ready_cb_time_ == -1) {
-    first_events_str =
-        starboard::FormatString("%-40s0 us", "SbPlayerCreate() called");
+    first_events_str = FormatString("%-40s0 us", "SbPlayerCreate() called");
   } else if (set_drm_system_ready_cb_time_ < player_creation_time_) {
-    first_events_str = starboard::FormatString(
+    first_events_str = FormatString(
         "%-40s0 us\n%-40s%" PRId64 " us", "set_drm_system_ready_cb called",
         "SbPlayerCreate() called",
         player_creation_time_ - set_drm_system_ready_cb_time_);
   } else {
-    first_events_str = starboard::FormatString(
+    first_events_str = FormatString(
         "%-40s0 us\n%-40s%" PRId64 " us", "SbPlayerCreate() called",
         "set_drm_system_ready_cb called",
         set_drm_system_ready_cb_time_ - player_creation_time_);
@@ -1305,7 +1307,7 @@
                                                               1);
 
   // clang-format off
-  LOG(INFO) << starboard::FormatString(
+  LOG(INFO) << FormatString(
       "\nSbPlayer startup latencies: %" PRId64 " us\n"
       "  Event name                              time since last event\n"
       "  %s\n"  // |first_events_str| populated above
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index ee6a1dc..8b29628 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -111,7 +111,8 @@
 #if SB_API_VERSION >= 15
     SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
 #endif  // SB_API_VERSION >= 15
-    MediaLog* media_log, DecodeTargetProvider* decode_target_provider)
+    MediaLog* media_log, MediaMetricsProvider* media_metrics_provider,
+    DecodeTargetProvider* decode_target_provider)
     : pipeline_identifier_(
           base::StringPrintf("%X", g_pipeline_identifier_counter++)),
       sbplayer_interface_(interface),
@@ -156,6 +157,7 @@
       audio_write_duration_local_(audio_write_duration_local),
       audio_write_duration_remote_(audio_write_duration_remote),
 #endif  // SB_API_VERSION >= 15
+      media_metrics_provider_(media_metrics_provider),
       last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime",
                                           pipeline_identifier_.c_str()),
                        0, "Last media time reported by the underlying player."),
@@ -337,6 +339,8 @@
   if (demuxer_) {
     stop_cb_ = stop_cb;
     demuxer_->Stop();
+    video_stream_ = nullptr;
+    audio_stream_ = nullptr;
     OnDemuxerStopped();
   } else {
     stop_cb.Run();
@@ -563,6 +567,13 @@
 
   std::vector<std::string> connectors;
 
+#if SB_HAS(PLAYER_WITH_URL)
+  // Url based player does not support audio connectors.
+  if (is_url_based_) {
+    return connectors;
+  }
+#endif  // SB_HAS(PLAYER_WITH_URL)
+
   auto configurations = player_bridge_->GetAudioConfigurations();
   for (auto&& configuration : configurations) {
     connectors.push_back(GetMediaAudioConnectorName(configuration.connector));
@@ -999,12 +1010,17 @@
     return;
   }
 
+  if (stopped_) {
+    return;
+  }
+
+  DCHECK(player_bridge_);
+
   DemuxerStream* stream =
       type == DemuxerStream::AUDIO ? audio_stream_ : video_stream_;
   DCHECK(stream);
 
-  // In case if Stop() has been called.
-  if (!player_bridge_) {
+  if (!player_bridge_ || !stream) {
     return;
   }
 
@@ -1157,6 +1173,14 @@
         playback_statistics_.OnPresenting(
             video_stream_->video_decoder_config());
       }
+
+#if SB_HAS(PLAYER_WITH_URL)
+      // Url based player does not support |audio_write_duration_for_preroll_|.
+      if (is_url_based_) {
+        break;
+      }
+#endif  // SB_HAS(PLAYER_WITH_URL)
+
 #if SB_API_VERSION >= 15
       audio_write_duration_for_preroll_ = audio_write_duration_ =
           HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations())
@@ -1229,10 +1253,12 @@
 
   if (stream->type() == DemuxerStream::AUDIO) {
     const AudioDecoderConfig& decoder_config = stream->audio_decoder_config();
+    media_metrics_provider_->SetHasAudio(decoder_config.codec());
     player_bridge_->UpdateAudioConfig(decoder_config, stream->mime_type());
   } else {
     DCHECK_EQ(stream->type(), DemuxerStream::VIDEO);
     const VideoDecoderConfig& decoder_config = stream->video_decoder_config();
+    media_metrics_provider_->SetHasVideo(decoder_config.codec());
     base::AutoLock auto_lock(lock_);
     bool natural_size_changed =
         (decoder_config.natural_size().width() != natural_size_.width() ||
@@ -1352,6 +1378,8 @@
 
 std::string SbPlayerPipeline::AppendStatisticsString(
     const std::string& message) const {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
   if (nullptr == video_stream_) {
     return message + ", playback statistics: n/a.";
   } else {
diff --git a/cobalt/media/base/sbplayer_pipeline.h b/cobalt/media/base/sbplayer_pipeline.h
index 59e19e5..dc851c8 100644
--- a/cobalt/media/base/sbplayer_pipeline.h
+++ b/cobalt/media/base/sbplayer_pipeline.h
@@ -28,6 +28,7 @@
 #include "cobalt/base/c_val.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/metrics_provider.h"
 #include "cobalt/media/base/pipeline.h"
 #include "cobalt/media/base/playback_statistics.h"
 #include "cobalt/media/base/sbplayer_bridge.h"
@@ -65,7 +66,8 @@
 #if SB_API_VERSION >= 15
       SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
 #endif  // SB_API_VERSION >= 15
-      MediaLog* media_log, DecodeTargetProvider* decode_target_provider);
+      MediaLog* media_log, MediaMetricsProvider* media_metrics_provider,
+      DecodeTargetProvider* decode_target_provider);
   ~SbPlayerPipeline() override;
 
   void Suspend() override;
@@ -305,6 +307,8 @@
   base::CVal<bool> ended_;
   base::CVal<SbPlayerState> player_state_;
 
+  MediaMetricsProvider* media_metrics_provider_;
+
   DecodeTargetProvider* decode_target_provider_;
 
 #if SB_API_VERSION >= 15
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index 8a121a6..6941c04 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -156,7 +156,7 @@
 #if SB_API_VERSION >= 15
       audio_write_duration_local, audio_write_duration_remote,
 #endif  // SB_API_VERSION >= 15
-      media_log_, decode_target_provider_.get());
+      media_log_, &media_metrics_provider_, decode_target_provider_.get());
 
   // Also we want to be notified of |main_loop_| destruction.
   main_loop_->AddDestructionObserver(this);
@@ -244,6 +244,7 @@
   is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https");
 
   StartPipeline(url);
+  media_metrics_provider_.Initialize(false);
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
@@ -270,6 +271,7 @@
 
   state_.is_media_source = true;
   StartPipeline(chunk_demuxer_.get());
+  media_metrics_provider_.Initialize(true);
 }
 
 void WebMediaPlayerImpl::LoadProgressive(
@@ -309,6 +311,7 @@
 
   state_.is_progressive = true;
   StartPipeline(progressive_demuxer_.get());
+  media_metrics_provider_.Initialize(false);
 }
 
 void WebMediaPlayerImpl::CancelLoad() {
@@ -324,6 +327,7 @@
   pipeline_->SetPlaybackRate(state_.playback_rate);
 
   media_log_->AddEvent<::media::MediaLogEvent::kPlay>();
+  media_metrics_provider_.SetHasPlayed();
 }
 
 void WebMediaPlayerImpl::Pause() {
@@ -653,6 +657,7 @@
   if (suppress_destruction_errors_) return;
 
   media_log_->NotifyError(error);
+  media_metrics_provider_.OnError(error);
 
   if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing) {
     // Any error that occurs before reaching ReadyStateHaveMetadata should
@@ -785,6 +790,7 @@
       break;
     case Pipeline::kPrerollCompleted:
       SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData);
+      media_metrics_provider_.SetHaveEnough();
       break;
   }
 }
@@ -963,6 +969,7 @@
       NOTREACHED();
       break;
   }
+  media_metrics_provider_.SetIsEME();
 }
 
 WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h
index 9db4419..adb7ed2 100644
--- a/cobalt/media/player/web_media_player_impl.h
+++ b/cobalt/media/player/web_media_player_impl.h
@@ -60,6 +60,7 @@
 #include "base/time/time.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/base/decode_target_provider.h"
+#include "cobalt/media/base/metrics_provider.h"
 #include "cobalt/media/base/pipeline.h"
 #include "cobalt/media/base/sbplayer_interface.h"
 #include "cobalt/media/player/web_media_player.h"
@@ -306,6 +307,8 @@
 
   ::media::MediaLog* const media_log_;
 
+  MediaMetricsProvider media_metrics_provider_;
+
   bool is_local_source_;
 
   std::unique_ptr<::media::Demuxer> progressive_demuxer_;
diff --git a/cobalt/media_capture/media_devices.cc b/cobalt/media_capture/media_devices.cc
index 7a75b18..3c671f3 100644
--- a/cobalt/media_capture/media_devices.cc
+++ b/cobalt/media_capture/media_devices.cc
@@ -18,6 +18,7 @@
 #include <string>
 #include <utility>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/media_capture/media_device_info.h"
@@ -66,6 +67,11 @@
   return mic;
 }
 
+void LogMicCreationSucceededHistogramItem(bool mic_creation_succeeded) {
+  base::UmaHistogramBoolean("Cobalt.MediaDevices.MicCreationSucceeded",
+                            mic_creation_succeeded);
+}
+
 }  // namespace.
 
 MediaDevices::MediaDevices(script::EnvironmentSettings* settings,
@@ -169,6 +175,9 @@
     speech::MicrophoneManager::MicrophoneError error, std::string message) {
   DLOG(INFO) << "MediaDevices::OnMicrophoneError " << message;
 
+  // Log failed mic creation in UMA histogram
+  LogMicCreationSucceededHistogramItem(false);
+
   // No special error handling logic besides logging the message above, so just
   // delegate to the OnMicrophoneStopped() functionality.
   OnMicrophoneStopped();
@@ -195,6 +204,9 @@
 }
 
 void MediaDevices::OnMicrophoneSuccess() {
+  // Log successful mic creation in UMA histogram
+  LogMicCreationSucceededHistogramItem(true);
+
   if (javascript_message_loop_->task_runner() !=
       base::ThreadTaskRunnerHandle::Get()) {
     javascript_message_loop_->task_runner()->PostTask(
diff --git a/cobalt/network/cobalt_net_log.cc b/cobalt/network/cobalt_net_log.cc
index 3df4117..f597b87 100644
--- a/cobalt/network/cobalt_net_log.cc
+++ b/cobalt/network/cobalt_net_log.cc
@@ -25,14 +25,29 @@
 
 CobaltNetLog::CobaltNetLog(const base::FilePath& log_path,
                            net::NetLogCaptureMode capture_mode)
-    : net_log_logger_(
-          net::FileNetLogObserver::CreateUnbounded(log_path, nullptr)) {
-  net_log_logger_->StartObserving(this, capture_mode);
-}
+    : capture_mode_(capture_mode),
+      net_log_logger_(
+          net::FileNetLogObserver::CreateUnbounded(log_path, nullptr)) {}
 
 CobaltNetLog::~CobaltNetLog() {
   // Remove the observers we own before we're destroyed.
-  net_log_logger_->StopObserving(nullptr, base::OnceClosure());
+  StopObserving();
+}
+
+void CobaltNetLog::StartObserving() {
+  if (!is_observing_) {
+    is_observing_ = true;
+    net_log_logger_->StartObserving(this, capture_mode_);
+  } else {
+    DLOG(WARNING) << "Already observing NetLog.";
+  }
+}
+
+void CobaltNetLog::StopObserving() {
+  if (is_observing_) {
+    is_observing_ = false;
+    net_log_logger_->StopObserving(nullptr, base::OnceClosure());
+  }
 }
 
 }  // namespace network
diff --git a/cobalt/network/cobalt_net_log.h b/cobalt/network/cobalt_net_log.h
index 4ee6fa8..dcc1e67 100644
--- a/cobalt/network/cobalt_net_log.h
+++ b/cobalt/network/cobalt_net_log.h
@@ -34,8 +34,13 @@
                ::net::NetLogCaptureMode capture_mode);
   ~CobaltNetLog() override;
 
+  void StartObserving();
+  void StopObserving();
+
  private:
-  std::unique_ptr<net::FileNetLogObserver> net_log_logger_;
+  bool is_observing_{false};
+  net::NetLogCaptureMode capture_mode_;
+  std::unique_ptr<net::FileNetLogObserver> net_log_logger_{nullptr};
 
   DISALLOW_COPY_AND_ASSIGN(CobaltNetLog);
 };
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc
index 8154b7c..0c8f67e 100644
--- a/cobalt/network/network_module.cc
+++ b/cobalt/network/network_module.cc
@@ -17,10 +17,12 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
+#include "cobalt/base/cobalt_paths.h"
 #include "cobalt/network/network_system.h"
 #include "cobalt/network/switches.h"
 #include "net/url_request/static_http_user_agent_settings.h"
@@ -33,6 +35,7 @@
 const char kCaptureModeIncludeCookiesAndCredentials[] =
     "IncludeCookiesAndCredentials";
 const char kCaptureModeIncludeSocketBytes[] = "IncludeSocketBytes";
+const char kDefaultNetLogName[] = "cobalt_netlog.json";
 #endif
 }  // namespace
 
@@ -110,15 +113,6 @@
   }
 }
 
-void NetworkModule::SetEnableClientHintHeadersFlagsFromPersistentSettings() {
-  // Called on initialization and when the persistent setting is changed.
-  if (options_.persistent_settings != nullptr) {
-    enable_client_hint_headers_flags_.store(
-        options_.persistent_settings->GetPersistentSettingAsInt(
-            kClientHintHeadersEnabledPersistentSettingsKey, 0));
-  }
-}
-
 void NetworkModule::EnsureStorageManagerStarted() {
   DCHECK(storage_manager_);
   storage_manager_->EnsureStarted();
@@ -137,8 +131,6 @@
   http_user_agent_settings_.reset(new net::StaticHttpUserAgentSettings(
       options_.preferred_language, user_agent_string));
 
-  SetEnableClientHintHeadersFlagsFromPersistentSettings();
-
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
 
@@ -156,11 +148,12 @@
   }
 
 #if defined(ENABLE_NETWORK_LOGGING)
+  base::FilePath result;
+  base::PathService::Get(cobalt::paths::DIR_COBALT_DEBUG_OUT, &result);
+  net_log_path_ = result.Append(kDefaultNetLogName);
+  net::NetLogCaptureMode capture_mode;
   if (command_line->HasSwitch(switches::kNetLog)) {
-    // If this is not a valid path, net logs will be sent to VLOG(1).
-    base::FilePath net_log_path =
-        command_line->GetSwitchValuePath(switches::kNetLog);
-    net::NetLogCaptureMode capture_mode;
+    net_log_path_ = command_line->GetSwitchValuePath(switches::kNetLog);
     if (command_line->HasSwitch(switches::kNetLogCaptureMode)) {
       std::string capture_mode_string =
           command_line->GetSwitchValueASCII(switches::kNetLogCaptureMode);
@@ -170,7 +163,10 @@
         capture_mode = net::NetLogCaptureMode::IncludeSocketBytes();
       }
     }
-    net_log_.reset(new CobaltNetLog(net_log_path, capture_mode));
+    net_log_.reset(new CobaltNetLog(net_log_path_, capture_mode));
+    net_log_->StartObserving();
+  } else {
+    net_log_.reset(new CobaltNetLog(net_log_path_, capture_mode));
   }
 #endif
 
@@ -234,12 +230,27 @@
 
 void NetworkModule::AddClientHintHeaders(
     net::URLFetcher& url_fetcher, ClientHintHeadersCallType call_type) const {
-  if (enable_client_hint_headers_flags_.load() & call_type) {
+  if (kEnabledClientHintHeaders & call_type) {
     for (const auto& header : client_hint_headers_) {
       url_fetcher.AddExtraRequestHeader(header);
     }
   }
 }
 
+void NetworkModule::StartNetLog() {
+#if defined(ENABLE_NETWORK_LOGGING)
+  LOG(INFO) << "Starting NetLog capture";
+  net_log_->StartObserving();
+#endif
+}
+
+base::FilePath NetworkModule::StopNetLog() {
+#if defined(ENABLE_NETWORK_LOGGING)
+  LOG(INFO) << "Stopping NetLog capture";
+  net_log_->StopObserving();
+#endif
+  return net_log_path_;
+}
+
 }  // namespace network
 }  // namespace cobalt
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h
index b6c8bd2..18ea05c 100644
--- a/cobalt/network/network_module.h
+++ b/cobalt/network/network_module.h
@@ -49,7 +49,7 @@
 namespace network {
 
 // Used to differentiate type of network call for Client Hint Headers.
-// Values correspond to bit masks against |enable_client_hint_headers_flags_|.
+// Values correspond to bit masks against |kEnabledClientHintHeaders|.
 enum ClientHintHeadersCallType : int32_t {
   kCallTypeLoader = (1u << 0),
   kCallTypeMedia = (1u << 1),
@@ -59,11 +59,10 @@
   kCallTypeXHR = (1u << 5),
 };
 
-const char kQuicEnabledPersistentSettingsKey[] = "QUICEnabled";
+// Determines which type of network calls should include Client Hint Headers.
+constexpr int32_t kEnabledClientHintHeaders = (kCallTypeLoader | kCallTypeXHR);
 
-// Holds bit mask flag, read into |enable_client_hint_headers_flags_|.
-const char kClientHintHeadersEnabledPersistentSettingsKey[] =
-    "clientHintHeadersEnabled";
+const char kQuicEnabledPersistentSettingsKey[] = "QUICEnabled";
 
 class NetworkSystem;
 // NetworkModule wraps various networking-related components such as
@@ -132,9 +131,6 @@
 
   void SetEnableQuicFromPersistentSettings();
 
-  // Checks persistent settings to determine if Client Hint Headers are enabled.
-  void SetEnableClientHintHeadersFlagsFromPersistentSettings();
-
   // Adds the Client Hint Headers to the provided URLFetcher if enabled.
   void AddClientHintHeaders(net::URLFetcher& url_fetcher,
                             ClientHintHeadersCallType call_type) const;
@@ -142,6 +138,11 @@
   // From base::MessageLoop::DestructionObserver.
   void WillDestroyCurrentMessageLoop() override;
 
+  // Used to capture NetLog from Devtools
+  void StartNetLog();
+  base::FilePath StopNetLog();
+
+
  private:
   void Initialize(const std::string& user_agent_string,
                   base::EventDispatcher* event_dispatcher);
@@ -149,7 +150,6 @@
   std::unique_ptr<network_bridge::NetPoster> CreateNetPoster();
 
   std::vector<std::string> client_hint_headers_;
-  starboard::atomic_int32_t enable_client_hint_headers_flags_;
   std::unique_ptr<storage::StorageManager> storage_manager_;
   std::unique_ptr<base::Thread> thread_;
   std::unique_ptr<URLRequestContext> url_request_context_;
@@ -163,7 +163,9 @@
   scoped_refptr<net::DialServiceProxy> dial_service_proxy_;
 #endif
   std::unique_ptr<network_bridge::NetPoster> net_poster_;
-  std::unique_ptr<CobaltNetLog> net_log_;
+
+  base::FilePath net_log_path_;
+  std::unique_ptr<CobaltNetLog> net_log_{nullptr};
   Options options_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkModule);
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc
index 7467b98..2efd31e 100644
--- a/cobalt/network/url_request_context.cc
+++ b/cobalt/network/url_request_context.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/network/url_request_context.h"
 
+#include <algorithm>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -48,6 +50,49 @@
 #include "net/url_request/data_protocol_handler.h"
 #include "net/url_request/url_request_job_factory_impl.h"
 
+namespace {
+
+const char kPersistentSettingsJson[] = "cache_settings.json";
+
+void LoadDiskCacheQuotaSettings(
+    cobalt::persistent_storage::PersistentSettings* settings,
+    int64_t max_bytes) {
+  auto total_size = 0;
+  std::map<disk_cache::ResourceType, uint32_t> quotas;
+  for (int i = 0; i < disk_cache::kTypeCount; i++) {
+    disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i;
+    std::string directory =
+        disk_cache::defaults::GetSubdirectory(resource_type);
+    uint32_t bucket_size =
+        static_cast<uint32_t>(settings->GetPersistentSettingAsDouble(
+            directory, disk_cache::defaults::GetQuota(resource_type)));
+    quotas[resource_type] = bucket_size;
+    total_size += bucket_size;
+  }
+
+  if (total_size <= max_bytes) {
+    for (int i = 0; i < disk_cache::kTypeCount; i++) {
+      disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i;
+      disk_cache::settings::SetQuota(resource_type, quotas[resource_type]);
+    }
+    return;
+  }
+
+  // Sum of quotas exceeds |max_bytes|. Set quotas to default values.
+  for (int i = 0; i < disk_cache::kTypeCount; i++) {
+    disk_cache::ResourceType resource_type = (disk_cache::ResourceType)i;
+    uint32_t default_quota = disk_cache::defaults::GetQuota(resource_type);
+    disk_cache::settings::SetQuota(resource_type, default_quota);
+    std::string directory =
+        disk_cache::defaults::GetSubdirectory(resource_type);
+    settings->SetPersistentSetting(
+        directory,
+        std::make_unique<base::Value>(static_cast<double>(default_quota)));
+  }
+}
+
+}  // namespace
+
 namespace cobalt {
 namespace network {
 namespace {
@@ -189,17 +234,24 @@
     // is less than 1 mb and subtract this from the max_cache_bytes.
     max_cache_bytes -= (1 << 20);
 
+    // Initialize and read caching persistent settings
+    cache_persistent_settings_ =
+        std::make_unique<cobalt::persistent_storage::PersistentSettings>(
+            kPersistentSettingsJson);
+    LoadDiskCacheQuotaSettings(cache_persistent_settings_.get(),
+                               max_cache_bytes);
+
     auto http_cache = std::make_unique<net::HttpCache>(
         storage_.http_network_session(),
         std::make_unique<net::HttpCache::DefaultBackend>(
-            net::DISK_CACHE, net::CACHE_BACKEND_COBALT,
+            net::DISK_CACHE, net::CACHE_BACKEND_DEFAULT,
             base::FilePath(std::string(path.data())),
             /* max_bytes */ max_cache_bytes),
         true);
     if (persistent_settings != nullptr) {
       auto cache_enabled = persistent_settings->GetPersistentSettingAsBool(
           disk_cache::kCacheEnabledPersistentSettingsKey, true);
-
+      disk_cache::settings::SetCacheEnabled(cache_enabled);
       if (!cache_enabled) {
         http_cache->set_mode(net::HttpCache::Mode::DISABLE);
       }
@@ -243,5 +295,17 @@
 }
 #endif  // defined(ENABLE_DEBUGGER)
 
+void URLRequestContext::UpdateCacheSizeSetting(disk_cache::ResourceType type,
+                                               uint32_t bytes) {
+  CHECK(cache_persistent_settings_);
+  cache_persistent_settings_->SetPersistentSetting(
+      disk_cache::defaults::GetSubdirectory(type),
+      std::make_unique<base::Value>(static_cast<double>(bytes)));
+}
+
+void URLRequestContext::ValidateCachePersistentSettings() {
+  cache_persistent_settings_->ValidatePersistentSettings();
+}
+
 }  // namespace network
 }  // namespace cobalt
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h
index 6e80479..a8e635c 100644
--- a/cobalt/network/url_request_context.h
+++ b/cobalt/network/url_request_context.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_NETWORK_URL_REQUEST_CONTEXT_H_
 #define COBALT_NETWORK_URL_REQUEST_CONTEXT_H_
 
+#include <memory>
 #include <string>
 
 #include "base/basictypes.h"
@@ -22,6 +23,7 @@
 #include "base/sequence_checker.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/cookies/cookie_monster.h"
+#include "net/disk_cache/cobalt/resource_type.h"
 #include "net/log/net_log.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
@@ -53,6 +55,9 @@
 
   bool using_http_cache();
 
+  void UpdateCacheSizeSetting(disk_cache::ResourceType type, uint32_t bytes);
+  void ValidateCachePersistentSettings();
+
  private:
   SEQUENCE_CHECKER(sequence_checker_);
   net::URLRequestContextStorage storage_;
@@ -70,6 +75,10 @@
   void OnQuicToggle(const std::string&);
 #endif  // defined(ENABLE_DEBUGGER)
 
+  // Persistent settings module for Cobalt disk cache quotas
+  std::unique_ptr<cobalt::persistent_storage::PersistentSettings>
+      cache_persistent_settings_;
+
   DISALLOW_COPY_AND_ASSIGN(URLRequestContext);
 };
 
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc
index 52bc864..2f2e5dd 100644
--- a/cobalt/persistent_storage/persistent_settings.cc
+++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -129,7 +129,9 @@
   base::AutoLock auto_lock(pref_store_lock_);
   auto persistent_settings = pref_store_->GetValues();
   const base::Value* result = persistent_settings->FindKey(key);
-  if (result && result->is_double()) return result->GetDouble();
+  if (result && result->is_double()) {
+    return result->GetDouble();
+  }
   return default_setting;
 }
 
diff --git a/cobalt/ui_navigation/scroll_engine/scroll_engine.cc b/cobalt/ui_navigation/scroll_engine/scroll_engine.cc
index 4c14f73..2c76797 100644
--- a/cobalt/ui_navigation/scroll_engine/scroll_engine.cc
+++ b/cobalt/ui_navigation/scroll_engine/scroll_engine.cc
@@ -395,6 +395,14 @@
   }
 }
 
+void ScrollEngine::Conceal(render_tree::ResourceProvider*, SbTimeMonotonic) {
+  nav_items_with_decaying_scroll_.clear();
+}
+
+void ScrollEngine::Freeze(SbTimeMonotonic) {
+  nav_items_with_decaying_scroll_.clear();
+}
+
 }  // namespace scroll_engine
 }  // namespace ui_navigation
 }  // namespace cobalt
diff --git a/cobalt/ui_navigation/scroll_engine/scroll_engine.h b/cobalt/ui_navigation/scroll_engine/scroll_engine.h
index 93e1e33..3e2653a 100644
--- a/cobalt/ui_navigation/scroll_engine/scroll_engine.h
+++ b/cobalt/ui_navigation/scroll_engine/scroll_engine.h
@@ -23,6 +23,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "cobalt/base/token.h"
+#include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/pointer_event_init.h"
 #include "cobalt/math/vector2d_f.h"
@@ -50,7 +51,7 @@
   base::Time time_stamp;
 };
 
-class ScrollEngine {
+class ScrollEngine : public browser::LifecycleObserver {
  public:
   ScrollEngine();
   ~ScrollEngine();
@@ -73,6 +74,17 @@
 
   base::Thread* thread() { return &scroll_engine_; }
 
+  // LifecycleObserver implementation.
+  void Blur(SbTimeMonotonic timestamp) override {}
+  void Conceal(render_tree::ResourceProvider* resource_provider,
+               SbTimeMonotonic timestamp) override;
+  void Freeze(SbTimeMonotonic timestamp) override;
+  void Unfreeze(render_tree::ResourceProvider* resource_provider,
+                SbTimeMonotonic timestamp) override {}
+  void Reveal(render_tree::ResourceProvider* resource_provider,
+              SbTimeMonotonic timestamp) override {}
+  void Focus(SbTimeMonotonic timestamp) override {}
+
  private:
   base::Thread scroll_engine_{"ScrollEngineThread"};
   base::RepeatingTimer free_scroll_timer_;
diff --git a/cobalt/version.h b/cobalt/version.h
index bd8a169..f54bb3b 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "24.lts.10"
+#define COBALT_VERSION "24.lts.20"
 
 #endif  // COBALT_VERSION_H_
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index 1e43688..f328ec1 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -43,9 +43,11 @@
 // The minimum number of microseconds between writes.
 const int64_t kWatchdogWriteWaitTime = 300000000;
 // The maximum number of most recent ping infos.
-const int kWatchdogMaxPingInfos = 20;
+const int kWatchdogMaxPingInfos = 60;
 // The maximum length of each ping info.
-const int kWatchdogMaxPingInfoLength = 128;
+const int kWatchdogMaxPingInfoLength = 1024;
+// The maximum number of milliseconds old of an unfetched Watchdog violation.
+const int64_t kWatchdogMaxViolationsAge = 86400000;
 
 // Persistent setting name and default setting for the boolean that controls
 // whether or not Watchdog is enabled. When disabled, Watchdog behaves like a
@@ -109,7 +111,6 @@
 
   // Starts monitor thread.
   is_monitoring_.store(true);
-  InitializeViolationsMap(this);
   SB_DCHECK(!SbThreadIsValid(watchdog_thread_));
   watchdog_thread_ = SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity,
                                     true, "Watchdog", &Watchdog::Monitor, this);
@@ -133,6 +134,29 @@
   SbThreadJoin(watchdog_thread_, nullptr);
 }
 
+std::shared_ptr<base::Value> Watchdog::GetViolationsMap() {
+  // Gets the Watchdog violations map with lazy initialization which loads the
+  // previous Watchdog violations file containing violations before app start,
+  // if it exists.
+  if (violations_map_ == nullptr) {
+    starboard::ScopedFile read_file(GetWatchdogFilePath().c_str(),
+                                    kSbFileOpenOnly | kSbFileRead);
+    if (read_file.IsValid()) {
+      int64_t kFileSize = read_file.GetSize();
+      std::vector<char> buffer(kFileSize + 1, 0);
+      read_file.ReadAll(buffer.data(), kFileSize);
+      violations_map_ = base::JSONReader::Read(std::string(buffer.data()));
+    }
+
+    if (violations_map_ == nullptr) {
+      SB_LOG(INFO) << "[Watchdog] No previous violations JSON.";
+      violations_map_ =
+          std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
+    }
+  }
+  return violations_map_;
+}
+
 std::string Watchdog::GetWatchdogFilePath() {
   // Gets the Watchdog violations file path with lazy initialization.
   if (watchdog_file_path_ == "") {
@@ -148,33 +172,17 @@
 }
 
 std::vector<std::string> Watchdog::GetWatchdogViolationClientNames() {
-  if (pending_write_) WriteWatchdogViolations();
-
-  std::string watchdog_json = ReadViolationFile(GetWatchdogFilePath().c_str());
   std::vector<std::string> names;
-  if (watchdog_json != "") {
-    std::unique_ptr<base::Value> violations_map =
-        base::JSONReader::Read(watchdog_json);
-    for (const auto& it : violations_map->DictItems()) {
-      names.push_back(it.first);
-    }
+
+  if (is_disabled_) return names;
+
+  starboard::ScopedLock scoped_lock(mutex_);
+  for (const auto& it : GetViolationsMap()->DictItems()) {
+    names.push_back(it.first);
   }
   return names;
 }
 
-void Watchdog::WriteWatchdogViolations() {
-  // Writes Watchdog violations to persistent storage as a json file.
-  std::string watchdog_json;
-  base::JSONWriter::Write(*violations_map_, &watchdog_json);
-  SB_LOG(INFO) << "[Watchdog] Writing violations to JSON:\n" << watchdog_json;
-  starboard::ScopedFile watchdog_file(GetWatchdogFilePath().c_str(),
-                                      kSbFileCreateAlways | kSbFileWrite);
-  watchdog_file.WriteAll(watchdog_json.c_str(),
-                         static_cast<int>(watchdog_json.size()));
-  pending_write_ = false;
-  time_last_written_microseconds_ = SbTimeGetMonotonicNow();
-}
-
 void Watchdog::UpdateState(base::ApplicationState state) {
   if (is_disabled_) return;
 
@@ -182,43 +190,41 @@
   state_ = state;
 }
 
+void Watchdog::WriteWatchdogViolations() {
+  // Writes Watchdog violations to persistent storage as a json file.
+  std::string watchdog_json;
+  base::JSONWriter::Write(*GetViolationsMap(), &watchdog_json);
+  SB_LOG(INFO) << "[Watchdog] Writing violations to JSON:\n" << watchdog_json;
+  starboard::ScopedFile watchdog_file(GetWatchdogFilePath().c_str(),
+                                      kSbFileCreateAlways | kSbFileWrite);
+  watchdog_file.WriteAll(watchdog_json.c_str(),
+                         static_cast<int>(watchdog_json.size()));
+  pending_write_ = false;
+  time_last_written_microseconds_ = SbTimeGetMonotonicNow();
+}
+
 void* Watchdog::Monitor(void* context) {
   starboard::ScopedLock scoped_lock(static_cast<Watchdog*>(context)->mutex_);
   while (1) {
     SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
-
-    // Iterates through client map to monitor all registered clients.
     bool watchdog_violation = false;
+
+    // Iterates through client map to monitor all name registered clients.
     for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
       Client* client = it.second.get();
-      // Ignores and resets clients in idle states, clients whose monitor_state
-      // is below the current application state. Resets time_wait_microseconds
-      // and time_interval_microseconds start values.
-      if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) {
-        client->time_registered_monotonic_microseconds = current_monotonic_time;
-        client->time_last_updated_monotonic_microseconds =
-            current_monotonic_time;
-        continue;
-      }
-
-      SbTimeMonotonic time_delta =
-          current_monotonic_time -
-          client->time_last_updated_monotonic_microseconds;
-      SbTimeMonotonic time_wait =
-          current_monotonic_time -
-          client->time_registered_monotonic_microseconds;
-
-      // Watchdog violation
-      if (time_delta > client->time_interval_microseconds &&
-          time_wait > client->time_wait_microseconds) {
+      if (MonitorClient(context, client, current_monotonic_time)) {
         watchdog_violation = true;
-        UpdateViolationsMap(context, client, time_delta);
-
-        // Resets time last updated.
-        client->time_last_updated_monotonic_microseconds =
-            current_monotonic_time;
       }
     }
+
+    // Iterates through client list to monitor all client registered clients.
+    for (auto& it : static_cast<Watchdog*>(context)->client_list_) {
+      Client* client = it.get();
+      if (MonitorClient(context, client, current_monotonic_time)) {
+        watchdog_violation = true;
+      }
+    }
+
     if (static_cast<Watchdog*>(context)->pending_write_)
       MaybeWriteWatchdogViolations(context);
     if (watchdog_violation) MaybeTriggerCrash(context);
@@ -233,11 +239,39 @@
   return nullptr;
 }
 
+bool Watchdog::MonitorClient(void* context, Client* client,
+                             SbTimeMonotonic current_monotonic_time) {
+  // Ignores and resets clients in idle states, clients whose monitor_state
+  // is below the current application state. Resets time_wait_microseconds
+  // and time_interval_microseconds start values.
+  if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) {
+    client->time_registered_monotonic_microseconds = current_monotonic_time;
+    client->time_last_updated_monotonic_microseconds = current_monotonic_time;
+    return false;
+  }
+
+  SbTimeMonotonic time_delta =
+      current_monotonic_time - client->time_last_updated_monotonic_microseconds;
+  SbTimeMonotonic time_wait =
+      current_monotonic_time - client->time_registered_monotonic_microseconds;
+
+  // Watchdog violation
+  if (time_delta > client->time_interval_microseconds &&
+      time_wait > client->time_wait_microseconds) {
+    UpdateViolationsMap(context, client, time_delta);
+    // Resets time last updated.
+    client->time_last_updated_monotonic_microseconds = current_monotonic_time;
+    return true;
+  }
+  return false;
+}
+
 void Watchdog::UpdateViolationsMap(void* context, Client* client,
                                    SbTimeMonotonic time_delta) {
-  // Gets violation dictionary with key client name from violations_map_.
+  // Gets violation dictionary with key client name from violations map.
   base::Value* violation_dict =
-      (static_cast<Watchdog*>(context)->violations_map_)->FindKey(client->name);
+      (static_cast<Watchdog*>(context)->GetViolationsMap())
+          ->FindKey(client->name);
 
   // Checks if new unique violation.
   bool new_violation = false;
@@ -256,9 +290,8 @@
       new_violation = true;
   }
 
-  // New unique violation.
   if (new_violation) {
-    // Creates new violation.
+    // New unique violation, creates violation in violations map.
     base::Value violation(base::Value::Type::DICTIONARY);
     violation.SetKey("pingInfos", client->ping_infos.Clone());
     violation.SetKey("monitorState",
@@ -287,28 +320,26 @@
     for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
       registered_clients.GetList().emplace_back(base::Value(it.first));
     }
+    for (auto& it : static_cast<Watchdog*>(context)->client_list_) {
+      registered_clients.GetList().emplace_back(base::Value(it->name));
+    }
     violation.SetKey("registeredClients", registered_clients.Clone());
 
-    // Adds new violation to violations_map_.
+    // Adds new violation to violations map.
     if (violation_dict == nullptr) {
       base::Value dict(base::Value::Type::DICTIONARY);
       dict.SetKey("description", base::Value(client->description));
       base::Value list(base::Value::Type::LIST);
       list.GetList().emplace_back(violation.Clone());
       dict.SetKey("violations", list.Clone());
-      (static_cast<Watchdog*>(context)->violations_map_)
+      (static_cast<Watchdog*>(context)->GetViolationsMap())
           ->SetKey(client->name, dict.Clone());
     } else {
       base::Value* violations = violation_dict->FindKey("violations");
       violations->GetList().emplace_back(violation.Clone());
     }
-    static_cast<Watchdog*>(context)->violations_count_++;
-    if (static_cast<Watchdog*>(context)->violations_count_ >
-        kWatchdogMaxViolations)
-      EvictWatchdogViolation(context);
-    // Consecutive non-unique violation.
   } else {
-    // Updates consecutive violation in violations_map_.
+    // Consecutive non-unique violation, updates violation in violations map.
     base::Value* violations = violation_dict->FindKey("violations");
     int last_index = violations->GetList().size() - 1;
     int64_t violation_duration =
@@ -319,81 +350,68 @@
         "violationDurationMilliseconds",
         base::Value(std::to_string(violation_duration + (time_delta / 1000))));
   }
-
   static_cast<Watchdog*>(context)->pending_write_ = true;
-}
 
-std::string Watchdog::ReadViolationFile(const char* file_path) {
-  starboard::ScopedFile read_file(file_path, kSbFileOpenOnly | kSbFileRead);
-  if (read_file.IsValid()) {
-    int64_t kFileSize = read_file.GetSize();
-    std::vector<char> buffer(kFileSize + 1, 0);
-    read_file.ReadAll(buffer.data(), kFileSize);
-    return std::string(buffer.data());
+  int violations_count = 0;
+  for (const auto& it :
+       (static_cast<Watchdog*>(context)->GetViolationsMap())->DictItems()) {
+    base::Value& violation_dict = it.second;
+    base::Value* violations = violation_dict.FindKey("violations");
+    violations_count += violations->GetList().size();
   }
-  return "";
-}
-
-void Watchdog::InitializeViolationsMap(void* context) {
-  // Loads the previous Watchdog violations file containing violations before
-  // app start, if it exists, to populate violations_map_.
-  static_cast<Watchdog*>(context)->violations_count_ = 0;
-
-  std::string watchdog_json =
-      static_cast<Watchdog*>(context)->ReadViolationFile(
-          (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str());
-  if (watchdog_json != "") {
-    static_cast<Watchdog*>(context)->violations_map_ =
-        base::JSONReader::Read(watchdog_json);
-  }
-
-  if (static_cast<Watchdog*>(context)->violations_map_ == nullptr) {
-    SB_LOG(INFO) << "[Watchdog] No previous violations JSON.";
-    static_cast<Watchdog*>(context)->violations_map_ =
-        std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
-  } else {
-    for (const auto& it :
-         (static_cast<Watchdog*>(context)->violations_map_)->DictItems()) {
-      base::Value& violation_dict = it.second;
-      base::Value* violations = violation_dict.FindKey("violations");
-      static_cast<Watchdog*>(context)->violations_count_ +=
-          violations->GetList().size();
-    }
+  if (violations_count > kWatchdogMaxViolations) {
+    EvictWatchdogViolation(context);
   }
 }
 
 void Watchdog::EvictWatchdogViolation(void* context) {
-  // Evicts a violation in violations_map_ prioritizing first the most frequent
+  // Evicts a violation in violations map prioritizing first the most frequent
   // violations (largest violations count by client name) and second the oldest
   // violation.
   std::string evicted_name = "";
   int evicted_count = 0;
-  int64_t evicted_timestamp = 0;
+  int64_t evicted_timestamp_millis = 0;
 
   for (const auto& it :
-       (static_cast<Watchdog*>(context)->violations_map_)->DictItems()) {
+       (static_cast<Watchdog*>(context)->GetViolationsMap())->DictItems()) {
     std::string name = it.first;
     base::Value& violation_dict = it.second;
     base::Value* violations = violation_dict.FindKey("violations");
     int count = violations->GetList().size();
-    int64_t timestamp =
+    int64_t violation_timestamp_millis =
         std::stoll(violations->GetList()[0]
                        .FindKey("timestampViolationMilliseconds")
                        ->GetString());
 
     if ((evicted_name == "") || (count > evicted_count) ||
-        ((count == evicted_count) && (timestamp < evicted_timestamp))) {
+        ((count == evicted_count) &&
+         (violation_timestamp_millis < evicted_timestamp_millis))) {
       evicted_name = name;
       evicted_count = count;
-      evicted_timestamp = timestamp;
+      evicted_timestamp_millis = violation_timestamp_millis;
     }
   }
 
   base::Value* violation_dict =
-      (static_cast<Watchdog*>(context)->violations_map_)->FindKey(evicted_name);
-  base::Value* violations = violation_dict->FindKey("violations");
-  violations->GetList().erase(violations->GetList().begin());
-  static_cast<Watchdog*>(context)->violations_count_--;
+      (static_cast<Watchdog*>(context)->GetViolationsMap())
+          ->FindKey(evicted_name);
+
+  if (violation_dict != nullptr) {
+    base::Value* violations = violation_dict->FindKey("violations");
+    violations->GetList().erase(violations->GetList().begin());
+    static_cast<Watchdog*>(context)->pending_write_ = true;
+
+    // Removes empty violations.
+    if (violations->GetList().empty()) {
+      (static_cast<Watchdog*>(context)->GetViolationsMap())
+          ->RemoveKey(evicted_name);
+    }
+    if (static_cast<Watchdog*>(context)->GetViolationsMap()->DictEmpty()) {
+      starboard::SbFileDeleteRecursive(
+          static_cast<Watchdog*>(context)->GetWatchdogFilePath().c_str(), true);
+      static_cast<Watchdog*>(context)->pending_write_ = false;
+    }
+  }
 }
 
 void Watchdog::MaybeWriteWatchdogViolations(void* context) {
@@ -409,7 +427,7 @@
     if (static_cast<Watchdog*>(context)->pending_write_)
       static_cast<Watchdog*>(context)->WriteWatchdogViolations();
     SB_LOG(ERROR) << "[Watchdog] Triggering violation Crash!";
-    CHECK(false);
+    *(reinterpret_cast<volatile char*>(0)) = 0;
   }
 }
 
@@ -419,19 +437,6 @@
                         int64_t time_wait_microseconds, Replace replace) {
   if (is_disabled_) return true;
 
-  // Validates parameters.
-  if (time_interval_microseconds < watchdog_monitor_frequency_ ||
-      time_wait_microseconds < 0) {
-    SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name;
-    if (time_interval_microseconds < watchdog_monitor_frequency_) {
-      SB_DLOG(ERROR) << "[Watchdog] Time interval less than min: "
-                     << watchdog_monitor_frequency_;
-    } else {
-      SB_DLOG(ERROR) << "[Watchdog] Time wait is negative.";
-    }
-    return false;
-  }
-
   starboard::ScopedLock scoped_lock(mutex_);
 
   int64_t current_time = SbTimeToPosix(SbTimeGetNow());
@@ -453,6 +458,65 @@
     }
   }
 
+  // Creates new client.
+  std::unique_ptr<Client> client = CreateClient(
+      name, description, monitor_state, time_interval_microseconds,
+      time_wait_microseconds, current_time, current_monotonic_time);
+  if (client == nullptr) return false;
+
+  // Registers.
+  auto result = client_map_.emplace(name, std::move(client));
+
+  if (result.second) {
+    SB_DLOG(INFO) << "[Watchdog] Registered: " << name;
+  } else {
+    SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name;
+  }
+  return result.second;
+}
+
+std::shared_ptr<Client> Watchdog::RegisterByClient(
+    std::string name, std::string description,
+    base::ApplicationState monitor_state, int64_t time_interval_microseconds,
+    int64_t time_wait_microseconds) {
+  if (is_disabled_) return nullptr;
+
+  starboard::ScopedLock scoped_lock(mutex_);
+
+  int64_t current_time = SbTimeToPosix(SbTimeGetNow());
+  SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
+
+  // Creates new client.
+  std::shared_ptr<Client> client = CreateClient(
+      name, description, monitor_state, time_interval_microseconds,
+      time_wait_microseconds, current_time, current_monotonic_time);
+  if (client == nullptr) return nullptr;
+
+  // Registers.
+  client_list_.emplace_back(client);
+
+  SB_DLOG(INFO) << "[Watchdog] Registered: " << name;
+  return client;
+}
+
+std::unique_ptr<Client> Watchdog::CreateClient(
+    std::string name, std::string description,
+    base::ApplicationState monitor_state, int64_t time_interval_microseconds,
+    int64_t time_wait_microseconds, int64_t current_time,
+    SbTimeMonotonic current_monotonic_time) {
+  // Validates parameters.
+  if (time_interval_microseconds < watchdog_monitor_frequency_ ||
+      time_wait_microseconds < 0) {
+    SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name;
+    if (time_interval_microseconds < watchdog_monitor_frequency_) {
+      SB_DLOG(ERROR) << "[Watchdog] Time interval less than min: "
+                     << watchdog_monitor_frequency_;
+    } else {
+      SB_DLOG(ERROR) << "[Watchdog] Time wait is negative.";
+    }
+    return nullptr;
+  }
+
   // Creates new Client.
   std::unique_ptr<Client> client(new Client);
   client->name = name;
@@ -466,15 +530,7 @@
   client->time_last_pinged_microseconds = current_time;
   client->time_last_updated_monotonic_microseconds = current_monotonic_time;
 
-  // Registers.
-  auto result = client_map_.emplace(name, std::move(client));
-
-  if (result.second) {
-    SB_DLOG(INFO) << "[Watchdog] Registered: " << name;
-  } else {
-    SB_DLOG(ERROR) << "[Watchdog] Unable to Register: " << name;
-  }
-  return result.second;
+  return std::move(client);
 }
 
 bool Watchdog::Unregister(const std::string& name, bool lock) {
@@ -493,11 +549,68 @@
   return result;
 }
 
+bool Watchdog::UnregisterByClient(std::shared_ptr<Client> client) {
+  if (is_disabled_) return true;
+
+  starboard::ScopedLock scoped_lock(mutex_);
+
+  std::string name = "";
+  if (client) name = client->name;
+
+  // Unregisters.
+  for (auto it = client_list_.begin(); it != client_list_.end(); it++) {
+    if (client == *it) {
+      client_list_.erase(it);
+      SB_DLOG(INFO) << "[Watchdog] Unregistered: " << name;
+      return true;
+    }
+  }
+  SB_DLOG(ERROR) << "[Watchdog] Unable to Unregister: " << name;
+  return false;
+}
+
 bool Watchdog::Ping(const std::string& name) { return Ping(name, ""); }
 
 bool Watchdog::Ping(const std::string& name, const std::string& info) {
   if (is_disabled_) return true;
 
+  starboard::ScopedLock scoped_lock(mutex_);
+
+  auto it = client_map_.find(name);
+  bool client_exists = it != client_map_.end();
+
+  if (client_exists) {
+    Client* client = it->second.get();
+    return PingHelper(client, name, info);
+  }
+  SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
+  return false;
+}
+
+bool Watchdog::PingByClient(std::shared_ptr<Client> client) {
+  return PingByClient(client, "");
+}
+
+bool Watchdog::PingByClient(std::shared_ptr<Client> client,
+                            const std::string& info) {
+  if (is_disabled_) return true;
+
+  std::string name = "";
+  if (client) name = client->name;
+
+  starboard::ScopedLock scoped_lock(mutex_);
+
+  for (auto it = client_list_.begin(); it != client_list_.end(); it++) {
+    if (client == *it) {
+      return PingHelper(client.get(), name, info);
+    }
+  }
+  SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
+  return false;
+}
+
+bool Watchdog::PingHelper(Client* client, const std::string& name,
+                          const std::string& info) {
   // Validates parameters.
   if (info.length() > kWatchdogMaxPingInfoLength) {
     SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
@@ -506,95 +619,109 @@
     return false;
   }
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  int64_t current_time = SbTimeToPosix(SbTimeGetNow());
+  SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
 
-  auto it = client_map_.find(name);
-  bool client_exists = it != client_map_.end();
+  // Updates last ping.
+  client->time_last_pinged_microseconds = current_time;
+  client->time_last_updated_monotonic_microseconds = current_monotonic_time;
 
-  if (client_exists) {
-    int64_t current_time = SbTimeToPosix(SbTimeGetNow());
-    SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow();
+  if (info != "") {
+    // Creates new ping_info.
+    base::Value ping_info(base::Value::Type::DICTIONARY);
+    ping_info.SetKey("timestampMilliseconds",
+                     base::Value(std::to_string(current_time / 1000)));
+    ping_info.SetKey("info", base::Value(info));
 
-    Client* client = it->second.get();
-    // Updates last ping.
-    client->time_last_pinged_microseconds = current_time;
-    client->time_last_updated_monotonic_microseconds = current_monotonic_time;
-
-    if (info != "") {
-      // Creates new ping_info.
-      base::Value ping_info(base::Value::Type::DICTIONARY);
-      ping_info.SetKey("timestampMilliseconds",
-                       base::Value(std::to_string(current_time / 1000)));
-      ping_info.SetKey("info", base::Value(info));
-
-      client->ping_infos.GetList().emplace_back(ping_info.Clone());
-      if (client->ping_infos.GetList().size() > kWatchdogMaxPingInfos)
-        client->ping_infos.GetList().erase(
-            client->ping_infos.GetList().begin());
-    }
-  } else {
-    SB_DLOG(ERROR) << "[Watchdog] Unable to Ping: " << name;
+    client->ping_infos.GetList().emplace_back(ping_info.Clone());
+    if (client->ping_infos.GetList().size() > kWatchdogMaxPingInfos)
+      client->ping_infos.GetList().erase(client->ping_infos.GetList().begin());
   }
-  return client_exists;
+  return true;
 }
 
 std::string Watchdog::GetWatchdogViolations(
     const std::vector<std::string>& clients, bool clear) {
-  // Gets a json string containing the Watchdog violations since the last
-  // call (up to the kWatchdogMaxViolations limit).
+  // Gets a json string containing the Watchdog violations of the given clients
+  // since the last call (up to the kWatchdogMaxViolations limit).
   if (is_disabled_) return "";
 
-  std::string watchdog_json = "";
-  std::string watchdog_json_fetched = "";
+  std::string fetched_violations_json = "";
 
   starboard::ScopedLock scoped_lock(mutex_);
 
-  if (pending_write_) WriteWatchdogViolations();
-
-  if (!static_cast<base::DictionaryValue*>(violations_map_.get())->empty()) {
-    // Get all Watchdog violations if clients is given.
+  if (!GetViolationsMap()->DictEmpty()) {
     if (clients.empty()) {
-      // Removes all Watchdog violations.
-      base::JSONWriter::Write(*violations_map_, &watchdog_json_fetched);
+      // Gets all Watchdog violations if no clients are given.
+      base::JSONWriter::Write(*GetViolationsMap(), &fetched_violations_json);
       if (clear) {
-        static_cast<base::DictionaryValue*>(violations_map_.get())->Clear();
-        violations_count_ = 0;
+        static_cast<base::DictionaryValue*>(GetViolationsMap().get())->Clear();
         starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
       }
     } else {
-      base::Value filtered_client_data(base::Value::Type::DICTIONARY);
-      for (int i = 0; i < clients.size(); i++) {
-        base::Value* violation_dict =
-            static_cast<base::DictionaryValue*>(violations_map_.get())
-                ->FindKey(clients[i]);
+      // Gets all Watchdog violations of the given clients.
+      base::Value fetched_violations(base::Value::Type::DICTIONARY);
+      for (std::string name : clients) {
+        base::Value* violation_dict = GetViolationsMap()->FindKey(name);
         if (violation_dict != nullptr) {
-          filtered_client_data.SetKey(clients[i], (*violation_dict).Clone());
+          fetched_violations.SetKey(name, (*violation_dict).Clone());
           if (clear) {
-            base::Value* violations = violation_dict->FindKey("violations");
-            int violations_count = violations->GetList().size();
-
-            static_cast<base::DictionaryValue*>(violations_map_.get())
-                ->RemoveKey(clients[i]);
-            violations_count_ -= violations_count;
-            if (!static_cast<base::DictionaryValue*>(violations_map_.get())
-                     ->empty()) {
-              WriteWatchdogViolations();
-            } else {
-              starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(),
-                                               true);
-            }
+            GetViolationsMap()->RemoveKey(name);
+            pending_write_ = true;
           }
         }
       }
-      if (!filtered_client_data.DictEmpty()) {
-        base::JSONWriter::Write(filtered_client_data, &watchdog_json_fetched);
+      if (!fetched_violations.DictEmpty()) {
+        base::JSONWriter::Write(fetched_violations, &fetched_violations_json);
+      }
+      if (clear) {
+        EvictOldWatchdogViolations();
       }
     }
-    SB_LOG(INFO) << "[Watchdog] Reading violations:\n" << watchdog_json_fetched;
+    SB_LOG(INFO) << "[Watchdog] Reading violations:\n"
+                 << fetched_violations_json;
   } else {
     SB_LOG(INFO) << "[Watchdog] No violations.";
   }
-  return watchdog_json_fetched;
+  return fetched_violations_json;
+}
+
+void Watchdog::EvictOldWatchdogViolations() {
+  int64_t current_timestamp_millis = SbTimeToPosix(SbTimeGetNow()) / 1000;
+  int64_t cutoff_timestamp_millis =
+      current_timestamp_millis - kWatchdogMaxViolationsAge;
+  std::vector<std::string> empty_violations;
+
+  // Iterates through map removing old violations.
+  for (const auto& map_it : GetViolationsMap()->DictItems()) {
+    std::string name = map_it.first;
+    base::Value& violation_dict = map_it.second;
+    base::Value* violations = violation_dict.FindKey("violations");
+    for (auto list_it = violations->GetList().begin();
+         list_it != violations->GetList().end();) {
+      int64_t violation_timestamp_millis = std::stoll(
+          list_it->FindKey("timestampViolationMilliseconds")->GetString());
+
+      if (violation_timestamp_millis < cutoff_timestamp_millis) {
+        list_it = violations->GetList().erase(list_it);
+        pending_write_ = true;
+      } else {
+        list_it++;
+      }
+    }
+    if (violations->GetList().empty()) {
+      empty_violations.push_back(name);
+    }
+  }
+
+  // Removes empty violations.
+  for (std::string name : empty_violations) {
+    GetViolationsMap()->RemoveKey(name);
+  }
+  if (GetViolationsMap()->DictEmpty()) {
+    starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true);
+    pending_write_ = false;
+  }
 }
 
 bool Watchdog::GetPersistentSettingWatchdogEnable() {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index 8aa9a7c..aed2ec1 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -88,9 +88,19 @@
                 base::ApplicationState monitor_state,
                 int64_t time_interval_microseconds,
                 int64_t time_wait_microseconds = 0, Replace replace = NONE);
+  std::shared_ptr<Client> RegisterByClient(std::string name,
+                                           std::string description,
+                                           base::ApplicationState monitor_state,
+                                           int64_t time_interval_microseconds,
+                                           int64_t time_wait_microseconds = 0);
   bool Unregister(const std::string& name, bool lock = true);
+  bool UnregisterByClient(std::shared_ptr<Client> client);
   bool Ping(const std::string& name);
   bool Ping(const std::string& name, const std::string& info);
+  bool PingByClient(std::shared_ptr<Client> client);
+  bool PingByClient(std::shared_ptr<Client> client, const std::string& info);
+  bool PingHelper(Client* client, const std::string& name,
+                  const std::string& info);
   std::string GetWatchdogViolations(
       const std::vector<std::string>& clients = {}, bool clear = true);
   bool GetPersistentSettingWatchdogEnable();
@@ -104,12 +114,21 @@
 #endif  // defined(_DEBUG)
 
  private:
+  std::shared_ptr<base::Value> GetViolationsMap();
   void WriteWatchdogViolations();
-  std::string ReadViolationFile(const char* file_path);
+  void EvictOldWatchdogViolations();
+  std::unique_ptr<Client> CreateClient(std::string name,
+                                       std::string description,
+                                       base::ApplicationState monitor_state,
+                                       int64_t time_interval_microseconds,
+                                       int64_t time_wait_microseconds,
+                                       int64_t current_time,
+                                       SbTimeMonotonic current_monotonic_time);
   static void* Monitor(void* context);
+  static bool MonitorClient(void* context, Client* client,
+                            SbTimeMonotonic current_monotonic_time);
   static void UpdateViolationsMap(void* context, Client* client,
                                   SbTimeMonotonic time_delta);
-  static void InitializeViolationsMap(void* context);
   static void EvictWatchdogViolation(void* context);
   static void MaybeWriteWatchdogViolations(void* context);
   static void MaybeTriggerCrash(void* context);
@@ -139,12 +158,13 @@
   SbTimeMonotonic time_last_written_microseconds_ = 0;
   // Number of microseconds between writes.
   int64_t write_wait_time_microseconds_;
-  // Dictionary of registered Watchdog clients.
+  // Dictionary of name registered Watchdog clients.
   std::unordered_map<std::string, std::unique_ptr<Client>> client_map_;
+  // List of client registered Watchdog clients, parallel data structure to
+  // client_map_.
+  std::vector<std::shared_ptr<Client>> client_list_;
   // Dictionary of lists of Watchdog violations represented as dictionaries.
-  std::unique_ptr<base::Value> violations_map_;
-  // Number of violations in violations_map_;
-  int violations_count_;
+  std::shared_ptr<base::Value> violations_map_;
   // Monitor thread.
   SbThread watchdog_thread_;
   // Flag to stop monitor thread.
diff --git a/cobalt/watchdog/watchdog_test.cc b/cobalt/watchdog/watchdog_test.cc
index 1cafc43..6668c2a 100644
--- a/cobalt/watchdog/watchdog_test.cc
+++ b/cobalt/watchdog/watchdog_test.cc
@@ -86,7 +86,6 @@
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
-
   ASSERT_FALSE(watchdog_->Register("test-name", "test-desc",
                                    base::kApplicationStateStarted,
                                    kWatchdogMonitorFrequency));
@@ -100,10 +99,6 @@
 }
 
 TEST_F(WatchdogTest, RegisterOnlyAcceptsValidParameters) {
-  ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
-                                  base::kApplicationStateStarted,
-                                  kWatchdogMonitorFrequency, 0));
-  ASSERT_TRUE(watchdog_->Unregister("test-name"));
   ASSERT_FALSE(watchdog_->Register("test-name-1", "test-desc-1",
                                    base::kApplicationStateStarted, 1, 0));
   ASSERT_FALSE(watchdog_->Unregister("test-name-1"));
@@ -127,7 +122,37 @@
   ASSERT_FALSE(watchdog_->Unregister("test-name-6"));
 }
 
-TEST_F(WatchdogTest, UnmatchedUnregistersShouldFail) {
+TEST_F(WatchdogTest, RegisterByClientOnlyAcceptsValidParameters) {
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted, 1, 0);
+  ASSERT_EQ(client, nullptr);
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client));
+  client = watchdog_->RegisterByClient("test-name", "test-desc",
+                                       base::kApplicationStateStarted, 0, 0);
+  ASSERT_EQ(client, nullptr);
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client));
+  client = watchdog_->RegisterByClient("test-name", "test-desc",
+                                       base::kApplicationStateStarted, -1, 0);
+  ASSERT_EQ(client, nullptr);
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client));
+  client = watchdog_->RegisterByClient("test-name", "test-desc",
+                                       base::kApplicationStateStarted,
+                                       kWatchdogMonitorFrequency, 1);
+  ASSERT_NE(client, nullptr);
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+  client = watchdog_->RegisterByClient("test-name", "test-desc",
+                                       base::kApplicationStateStarted,
+                                       kWatchdogMonitorFrequency, 0);
+  ASSERT_NE(client, nullptr);
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+  client = watchdog_->RegisterByClient("test-name", "test-desc",
+                                       base::kApplicationStateStarted,
+                                       kWatchdogMonitorFrequency, -1);
+  ASSERT_EQ(client, nullptr);
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client));
+}
+
+TEST_F(WatchdogTest, UnmatchedUnregisterShouldFail) {
   ASSERT_FALSE(watchdog_->Unregister("test-name"));
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
@@ -136,30 +161,58 @@
   ASSERT_FALSE(watchdog_->Unregister("test-name"));
 }
 
-TEST_F(WatchdogTest, UnmatchedPingsShouldFail) {
+TEST_F(WatchdogTest, UnmatchedUnregisterByClientShouldFail) {
+  std::shared_ptr<Client> client_test(new Client);
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client_test));
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  ASSERT_NE(client, nullptr);
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+  ASSERT_FALSE(watchdog_->UnregisterByClient(client));
+}
+
+TEST_F(WatchdogTest, UnmatchedPingShouldFail) {
   ASSERT_FALSE(watchdog_->Ping("test-name"));
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
   ASSERT_TRUE(watchdog_->Ping("test-name"));
-  ASSERT_TRUE(watchdog_->Ping("test-name"));
   ASSERT_TRUE(watchdog_->Unregister("test-name"));
   ASSERT_FALSE(watchdog_->Ping("test-name"));
 }
 
+TEST_F(WatchdogTest, UnmatchedPingByClientShouldFail) {
+  std::shared_ptr<Client> client_test(new Client);
+  ASSERT_FALSE(watchdog_->PingByClient(client_test));
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  ASSERT_NE(client, nullptr);
+  ASSERT_TRUE(watchdog_->PingByClient(client));
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+  ASSERT_FALSE(watchdog_->PingByClient(client));
+}
+
 TEST_F(WatchdogTest, PingOnlyAcceptsValidParameters) {
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
   ASSERT_TRUE(watchdog_->Ping("test-name", "42"));
-  ASSERT_FALSE(
-      watchdog_->Ping("test-name",
-                      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-                      "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-                      "xxxxxxxxxxxxxxxxxxxxxxxxxxx"));
+  ASSERT_FALSE(watchdog_->Ping("test-name", std::string(1025, 'x')));
   ASSERT_TRUE(watchdog_->Unregister("test-name"));
 }
 
+TEST_F(WatchdogTest, PingByClientOnlyAcceptsValidParameters) {
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  ASSERT_NE(client, nullptr);
+  ASSERT_TRUE(watchdog_->PingByClient(client, "42"));
+  ASSERT_FALSE(watchdog_->PingByClient(client, std::string(1025, 'x')));
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+}
+
 TEST_F(WatchdogTest, ViolationsJsonShouldPersistAndBeValid) {
   ASSERT_EQ(watchdog_->GetWatchdogViolations(), "");
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
@@ -281,11 +334,12 @@
   std::string json = watchdog_->GetWatchdogViolations();
   ASSERT_NE(json.find("test-name-1"), std::string::npos);
   ASSERT_EQ(json.find("test-name-2"), std::string::npos);
-  ASSERT_TRUE(watchdog_->Register("test-name-2", "test-desc-2",
-                                  base::kApplicationStateStarted,
-                                  kWatchdogMonitorFrequency));
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name-2", "test-desc-2", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  ASSERT_NE(client, nullptr);
   SbThreadSleep(kWatchdogSleepDuration);
-  ASSERT_TRUE(watchdog_->Unregister("test-name-2"));
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
   json = watchdog_->GetWatchdogViolations();
   ASSERT_EQ(json.find("test-name-1"), std::string::npos);
   ASSERT_NE(json.find("test-name-2"), std::string::npos);
@@ -296,7 +350,7 @@
   ASSERT_TRUE(watchdog_->Register("test-name", "test_desc",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
-  for (int i = 0; i < 21; i++) {
+  for (int i = 0; i < 61; i++) {
     ASSERT_TRUE(watchdog_->Ping("test-name", std::to_string(i)));
   }
   SbThreadSleep(kWatchdogSleepDuration);
@@ -307,7 +361,7 @@
   base::Value* violation_dict = violations_map->FindKey("test-name");
   base::Value* violations = violation_dict->FindKey("violations");
   base::Value* pingInfos = violations->GetList()[0].FindKey("pingInfos");
-  ASSERT_EQ(pingInfos->GetList().size(), 20);
+  ASSERT_EQ(pingInfos->GetList().size(), 60);
   ASSERT_EQ(pingInfos->GetList()[0].FindKey("info")->GetString(), "1");
   ASSERT_TRUE(watchdog_->Unregister("test-name"));
 }
@@ -408,7 +462,7 @@
   ASSERT_TRUE(watchdog_->Unregister("test-name"));
 }
 
-TEST_F(WatchdogTest, PingsShouldPreventViolations) {
+TEST_F(WatchdogTest, PingShouldPreventViolations) {
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
@@ -438,6 +492,20 @@
   ASSERT_TRUE(watchdog_->Unregister("test-name"));
 }
 
+TEST_F(WatchdogTest, PingByClientShouldPreventViolations) {
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  SbThreadSleep(kWatchdogMonitorFrequency / 2);
+  ASSERT_TRUE(watchdog_->PingByClient(client));
+  SbThreadSleep(kWatchdogMonitorFrequency / 2);
+  ASSERT_TRUE(watchdog_->PingByClient(client));
+  ASSERT_EQ(watchdog_->GetWatchdogViolations(), "");
+  SbThreadSleep(kWatchdogSleepDuration);
+  ASSERT_NE(watchdog_->GetWatchdogViolations(), "");
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+}
+
 TEST_F(WatchdogTest, UnregisterShouldPreventViolations) {
   ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
                                   base::kApplicationStateStarted,
@@ -447,6 +515,15 @@
   ASSERT_EQ(watchdog_->GetWatchdogViolations(), "");
 }
 
+TEST_F(WatchdogTest, UnregisterByClientShouldPreventViolations) {
+  std::shared_ptr<Client> client = watchdog_->RegisterByClient(
+      "test-name", "test-desc", base::kApplicationStateStarted,
+      kWatchdogMonitorFrequency);
+  ASSERT_TRUE(watchdog_->UnregisterByClient(client));
+  SbThreadSleep(kWatchdogSleepDuration);
+  ASSERT_EQ(watchdog_->GetWatchdogViolations(), "");
+}
+
 TEST_F(WatchdogTest, KillSwitchShouldPreventViolations) {
   TearDown();
   watchdog_ = new watchdog::Watchdog();
@@ -502,14 +579,13 @@
   SbThreadSleep(kWatchdogSleepDuration);
   ASSERT_TRUE(watchdog_->Unregister("test-name-1"));
   ASSERT_TRUE(watchdog_->Unregister("test-name-2"));
+
   std::vector<std::string> names = watchdog_->GetWatchdogViolationClientNames();
   ASSERT_EQ(names.size(), 2);
-  std::set<std::string> expected_names = {"test-name-1", "test-name-2"};
-  for (std::vector<std::string>::const_iterator it = names.begin();
-       it != names.end(); ++it) {
-    const std::string name = *it;
-    ASSERT_TRUE((expected_names.find(name) != expected_names.end()));
-  }
+  ASSERT_TRUE(std::find(names.begin(), names.end(), "test-name-1") !=
+              names.end());
+  ASSERT_TRUE(std::find(names.begin(), names.end(), "test-name-2") !=
+              names.end());
   watchdog_->GetWatchdogViolations();
   names = watchdog_->GetWatchdogViolationClientNames();
   ASSERT_EQ(names.size(), 0);
@@ -522,13 +598,10 @@
   ASSERT_TRUE(watchdog_->Register("test-name-2", "test-desc-2",
                                   base::kApplicationStateStarted,
                                   kWatchdogMonitorFrequency));
-  ASSERT_TRUE(watchdog_->Register("test-name-3", "test-desc-3",
-                                  base::kApplicationStateStarted,
-                                  kWatchdogMonitorFrequency));
   SbThreadSleep(kWatchdogSleepDuration);
   ASSERT_TRUE(watchdog_->Unregister("test-name-1"));
   ASSERT_TRUE(watchdog_->Unregister("test-name-2"));
-  ASSERT_TRUE(watchdog_->Unregister("test-name-3"));
+
   const std::vector<std::string> clients = {"test-name-1"};
   std::string json = watchdog_->GetWatchdogViolations(clients);
   ASSERT_NE(json, "");
@@ -538,45 +611,29 @@
   ASSERT_NE(violation_dict, nullptr);
   violation_dict = violations_map->FindKey("test-name-2");
   ASSERT_EQ(violation_dict, nullptr);
-  violation_dict = violations_map->FindKey("test-name-3");
-  ASSERT_EQ(violation_dict, nullptr);
-
-  std::string file_json = "";
-  starboard::ScopedFile read_file(watchdog_->GetWatchdogFilePath().c_str(),
-                                  kSbFileOpenOnly | kSbFileRead);
-  if (read_file.IsValid()) {
-    int64_t kFileSize = read_file.GetSize();
-    std::vector<char> buffer(kFileSize + 1, 0);
-    read_file.ReadAll(buffer.data(), kFileSize);
-    file_json = std::string(buffer.data());
-  }
-  ASSERT_NE(file_json, "");
-  violations_map = base::JSONReader::Read(file_json);
-  ASSERT_NE(violations_map, nullptr);
-  violation_dict = violations_map->FindKey("test-name-2");
-  ASSERT_NE(violation_dict, nullptr);
-  violation_dict = violations_map->FindKey("test-name-3");
-  ASSERT_NE(violation_dict, nullptr);
-  violation_dict = violations_map->FindKey("test-name-1");
-  ASSERT_EQ(violation_dict, nullptr);
-
   json = watchdog_->GetWatchdogViolations(clients);
   ASSERT_EQ(json, "");
+}
 
-  const std::vector<std::string> clients2 = {"test-name-2", "test-name-3"};
-  json = watchdog_->GetWatchdogViolations(clients2);
-  ASSERT_NE(json, "");
-  violations_map = base::JSONReader::Read(json);
-  ASSERT_NE(violations_map, nullptr);
-  violation_dict = violations_map->FindKey("test-name-1");
-  ASSERT_EQ(violation_dict, nullptr);
-  violation_dict = violations_map->FindKey("test-name-2");
-  ASSERT_NE(violation_dict, nullptr);
-  violation_dict = violations_map->FindKey("test-name-3");
-  ASSERT_NE(violation_dict, nullptr);
-  starboard::ScopedFile read_file_again(
-      watchdog_->GetWatchdogFilePath().c_str(), kSbFileOpenOnly | kSbFileRead);
-  ASSERT_EQ(read_file_again.IsValid(), false);
+TEST_F(WatchdogTest, EvictOldWatchdogViolations) {
+  // Creates old Violation json file.
+  std::unique_ptr<base::Value> dummy_map =
+      std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
+  dummy_map->SetKey("test-name-old",
+                    CreateDummyViolationDict("test-desc-old", 0, 1));
+  std::string json;
+  base::JSONWriter::Write(*dummy_map, &json);
+  starboard::ScopedFile file(watchdog_->GetWatchdogFilePath().c_str(),
+                             kSbFileCreateAlways | kSbFileWrite);
+  TearDown();
+  file.WriteAll(json.c_str(), static_cast<int>(json.size()));
+  watchdog_ = new watchdog::Watchdog();
+  watchdog_->InitializeCustom(nullptr, std::string(kWatchdogViolationsJson),
+                              kWatchdogMonitorFrequency);
+
+  ASSERT_NE(watchdog_->GetWatchdogViolations({}, false), "");
+  ASSERT_EQ(watchdog_->GetWatchdogViolations({"test-name-new"}), "");
+  ASSERT_EQ(watchdog_->GetWatchdogViolations({}, false), "");
 }
 
 }  // namespace watchdog
diff --git a/cobalt/webdriver/testdata/simple_test.py b/cobalt/webdriver/testdata/simple_test.py
index 5b5a9a1..168436b 100755
--- a/cobalt/webdriver/testdata/simple_test.py
+++ b/cobalt/webdriver/testdata/simple_test.py
@@ -176,7 +176,7 @@
   """
   request = ElementRequest(session_id, element_id, GET, 'screenshot')
   if request:
-    with open(filename, 'w', encoding='utf-8') as f:
+    with open(filename, 'wb') as f:
       f.write(binascii.a2b_base64(request['value']))
       f.close()
 
diff --git a/cobalt/worker/service_worker_context.cc b/cobalt/worker/service_worker_context.cc
index b4b4bac..ad96c62 100644
--- a/cobalt/worker/service_worker_context.cc
+++ b/cobalt/worker/service_worker_context.cc
@@ -163,15 +163,14 @@
 
 ServiceWorkerContext::ServiceWorkerContext(
     web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop,
-    const GURL& url)
+    web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop)
     : message_loop_(message_loop) {
   DCHECK_EQ(message_loop_, base::MessageLoop::current());
   jobs_ =
       std::make_unique<ServiceWorkerJobs>(this, network_module, message_loop);
 
   ServiceWorkerPersistentSettings::Options options(web_settings, network_module,
-                                                   platform_info, this, url);
+                                                   platform_info, this);
   scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options));
   DCHECK(scope_to_registration_map_);
 }
diff --git a/cobalt/worker/service_worker_context.h b/cobalt/worker/service_worker_context.h
index e51882d..460200f 100644
--- a/cobalt/worker/service_worker_context.h
+++ b/cobalt/worker/service_worker_context.h
@@ -51,7 +51,7 @@
   ServiceWorkerContext(web::WebSettings* web_settings,
                        network::NetworkModule* network_module,
                        web::UserAgentPlatformInfo* platform_info,
-                       base::MessageLoop* message_loop, const GURL& url);
+                       base::MessageLoop* message_loop);
   ~ServiceWorkerContext();
 
   base::MessageLoop* message_loop() { return message_loop_; }
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index fa0451b..ecbb126 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -852,7 +852,8 @@
   ServiceWorkerObject* installing_worker = registration->installing_worker();
   // 11. If the result of running the Should Skip Event algorithm with
   //     installingWorker and "install" is false, then:
-  if (!installing_worker->ShouldSkipEvent(base::Tokens::install())) {
+  if (installing_worker &&
+      !installing_worker->ShouldSkipEvent(base::Tokens::install())) {
     // 11.1. Let forceBypassCache be true if job’s force bypass cache flag is
     //       set, and false otherwise.
     bool force_bypass_cache = job->force_bypass_cache_flag;
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc
index fc81821..368ba1a 100644
--- a/cobalt/worker/service_worker_persistent_settings.cc
+++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -119,12 +119,6 @@
     url::Origin storage_key =
         url::Origin::Create(GURL(dict[kSettingsStorageKeyKey]->GetString()));
 
-    // Only add persisted workers to the registration_map
-    // if their storage_key matches the origin of the initial_url.
-    if (!storage_key.IsSameOriginWith(url::Origin::Create(options_.url))) {
-      continue;
-    }
-
     if (!CheckPersistentValue(key_string, kSettingsScopeUrlKey, dict,
                               base::Value::Type::STRING))
       continue;
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h
index f855895..55142a1 100644
--- a/cobalt/worker/service_worker_persistent_settings.h
+++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -48,17 +48,15 @@
     Options(web::WebSettings* web_settings,
             network::NetworkModule* network_module,
             web::UserAgentPlatformInfo* platform_info,
-            ServiceWorkerContext* service_worker_context, const GURL& url)
+            ServiceWorkerContext* service_worker_context)
         : web_settings(web_settings),
           network_module(network_module),
           platform_info(platform_info),
-          service_worker_context(service_worker_context),
-          url(url) {}
+          service_worker_context(service_worker_context) {}
     web::WebSettings* web_settings;
     network::NetworkModule* network_module;
     web::UserAgentPlatformInfo* platform_info;
     ServiceWorkerContext* service_worker_context;
-    const GURL& url;
   };
 
   explicit ServiceWorkerPersistentSettings(const Options& options);
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
index 53e47b4..e69be69 100644
--- a/components/metrics/BUILD.gn
+++ b/components/metrics/BUILD.gn
@@ -141,6 +141,11 @@
       "system_memory_stats_recorder_win.cc",
       "system_session_analyzer_win.cc",
       "system_session_analyzer_win.h",
+
+      # Files with unused symbols. ex: DriveMetricsProvider::HasSeekPenalty
+      # These files give linker errors about undefined symbols during modualr builds.
+      "drive_metrics_provider.cc",
+      "drive_metrics_provider.h",
     ]
   }
 
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn
index ad64baf..46eba0c 100644
--- a/components/variations/BUILD.gn
+++ b/components/variations/BUILD.gn
@@ -87,6 +87,17 @@
     ]
   }
 
+  if (use_cobalt_customizations) {
+      # Files with unused symbols. ex: base::FeatureList::RegisterFieldTrialOverride
+      # These files give linker errors about undefined symbols during modualr builds.   
+    sources -= [
+      "variations_seed_processor.cc",
+      "variations_seed_processor.h",
+      "variations_seed_simulator.cc",
+      "variations_seed_simulator.h",
+    ]
+  }
+
   if (!use_cobalt_customizations && (is_android || is_ios)) {
     sources += [
       "variations_request_scheduler_mobile.cc",
@@ -128,7 +139,6 @@
   }
 }
 
-# TODO(b/283258321): Re-enable as many tests as posible.
 if (!use_cobalt_customizations) {
   static_library("test_support") {
     testonly = true
diff --git a/net/BUILD.gn b/net/BUILD.gn
index e694523..2fb59e7 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -427,6 +427,7 @@
     "disk_cache/cobalt/cobalt_disk_cache.cc",
     "disk_cache/cobalt/cobalt_backend_impl.cc",
     "disk_cache/cobalt/cobalt_backend_impl.h",
+    "disk_cache/cobalt/resource_type.cc",
     "disk_cache/cobalt/resource_type.h",
     "disk_cache/net_log_parameters.cc",
     "disk_cache/net_log_parameters.h",
@@ -4217,7 +4218,6 @@
     "//base:i18n",
     "//base/test:test_support",
     "//base/third_party/dynamic_annotations",
-    "//cobalt/persistent_storage:persistent_settings",
     "//crypto",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/net/android/unittest_support/AndroidManifest.xml b/net/android/unittest_support/AndroidManifest.xml
index d4f4261..63a5fea 100644
--- a/net/android/unittest_support/AndroidManifest.xml
+++ b/net/android/unittest_support/AndroidManifest.xml
@@ -10,7 +10,7 @@
       android:versionCode="1"
       android:versionName="1.0">
 
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" />
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
diff --git a/net/base/cache_type.h b/net/base/cache_type.h
index debae51..c5e72ef 100644
--- a/net/base/cache_type.h
+++ b/net/base/cache_type.h
@@ -24,9 +24,6 @@
   CACHE_BACKEND_DEFAULT,
   CACHE_BACKEND_BLOCKFILE,  // The |BackendImpl|.
   CACHE_BACKEND_SIMPLE,     // The |SimpleBackendImpl|.
-#if defined(STARBOARD)
-  CACHE_BACKEND_COBALT      // The |CobaltBackendImpl|,
-#endif
 };
 
 }  // namespace net
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.cc b/net/disk_cache/cobalt/cobalt_backend_impl.cc
index b60ab38..1bf9108 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.cc
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.cc
@@ -30,8 +30,6 @@
 
 namespace {
 
-const char kPersistentSettingsJson[] = "cache_settings.json";
-
 void CompletionOnceCallbackHandler(
     scoped_refptr<CobaltBackendImpl::RefCountedRunner> runner,
     int result) {
@@ -53,36 +51,23 @@
   return kOther;
 }
 
-void ReadDiskCacheSize(
-    cobalt::persistent_storage::PersistentSettings* settings,
-    int64_t max_bytes) {
-  auto total_size = 0;
-  disk_cache::ResourceTypeMetadata kTypeMetadataNew[disk_cache::kTypeCount];
-
-  for (int i = 0; i < disk_cache::kTypeCount; i++) {
-    auto metadata = disk_cache::kTypeMetadata[i];
-    uint32_t bucket_size =
-        static_cast<uint32_t>(settings->GetPersistentSettingAsDouble(
-            metadata.directory, metadata.max_size_bytes));
-    kTypeMetadataNew[i] = {metadata.directory, bucket_size};
-
-    total_size += bucket_size;
+bool NeedsBackend(ResourceType resource_type) {
+  switch (resource_type) {
+    case kHTML:
+    case kCSS:
+    case kImage:
+    case kFont:
+    case kUncompiledScript:
+    case kOther:
+    case kSplashScreen:
+      return true;
+    case kCompiledScript:
+    case kCacheApi:
+    case kServiceWorkerScript:
+    default:
+      return false;
   }
-
-  // Check if PersistentSettings values are valid and can replace the disk_cache::kTypeMetadata.
-  if (total_size <= max_bytes) {
-    std::copy(std::begin(kTypeMetadataNew), std::end(kTypeMetadataNew), std::begin(disk_cache::kTypeMetadata));
-    return;
-  }
-
-  // PersistentSettings values are invalid and will be replaced by the default values in
-  // disk_cache::kTypeMetadata.
-  for (int i = 0; i < disk_cache::kTypeCount; i++) {
-    auto metadata = disk_cache::kTypeMetadata[i];
-    settings->SetPersistentSetting(
-            metadata.directory,
-            std::make_unique<base::Value>(static_cast<double>(metadata.max_size_bytes)));
-  }
+  return false;
 }
 
 }  // namespace
@@ -94,57 +79,43 @@
     net::CacheType cache_type,
     net::NetLog* net_log)
     : weak_factory_(this) {
-  persistent_settings_ =
-      std::make_unique<cobalt::persistent_storage::PersistentSettings>(
-          kPersistentSettingsJson);
-  ReadDiskCacheSize(persistent_settings_.get(), max_bytes);
-
-  // Initialize disk backend for each resource type.
   int64_t total_size = 0;
   for (int i = 0; i < kTypeCount; i++) {
-    auto metadata = kTypeMetadata[i];
-    base::FilePath dir = path.Append(FILE_PATH_LITERAL(metadata.directory));
-    int64_t bucket_size = metadata.max_size_bytes;
+    ResourceType resource_type = (ResourceType)i;
+    if (!NeedsBackend(resource_type)) {
+      continue;
+    }
+    std::string sub_directory = defaults::GetSubdirectory(resource_type);
+    base::FilePath dir = path.Append(FILE_PATH_LITERAL(sub_directory));
+    uint32_t bucket_size = disk_cache::settings::GetQuota(resource_type);
     total_size += bucket_size;
     SimpleBackendImpl* simple_backend = new SimpleBackendImpl(
-        dir, cleanup_tracker, /* file_tracker = */ nullptr, bucket_size,
+        dir, cleanup_tracker, /*file_tracker=*/nullptr, bucket_size,
         cache_type, net_log);
-    simple_backend_map_[(ResourceType)i] = simple_backend;
+    simple_backend_map_[resource_type] = simple_backend;
   }
-
   // Must be at least enough space for each backend.
   DCHECK(total_size <= max_bytes);
 }
 
 CobaltBackendImpl::~CobaltBackendImpl() {
   for (int i = 0; i < kTypeCount; i++) {
-    delete simple_backend_map_[(ResourceType)i];
+    ResourceType resource_type = (ResourceType)i;
+    if (simple_backend_map_.count(resource_type) == 1) {
+      delete simple_backend_map_[resource_type];
+    }
   }
   simple_backend_map_.clear();
 }
 
 void CobaltBackendImpl::UpdateSizes(ResourceType type, uint32_t bytes) {
-  if (bytes == disk_cache::kTypeMetadata[type].max_size_bytes)
+  if (simple_backend_map_.count(type) == 0) {
     return;
-
-  // Static cast value to double since base::Value cannot be a long.
-  persistent_settings_->SetPersistentSetting(
-      disk_cache::kTypeMetadata[type].directory,
-      std::make_unique<base::Value>(static_cast<double>(bytes)));
-
-  disk_cache::kTypeMetadata[type].max_size_bytes = bytes;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[type];
   simple_backend->SetMaxSize(bytes);
 }
 
-uint32_t CobaltBackendImpl::GetQuota(ResourceType type) {
-  return disk_cache::kTypeMetadata[type].max_size_bytes;
-}
-
-void CobaltBackendImpl::ValidatePersistentSettings() {
-  persistent_settings_->ValidatePersistentSettings();
-}
-
 net::Error CobaltBackendImpl::Init(CompletionOnceCallback completion_callback) {
   auto closure_runner =
       base::MakeRefCounted<RefCountedRunner>(std::move(completion_callback));
@@ -173,6 +144,9 @@
                                         net::RequestPriority request_priority,
                                         Entry** entry,
                                         CompletionOnceCallback callback) {
+  if (simple_backend_map_.count(GetType(key)) == 0) {
+    return net::Error::ERR_BLOCKED_BY_CLIENT;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
   return simple_backend->OpenEntry(key, request_priority, entry,
                                    std::move(callback));
@@ -183,11 +157,11 @@
                                           Entry** entry,
                                           CompletionOnceCallback callback) {
   ResourceType type = GetType(key);
-  auto quota = disk_cache::kTypeMetadata[type].max_size_bytes;
-  if (quota == 0) {
+  auto quota = disk_cache::settings::GetQuota(type);
+  if (quota == 0 || simple_backend_map_.count(type) == 0) {
     return net::Error::ERR_BLOCKED_BY_CLIENT;
   }
-  SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
+  SimpleBackendImpl* simple_backend = simple_backend_map_[type];
   return simple_backend->CreateEntry(key, request_priority, entry,
                                      std::move(callback));
 }
@@ -195,6 +169,9 @@
 net::Error CobaltBackendImpl::DoomEntry(const std::string& key,
                                         net::RequestPriority priority,
                                         CompletionOnceCallback callback) {
+  if (simple_backend_map_.count(GetType(key)) == 0) {
+    return net::Error::ERR_BLOCKED_BY_CLIENT;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
   return simple_backend->DoomEntry(key, priority, std::move(callback));
 }
@@ -273,6 +250,9 @@
 }
 
 void CobaltBackendImpl::OnExternalCacheHit(const std::string& key) {
+  if (simple_backend_map_.count(GetType(key)) == 0) {
+    return;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
   simple_backend->OnExternalCacheHit(key);
 }
@@ -291,6 +271,10 @@
 
 net::Error CobaltBackendImpl::DoomAllEntriesOfType(disk_cache::ResourceType type,
                         CompletionOnceCallback callback) {
+  if (simple_backend_map_.count(type) == 0) {
+    std::move(callback).Run(net::OK);
+    return net::OK;
+  }
   SimpleBackendImpl* simple_backend = simple_backend_map_[type];
   return simple_backend->DoomAllEntries(std::move(callback));
 }
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h
index b3b1b54..b1bfe31 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.h
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -23,7 +23,6 @@
 #include <utility>
 
 #include "base/callback_helpers.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/base/completion_once_callback.h"
 #include "net/disk_cache/cobalt/resource_type.h"
 #include "net/disk_cache/disk_cache.h"
@@ -50,8 +49,6 @@
 
   net::Error Init(CompletionOnceCallback completion_callback);
   void UpdateSizes(ResourceType type, uint32_t bytes);
-  uint32_t GetQuota(ResourceType type);
-  void ValidatePersistentSettings();
 
   // Backend interface.
   net::CacheType GetCacheType() const override;
@@ -117,10 +114,6 @@
   base::WeakPtrFactory<CobaltBackendImpl> weak_factory_;
 
   std::map<ResourceType, SimpleBackendImpl*> simple_backend_map_;
-
-  // Json PrefStore used for persistent settings.
-  std::unique_ptr<cobalt::persistent_storage::PersistentSettings>
-      persistent_settings_;
 };
 
 }  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/resource_type.cc b/net/disk_cache/cobalt/resource_type.cc
new file mode 100644
index 0000000..6a3f2f3
--- /dev/null
+++ b/net/disk_cache/cobalt/resource_type.cc
@@ -0,0 +1,175 @@
+// Copyright 2023 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 "net/disk_cache/cobalt/resource_type.h"
+
+#include "base/logging.h"
+
+namespace disk_cache {
+namespace defaults {
+
+std::string GetSubdirectory(ResourceType resource_type) {
+  switch (resource_type) {
+    case kOther:
+      return "other";
+    case kHTML:
+      return "html";
+    case kCSS:
+      return "css";
+    case kImage:
+      return "image";
+    case kFont:
+      return "font";
+    case kSplashScreen:
+      return "splash";
+    case kUncompiledScript:
+      return "uncompiled_js";
+    case kCompiledScript:
+      return "compiled_js";
+    case kCacheApi:
+      return "cache_api";
+    case kServiceWorkerScript:
+      return "service_worker_js";
+    default:
+      NOTREACHED() << "Unexpected resource_type " << resource_type;
+      break;
+  }
+  return "";
+}
+
+uint32_t GetQuota(ResourceType resource_type) {
+  switch (resource_type) {
+    case kOther:
+      return 3 * 1024 * 1024;
+    case kHTML:
+      return 2 * 1024 * 1024;
+    case kCSS:
+      return 1 * 1024 * 1024;
+    case kImage:
+      return 0;
+    case kFont:
+      return 3 * 1024 * 1024;
+    case kSplashScreen:
+      return 2 * 1024 * 1024;
+    case kUncompiledScript:
+      return 3 * 1024 * 1024;
+    case kCompiledScript:
+      return 3 * 1024 * 1024;
+    case kCacheApi:
+      return 3 * 1024 * 1024;
+    case kServiceWorkerScript:
+      return 3 * 1024 * 1024;
+    default:
+      NOTREACHED() << "Unexpected resource_type " << resource_type;
+      break;
+  }
+  return 0;
+}
+
+}  // namespace defaults
+
+namespace settings {
+
+namespace {
+
+starboard::atomic_int32_t other_quota = starboard::atomic_int32_t(defaults::GetQuota(kOther));
+starboard::atomic_int32_t html_quota = starboard::atomic_int32_t(defaults::GetQuota(kHTML));
+starboard::atomic_int32_t css_quota = starboard::atomic_int32_t(defaults::GetQuota(kCSS));
+starboard::atomic_int32_t image_quota = starboard::atomic_int32_t(defaults::GetQuota(kImage));
+starboard::atomic_int32_t font_quota = starboard::atomic_int32_t(defaults::GetQuota(kFont));
+starboard::atomic_int32_t splash_screen_quota = starboard::atomic_int32_t(defaults::GetQuota(kSplashScreen));
+starboard::atomic_int32_t uncompiled_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kUncompiledScript));
+starboard::atomic_int32_t compiled_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kCompiledScript));
+starboard::atomic_int32_t cache_api_quota = starboard::atomic_int32_t(defaults::GetQuota(kCacheApi));
+starboard::atomic_int32_t service_worker_script_quota = starboard::atomic_int32_t(defaults::GetQuota(kServiceWorkerScript));
+starboard::atomic_bool cache_enabled = starboard::atomic_bool(true);
+
+}  // namespace
+
+uint32_t GetQuota(ResourceType resource_type) {
+  switch (resource_type) {
+    case kOther:
+      return other_quota.load();
+    case kHTML:
+      return html_quota.load();
+    case kCSS:
+      return css_quota.load();
+    case kImage:
+      return image_quota.load();
+    case kFont:
+      return font_quota.load();
+    case kSplashScreen:
+      return splash_screen_quota.load();
+    case kUncompiledScript:
+      return uncompiled_script_quota.load();
+    case kCompiledScript:
+      return compiled_script_quota.load();
+    case kCacheApi:
+      return cache_api_quota.load();
+    case kServiceWorkerScript:
+      return service_worker_script_quota.load();
+    default:
+      NOTREACHED() << "Unexpected resource_type " << resource_type;
+  }
+  return 0;
+}
+
+void SetQuota(ResourceType resource_type, uint32_t value) {
+  switch (resource_type) {
+    case kOther:
+      other_quota.store(static_cast<int32_t>(value));
+      break;
+    case kHTML:
+      html_quota.store(static_cast<int32_t>(value));
+      break;
+    case kCSS:
+      css_quota.store(static_cast<int32_t>(value));
+      break;
+    case kImage:
+      image_quota.store(static_cast<int32_t>(value));
+      break;
+    case kFont:
+      font_quota.store(static_cast<int32_t>(value));
+      break;
+    case kSplashScreen:
+      splash_screen_quota.store(static_cast<int32_t>(value));
+      break;
+    case kUncompiledScript:
+      uncompiled_script_quota.store(static_cast<int32_t>(value));
+      break;
+    case kCompiledScript:
+      compiled_script_quota.store(static_cast<int32_t>(value));
+      break;
+    case kCacheApi:
+      cache_api_quota.store(static_cast<int32_t>(value));
+      break;
+    case kServiceWorkerScript:
+      service_worker_script_quota.store(static_cast<int32_t>(value));
+      break;
+    default:
+      NOTREACHED() << "Unexpected resource_type " << resource_type;
+      break;
+  }
+}
+
+bool GetCacheEnabled() {
+  return cache_enabled.load();
+}
+
+void SetCacheEnabled(bool value) {
+  cache_enabled.store(value);
+}
+
+}  // namespace settings
+}  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/resource_type.h b/net/disk_cache/cobalt/resource_type.h
index f291033..f401b65 100644
--- a/net/disk_cache/cobalt/resource_type.h
+++ b/net/disk_cache/cobalt/resource_type.h
@@ -17,6 +17,9 @@
 
 #include <string>
 
+#include "starboard/common/atomic.h"
+#include "starboard/types.h"
+
 namespace disk_cache {
 
 /* Note: If adding a new resource type, add corresponding metadata below. */
@@ -34,22 +37,21 @@
   kTypeCount = 10,
 };
 
-struct ResourceTypeMetadata {
-  std::string directory;
-  uint32_t max_size_bytes;
-};
+namespace defaults {
 
-static uint32_t kInitialBytes = static_cast<uint32_t> (3 * 1024 * 1024);
-// These values are updated on start up in application.cc, using the
-// persisted values saved in settings.json.
-static ResourceTypeMetadata kTypeMetadata[] = {
-    {"other", kInitialBytes},         {"html", 2 * 1024 * 1024},
-    {"css", 1 * 1024 * 1024},         {"image", 0},
-    {"font", kInitialBytes},          {"splash", 2 * 1024 * 1024},
-    {"uncompiled_js", kInitialBytes}, {"compiled_js", kInitialBytes},
-    {"cache_api", kInitialBytes},     {"service_worker_js", kInitialBytes},
-};
+std::string GetSubdirectory(ResourceType resource_type);
+uint32_t GetQuota(ResourceType resource_type);
 
+}  // namespace defaults
+
+namespace settings {
+
+uint32_t GetQuota(ResourceType resource_type);
+void SetQuota(ResourceType resource_type, uint32_t value);
+bool GetCacheEnabled();
+void SetCacheEnabled(bool value);
+
+}  // namespace settings
 }  // namespace disk_cache
 
 #endif  // NET_DISK_CACHE_COBALT_RESOURCE_TYPE_H_
diff --git a/net/disk_cache/disk_cache.cc b/net/disk_cache/disk_cache.cc
index b8abfac..81457ca 100644
--- a/net/disk_cache/disk_cache.cc
+++ b/net/disk_cache/disk_cache.cc
@@ -269,7 +269,8 @@
 #if defined(OS_ANDROID)
                                 nullptr,
 #endif
-                                net_log, backend, base::OnceClosure(),
+                                net_log, backend,
+                                base::OnceClosure(),
                                 std::move(callback));
 }
 
@@ -286,7 +287,8 @@
     base::android::ApplicationStatusListener* app_status_listener) {
   return CreateCacheBackendImpl(type, backend_type, path, max_bytes, force,
                                 std::move(app_status_listener), net_log,
-                                backend, base::OnceClosure(),
+                                backend,
+                                base::OnceClosure(),
                                 std::move(callback));
 }
 #endif
diff --git a/net/test/android/javatests/AndroidManifest.xml b/net/test/android/javatests/AndroidManifest.xml
index af051a4..9bccb8e 100644
--- a/net/test/android/javatests/AndroidManifest.xml
+++ b/net/test/android/javatests/AndroidManifest.xml
@@ -8,7 +8,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="org.chromium.net.test.support">
 
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" />
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
 
diff --git a/starboard/BUILD.gn b/starboard/BUILD.gn
index 3ab468c..aab526b 100644
--- a/starboard/BUILD.gn
+++ b/starboard/BUILD.gn
@@ -51,8 +51,13 @@
   if (sb_filter_based_player) {
     deps += [
       "//starboard/shared/starboard/player/filter/testing:player_filter_tests($starboard_toolchain)",
-      "//starboard/shared/starboard/player/filter/tools:audio_dmp_player($starboard_toolchain)",
+      "//starboard/shared/starboard/player/filter/testing:player_filter_tests_install($starboard_toolchain)",
     ]
+
+    # TODO: b/296715826 - Fix build error for windows modular builds.
+    if (!(sb_is_modular && is_host_win)) {
+      deps += [ "//starboard/shared/starboard/player/filter/tools:audio_dmp_player($starboard_toolchain)" ]
+    }
   }
 
   if (sb_enable_benchmark) {
@@ -71,6 +76,7 @@
       data_deps = [
         "//starboard/loader_app($starboard_toolchain)",
         "//third_party/crashpad/handler:crashpad_handler(//$starboard_path/toolchain:native_target)",
+        "//third_party/crashpad/tools:crashpad_database_util(//$starboard_path/toolchain:native_target)",
       ]
     }
   }
@@ -109,7 +115,7 @@
     }
   } else {
     public_deps += [
-      ":starboard_platform_group($starboard_toolchain)",
+      ":starboard_platform_group_static($starboard_toolchain)",
       "//starboard/common",
     ]
 
@@ -184,6 +190,10 @@
   starboard_platform_target("starboard_platform_group") {
   }
 
+  starboard_platform_target("starboard_platform_group_static") {
+    target_type = "group"
+  }
+
   if (platform_tests_path == "") {
     # If 'starboard_platform_tests' is not defined by the platform, then an
     # empty 'starboard_platform_tests' target is defined.
diff --git a/starboard/android/apk/apk_sources.gni b/starboard/android/apk/apk_sources.gni
index d1f668c..4564202 100644
--- a/starboard/android/apk/apk_sources.gni
+++ b/starboard/android/apk/apk_sources.gni
@@ -47,6 +47,7 @@
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/Log.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java",
+  "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java",
   "//starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaImage.java",
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle
index b64b5d3..a50b50b 100644
--- a/starboard/android/apk/app/build.gradle
+++ b/starboard/android/apk/app/build.gradle
@@ -75,7 +75,7 @@
     defaultConfig {
         applicationId "dev.cobalt.coat"
         minSdkVersion 24
-        targetSdkVersion 31
+        targetSdkVersion 34
         versionCode 1
         versionName "${buildId}"
         manifestPlaceholders = [
diff --git a/starboard/android/apk/app/src/app/AndroidManifest.xml b/starboard/android/apk/app/src/app/AndroidManifest.xml
index 6b2602d..0c23b47 100644
--- a/starboard/android/apk/app/src/app/AndroidManifest.xml
+++ b/starboard/android/apk/app/src/app/AndroidManifest.xml
@@ -31,6 +31,7 @@
   <uses-permission android:name="android.permission.RECORD_AUDIO"/>
   <!-- This is needed when targeting API 28+ to use foreground services -->
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
 
   <!-- https://iabtechlab.com/OTT-IFA, AdvertisingIdClient.Info.getId() -->
   <uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
index d942102..129b3c1 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -32,7 +32,7 @@
 import androidx.annotation.CallSuper;
 import com.google.androidgamesdk.GameActivity;
 import dev.cobalt.media.AudioOutputManager;
-import dev.cobalt.media.MediaCodecUtil;
+import dev.cobalt.media.MediaCodecCapabilitiesLogger;
 import dev.cobalt.media.VideoSurfaceView;
 import dev.cobalt.util.DisplayUtil;
 import dev.cobalt.util.Log;
@@ -146,7 +146,7 @@
   protected void onStart() {
     if (!isReleaseBuild()) {
       getStarboardBridge().getAudioOutputManager().dumpAllOutputDevices();
-      MediaCodecUtil.dumpAllDecoders();
+      MediaCodecCapabilitiesLogger.dumpAllDecoders();
     }
     if (forceCreateNewVideoSurfaceView) {
       Log.w(TAG, "Force to create a new video surface.");
@@ -159,8 +159,6 @@
 
     getStarboardBridge().onActivityStart(this, keyboardEditor);
     super.onStart();
-
-    nativeInitializeMediaCapabilitiesInBackground();
   }
 
   @Override
@@ -396,6 +394,4 @@
   public long getAppStartTimestamp() {
     return timeInNanoseconds;
   }
-
-  private static native void nativeInitializeMediaCapabilitiesInBackground();
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
index e24faf6..8f7742b 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
@@ -22,7 +22,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
+import android.content.pm.ServiceInfo;
 import android.os.Build.VERSION;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -37,17 +37,19 @@
   private static final int NOTIFICATION_ID = 193266736; // CL number for uniqueness.
   private static final String NOTIFICATION_CHANNEL_ID = "dev.cobalt.coat media playback service";
   private static final String NOTIFICATION_CHANNEL_NAME = "Media playback service";
+  private boolean channelCreated = true;
+  private NotificationManager notificationManager = null;
 
   @Override
   public void onCreate() {
-    if (getStarboardBridge() == null) {
-      Log.e(TAG, "StarboardBridge already destroyed.");
-      return;
-    }
     Log.i(TAG, "Creating a Media playback foreground service.");
     super.onCreate();
-    getStarboardBridge().onServiceStart(this);
-    createNotificationChannel();
+    if (getStarboardBridge() != null) {
+      getStarboardBridge().onServiceStart(this);
+    }
+    this.notificationManager =
+        (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+    this.channelCreated = createNotificationChannel();
   }
 
   @Override
@@ -66,64 +68,79 @@
 
   @Override
   public void onDestroy() {
-    if (getStarboardBridge() == null) {
-      Log.e(TAG, "StarboardBridge already destroyed.");
-      return;
-    }
     Log.i(TAG, "Destroying the Media playback service.");
-    getStarboardBridge().onServiceDestroy(this);
+
+    if (VERSION.SDK_INT >= 26 && this.channelCreated) {
+      this.notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+    }
+
+    if (getStarboardBridge() != null) {
+      getStarboardBridge().onServiceDestroy(this);
+    }
     super.onDestroy();
   }
 
   public void startService() {
-    try {
-      startForeground(NOTIFICATION_ID, buildNotification());
-    } catch (IllegalStateException e) {
-      Log.e(TAG, "Failed to start Foreground Service", e);
+    if (this.channelCreated) {
+      try {
+        if (VERSION.SDK_INT >= 29) {
+          startForeground(
+              NOTIFICATION_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST);
+        } else {
+          startForeground(NOTIFICATION_ID, buildNotification());
+        }
+      } catch (IllegalStateException e) {
+        Log.e(TAG, "Failed to start Foreground Service", e);
+      }
     }
   }
 
   public void stopService() {
     // Let service itself handle notification deletion.
-    stopForeground(true);
+    if (this.channelCreated) {
+      stopForeground(true);
+    }
     stopSelf();
   }
 
-  private void createNotificationChannel() {
-    if (Build.VERSION.SDK_INT >= 26) {
+  private boolean createNotificationChannel() {
+    if (VERSION.SDK_INT >= 26) {
       try {
         createNotificationChannelInternalV26();
       } catch (RemoteException e) {
         Log.e(TAG, "Failed to create Notification Channel.", e);
+        return false;
       }
     }
+    return true;
   }
 
   @RequiresApi(26)
   private void createNotificationChannelInternalV26() throws RemoteException {
-    NotificationManager notificationManager =
-        (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
     NotificationChannel channel =
         new NotificationChannel(
             NOTIFICATION_CHANNEL_ID,
             NOTIFICATION_CHANNEL_NAME,
             notificationManager.IMPORTANCE_DEFAULT);
     channel.setDescription("Channel for showing persistent notification");
-    notificationManager.createNotificationChannel(channel);
+    this.notificationManager.createNotificationChannel(channel);
   }
 
   Notification buildNotification() {
+    String channelId = "";
+    if (VERSION.SDK_INT >= 26) {
+      // Channel with ID=NOTIFICATION_CHANNEL_ID is created for version >= 26
+      channelId = NOTIFICATION_CHANNEL_ID;
+    }
+
     NotificationCompat.Builder builder =
-        new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+        new NotificationCompat.Builder(this, channelId)
             .setShowWhen(false)
             .setPriority(NotificationCompat.PRIORITY_MIN)
             .setSmallIcon(android.R.drawable.stat_sys_warning)
             .setContentTitle("Media playback service")
             .setContentText("Media playback service is running");
 
-    if (VERSION.SDK_INT >= 26) {
-      builder.setChannelId(NOTIFICATION_CHANNEL_ID);
-    }
     return builder.build();
   }
 
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 61f4bca..5d6e8f9 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
@@ -96,7 +96,7 @@
   private final Holder<Activity> activityHolder;
   private final Holder<Service> serviceHolder;
   private final String[] args;
-  private final String startDeepLink;
+  private String startDeepLink;
   private final Runnable stopRequester =
       new Runnable() {
         @Override
@@ -105,7 +105,8 @@
         }
       };
 
-  private volatile boolean starboardStopped = false;
+  private volatile boolean starboardApplicationStopped = false;
+  private volatile boolean starboardApplicationReady = false;
 
   private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>();
   private final HashMap<String, CobaltService> cobaltServices = new HashMap<>();
@@ -166,7 +167,7 @@
   }
 
   protected void onActivityDestroy(Activity activity) {
-    if (starboardStopped) {
+    if (starboardApplicationStopped) {
       // We can't restart the starboard app, so kill the process for a clean start next time.
       Log.i(TAG, "Activity destroyed after shutdown; killing app.");
       System.exit(0);
@@ -265,7 +266,7 @@
   @SuppressWarnings("unused")
   @UsedByNative
   protected void afterStopped() {
-    starboardStopped = true;
+    starboardApplicationStopped = true;
     ttsHelper.shutdown();
     userAuthorizer.shutdown();
     for (CobaltService service : cobaltServices.values()) {
@@ -285,8 +286,21 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
+  protected void starboardApplicationStarted() {
+    starboardApplicationReady = true;
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
+  protected void starboardApplicationStopping() {
+    starboardApplicationReady = false;
+    starboardApplicationStopped = true;
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
   public void requestStop(int errorLevel) {
-    if (!starboardStopped) {
+    if (starboardApplicationReady) {
       Log.i(TAG, "Request to stop");
       nativeStopApp(errorLevel);
     }
@@ -305,7 +319,10 @@
   }
 
   public boolean onSearchRequested() {
-    return nativeOnSearchRequested();
+    if (starboardApplicationReady) {
+      return nativeOnSearchRequested();
+    }
+    return false;
   }
 
   private native boolean nativeOnSearchRequested();
@@ -349,7 +366,13 @@
 
   /** Sends an event to the web app to navigate to the given URL */
   public void handleDeepLink(String url) {
-    nativeHandleDeepLink(url);
+    if (starboardApplicationReady) {
+      nativeHandleDeepLink(url);
+    } else {
+      // If this deep link event is received before the starboard application
+      // is ready, it replaces the start deep link.
+      startDeepLink = url;
+    }
   }
 
   private native void nativeHandleDeepLink(String url);
@@ -760,6 +783,10 @@
     return service;
   }
 
+  public CobaltService getOpenedCobaltService(String serviceName) {
+    return cobaltServices.get(serviceName);
+  }
+
   @SuppressWarnings("unused")
   @UsedByNative
   void closeCobaltService(String serviceName) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 9fb274f..0e02ebc 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -142,39 +142,6 @@
     audioTrackBridgeList.remove(audioTrackBridge);
   }
 
-  /** Returns the maximum number of HDMI channels. */
-  @SuppressWarnings("unused")
-  @UsedByNative
-  int getMaxChannels() {
-    // The aac audio decoder on this platform will switch its output from 5.1
-    // to stereo right before providing the first output buffer when
-    // attempting to decode 5.1 input.  Since this heavily violates invariants
-    // of the shared starboard player framework, disable 5.1 on this platform.
-    // It is expected that we will be able to resolve this issue with Xiaomi
-    // by Android P, so only do this workaround for SDK versions < 27.
-    if (android.os.Build.MODEL.equals("MIBOX3") && android.os.Build.VERSION.SDK_INT < 27) {
-      return 2;
-    }
-
-    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-    AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-    int maxChannels = 2;
-    for (AudioDeviceInfo info : deviceInfos) {
-      int type = info.getType();
-      if (type == AudioDeviceInfo.TYPE_HDMI || type == AudioDeviceInfo.TYPE_HDMI_ARC) {
-        int[] channelCounts = info.getChannelCounts();
-        if (channelCounts.length == 0) {
-          // An empty array indicates that the device supports arbitrary channel masks.
-          return 8;
-        }
-        for (int count : channelCounts) {
-          maxChannels = Math.max(maxChannels, count);
-        }
-      }
-    }
-    return maxChannels;
-  }
-
   /** Stores info from AudioDeviceInfo to be passed to the native app. */
   @SuppressWarnings("unused")
   @UsedByNative
@@ -208,8 +175,19 @@
       outDeviceInfo.type = deviceInfos[index].getType();
       outDeviceInfo.channels = 2;
 
+      // The aac audio decoder on this platform will switch its output from 5.1
+      // to stereo right before providing the first output buffer when
+      // attempting to decode 5.1 input.  Since this heavily violates invariants
+      // of the shared starboard player framework, disable 5.1 on this platform.
+      // It is expected that we will be able to resolve this issue with Xiaomi
+      // by Android P, so only do this workaround for SDK versions < 27.
+      if (android.os.Build.MODEL.equals("MIBOX3") && android.os.Build.VERSION.SDK_INT < 27) {
+        return true;
+      }
+
       int[] channelCounts = deviceInfos[index].getChannelCounts();
       if (channelCounts.length == 0) {
+        // An empty array indicates that the device supports arbitrary channel masks.
         outDeviceInfo.channels = 8;
       } else {
         for (int count : channelCounts) {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 8db14f6..8164add 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -279,15 +279,15 @@
     }
 
     private boolean formatHasCropValues() {
-      if (!mFormatHasCropValues.isPresent()) {
+      if (!mFormatHasCropValues.isPresent() && mFormat != null) {
         boolean hasCropValues =
             mFormat.containsKey(KEY_CROP_RIGHT)
                 && mFormat.containsKey(KEY_CROP_LEFT)
                 && mFormat.containsKey(KEY_CROP_BOTTOM)
                 && mFormat.containsKey(KEY_CROP_TOP);
-        mFormatHasCropValues = Optional.of(hasCropValues);
+        mFormatHasCropValues = Optional.ofNullable(hasCropValues);
       }
-      return mFormatHasCropValues.get();
+      return mFormatHasCropValues.orElse(false);
     }
 
     @SuppressWarnings("unused")
@@ -299,13 +299,17 @@
     @SuppressWarnings("unused")
     @UsedByNative
     private int textureWidth() {
-      return mFormat.getInteger(MediaFormat.KEY_WIDTH);
+      return (mFormat != null && mFormat.containsKey(MediaFormat.KEY_WIDTH))
+          ? mFormat.getInteger(MediaFormat.KEY_WIDTH)
+          : 0;
     }
 
     @SuppressWarnings("unused")
     @UsedByNative
     private int textureHeight() {
-      return mFormat.getInteger(MediaFormat.KEY_HEIGHT);
+      return (mFormat != null && mFormat.containsKey(MediaFormat.KEY_HEIGHT))
+          ? mFormat.getInteger(MediaFormat.KEY_HEIGHT)
+          : 0;
     }
 
     @SuppressWarnings("unused")
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java
new file mode 100644
index 0000000..f1c640d
--- /dev/null
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecCapabilitiesLogger.java
@@ -0,0 +1,285 @@
+// 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.
+
+package dev.cobalt.media;
+
+import static dev.cobalt.media.Log.TAG;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.os.Build;
+import dev.cobalt.util.Log;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+/** Utility class to log MediaCodec capabilities. */
+public class MediaCodecCapabilitiesLogger {
+  /** Utility class to save the maximum supported resolution and frame rate of a decoder. */
+  static class ResolutionAndFrameRate {
+    public ResolutionAndFrameRate(Integer width, Integer height, Double frameRate) {
+      this.width = width;
+      this.height = height;
+      this.frameRate = frameRate;
+    }
+
+    public boolean isCovered(Integer width, Integer height, Double frameRate) {
+      return this.width >= width && this.height >= height && this.frameRate >= frameRate;
+    }
+
+    public Integer width = -1;
+    public Integer height = -1;
+    public Double frameRate = -1.0;
+  }
+
+  /** Returns a string detailing SDR and HDR capabilities of a decoder. */
+  private static String getSupportedResolutionsAndFrameRates(
+      VideoCapabilities videoCapabilities, boolean isHdrCapable) {
+    ArrayList<ArrayList<Integer>> resolutionList =
+        new ArrayList<>(
+            Arrays.asList(
+                new ArrayList<>(Arrays.asList(7680, 4320)),
+                new ArrayList<>(Arrays.asList(3840, 2160)),
+                new ArrayList<>(Arrays.asList(2560, 1440)),
+                new ArrayList<>(Arrays.asList(1920, 1080)),
+                new ArrayList<>(Arrays.asList(1280, 720))));
+    ArrayList<Double> frameRateList =
+        new ArrayList<>(Arrays.asList(60.0, 59.997, 50.0, 48.0, 30.0, 29.997, 25.0, 24.0, 23.997));
+    ArrayList<ResolutionAndFrameRate> supportedResolutionsAndFrameRates = new ArrayList<>();
+    for (Double frameRate : frameRateList) {
+      for (ArrayList<Integer> resolution : resolutionList) {
+        boolean isResolutionAndFrameRateCovered = false;
+        for (ResolutionAndFrameRate resolutionAndFrameRate : supportedResolutionsAndFrameRates) {
+          if (resolutionAndFrameRate.isCovered(resolution.get(0), resolution.get(1), frameRate)) {
+            isResolutionAndFrameRateCovered = true;
+            break;
+          }
+        }
+        if (videoCapabilities.areSizeAndRateSupported(
+            resolution.get(0), resolution.get(1), frameRate)) {
+          if (!isResolutionAndFrameRateCovered) {
+            supportedResolutionsAndFrameRates.add(
+                new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate));
+          }
+          continue;
+        }
+        if (isResolutionAndFrameRateCovered) {
+          // This configuration should be covered by a supported configuration, return long form.
+          return getLongFormSupportedResolutionsAndFrameRates(
+              resolutionList, frameRateList, videoCapabilities, isHdrCapable);
+        }
+      }
+    }
+    return convertResolutionAndFrameRatesToString(supportedResolutionsAndFrameRates, isHdrCapable);
+  }
+
+  /**
+   * Like getSupportedResolutionsAndFrameRates(), but returns the full information for each frame
+   * rate and resolution combination.
+   */
+  private static String getLongFormSupportedResolutionsAndFrameRates(
+      ArrayList<ArrayList<Integer>> resolutionList,
+      ArrayList<Double> frameRateList,
+      VideoCapabilities videoCapabilities,
+      boolean isHdrCapable) {
+    ArrayList<ResolutionAndFrameRate> supported = new ArrayList<>();
+    for (Double frameRate : frameRateList) {
+      for (ArrayList<Integer> resolution : resolutionList) {
+        if (videoCapabilities.areSizeAndRateSupported(
+            resolution.get(0), resolution.get(1), frameRate)) {
+          supported.add(
+              new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate));
+        }
+      }
+    }
+    return convertResolutionAndFrameRatesToString(supported, isHdrCapable);
+  }
+
+  /** Convert a list of ResolutionAndFrameRate to a human readable string. */
+  private static String convertResolutionAndFrameRatesToString(
+      ArrayList<ResolutionAndFrameRate> supported, boolean isHdrCapable) {
+    if (supported.isEmpty()) {
+      return "None.";
+    }
+    String frameRateAndResolutionString = "";
+    for (ResolutionAndFrameRate resolutionAndFrameRate : supported) {
+      frameRateAndResolutionString +=
+          String.format(
+              Locale.US,
+              "[%d x %d, %.3f fps], ",
+              resolutionAndFrameRate.width,
+              resolutionAndFrameRate.height,
+              resolutionAndFrameRate.frameRate);
+    }
+    frameRateAndResolutionString += isHdrCapable ? "hdr/sdr" : "sdr";
+    return frameRateAndResolutionString;
+  }
+
+  private interface CodecFeatureSupported {
+    boolean isSupported(String name, CodecCapabilities codecCapabilities);
+  }
+
+  static TreeMap<String, CodecFeatureSupported> featureMap;
+
+  private static void ensurefeatureMapInitialized() {
+    if (featureMap != null) {
+      return;
+    }
+    featureMap = new TreeMap<>();
+    featureMap.put(
+        "AdaptivePlayback",
+        (name, codecCapabilities) -> {
+          return codecCapabilities.isFeatureSupported(
+              MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
+        });
+    if (Build.VERSION.SDK_INT >= 29) {
+      featureMap.put(
+          "FrameParsing",
+          (name, codecCapabilities) -> {
+            return codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_FrameParsing);
+          });
+    }
+    if (Build.VERSION.SDK_INT >= 30) {
+      featureMap.put(
+          "LowLatency",
+          (name, codecCapabilities) -> {
+            return codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency);
+          });
+    }
+    if (Build.VERSION.SDK_INT >= 29) {
+      featureMap.put(
+          "MultipleFrames",
+          (name, codecCapabilities) -> {
+            return codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_MultipleFrames);
+          });
+    }
+    if (Build.VERSION.SDK_INT >= 26) {
+      featureMap.put(
+          "PartialFrame",
+          (name, codecCapabilities) -> {
+            return codecCapabilities.isFeatureSupported(
+                MediaCodecInfo.CodecCapabilities.FEATURE_PartialFrame);
+          });
+    }
+    featureMap.put(
+        "SecurePlayback",
+        (name, codecCapabilities) -> {
+          return codecCapabilities.isFeatureSupported(
+              MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
+        });
+    featureMap.put(
+        "TunneledPlayback",
+        (name, codecCapabilities) -> {
+          return codecCapabilities.isFeatureSupported(
+              MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
+        });
+  }
+
+  private static String getAllFeatureNames() {
+    ensurefeatureMapInitialized();
+    return featureMap.keySet().toString();
+  }
+
+  private static String getSupportedFeaturesAsString(
+      String name, CodecCapabilities codecCapabilities) {
+    StringBuilder featuresAsString = new StringBuilder();
+
+    ensurefeatureMapInitialized();
+    for (Map.Entry<String, CodecFeatureSupported> entry : featureMap.entrySet()) {
+      if (entry.getValue().isSupported(name, codecCapabilities)) {
+        if (featuresAsString.length() > 0) {
+          featuresAsString.append(", ");
+        }
+        featuresAsString.append(entry.getKey());
+      }
+    }
+    return featuresAsString.toString();
+  }
+
+  /**
+   * Debug utility function that can be locally added to dump information about all decoders on a
+   * particular system.
+   */
+  public static void dumpAllDecoders() {
+    StringBuilder decoderDumpString = new StringBuilder();
+    for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
+      if (info.isEncoder()) {
+        continue;
+      }
+      for (String supportedType : info.getSupportedTypes()) {
+        String name = info.getName();
+        decoderDumpString.append(
+            String.format(
+                Locale.US,
+                "name: %s (%s, %s):",
+                name,
+                supportedType,
+                MediaCodecUtil.isCodecDenyListed(name) ? "denylisted" : "not denylisted"));
+        CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType);
+        VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities();
+        String resultName =
+            (codecCapabilities.isFeatureSupported(
+                        MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback)
+                    && !name.endsWith(MediaCodecUtil.getSecureDecoderSuffix()))
+                ? (name + MediaCodecUtil.getSecureDecoderSuffix())
+                : name;
+        boolean isHdrCapable =
+            MediaCodecUtil.isHdrCapableVideoDecoder(
+                codecCapabilities.getMimeType(), codecCapabilities);
+        if (videoCapabilities != null) {
+          String frameRateAndResolutionString =
+              getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable);
+          decoderDumpString.append(
+              String.format(
+                  Locale.US,
+                  "\n\t"
+                      + "widths: %s, "
+                      + "heights: %s, "
+                      + "bitrates: %s, "
+                      + "framerates: %s, "
+                      + "supported sizes and framerates: %s",
+                  videoCapabilities.getSupportedWidths().toString(),
+                  videoCapabilities.getSupportedHeights().toString(),
+                  videoCapabilities.getBitrateRange().toString(),
+                  videoCapabilities.getSupportedFrameRates().toString(),
+                  frameRateAndResolutionString));
+        }
+        String featuresAsString = getSupportedFeaturesAsString(name, codecCapabilities);
+        if (featuresAsString.isEmpty()) {
+          decoderDumpString.append(" No extra features supported");
+        } else {
+          decoderDumpString.append("\n\tsupported features: ");
+          decoderDumpString.append(featuresAsString);
+        }
+        decoderDumpString.append("\n");
+      }
+    }
+    Log.v(
+        TAG,
+        "\n"
+            + "==================================================\n"
+            + "Full list of decoder features: "
+            + getAllFeatureNames()
+            + "\nUnsupported features for each codec are not listed\n"
+            + decoderDumpString.toString()
+            + "==================================================");
+  }
+}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index 9afa2bc..50610e0 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -28,7 +28,6 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -442,7 +441,7 @@
 
       // Filter blacklisted video decoders.
       String name = codecInfo.getName();
-      if (!isVp9AllowListed && videoCodecDenyList.contains(name)) {
+      if (!isVp9AllowListed && isCodecDenyListed(name)) {
         Log.v(TAG, "Rejecting %s, reason: codec is on deny list", name);
         continue;
       }
@@ -451,7 +450,7 @@
         // denylisted.
         String nameWithoutSecureSuffix =
             name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length());
-        if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) {
+        if (!isVp9AllowListed && isCodecDenyListed(nameWithoutSecureSuffix)) {
           String format = "Rejecting %s, reason: offpsec denylisted secure decoder";
           Log.v(TAG, format, name);
           continue;
@@ -466,6 +465,16 @@
     return codecCapabilityInfos.toArray(array);
   }
 
+  /** Returns whether the codec is denylisted. */
+  public static boolean isCodecDenyListed(String codecName) {
+    return videoCodecDenyList.contains(codecName);
+  }
+
+  /** Simply returns SECURE_DECODER_SUFFIX to allow access to it. */
+  public static String getSecureDecoderSuffix() {
+    return SECURE_DECODER_SUFFIX;
+  }
+
   /** Determine whether codecCapabilities is capable of playing HDR. */
   public static boolean isHdrCapableVideoDecoder(
       String mimeType, CodecCapabilities codecCapabilities) {
@@ -570,7 +579,7 @@
     for (VideoDecoderCache.CachedDecoder decoder :
         VideoDecoderCache.getCachedDecoders(mimeType, decoderCacheTtlMs)) {
       String name = decoder.info.getName();
-      if (!isVp9AllowListed && videoCodecDenyList.contains(name)) {
+      if (!isVp9AllowListed && isCodecDenyListed(name)) {
         Log.v(TAG, "Rejecting " + name + ", reason: codec is on deny list");
         continue;
       }
@@ -596,7 +605,7 @@
         // denylisted.
         String nameWithoutSecureSuffix =
             name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length());
-        if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) {
+        if (!isVp9AllowListed && isCodecDenyListed(nameWithoutSecureSuffix)) {
           Log.v(TAG, "Rejecting " + name + ", reason: denylisted secure decoder");
         }
       }
@@ -738,8 +747,7 @@
    * "" otherwise.
    */
   @UsedByNative
-  public static String findAudioDecoder(
-      String mimeType, int bitrate, boolean mustSupportTunnelMode) {
+  public static String findAudioDecoder(String mimeType, int bitrate) {
     // Note: MediaCodecList is sorted by the framework such that the best decoders come first.
     for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
       if (info.isEncoder()) {
@@ -757,202 +765,9 @@
         if (!bitrateRange.contains(bitrate)) {
           continue;
         }
-        if (mustSupportTunnelMode
-            && !codecCapabilities.isFeatureSupported(CodecCapabilities.FEATURE_TunneledPlayback)) {
-          continue;
-        }
-        // TODO: Determine if we can safely check if an audio codec requires the tunneled playback
-        //  feature. i.e., reject when |mustSupportTunnelMode| == false
-        //  and codecCapabilities.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback) ==
-        //  true.
         return name;
       }
     }
     return "";
   }
-
-  /** Utility class to save the maximum supported resolution and frame rate of a decoder. */
-  static class ResolutionAndFrameRate {
-    public ResolutionAndFrameRate(Integer width, Integer height, Double frameRate) {
-      this.width = width;
-      this.height = height;
-      this.frameRate = frameRate;
-    }
-
-    public boolean isCovered(Integer width, Integer height, Double frameRate) {
-      return this.width >= width && this.height >= height && this.frameRate >= frameRate;
-    }
-
-    public Integer width = -1;
-    public Integer height = -1;
-    public Double frameRate = -1.0;
-  }
-
-  /** Returns a string detailing SDR and HDR capabilities of a decoder. */
-  public static String getSupportedResolutionsAndFrameRates(
-      VideoCapabilities videoCapabilities, boolean isHdrCapable) {
-    ArrayList<ArrayList<Integer>> resolutionList =
-        new ArrayList<>(
-            Arrays.asList(
-                new ArrayList<>(Arrays.asList(7680, 4320)),
-                new ArrayList<>(Arrays.asList(3840, 2160)),
-                new ArrayList<>(Arrays.asList(2560, 1440)),
-                new ArrayList<>(Arrays.asList(1920, 1080)),
-                new ArrayList<>(Arrays.asList(1280, 720))));
-    ArrayList<Double> frameRateList =
-        new ArrayList<>(Arrays.asList(60.0, 59.997, 50.0, 48.0, 30.0, 29.997, 25.0, 24.0, 23.997));
-    ArrayList<ResolutionAndFrameRate> supportedResolutionsAndFrameRates = new ArrayList<>();
-    for (Double frameRate : frameRateList) {
-      for (ArrayList<Integer> resolution : resolutionList) {
-        boolean isResolutionAndFrameRateCovered = false;
-        for (ResolutionAndFrameRate resolutionAndFrameRate : supportedResolutionsAndFrameRates) {
-          if (resolutionAndFrameRate.isCovered(resolution.get(0), resolution.get(1), frameRate)) {
-            isResolutionAndFrameRateCovered = true;
-            break;
-          }
-        }
-        if (videoCapabilities.areSizeAndRateSupported(
-            resolution.get(0), resolution.get(1), frameRate)) {
-          if (!isResolutionAndFrameRateCovered) {
-            supportedResolutionsAndFrameRates.add(
-                new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate));
-          }
-          continue;
-        }
-        if (isResolutionAndFrameRateCovered) {
-          // This configuration should be covered by a supported configuration, return long form.
-          return getLongFormSupportedResolutionsAndFrameRates(
-              resolutionList, frameRateList, videoCapabilities, isHdrCapable);
-        }
-      }
-    }
-    return convertResolutionAndFrameRatesToString(supportedResolutionsAndFrameRates, isHdrCapable);
-  }
-
-  /**
-   * Like getSupportedResolutionsAndFrameRates(), but returns the full information for each frame
-   * rate and resolution combination.
-   */
-  public static String getLongFormSupportedResolutionsAndFrameRates(
-      ArrayList<ArrayList<Integer>> resolutionList,
-      ArrayList<Double> frameRateList,
-      VideoCapabilities videoCapabilities,
-      boolean isHdrCapable) {
-    ArrayList<ResolutionAndFrameRate> supported = new ArrayList<>();
-    for (Double frameRate : frameRateList) {
-      for (ArrayList<Integer> resolution : resolutionList) {
-        if (videoCapabilities.areSizeAndRateSupported(
-            resolution.get(0), resolution.get(1), frameRate)) {
-          supported.add(
-              new ResolutionAndFrameRate(resolution.get(0), resolution.get(1), frameRate));
-        }
-      }
-    }
-    return convertResolutionAndFrameRatesToString(supported, isHdrCapable);
-  }
-
-  public static String convertResolutionAndFrameRatesToString(
-      ArrayList<ResolutionAndFrameRate> supported, boolean isHdrCapable) {
-    if (supported.isEmpty()) {
-      return "None. ";
-    }
-    String frameRateAndResolutionString = "";
-    for (ResolutionAndFrameRate resolutionAndFrameRate : supported) {
-      frameRateAndResolutionString +=
-          String.format(
-              Locale.US,
-              "[%d x %d, %.3f fps], ",
-              resolutionAndFrameRate.width,
-              resolutionAndFrameRate.height,
-              resolutionAndFrameRate.frameRate);
-    }
-    frameRateAndResolutionString += isHdrCapable ? "hdr/sdr, " : "sdr, ";
-    return frameRateAndResolutionString;
-  }
-
-  /**
-   * Debug utility function that can be locally added to dump information about all decoders on a
-   * particular system.
-   */
-  public static void dumpAllDecoders() {
-    String decoderDumpString = "";
-    for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) {
-      if (info.isEncoder()) {
-        continue;
-      }
-      for (String supportedType : info.getSupportedTypes()) {
-        String name = info.getName();
-        decoderDumpString +=
-            String.format(
-                Locale.US,
-                "name: %s (%s, %s): ",
-                name,
-                supportedType,
-                videoCodecDenyList.contains(name) ? "denylisted" : "not denylisted");
-        CodecCapabilities codecCapabilities = info.getCapabilitiesForType(supportedType);
-        VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities();
-        String resultName =
-            (codecCapabilities.isFeatureSupported(
-                        MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback)
-                    && !name.endsWith(SECURE_DECODER_SUFFIX))
-                ? (name + SECURE_DECODER_SUFFIX)
-                : name;
-        boolean isHdrCapable =
-            isHdrCapableVideoDecoder(codecCapabilities.getMimeType(), codecCapabilities);
-        if (videoCapabilities != null) {
-          String frameRateAndResolutionString =
-              getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable);
-          decoderDumpString +=
-              String.format(
-                  Locale.US,
-                  "\n\t\t"
-                      + "widths: %s, "
-                      + "heights: %s, "
-                      + "bitrates: %s, "
-                      + "framerates: %s, "
-                      + "supported sizes and framerates: %s",
-                  videoCapabilities.getSupportedWidths().toString(),
-                  videoCapabilities.getSupportedHeights().toString(),
-                  videoCapabilities.getBitrateRange().toString(),
-                  videoCapabilities.getSupportedFrameRates().toString(),
-                  frameRateAndResolutionString);
-        }
-        boolean isAdaptivePlaybackSupported =
-            codecCapabilities.isFeatureSupported(
-                MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
-        boolean isSecurePlaybackSupported =
-            codecCapabilities.isFeatureSupported(
-                MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback);
-        boolean isTunneledPlaybackSupported =
-            codecCapabilities.isFeatureSupported(
-                MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback);
-        if (isAdaptivePlaybackSupported
-            || isSecurePlaybackSupported
-            || isTunneledPlaybackSupported) {
-          decoderDumpString +=
-              String.format(
-                  Locale.US,
-                  "(%s%s%s",
-                  isAdaptivePlaybackSupported ? "AdaptivePlayback, " : "",
-                  isSecurePlaybackSupported ? "SecurePlayback, " : "",
-                  isTunneledPlaybackSupported ? "TunneledPlayback, " : "");
-          // Remove trailing space and comma
-          decoderDumpString = decoderDumpString.substring(0, decoderDumpString.length() - 2);
-          decoderDumpString += ")";
-        } else {
-          decoderDumpString += " No extra features supported";
-        }
-        decoderDumpString += "\n";
-      }
-    }
-    Log.v(
-        TAG,
-        " \n"
-            + "==================================================\n"
-            + "Full list of decoder features: [AdaptivePlayback, SecurePlayback,"
-            + " TunneledPlayback]\n"
-            + "Unsupported features for each codec are not listed\n"
-            + decoderDumpString
-            + "==================================================");
-  }
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index d4a12a5..190a160 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -373,6 +373,11 @@
     mSchemeUUID = schemeUUID;
     mMediaDrm = new MediaDrm(schemeUUID);
 
+    // Get info of hdcp connection
+    if (Build.VERSION.SDK_INT >= 29) {
+      getConnectedHdcpLevelInfoV29(mMediaDrm);
+    }
+
     mNativeMediaDrmBridge = nativeMediaDrmBridge;
     if (!isNativeMediaDrmBridgeValid()) {
       throw new IllegalArgumentException(
@@ -738,6 +743,40 @@
     mediaDrm.close();
   }
 
+  @RequiresApi(29)
+  private void getConnectedHdcpLevelInfoV29(MediaDrm mediaDrm) {
+    int hdcpLevel = mediaDrm.getConnectedHdcpLevel();
+    switch (hdcpLevel) {
+      case MediaDrm.HDCP_V1:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_V1.");
+        break;
+      case MediaDrm.HDCP_V2:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2.");
+        break;
+      case MediaDrm.HDCP_V2_1:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_1.");
+        break;
+      case MediaDrm.HDCP_V2_2:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_2.");
+        break;
+      case MediaDrm.HDCP_V2_3:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_V2_3.");
+        break;
+      case MediaDrm.HDCP_NONE:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_NONE.");
+        break;
+      case MediaDrm.HDCP_NO_DIGITAL_OUTPUT:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_NO_DIGITAL_OUTPUT.");
+        break;
+      case MediaDrm.HDCP_LEVEL_UNKNOWN:
+        Log.i(TAG, "MediaDrm HDCP Level is HDCP_LEVEL_UNKNOWN.");
+        break;
+      default:
+        Log.i(TAG, String.format("Unknown MediaDrm HDCP level %d.", hdcpLevel));
+        break;
+    }
+  }
+
   private boolean isNativeMediaDrmBridgeValid() {
     return mNativeMediaDrmBridge != INVALID_NATIVE_MEDIA_DRM_BRIDGE;
   }
diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc
index dbdb8c0..7bccf50 100644
--- a/starboard/android/shared/android_main.cc
+++ b/starboard/android/shared/android_main.cc
@@ -17,6 +17,7 @@
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/log_internal.h"
+#include "starboard/common/atomic.h"
 #include "starboard/common/file.h"
 #include "starboard/common/semaphore.h"
 #include "starboard/common/string.h"
@@ -48,7 +49,7 @@
 // Safeguard to avoid sending AndroidCommands either when there is no instance
 // of the Starboard application, or after the run loop has exited and the
 // ALooper receiving the commands is no longer being polled.
-bool g_app_running = false;
+atomic_bool g_app_running;
 
 std::vector<std::string> GetArgs() {
   std::vector<std::string> args;
@@ -235,7 +236,7 @@
 
   // Mark the app running before signaling app created so there's no race to
   // allow sending the first AndroidCommand after onCreate() returns.
-  g_app_running = true;
+  g_app_running.store(true);
 
   // Signal GameActivity_onCreate() that it may proceed.
   g_app_created_semaphore->Put();
@@ -243,12 +244,12 @@
   // Enter the Starboard run loop until stopped.
   int error_level =
       app.Run(std::move(command_line), GetStartDeepLink().c_str());
-#endif  // SB_API_VERSION >= 15
 
   // Mark the app not running before informing StarboardBridge that the app is
   // stopped so that we won't send any more AndroidCommands as a result of
   // shutting down the Activity.
-  g_app_running = false;
+  g_app_running.store(false);
+#endif  // SB_API_VERSION >= 15
 
   // Our launcher.py looks for this to know when the app (test) is done.
   SB_LOG(INFO) << "***Application Stopped*** " << error_level;
@@ -262,13 +263,13 @@
 }
 
 void OnStart(GameActivity* activity) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStart);
   }
 }
 
 void OnResume(GameActivity* activity) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     // Stop the MediaPlaybackService if activity state transits from background
     // to foreground. Note that the MediaPlaybackService may already have
     // been stopped before Cobalt's lifecycle state transits from Concealed
@@ -279,7 +280,7 @@
 }
 
 void OnPause(GameActivity* activity) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     // Start the MediaPlaybackService before activity state transits from
     // foreground to background.
     ApplicationAndroid::Get()->StartMediaPlaybackService();
@@ -288,28 +289,28 @@
 }
 
 void OnStop(GameActivity* activity) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStop);
   }
 }
 
 bool OnTouchEvent(GameActivity* activity,
                   const GameActivityMotionEvent* event) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     return ApplicationAndroid::Get()->SendAndroidMotionEvent(event);
   }
   return false;
 }
 
 bool OnKey(GameActivity* activity, const GameActivityKeyEvent* event) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     return ApplicationAndroid::Get()->SendAndroidKeyEvent(event);
   }
   return false;
 }
 
 void OnWindowFocusChanged(GameActivity* activity, bool focused) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         focused ? AndroidCommand::kWindowFocusGained
                 : AndroidCommand::kWindowFocusLost);
@@ -317,14 +318,14 @@
 }
 
 void OnNativeWindowCreated(GameActivity* activity, ANativeWindow* window) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         AndroidCommand::kNativeWindowCreated, window);
   }
 }
 
 void OnNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendAndroidCommand(
         AndroidCommand::kNativeWindowDestroyed);
   }
@@ -380,7 +381,7 @@
 Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env,
                                                              jobject jcaller,
                                                              jint volumeDelta) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     SbKey key =
         volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown;
     ApplicationAndroid::Get()->SendKeyboardInject(key);
@@ -390,7 +391,7 @@
 extern "C" SB_EXPORT_PLATFORM void
 Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env,
                                                            jobject jcaller) {
-  if (g_app_running) {
+  if (g_app_running.load()) {
     ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute);
   }
 }
@@ -407,9 +408,13 @@
   CommandLine command_line(GetArgs());
   LogInit(command_line);
 
+#if SB_IS(EVERGREEN_COMPATIBLE)
+  InstallCrashpadHandler(command_line);
+#endif  // SB_IS(EVERGREEN_COMPATIBLE)
+
   // Mark the app running before signaling app created so there's no race to
   // allow sending the first AndroidCommand after onCreate() returns.
-  g_app_running = true;
+  g_app_running.store(true);
 
   // Signal GameActivity_onCreate() that it may proceed.
   g_app_created_semaphore->Put();
@@ -417,6 +422,12 @@
   // Enter the Starboard run loop until stopped.
   int error_level =
       app.Run(std::move(command_line), GetStartDeepLink().c_str());
+
+  // Mark the app not running before informing StarboardBridge that the app is
+  // stopped so that we won't send any more AndroidCommands as a result of
+  // shutting down the Activity.
+  g_app_running.store(false);
+
   return error_level;
 }
 #endif  // SB_API_VERSION >= 15
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index 33b26ad..189517e 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -140,9 +140,15 @@
   jobject local_ref = env->CallStarboardObjectMethodOrAbort(
       "getResourceOverlay", "()Ldev/cobalt/coat/ResourceOverlay;");
   resource_overlay_ = env->ConvertLocalRefToGlobalRef(local_ref);
+
+  env->CallStarboardVoidMethodOrAbort("starboardApplicationStarted", "()V");
 }
 
 ApplicationAndroid::~ApplicationAndroid() {
+  // Inform StarboardBridge that
+  JniEnvExt* env = JniEnvExt::Get();
+  env->CallStarboardVoidMethodOrAbort("starboardApplicationStopping", "()V");
+
   // The application is exiting.
   // Release the global reference.
   if (resource_overlay_) {
@@ -161,6 +167,7 @@
   {
     // Signal for any potentially waiting window creation or destroy commands.
     ScopedLock lock(android_command_mutex_);
+    application_destroying_.store(true);
     android_command_condition_.Signal();
   }
 }
@@ -412,7 +419,7 @@
   switch (type) {
     case AndroidCommand::kNativeWindowCreated:
     case AndroidCommand::kNativeWindowDestroyed:
-      while (native_window_ != data) {
+      while ((native_window_ != data) && !application_destroying_.load()) {
         android_command_condition_.Wait();
       }
       break;
diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h
index e8917c1..bca3b1f 100644
--- a/starboard/android/shared/application_android.h
+++ b/starboard/android/shared/application_android.h
@@ -25,6 +25,7 @@
 #include "starboard/android/shared/input_events_generator.h"
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/atomic.h"
+#include "starboard/common/atomic.h"
 #include "starboard/common/condition_variable.h"
 #include "starboard/common/mutex.h"
 #include "starboard/common/scoped_ptr.h"
@@ -154,6 +155,9 @@
   // already requested it be stopped.
   SbAtomic32 android_stop_count_ = 0;
 
+  // Set to true in the destructor to ensure other threads stop waiting.
+  atomic_bool application_destroying_;
+
   // The last Activity lifecycle state command received.
   AndroidCommand::CommandType activity_state_;
 
diff --git a/starboard/android/shared/audio_decoder.cc b/starboard/android/shared/audio_decoder.cc
index dfa32dd..60e7a44 100644
--- a/starboard/android/shared/audio_decoder.cc
+++ b/starboard/android/shared/audio_decoder.cc
@@ -94,9 +94,10 @@
 
   output_cb_ = output_cb;
   error_cb_ = error_cb;
-
-  media_decoder_->Initialize(
-      std::bind(&AudioDecoder::ReportError, this, _1, _2));
+  if (media_decoder_) {
+    media_decoder_->Initialize(
+        std::bind(&AudioDecoder::ReportError, this, _1, _2));
+  }
 }
 
 void AudioDecoder::Decode(const InputBuffers& input_buffers,
@@ -108,15 +109,20 @@
 
   audio_frame_discarder_.OnInputBuffers(input_buffers);
 
+#if STARBOARD_ANDROID_SHARED_AUDIO_DECODER_VERBOSE
   for (const auto& input_buffer : input_buffers) {
     VERBOSE_MEDIA_LOG() << "T1: timestamp " << input_buffer->timestamp();
   }
+#endif
 
-  media_decoder_->WriteInputBuffers(input_buffers);
+  if (media_decoder_) {
+    media_decoder_->WriteInputBuffers(input_buffers);
+  }
 
   ScopedLock lock(decoded_audios_mutex_);
-  if (media_decoder_->GetNumberOfPendingTasks() + decoded_audios_.size() <=
-      kMaxPendingWorkSize) {
+  if (media_decoder_ &&
+      (media_decoder_->GetNumberOfPendingTasks() + decoded_audios_.size() <=
+       kMaxPendingWorkSize)) {
     Schedule(consumed_cb);
   } else {
     consumed_cb_ = consumed_cb;
@@ -128,7 +134,9 @@
   SB_DCHECK(output_cb_);
   SB_DCHECK(media_decoder_);
 
-  media_decoder_->WriteEndOfStream();
+  if (media_decoder_) {
+    media_decoder_->WriteEndOfStream();
+  }
 }
 
 scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read(
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index b8139a2..1d156b9 100644
--- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -113,6 +113,7 @@
     frame_buffers[0] = silence_buffer.data();
 
     // Set default values.
+    has_error_ = false;
     min_required_frames_ = task.default_required_frames;
     total_consumed_frames_ = 0;
     last_underrun_count_ = -1;
@@ -148,6 +149,13 @@
       min_required_frames_ = max_required_frames_;
     }
 
+    if (has_error_) {
+      SB_LOG(ERROR) << "There's an error while running the test. Fallback to "
+                       "max required frames "
+                    << max_required_frames_ << ".";
+      min_required_frames_ = max_required_frames_;
+    }
+
     if (start_threshold > min_required_frames_) {
       SB_LOG(INFO) << "Audio sink min required frames is overwritten from "
                    << min_required_frames_ << " to audio track start threshold "
@@ -197,9 +205,10 @@
                                         const std::string& error_message,
                                         void* context) {
   SB_LOG(ERROR) << "Error occurred while writing frames: " << error_message;
-  // TODO: Handle errors during minimum frames test, maybe by terminating the
-  //       test earlier.
-  SB_NOTREACHED();
+
+  MinRequiredFramesTester* tester =
+      static_cast<MinRequiredFramesTester*>(context);
+  tester->has_error_ = true;
 }
 
 void MinRequiredFramesTester::UpdateSourceStatus(int* frames_in_buffer,
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.h b/starboard/android/shared/audio_sink_min_required_frames_tester.h
index 669217a..934bdb9 100644
--- a/starboard/android/shared/audio_sink_min_required_frames_tester.h
+++ b/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -107,6 +107,7 @@
   std::vector<const TestTask> test_tasks_;
   AudioTrackAudioSink* audio_sink_ = nullptr;
   int min_required_frames_;
+  std::atomic_bool has_error_;
 
   // Used only by audio sink thread.
   int total_consumed_frames_;
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index ada20e9..98810e7 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -18,6 +18,7 @@
 #include <string>
 #include <vector>
 
+#include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/common/string.h"
 #include "starboard/shared/starboard/media/media_util.h"
 #include "starboard/shared/starboard/player/filter/common.h"
@@ -50,8 +51,10 @@
 
 const size_t kSilenceFramesPerAppend = 1024;
 
-const int kMaxRequiredFrames = 16 * 1024;
-const int kRequiredFramesIncrement = 2 * 1024;
+const int kMaxRequiredFramesLocal = 16 * 1024;
+const int kMaxRequiredFramesRemote = 32 * 1024;
+const int kMaxRequiredFrames = kMaxRequiredFramesRemote;
+const int kRequiredFramesIncrement = 4 * 1024;
 const int kMinStablePlayedFrames = 12 * 1024;
 
 const int kSampleFrequency22050 = 22050;
@@ -67,6 +70,38 @@
   return (max_frames + 15) / 16 * 16;  // align to 16
 }
 
+bool HasRemoteAudioOutput() {
+#if SB_API_VERSION >= 15
+  // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The
+  // limit here is to avoid infinite loop and also match
+  // SbPlayerBridge::GetAudioConfigurations().
+  const int kMaxAudioConfigurations = 32;
+  SbMediaAudioConfiguration configuration;
+  int index = 0;
+  while (index < kMaxAudioConfigurations &&
+         MediaCapabilitiesCache::GetInstance()->GetAudioConfiguration(
+             index, &configuration)) {
+    switch (configuration.connector) {
+      case kSbMediaAudioConnectorUnknown:
+      case kSbMediaAudioConnectorAnalog:
+      case kSbMediaAudioConnectorBuiltIn:
+      case kSbMediaAudioConnectorHdmi:
+      case kSbMediaAudioConnectorSpdif:
+      case kSbMediaAudioConnectorUsb:
+        break;
+      case kSbMediaAudioConnectorBluetooth:
+      case kSbMediaAudioConnectorRemoteWired:
+      case kSbMediaAudioConnectorRemoteWireless:
+      case kSbMediaAudioConnectorRemoteOther:
+        return true;
+    }
+    index++;
+  }
+  return false;
+#endif  // SB_API_VERSION >= 15
+  return false;
+}
+
 }  // namespace
 
 AudioTrackAudioSink::AudioTrackAudioSink(
@@ -487,11 +522,18 @@
   auto onMinRequiredFramesForWebAudioReceived =
       [&](int number_of_channels, SbMediaAudioSampleType sample_type,
           int sample_rate, int min_required_frames) {
+        bool has_remote_audio_output = HasRemoteAudioOutput();
         SB_LOG(INFO) << "Received min required frames " << min_required_frames
                      << " for " << number_of_channels << " channels, "
-                     << sample_rate << "hz.";
+                     << sample_rate << "hz, with "
+                     << (has_remote_audio_output ? "remote" : "local")
+                     << " audio output device.";
         ScopedLock lock(min_required_frames_map_mutex_);
-        min_required_frames_map_[sample_rate] = min_required_frames;
+        has_remote_audio_output_ = has_remote_audio_output;
+        min_required_frames_map_[sample_rate] =
+            std::min(min_required_frames, has_remote_audio_output_
+                                              ? kMaxRequiredFramesRemote
+                                              : kMaxRequiredFramesLocal);
       };
 
   SbMediaAudioSampleType sample_type = kSbMediaAudioSampleTypeFloat32;
@@ -512,20 +554,27 @@
     int channels,
     SbMediaAudioSampleType sample_type,
     int sampling_frequency_hz) {
-  if (sampling_frequency_hz <= kSampleFrequency22050) {
-    ScopedLock lock(min_required_frames_map_mutex_);
-    if (min_required_frames_map_.find(kSampleFrequency22050) !=
-        min_required_frames_map_.end()) {
-      return min_required_frames_map_[kSampleFrequency22050];
-    }
-  } else if (sampling_frequency_hz <= kSampleFrequency48000) {
-    ScopedLock lock(min_required_frames_map_mutex_);
-    if (min_required_frames_map_.find(kSampleFrequency48000) !=
-        min_required_frames_map_.end()) {
-      return min_required_frames_map_[kSampleFrequency48000];
+  bool has_remote_audio_output = HasRemoteAudioOutput();
+  ScopedLock lock(min_required_frames_map_mutex_);
+  if (has_remote_audio_output == has_remote_audio_output_) {
+    // There's no audio output type change, we can use the numbers we got from
+    // the tests at app launch.
+    if (sampling_frequency_hz <= kSampleFrequency22050) {
+      if (min_required_frames_map_.find(kSampleFrequency22050) !=
+          min_required_frames_map_.end()) {
+        return min_required_frames_map_[kSampleFrequency22050];
+      }
+    } else if (sampling_frequency_hz <= kSampleFrequency48000) {
+      if (min_required_frames_map_.find(kSampleFrequency48000) !=
+          min_required_frames_map_.end()) {
+        return min_required_frames_map_[kSampleFrequency48000];
+      }
     }
   }
-  return kMaxRequiredFrames;
+  // We cannot find a matched result from our tests, or the audio output type
+  // has changed. We use the default max required frames to avoid underruns.
+  return has_remote_audio_output ? kMaxRequiredFramesRemote
+                                 : kMaxRequiredFramesLocal;
 }
 
 }  // namespace shared
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h
index 88974ff..b115deb 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -97,6 +97,7 @@
   // The minimum frames required to avoid underruns of different frequencies.
   std::map<int, int> min_required_frames_map_;
   MinRequiredFramesTester min_required_frames_tester_;
+  bool has_remote_audio_output_ = false;
 };
 
 class AudioTrackAudioSink : public SbAudioSinkPrivate {
diff --git a/starboard/android/shared/input_events_generator.cc b/starboard/android/shared/input_events_generator.cc
index 0b345242..8115b6b 100644
--- a/starboard/android/shared/input_events_generator.cc
+++ b/starboard/android/shared/input_events_generator.cc
@@ -679,6 +679,12 @@
     PushKeyEvent(key, type, window_, android_event, events);
   }
 
+  // Cobalt does not handle the kSbKeyUnknown event, return false,
+  // so the key event can be handled by the next receiver.
+  if (key == kSbKeyUnknown) {
+    return false;
+  }
+
   return true;
 }
 
diff --git a/starboard/android/shared/jni_env_ext.cc b/starboard/android/shared/jni_env_ext.cc
index 06535d5..98a65fd 100644
--- a/starboard/android/shared/jni_env_ext.cc
+++ b/starboard/android/shared/jni_env_ext.cc
@@ -72,7 +72,7 @@
 }
 
 JniEnvExt* JniEnvExt::Get() {
-  JNIEnv* env;
+  JNIEnv* env = nullptr;
   if (JNI_OK != g_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
     // Tell the JVM our thread name so it doesn't change it.
     char thread_name[16];
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
index 7b8997b..fe96cc8 100644
--- a/starboard/android/shared/media_capabilities_cache.cc
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -40,6 +40,114 @@
 
 const char SECURE_DECODER_SUFFIX[] = ".secure";
 
+// Constants for output types from
+// https://developer.android.com/reference/android/media/AudioDeviceInfo.
+constexpr int TYPE_AUX_LINE = 19;
+constexpr int TYPE_BLE_BROADCAST = 30;
+constexpr int TYPE_BLE_HEADSET = 26;
+constexpr int TYPE_BLE_SPEAKER = 27;
+constexpr int TYPE_BLUETOOTH_A2DP = 8;
+constexpr int TYPE_BLUETOOTH_SCO = 7;
+constexpr int TYPE_BUILTIN_EARPIECE = 1;
+constexpr int TYPE_BUILTIN_MIC = 15;
+constexpr int TYPE_BUILTIN_SPEAKER = 2;
+constexpr int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+constexpr int TYPE_BUS = 21;
+constexpr int TYPE_DOCK = 13;
+constexpr int TYPE_DOCK_ANALOG = 31;
+constexpr int TYPE_FM = 14;
+constexpr int TYPE_FM_TUNER = 16;
+constexpr int TYPE_HDMI = 9;
+constexpr int TYPE_HDMI_ARC = 10;
+constexpr int TYPE_HDMI_EARC = 29;
+constexpr int TYPE_HEARING_AID = 23;
+constexpr int TYPE_IP = 20;
+constexpr int TYPE_LINE_ANALOG = 5;
+constexpr int TYPE_LINE_DIGITAL = 6;
+constexpr int TYPE_REMOTE_SUBMIX = 25;
+constexpr int TYPE_TELEPHONY = 18;
+constexpr int TYPE_TV_TUNER = 17;
+constexpr int TYPE_UNKNOWN = 0;
+constexpr int TYPE_USB_ACCESSORY = 12;
+constexpr int TYPE_USB_DEVICE = 11;
+constexpr int TYPE_USB_HEADSET = 22;
+constexpr int TYPE_WIRED_HEADPHONES = 4;
+constexpr int TYPE_WIRED_HEADSET = 3;
+
+#if SB_API_VERSION >= 15
+SbMediaAudioConnector GetConnectorFromAndroidOutputType(
+    int android_output_device_type) {
+  switch (android_output_device_type) {
+    case TYPE_AUX_LINE:
+      return kSbMediaAudioConnectorAnalog;
+    case TYPE_BLE_BROADCAST:
+      return kSbMediaAudioConnectorBluetooth;
+    case TYPE_BLE_HEADSET:
+      return kSbMediaAudioConnectorBluetooth;
+    case TYPE_BLE_SPEAKER:
+      return kSbMediaAudioConnectorBluetooth;
+    case TYPE_BLUETOOTH_A2DP:
+      return kSbMediaAudioConnectorBluetooth;
+    case TYPE_BLUETOOTH_SCO:
+      return kSbMediaAudioConnectorBluetooth;
+    case TYPE_BUILTIN_EARPIECE:
+      return kSbMediaAudioConnectorBuiltIn;
+    case TYPE_BUILTIN_MIC:
+      return kSbMediaAudioConnectorBuiltIn;
+    case TYPE_BUILTIN_SPEAKER:
+      return kSbMediaAudioConnectorBuiltIn;
+    case TYPE_BUILTIN_SPEAKER_SAFE:
+      return kSbMediaAudioConnectorBuiltIn;
+    case TYPE_BUS:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_DOCK:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_DOCK_ANALOG:
+      return kSbMediaAudioConnectorAnalog;
+    case TYPE_FM:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_FM_TUNER:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_HDMI:
+      return kSbMediaAudioConnectorHdmi;
+    case TYPE_HDMI_ARC:
+      return kSbMediaAudioConnectorHdmi;
+    case TYPE_HDMI_EARC:
+      return kSbMediaAudioConnectorHdmi;
+    case TYPE_HEARING_AID:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_IP:
+      return kSbMediaAudioConnectorRemoteWired;
+    case TYPE_LINE_ANALOG:
+      return kSbMediaAudioConnectorAnalog;
+    case TYPE_LINE_DIGITAL:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_REMOTE_SUBMIX:
+      return kSbMediaAudioConnectorRemoteOther;
+    case TYPE_TELEPHONY:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_TV_TUNER:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_UNKNOWN:
+      return kSbMediaAudioConnectorUnknown;
+    case TYPE_USB_ACCESSORY:
+      return kSbMediaAudioConnectorUsb;
+    case TYPE_USB_DEVICE:
+      return kSbMediaAudioConnectorUsb;
+    case TYPE_USB_HEADSET:
+      return kSbMediaAudioConnectorUsb;
+    case TYPE_WIRED_HEADPHONES:
+      return kSbMediaAudioConnectorAnalog;
+    case TYPE_WIRED_HEADSET:
+      return kSbMediaAudioConnectorAnalog;
+  }
+
+  SB_LOG(WARNING) << "Encountered unknown audio output device type "
+                  << android_output_device_type;
+  return kSbMediaAudioConnectorUnknown;
+}
+#endif  // SB_API_VERSION >= 15
+
 bool EndsWith(const std::string& str, const std::string& suffix) {
   if (str.size() < suffix.size()) {
     return false;
@@ -135,13 +243,47 @@
                                        encoding) == JNI_TRUE;
 }
 
-int GetMaxAudioOutputChannels() {
+bool GetAudioConfiguration(int index,
+                           SbMediaAudioConfiguration* configuration) {
+  *configuration = {};
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jobject> j_audio_output_manager(
       env->CallStarboardObjectMethodOrAbort(
           "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
-  return static_cast<int>(env->CallIntMethodOrAbort(
-      j_audio_output_manager.Get(), "getMaxChannels", "()I"));
+  ScopedLocalJavaRef<jobject> j_output_device_info(env->NewObjectOrAbort(
+      "dev/cobalt/media/AudioOutputManager$OutputDeviceInfo", "()V"));
+
+  bool succeeded = env->CallBooleanMethodOrAbort(
+      j_audio_output_manager.Get(), "getOutputDeviceInfo",
+      "(ILdev/cobalt/media/AudioOutputManager$OutputDeviceInfo;)Z", index,
+      j_output_device_info.Get());
+
+  if (!succeeded) {
+    SB_LOG(WARNING)
+        << "Call to AudioOutputManager.getOutputDeviceInfo() failed.";
+    return false;
+  }
+
+  auto call_int_method = [env, &j_output_device_info](const char* name) {
+    return env->CallIntMethodOrAbort(j_output_device_info.Get(), name, "()I");
+  };
+
+#if SB_API_VERSION >= 15
+  configuration->connector =
+      GetConnectorFromAndroidOutputType(call_int_method("getType"));
+#else   // SB_API_VERSION >= 15
+  configuration->connector = kSbMediaAudioConnectorHdmi;
+#endif  // SB_API_VERSION >= 15
+  configuration->latency = 0;
+  configuration->coding_type = kSbMediaAudioCodingTypePcm;
+  configuration->number_of_channels = call_int_method("getChannels");
+
+  if (configuration->connector != kSbMediaAudioConnectorHdmi) {
+    configuration->number_of_channels = 2;
+  }
+
+  return true;
 }
 
 }  // namespace
@@ -311,21 +453,27 @@
   return supported;
 }
 
-int MediaCapabilitiesCache::GetMaxAudioOutputChannels() {
+bool MediaCapabilitiesCache::GetAudioConfiguration(
+    int index,
+    SbMediaAudioConfiguration* configuration) {
+  SB_DCHECK(index >= 0);
   if (!is_enabled_) {
-    return ::starboard::android::shared::GetMaxAudioOutputChannels();
+    return ::starboard::android::shared::GetAudioConfiguration(index,
+                                                               configuration);
   }
 
   ScopedLock scoped_lock(mutex_);
   UpdateMediaCapabilities_Locked();
-  return max_audio_output_channels_;
+  if (index < audio_configurations_.size()) {
+    *configuration = audio_configurations_[index];
+    return true;
+  }
+  return false;
 }
 
 bool MediaCapabilitiesCache::HasAudioDecoderFor(const std::string& mime_type,
-                                                int bitrate,
-                                                bool must_support_tunnel_mode) {
-  return !FindAudioDecoder(mime_type, bitrate, must_support_tunnel_mode)
-              .empty();
+                                                int bitrate) {
+  return !FindAudioDecoder(mime_type, bitrate).empty();
 }
 
 bool MediaCapabilitiesCache::HasVideoDecoderFor(
@@ -347,16 +495,14 @@
 
 std::string MediaCapabilitiesCache::FindAudioDecoder(
     const std::string& mime_type,
-    int bitrate,
-    bool must_support_tunnel_mode) {
+    int bitrate) {
   if (!is_enabled_) {
     JniEnvExt* env = JniEnvExt::Get();
     ScopedLocalJavaRef<jstring> j_mime(
         env->NewStringStandardUTFOrAbort(mime_type.c_str()));
     jobject j_decoder_name = env->CallStaticObjectMethodOrAbort(
         "dev/cobalt/media/MediaCodecUtil", "findAudioDecoder",
-        "(Ljava/lang/String;IZ)Ljava/lang/String;", j_mime.Get(), bitrate,
-        must_support_tunnel_mode);
+        "(Ljava/lang/String;I)Ljava/lang/String;", j_mime.Get(), bitrate);
     return env->GetStringStandardUTFOrAbort(
         static_cast<jstring>(j_decoder_name));
   }
@@ -365,11 +511,6 @@
   UpdateMediaCapabilities_Locked();
 
   for (auto& audio_capability : audio_codec_capabilities_map_[mime_type]) {
-    // Reject if tunnel mode is required but codec doesn't support it.
-    if (must_support_tunnel_mode &&
-        !audio_capability->is_tunnel_mode_supported()) {
-      continue;
-    }
     // Reject if bitrate is not supported.
     if (!audio_capability->IsBitrateSupported(bitrate)) {
       continue;
@@ -463,25 +604,6 @@
   return "";
 }
 
-void MediaCapabilitiesCache::ClearCache() {
-  ScopedLock scoped_lock(mutex_);
-  is_initialized_ = false;
-  supported_hdr_types_is_dirty_ = true;
-  audio_output_channels_is_dirty_ = true;
-  is_widevine_supported_ = false;
-  is_cbcs_supported_ = false;
-  supported_transfer_ids_.clear();
-  passthrough_supportabilities_.clear();
-  audio_codec_capabilities_map_.clear();
-  video_codec_capabilities_map_.clear();
-  max_audio_output_channels_ = -1;
-}
-
-void MediaCapabilitiesCache::Initialize() {
-  ScopedLock scoped_lock(mutex_);
-  UpdateMediaCapabilities_Locked();
-}
-
 MediaCapabilitiesCache::MediaCapabilitiesCache() {
   // Enable mime and key system caches.
   MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true);
@@ -490,22 +612,20 @@
 
 void MediaCapabilitiesCache::UpdateMediaCapabilities_Locked() {
   mutex_.DCheckAcquired();
+  if (capabilities_is_dirty_.exchange(false)) {
+    // We use a different cache strategy (load and cache) for passthrough
+    // supportabilities, so we only clear |passthrough_supportabilities_| here.
+    passthrough_supportabilities_.clear();
 
-  if (supported_hdr_types_is_dirty_.exchange(false)) {
+    audio_codec_capabilities_map_.clear();
+    video_codec_capabilities_map_.clear();
+    audio_configurations_.clear();
+    is_widevine_supported_ = GetIsWidevineSupported();
+    is_cbcs_supported_ = GetIsCbcsSupported();
     supported_transfer_ids_ = GetSupportedHdrTypes();
+    LoadCodecInfos_Locked();
+    LoadAudioConfigurations_Locked();
   }
-  if (audio_output_channels_is_dirty_.exchange(false)) {
-    max_audio_output_channels_ =
-        ::starboard::android::shared::GetMaxAudioOutputChannels();
-  }
-
-  if (is_initialized_) {
-    return;
-  }
-  is_widevine_supported_ = GetIsWidevineSupported();
-  is_cbcs_supported_ = GetIsCbcsSupported();
-  LoadCodecInfos_Locked();
-  is_initialized_ = true;
 }
 
 void MediaCapabilitiesCache::LoadCodecInfos_Locked() {
@@ -558,33 +678,37 @@
   }
 }
 
+void MediaCapabilitiesCache::LoadAudioConfigurations_Locked() {
+  SB_DCHECK(audio_configurations_.empty());
+  mutex_.DCheckAcquired();
+
+  // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The
+  // limit here is to avoid infinite loop and also match
+  // SbPlayerBridge::GetAudioConfigurations().
+  const int kMaxAudioConfigurations = 32;
+  SbMediaAudioConfiguration configuration;
+  while (audio_configurations_.size() < kMaxAudioConfigurations &&
+         ::starboard::android::shared::GetAudioConfiguration(
+             audio_configurations_.size(), &configuration)) {
+    audio_configurations_.push_back(configuration);
+  }
+}
+
 extern "C" SB_EXPORT_PLATFORM void
 Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() {
-  SB_DLOG(INFO) << "Display device has changed.";
-  MediaCapabilitiesCache::GetInstance()->ClearSupportedHdrTypes();
+  // Display device change could change hdr capabilities.
+  MediaCapabilitiesCache::GetInstance()->ClearCache();
   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
 }
 
 extern "C" SB_EXPORT_PLATFORM void
 Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() {
-  SB_DLOG(INFO) << "Audio device has changed.";
-  MediaCapabilitiesCache::GetInstance()->ClearAudioOutputChannels();
+  // Audio output device change could change passthrough decoder capabilities,
+  // so we have to reload codec capabilities.
+  MediaCapabilitiesCache::GetInstance()->ClearCache();
   MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
 }
 
-void* MediaCapabilitiesCacheInitializationThreadEntry(void* context) {
-  SB_LOG(INFO) << "Initialize MediaCapabilitiesCache in background.";
-  MediaCapabilitiesCache::GetInstance()->Initialize();
-  return 0;
-}
-
-extern "C" SB_EXPORT_PLATFORM void
-Java_dev_cobalt_coat_CobaltActivity_nativeInitializeMediaCapabilitiesInBackground() {
-  SbThreadCreate(0, kSbThreadPriorityNormal, kSbThreadNoAffinity, false,
-                 "media_capabilities_cache_thread",
-                 MediaCapabilitiesCacheInitializationThreadEntry, nullptr);
-}
-
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h
index db3879e..656398a 100644
--- a/starboard/android/shared/media_capabilities_cache.h
+++ b/starboard/android/shared/media_capabilities_cache.h
@@ -125,11 +125,10 @@
 
   bool IsPassthroughSupported(SbMediaAudioCodec codec);
 
-  int GetMaxAudioOutputChannels();
+  bool GetAudioConfiguration(int index,
+                             SbMediaAudioConfiguration* configuration);
 
-  bool HasAudioDecoderFor(const std::string& mime_type,
-                          int bitrate,
-                          bool must_support_tunnel_mode);
+  bool HasAudioDecoderFor(const std::string& mime_type, int bitrate);
 
   bool HasVideoDecoderFor(const std::string& mime_type,
                           bool must_support_secure,
@@ -141,9 +140,7 @@
                           int bitrate,
                           int fps);
 
-  std::string FindAudioDecoder(const std::string& mime_type,
-                               int bitrate,
-                               bool must_support_tunnel_mode);
+  std::string FindAudioDecoder(const std::string& mime_type, int bitrate);
 
   std::string FindVideoDecoder(const std::string& mime_type,
                                bool must_support_secure,
@@ -158,11 +155,7 @@
 
   bool IsEnabled() const { return is_enabled_; }
   void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; }
-  void ClearCache();
-
-  void Initialize();
-  void ClearSupportedHdrTypes() { supported_hdr_types_is_dirty_ = true; }
-  void ClearAudioOutputChannels() { audio_output_channels_is_dirty_ = true; }
+  void ClearCache() { capabilities_is_dirty_ = true; }
 
  private:
   MediaCapabilitiesCache();
@@ -172,6 +165,7 @@
   MediaCapabilitiesCache& operator=(const MediaCapabilitiesCache&) = delete;
 
   void UpdateMediaCapabilities_Locked();
+  void LoadAudioConfigurations_Locked();
   void LoadCodecInfos_Locked();
 
   Mutex mutex_;
@@ -186,14 +180,12 @@
 
   std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_;
   std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_;
-
-  std::atomic_bool is_enabled_{true};
-  std::atomic_bool supported_hdr_types_is_dirty_{true};
-  std::atomic_bool audio_output_channels_is_dirty_{true};
-  bool is_initialized_ = false;
+  std::vector<SbMediaAudioConfiguration> audio_configurations_;
   bool is_widevine_supported_ = false;
   bool is_cbcs_supported_ = false;
-  int max_audio_output_channels_ = -1;
+
+  std::atomic_bool is_enabled_{true};
+  std::atomic_bool capabilities_is_dirty_{true};
 };
 
 }  // namespace shared
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc
index fe97038..87bdf32 100644
--- a/starboard/android/shared/media_codec_bridge.cc
+++ b/starboard/android/shared/media_codec_bridge.cc
@@ -169,8 +169,7 @@
 
   std::string decoder_name =
       MediaCapabilitiesCache::GetInstance()->FindAudioDecoder(
-          mime, /* bitrate = */ 0,
-          /* must_support_tunnel_mode = */ false);
+          mime, /* bitrate = */ 0);
 
   if (decoder_name.empty()) {
     SB_LOG(ERROR) << "Failed to find decoder for " << audio_stream_info.codec
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc
index 78425b7..cf93733 100644
--- a/starboard/android/shared/media_decoder.cc
+++ b/starboard/android/shared/media_decoder.cc
@@ -146,9 +146,11 @@
 
 MediaDecoder::~MediaDecoder() {
   SB_DCHECK(thread_checker_.CalledOnValidThread());
-
   destroying_.store(true);
-  condition_variable_.Signal();
+  {
+    ScopedLock scoped_lock(mutex_);
+    condition_variable_.Signal();
+  }
 
   if (SbThreadIsValid(decoder_thread_)) {
     SbThreadJoin(decoder_thread_, NULL);
@@ -184,11 +186,15 @@
 
 void MediaDecoder::WriteInputBuffers(const InputBuffers& input_buffers) {
   SB_DCHECK(thread_checker_.CalledOnValidThread());
-  SB_DCHECK(!input_buffers.empty());
   if (stream_ended_.load()) {
     SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
     return;
   }
+  if (input_buffers.empty()) {
+    SB_LOG(ERROR) << "No input buffer to decode.";
+    SB_DCHECK(!input_buffers.empty());
+    return;
+  }
 
   if (!SbThreadIsValid(decoder_thread_)) {
     decoder_thread_ = SbThreadCreate(
@@ -463,24 +469,20 @@
   }
 
   jint status;
-  if (event.type == Event::kWriteCodecConfig) {
-    if (!drm_system_ || (drm_system_ && drm_system_->IsReady())) {
-      status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
-                                                     kNoOffset, size, kNoPts,
-                                                     BUFFER_FLAG_CODEC_CONFIG);
-    } else {
-      status = MEDIA_CODEC_NO_KEY;
-    }
+  if (drm_system_ && !drm_system_->IsReady()) {
+    // Drm system initialization is asynchronous. If there's a drm system, we
+    // should wait until it's initialized to avoid errors.
+    status = MEDIA_CODEC_NO_KEY;
+  } else if (event.type == Event::kWriteCodecConfig) {
+    status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
+                                                   kNoOffset, size, kNoPts,
+                                                   BUFFER_FLAG_CODEC_CONFIG);
   } else if (event.type == Event::kWriteInputBuffer) {
     jlong pts_us = input_buffer->timestamp();
     if (drm_system_ && input_buffer->drm_info()) {
-      if (drm_system_->IsReady()) {
-        status = media_codec_bridge_->QueueSecureInputBuffer(
-            dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(),
-            pts_us);
-      } else {
-        status = MEDIA_CODEC_NO_KEY;
-      }
+      status = media_codec_bridge_->QueueSecureInputBuffer(
+          dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(),
+          pts_us);
     } else {
       status = media_codec_bridge_->QueueInputBuffer(
           dequeue_input_result.index, kNoOffset, size, pts_us, kNoBufferFlags);
diff --git a/starboard/android/shared/media_get_audio_configuration.cc b/starboard/android/shared/media_get_audio_configuration.cc
index 6b645b6..572fceb 100644
--- a/starboard/android/shared/media_get_audio_configuration.cc
+++ b/starboard/android/shared/media_get_audio_configuration.cc
@@ -19,112 +19,6 @@
 #include "starboard/android/shared/media_capabilities_cache.h"
 #include "starboard/common/media.h"
 
-// Constants for output types from
-// https://developer.android.com/reference/android/media/AudioDeviceInfo.
-constexpr int TYPE_AUX_LINE = 19;
-constexpr int TYPE_BLE_BROADCAST = 30;
-constexpr int TYPE_BLE_HEADSET = 26;
-constexpr int TYPE_BLE_SPEAKER = 27;
-constexpr int TYPE_BLUETOOTH_A2DP = 8;
-constexpr int TYPE_BLUETOOTH_SCO = 7;
-constexpr int TYPE_BUILTIN_EARPIECE = 1;
-constexpr int TYPE_BUILTIN_MIC = 15;
-constexpr int TYPE_BUILTIN_SPEAKER = 2;
-constexpr int TYPE_BUILTIN_SPEAKER_SAFE = 24;
-constexpr int TYPE_BUS = 21;
-constexpr int TYPE_DOCK = 13;
-constexpr int TYPE_DOCK_ANALOG = 31;
-constexpr int TYPE_FM = 14;
-constexpr int TYPE_FM_TUNER = 16;
-constexpr int TYPE_HDMI = 9;
-constexpr int TYPE_HDMI_ARC = 10;
-constexpr int TYPE_HDMI_EARC = 29;
-constexpr int TYPE_HEARING_AID = 23;
-constexpr int TYPE_IP = 20;
-constexpr int TYPE_LINE_ANALOG = 5;
-constexpr int TYPE_LINE_DIGITAL = 6;
-constexpr int TYPE_REMOTE_SUBMIX = 25;
-constexpr int TYPE_TELEPHONY = 18;
-constexpr int TYPE_TV_TUNER = 17;
-constexpr int TYPE_UNKNOWN = 0;
-constexpr int TYPE_USB_ACCESSORY = 12;
-constexpr int TYPE_USB_DEVICE = 11;
-constexpr int TYPE_USB_HEADSET = 22;
-constexpr int TYPE_WIRED_HEADPHONES = 4;
-constexpr int TYPE_WIRED_HEADSET = 3;
-
-SbMediaAudioConnector GetConnectorFromAndroidOutputType(
-    int android_output_device_type) {
-  switch (android_output_device_type) {
-    case TYPE_AUX_LINE:
-      return kSbMediaAudioConnectorAnalog;
-    case TYPE_BLE_BROADCAST:
-      return kSbMediaAudioConnectorBluetooth;
-    case TYPE_BLE_HEADSET:
-      return kSbMediaAudioConnectorBluetooth;
-    case TYPE_BLE_SPEAKER:
-      return kSbMediaAudioConnectorBluetooth;
-    case TYPE_BLUETOOTH_A2DP:
-      return kSbMediaAudioConnectorBluetooth;
-    case TYPE_BLUETOOTH_SCO:
-      return kSbMediaAudioConnectorBluetooth;
-    case TYPE_BUILTIN_EARPIECE:
-      return kSbMediaAudioConnectorBuiltIn;
-    case TYPE_BUILTIN_MIC:
-      return kSbMediaAudioConnectorBuiltIn;
-    case TYPE_BUILTIN_SPEAKER:
-      return kSbMediaAudioConnectorBuiltIn;
-    case TYPE_BUILTIN_SPEAKER_SAFE:
-      return kSbMediaAudioConnectorBuiltIn;
-    case TYPE_BUS:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_DOCK:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_DOCK_ANALOG:
-      return kSbMediaAudioConnectorAnalog;
-    case TYPE_FM:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_FM_TUNER:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_HDMI:
-      return kSbMediaAudioConnectorHdmi;
-    case TYPE_HDMI_ARC:
-      return kSbMediaAudioConnectorHdmi;
-    case TYPE_HDMI_EARC:
-      return kSbMediaAudioConnectorHdmi;
-    case TYPE_HEARING_AID:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_IP:
-      return kSbMediaAudioConnectorRemoteWired;
-    case TYPE_LINE_ANALOG:
-      return kSbMediaAudioConnectorAnalog;
-    case TYPE_LINE_DIGITAL:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_REMOTE_SUBMIX:
-      return kSbMediaAudioConnectorRemoteOther;
-    case TYPE_TELEPHONY:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_TV_TUNER:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_UNKNOWN:
-      return kSbMediaAudioConnectorUnknown;
-    case TYPE_USB_ACCESSORY:
-      return kSbMediaAudioConnectorUsb;
-    case TYPE_USB_DEVICE:
-      return kSbMediaAudioConnectorUsb;
-    case TYPE_USB_HEADSET:
-      return kSbMediaAudioConnectorUsb;
-    case TYPE_WIRED_HEADPHONES:
-      return kSbMediaAudioConnectorAnalog;
-    case TYPE_WIRED_HEADSET:
-      return kSbMediaAudioConnectorAnalog;
-  }
-
-  SB_LOG(WARNING) << "Encountered unknown audio output device type "
-                  << android_output_device_type;
-  return kSbMediaAudioConnectorUnknown;
-}
-
 // TODO(b/284140486): Refine the implementation so it works when the audio
 // outputs are changed during the query.
 bool SbMediaGetAudioConfiguration(
@@ -146,56 +40,13 @@
     return false;
   }
 
-  *out_configuration = {};
-
-  JniEnvExt* env = JniEnvExt::Get();
-  ScopedLocalJavaRef<jobject> j_audio_output_manager(
-      env->CallStarboardObjectMethodOrAbort(
-          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
-  ScopedLocalJavaRef<jobject> j_output_device_info(env->NewObjectOrAbort(
-      "dev/cobalt/media/AudioOutputManager$OutputDeviceInfo", "()V"));
-
-  bool succeeded = env->CallBooleanMethodOrAbort(
-      j_audio_output_manager.Get(), "getOutputDeviceInfo",
-      "(ILdev/cobalt/media/AudioOutputManager$OutputDeviceInfo;)Z",
-      output_index, j_output_device_info.Get());
-
-  if (!succeeded) {
-    SB_LOG(WARNING)
-        << "Call to AudioOutputManager.getOutputDeviceInfo() failed.";
-    return false;
-  }
-
-  auto call_int_method = [env, &j_output_device_info](const char* name) {
-    return env->CallIntMethodOrAbort(j_output_device_info.Get(), name, "()I");
-  };
-
-  out_configuration->connector =
-      GetConnectorFromAndroidOutputType(call_int_method("getType"));
-  out_configuration->latency = 0;
-  out_configuration->coding_type = kSbMediaAudioCodingTypePcm;
-  out_configuration->number_of_channels = call_int_method("getChannels");
-
-  if (out_configuration->connector == kSbMediaAudioConnectorHdmi) {
-    // Keep the previous logic for HDMI to reduce risk.
-    // TODO(b/284140486): Update this using same logic as other connectors.
-    int channels =
-        MediaCapabilitiesCache::GetInstance()->GetMaxAudioOutputChannels();
-    if (channels < 2) {
-      SB_LOG(WARNING) << "The supported channels from output device is "
-                      << channels << ", set to 2 channels instead.";
-      out_configuration->number_of_channels = 2;
-    } else {
-      out_configuration->number_of_channels = channels;
-    }
-  } else {
-    out_configuration->number_of_channels = 2;
-  }
+  bool result = MediaCapabilitiesCache::GetInstance()->GetAudioConfiguration(
+      output_index, out_configuration);
 
   SB_LOG(INFO) << "Audio connector type for index " << output_index << " is "
                << GetMediaAudioConnectorName(out_configuration->connector)
                << " and it has " << out_configuration->number_of_channels
                << " channels.";
 
-  return true;
+  return result;
 }
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc
index 46b73e5..9844406 100644
--- a/starboard/android/shared/media_is_audio_supported.cc
+++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -40,7 +40,6 @@
     return false;
   }
 
-  bool enable_tunnel_mode = false;
   bool enable_audio_passthrough = true;
   if (mime_type) {
     if (!mime_type->is_valid()) {
@@ -53,13 +52,6 @@
       return false;
     }
 
-    // Allows for enabling tunneled playback. Disabled by default.
-    // (https://source.android.com/devices/tv/multimedia-tunneling)
-    if (!mime_type->ValidateBoolParameter("tunnelmode")) {
-      return false;
-    }
-    enable_tunnel_mode = mime_type->GetParamBoolValue("tunnelmode", false);
-
     // Enables audio passthrough if the codec supports it.
     if (!mime_type->ValidateBoolParameter("audiopassthrough")) {
       return false;
@@ -75,14 +67,6 @@
     }
   }
 
-  if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported(
-                                kSbMediaAudioSampleTypeInt16Deprecated)) {
-    SB_LOG(WARNING)
-        << "Tunnel mode is rejected because int16 sample is required "
-           "but not supported.";
-    return false;
-  }
-
   // Android uses a libopus based opus decoder for clear content, or a platform
   // opus decoder for encrypted content, if available.
   if (audio_codec == kSbMediaAudioCodecOpus) {
@@ -90,8 +74,7 @@
   }
 
   bool media_codec_supported =
-      MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor(
-          mime, bitrate, enable_tunnel_mode);
+      MediaCapabilitiesCache::GetInstance()->HasAudioDecoderFor(mime, bitrate);
 
   if (!media_codec_supported) {
     return false;
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index 18db0ba..57d3b44 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -303,7 +303,6 @@
     MimeType audio_mime_type(audio_mime);
     if (!audio_mime.empty()) {
       if (!audio_mime_type.is_valid() ||
-          !audio_mime_type.ValidateBoolParameter("tunnelmode") ||
           !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") ||
           !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) {
         *error_message =
@@ -331,20 +330,15 @@
     bool enable_tunnel_mode = false;
     if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone &&
         creation_parameters.video_codec() != kSbMediaVideoCodecNone) {
-      bool enable_tunnel_mode =
-          audio_mime_type.GetParamBoolValue("tunnelmode", false) &&
+      enable_tunnel_mode =
           video_mime_type.GetParamBoolValue("tunnelmode", false);
 
-      if (!enable_tunnel_mode) {
-        SB_LOG(INFO) << "Tunnel mode is disabled. "
-                     << "Audio mime parameter \"tunnelmode\" value: "
-                     << audio_mime_type.GetParamStringValue("tunnelmode",
-                                                            "<not provided>")
-                     << ", video mime parameter \"tunnelmode\" value: "
-                     << video_mime_type.GetParamStringValue("tunnelmode",
-                                                            "<not provided>")
-                     << ".";
-      }
+      SB_LOG(INFO) << "Tunnel mode is "
+                   << (enable_tunnel_mode ? "enabled. " : "disabled. ")
+                   << "Video mime parameter \"tunnelmode\" value: "
+                   << video_mime_type.GetParamStringValue("tunnelmode",
+                                                          "<not provided>")
+                   << ".";
     } else {
       SB_LOG(INFO) << "Tunnel mode requires both an audio and video stream. "
                    << "Audio codec: "
@@ -365,12 +359,19 @@
     SB_LOG_IF(INFO, !force_improved_support_check)
         << "Improved support check is disabled for queries under 4K.";
     bool force_secure_pipeline_under_tunnel_mode = false;
-    if (enable_tunnel_mode &&
-        IsTunnelModeSupported(creation_parameters,
-                              &force_secure_pipeline_under_tunnel_mode,
-                              force_improved_support_check)) {
-      tunnel_mode_audio_session_id = GenerateAudioSessionId(
-          creation_parameters, force_improved_support_check);
+    if (enable_tunnel_mode) {
+      if (IsTunnelModeSupported(creation_parameters,
+                                &force_secure_pipeline_under_tunnel_mode,
+                                force_improved_support_check)) {
+        tunnel_mode_audio_session_id = GenerateAudioSessionId(
+            creation_parameters, force_improved_support_check);
+        SB_LOG(INFO) << "Generated tunnel mode audio session id "
+                     << tunnel_mode_audio_session_id;
+      } else {
+        SB_LOG(INFO) << "IsTunnelModeSupported() failed, disable tunnel mode.";
+      }
+    } else {
+      SB_LOG(INFO) << "Tunnel mode not enabled.";
     }
 
     if (tunnel_mode_audio_session_id == -1) {
diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc
index 723a1cc..44e899b 100644
--- a/starboard/android/shared/video_decoder.cc
+++ b/starboard/android/shared/video_decoder.cc
@@ -29,6 +29,8 @@
 #include "starboard/android/shared/media_common.h"
 #include "starboard/android/shared/video_render_algorithm.h"
 #include "starboard/android/shared/window_internal.h"
+#include "starboard/common/media.h"
+#include "starboard/common/player.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/decode_target.h"
@@ -397,6 +399,13 @@
       TeardownCodec();
     }
   }
+
+  SB_LOG(INFO) << "Created VideoDecoder for codec "
+               << GetMediaVideoCodecName(video_codec_) << ", with output mode "
+               << GetPlayerOutputModeName(output_mode_)
+               << ", max video capabilities \"" << max_video_capabilities_
+               << "\", and tunnel mode audio session id "
+               << tunnel_mode_audio_session_id_;
 }
 
 VideoDecoder::~VideoDecoder() {
diff --git a/starboard/android/shared/video_window.cc b/starboard/android/shared/video_window.cc
index 5e401ec..d6998e3 100644
--- a/starboard/android/shared/video_window.cc
+++ b/starboard/android/shared/video_window.cc
@@ -211,20 +211,23 @@
     return;
   }
 
+  JniEnvExt* env = JniEnvExt::Get();
+  if (!env) {
+    SB_LOG(INFO) << "Tried to clear video window when JniEnvExt was null.";
+    return;
+  }
+
   if (force_reset_surface) {
-    JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface",
-                                                     "()V");
+    env->CallStarboardVoidMethodOrAbort("resetVideoSurface", "()V");
     return;
   } else if (g_reset_surface_on_clear_window) {
     int width = ANativeWindow_getWidth(g_native_video_window);
     int height = ANativeWindow_getHeight(g_native_video_window);
     if (width <= height) {
-      JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("resetVideoSurface",
-                                                       "()V");
+      env->CallStarboardVoidMethodOrAbort("resetVideoSurface", "()V");
       return;
     }
   }
-
   ClearNativeWindow(g_native_video_window);
 }
 
diff --git a/starboard/build/config/BUILDCONFIG.gn b/starboard/build/config/BUILDCONFIG.gn
index 4075ed3..315e402 100644
--- a/starboard/build/config/BUILDCONFIG.gn
+++ b/starboard/build/config/BUILDCONFIG.gn
@@ -37,6 +37,10 @@
   build_with_separate_cobalt_toolchain = false
 }
 
+_is_on_pythonpath = exec_script("//starboard/build/is_on_path.py", [], "json")
+assert(!is_starboard || _is_on_pythonpath,
+       "The current repo is not first on the PYTHONPATH.")
+
 assert(!(is_starboard && is_native_target_build),
        "Targets should be built for Starboard or natively, but not both")
 
@@ -99,7 +103,7 @@
 host_toolchain = "//starboard/build/toolchain/$host_os:$_host_toolchain_cpu"
 
 if (build_with_separate_cobalt_toolchain) {
-  cobalt_toolchain = "//starboard/build/toolchain:clang"
+  cobalt_toolchain = "//$starboard_path/toolchain:cobalt"
   starboard_toolchain = "//$starboard_path/toolchain:starboard"
 } else {
   cobalt_toolchain = "//$starboard_path/toolchain:target"
@@ -253,7 +257,8 @@
     }
   }
 
-  if (defined(invoker.install_content) && invoker.install_content) {
+  if (defined(invoker.install_content) && invoker.install_content &&
+      current_toolchain == default_toolchain) {
     # 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") {
@@ -294,6 +299,13 @@
         rebase_path(files_list, root_build_dir),
       ]
     }
+  } else if (current_toolchain == starboard_toolchain &&
+             build_with_separate_cobalt_toolchain) {
+    outer_target_name = target_name
+    group("${target_name}_install_content") {
+      forward_variables_from(invoker, [ "testonly" ])
+      deps = [ ":${outer_target_name}_install_content($default_toolchain)" ]
+    }
   }
 }
 
@@ -401,8 +413,11 @@
       forward_variables_from(invoker, [ "testonly" ])
       deps = [
         ":${actual_target_name}_loader($starboard_toolchain)",
-        ":${actual_target_name}_loader_copy($starboard_toolchain)",
+        ":${actual_target_name}_loader_install($starboard_toolchain)",
       ]
+      if (!is_host_win) {
+        deps += [ ":${actual_target_name}_loader_copy($starboard_toolchain)" ]
+      }
     }
     if (current_toolchain == starboard_toolchain) {
       executable("${actual_target_name}_loader") {
@@ -410,6 +425,10 @@
         forward_variables_from(invoker, [ "testonly" ])
         sources = [ "//$starboard_path/starboard_loader.cc" ]
 
+        if (defined(extra_platform_loader_sources)) {
+          sources += extra_platform_loader_sources
+        }
+
         if (use_asan) {
           sources += [ "//$starboard_path/sanitizer_options.cc" ]
         }
@@ -424,18 +443,25 @@
         ldflags = [
           "-Wl,-rpath=" + rebase_path("$root_build_dir/starboard"),
           "-Wl,-rpath=" + rebase_path("$root_build_dir"),
+          "-Wl,-rpath=\$ORIGIN/../lib",
+          "-Wl,-rpath=\$ORIGIN",
         ]
 
         deps = [
           ":$original_target_name($cobalt_toolchain)",
           "//starboard:starboard_platform_group($starboard_toolchain)",
         ]
+        if (!separate_install_targets_for_bundling) {
+          deps += [ "//starboard:starboard_platform_group_install($starboard_toolchain)" ]
+        }
       }
-      copy("${actual_target_name}_loader_copy") {
-        forward_variables_from(invoker, [ "testonly" ])
-        sources = [ "$root_out_dir/${actual_target_name}_loader" ]
-        outputs = [ "$root_build_dir/${actual_target_name}_loader" ]
-        deps = [ ":${actual_target_name}_loader" ]
+      if (!is_host_win) {
+        copy("${actual_target_name}_loader_copy") {
+          forward_variables_from(invoker, [ "testonly" ])
+          sources = [ "$root_out_dir/${actual_target_name}_loader" ]
+          outputs = [ "$root_build_dir/${actual_target_name}_loader" ]
+          deps = [ ":${actual_target_name}_loader" ]
+        }
       }
     }
   }
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni
index 1edd94b..6c9d354 100644
--- a/starboard/build/config/base_configuration.gni
+++ b/starboard/build/config/base_configuration.gni
@@ -44,9 +44,6 @@
   # Directory path to static contents' data.
   sb_static_contents_output_data_dir = "$root_out_dir/content/data"
 
-  # Whether this is a modular build.
-  sb_is_modular = false
-
   # Whether this is an Evergreen build.
   sb_is_evergreen = false
 
diff --git a/starboard/build/config/install.gni b/starboard/build/config/install.gni
index 9ef3f3d..b58479a 100644
--- a/starboard/build/config/install.gni
+++ b/starboard/build/config/install.gni
@@ -16,7 +16,7 @@
   # Top-level directory for staging deploy build output. Platform install
   # actions should use ${sb_install_output_dir} defined in this file to place
   # artifacts for each deploy target in its own subdirectoy.
-  sb_install_output_dir = "$root_out_dir/install"
+  sb_install_output_dir = "$root_build_dir/install"
 
   # Sub-directory for install content.
   sb_install_content_subdir = ""
diff --git a/starboard/build/config/modular/BUILD.gn b/starboard/build/config/modular/BUILD.gn
new file mode 100644
index 0000000..d266109
--- /dev/null
+++ b/starboard/build/config/modular/BUILD.gn
@@ -0,0 +1,210 @@
+# Copyright 2023 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.
+
+config("modular") {
+  cflags = [
+    "-ffunction-sections",
+    "-fdata-sections",
+    "-nostdlibinc",
+    "-isystem" + rebase_path("//third_party/llvm-project/libcxxabi/include",
+                             root_build_dir),
+    "-isystem" + rebase_path("//third_party/llvm-project/libunwind/include",
+                             root_build_dir),
+    "-isystem" + rebase_path("//third_party/llvm-project/libcxx/include",
+                             root_build_dir),
+    "-isystem" + rebase_path("//third_party/musl/include", root_build_dir),
+    "-isystem" + rebase_path("//third_party/musl/arch/generic", root_build_dir),
+  ]
+
+  if (!is_host_win) {
+    # Causes error on windows. clang++: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'
+    cflags += [ "-fPIC" ]
+  }
+
+  cflags_cc = [
+    "-nostdinc++",
+    "-std=c++17",
+  ]
+
+  defines = [
+    # Ensure that the Starboardized __external_threading file is included.
+    "_LIBCPP_HAS_THREAD_API_EXTERNAL",
+
+    # Ensure that only the forward declarations and type definitions are included
+    # in __external_threading.
+    "_LIBCPP_HAS_THREAD_LIBRARY_EXTERNAL",
+
+    # Enable GNU extensions to get prototypes like ffsl.
+    "_GNU_SOURCE=1",
+
+    "_LIBCPP_HAS_MUSL_LIBC",
+    "__STDC_FORMAT_MACROS",  # so that we get PRI*
+
+    # File format of the shared object we will generate.
+    "__ELF__",
+
+    # Use scalar portable implementations instead of Clang/GCC vector
+    # extensions in SkVx.h.
+    "SKNX_NO_SIMD",
+
+    # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some
+    # nasty macros (|Status|, for example) that conflict with Chromium base.
+    "MESA_EGL_NO_X11_HEADERS",
+  ]
+
+  if (is_debug) {
+    cflags += [
+      "-O0",
+      "-frtti",
+    ]
+    if (!cobalt_fastbuild) {
+      cflags += [
+        # This flag causes an increase in binary size on certain platforms. Refer b/297357707
+        "-g",
+      ]
+    }
+  } else if (is_devel) {
+    cflags += [
+      "-O2",
+      "-frtti",
+    ]
+    if (!cobalt_fastbuild) {
+      cflags += [
+        # This flag causes an increase in binary size on certain platforms. Refer b/297357707
+        "-g",
+      ]
+    }
+  } else {
+    cflags += [
+      "-fno-rtti",
+      "-gline-tables-only",
+    ]
+  }
+
+  if (is_clang) {
+    cflags += [
+      "-fcolor-diagnostics",
+
+      # Default visibility to hidden, to enable dead stripping.
+      "-fvisibility=hidden",
+
+      # Warns on switches on enums that cover all enum values but
+      # also contain a default: branch. Chrome is full of that.
+      "-Wno-covered-switch-default",
+
+      # protobuf uses hash_map.
+      "-Wno-deprecated",
+
+      "-fno-exceptions",
+
+      # Enable unwind tables used by libunwind for stack traces.
+      "-funwind-tables",
+
+      # Disable usage of frame pointers.
+      "-fomit-frame-pointer",
+
+      # Don"t warn about the "struct foo f = {0};" initialization pattern.
+      "-Wno-missing-field-initializers",
+
+      # Do not warn for implicit sign conversions.
+      "-Wno-sign-conversion",
+
+      "-fno-strict-aliasing",  # See http://crbug.com/32204
+
+      "-Wno-unnamed-type-template-args",
+
+      # Triggered by the COMPILE_ASSERT macro.
+      "-Wno-unused-local-typedef",
+
+      # Do not warn if a function or variable cannot be implicitly
+      # instantiated.
+      "-Wno-undefined-var-template",
+
+      # Do not warn about an implicit exception spec mismatch.
+      "-Wno-implicit-exception-spec-mismatch",
+
+      # It's OK not to use some input parameters.
+      "-Wno-unused-parameter",
+      "-Wno-conversion",
+      "-Wno-bitwise-op-parentheses",
+      "-Wno-shift-op-parentheses",
+      "-Wno-shorten-64-to-32",
+      "-fno-use-cxa-atexit",
+    ]
+  }
+
+  if (is_clang_16 || is_host_win) {
+    cflags += [
+      # Do not remove null pointer checks.
+      "-fno-delete-null-pointer-checks",
+    ]
+  }
+
+  if (use_asan) {
+    cflags += [
+      "-fsanitize=address",
+      "-fno-omit-frame-pointer",
+    ]
+
+    defines += [ "ADDRESS_SANITIZER" ]
+
+    if (asan_symbolizer_path != "") {
+      defines += [ "ASAN_SYMBOLIZER_PATH=\"${asan_symbolizer_path}\"" ]
+    }
+  } else if (use_tsan) {
+    cflags += [
+      "-fsanitize=thread",
+      "-fno-omit-frame-pointer",
+    ]
+
+    defines += [ "THREAD_SANITIZER" ]
+  }
+}
+
+config("speed") {
+  cflags = [ "-O2" ]
+}
+
+config("size") {
+  cflags = [ "-Os" ]
+}
+
+config("pedantic_warnings") {
+  cflags = [
+    "-Wall",
+    "-Wextra",
+    "-Wunreachable-code",
+  ]
+}
+
+config("no_pedantic_warnings") {
+  cflags = [
+    # 'this' pointer cannot be NULL...pointer may be assumed
+    # to always convert to true.
+    "-Wno-undefined-bool-conversion",
+
+    # Skia doesn't use overrides.
+    "-Wno-inconsistent-missing-override",
+
+    # Do not warn for implicit type conversions that may change a value.
+    "-Wno-conversion",
+
+    # shifting a negative signed value is undefined
+    "-Wno-shift-negative-value",
+
+    # Width of bit-field exceeds width of its type- value will be truncated
+    "-Wno-bitfield-width",
+    "-Wno-undefined-var-template",
+  ]
+}
diff --git a/starboard/build/config/modular/variables.gni b/starboard/build/config/modular/variables.gni
new file mode 100644
index 0000000..7b220fa
--- /dev/null
+++ b/starboard/build/config/modular/variables.gni
@@ -0,0 +1,20 @@
+# Copyright 2023 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.
+
+assert(current_toolchain == default_toolchain,
+       "Cannot access variables for non-default toolchains")
+
+if (!is_host_win) {
+  is_clang_16 = true
+}
diff --git a/starboard/build/config/modular/x64/BUILD.gn b/starboard/build/config/modular/x64/BUILD.gn
new file mode 100644
index 0000000..3eb0f53
--- /dev/null
+++ b/starboard/build/config/modular/x64/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2023 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.
+
+config("sabi_flags") {
+  cflags = [
+    "-march=x86-64",
+    "-target",
+    "x86_64-unknown-linux-elf",
+  ]
+}
+
+config("x64") {
+  configs = [
+    "//starboard/build/config/sabi",
+    ":sabi_flags",
+  ]
+
+  cflags = [ "-isystem" +
+             rebase_path("//third_party/musl/arch/x86_64", root_build_dir) ]
+}
diff --git a/starboard/build/config/starboard_target_type.gni b/starboard/build/config/starboard_target_type.gni
index c1ebc17..c3c5916 100644
--- a/starboard/build/config/starboard_target_type.gni
+++ b/starboard/build/config/starboard_target_type.gni
@@ -25,13 +25,17 @@
 }
 
 template("starboard_platform_target") {
-  target(starboard_target_type, target_name) {
+  target_type = starboard_target_type
+  if (defined(invoker.target_type)) {
+    target_type = invoker.target_type
+  }
+  target(target_type, target_name) {
     forward_variables_from(invoker, [ "extra_configs" ])
 
     if (defined(invoker.extra_configs)) {
       configs += extra_configs
     }
-    if (starboard_target_type == "shared_library") {
+    if (target_type == "shared_library") {
       build_loader = false
     }
     public_deps = [
diff --git a/starboard/build/is_on_path.py b/starboard/build/is_on_path.py
new file mode 100644
index 0000000..c9c149b
--- /dev/null
+++ b/starboard/build/is_on_path.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# Copyright 2023 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.
+"""Script for checking if the current repo on the path."""
+
+import os
+
+
+def main():
+  try:
+    # Try to import this file and compare its path to the current file.
+    import starboard.build.is_on_path  # pylint: disable=import-outside-toplevel
+    this_file = os.path.realpath(__file__)
+    imported_file = os.path.realpath(starboard.build.is_on_path.__file__)
+    print(str(this_file == imported_file).lower())
+  except ImportError:
+    print('false')
+
+
+if __name__ == '__main__':
+  main()
diff --git a/starboard/build/toolchain/BUILD.gn b/starboard/build/toolchain/BUILD.gn
index 21a6a3c..5f665db 100644
--- a/starboard/build/toolchain/BUILD.gn
+++ b/starboard/build/toolchain/BUILD.gn
@@ -64,8 +64,5 @@
     if (defined(native_snarl_linker)) {
       using_snarl_linker = true
     }
-    toolchain_args = {
-      is_clang = true
-    }
   }
 }
diff --git a/starboard/build/toolchain/cobalt_toolchains.gni b/starboard/build/toolchain/cobalt_toolchains.gni
new file mode 100644
index 0000000..4277164
--- /dev/null
+++ b/starboard/build/toolchain/cobalt_toolchains.gni
@@ -0,0 +1,53 @@
+# Copyright 2023 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.
+
+import("//build/config/win/visual_studio_version.gni")
+import("//build/toolchain/gcc_toolchain.gni")
+
+template("cobalt_clang_toolchain") {
+  gcc_toolchain(target_name) {
+    forward_variables_from(invoker.variables,
+                           [
+                             "native_linker_path",
+                             "executable_extension",
+                             "tail_lib_dependencies",
+                             "shlib_extension",
+                           ])
+    assert(defined(native_linker_path),
+           "native_linker_path has to be defined by the platform")
+    if (!is_host_win) {
+      prefix = rebase_path("$clang_base_path/bin", root_build_dir)
+      cc = "$prefix/clang"
+      cxx = "$prefix/clang++"
+      ld = native_linker_path
+      readelf = "readelf"
+      ar = "${prefix}/llvm-ar"
+      nm = "nm"
+    } else {
+      prefix = llvm_clang_path
+      cc = "$prefix/clang.exe"
+      cxx = "$prefix/clang++.exe"
+      ld = native_linker_path
+      readelf = "$prefix/llvm-readobj.exe"
+      ar = "${prefix}/llvm-ar.exe"
+      nm = "${prefix}/llvm-nm.exe"
+    }
+    toolchain_args = {
+      if (defined(invoker.toolchain_args)) {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+      is_clang = true
+    }
+  }
+}
diff --git a/starboard/common/metrics/stats_tracker.h b/starboard/common/metrics/stats_tracker.h
index 9406175..7a217d9 100644
--- a/starboard/common/metrics/stats_tracker.h
+++ b/starboard/common/metrics/stats_tracker.h
@@ -52,8 +52,6 @@
 
   StatsTracker& stats_tracker() {
     if (!stats_tracker_) {
-      SB_DLOG_IF(ERROR, !undefined_logged_)
-          << "[once] StatsTracker is not defined.";
       undefined_logged_ = true;
       return undefined_stats_tracker_;
     }
diff --git a/starboard/configuration.h b/starboard/configuration.h
index 651f131..da4eff5 100644
--- a/starboard/configuration.h
+++ b/starboard/configuration.h
@@ -35,7 +35,7 @@
 
 // The minimum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MINIMUM_API_VERSION 12
+#define SB_MINIMUM_API_VERSION 13
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
diff --git a/starboard/doc/evergreen/cobalt_evergreen_overview.md b/starboard/doc/evergreen/cobalt_evergreen_overview.md
index 2f35e8b..f258cbb 100644
--- a/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -305,8 +305,8 @@
 
 1. Build the `crashpad_database_util` target and deploy it onto the device.
 ```
-$ cobalt/build/gn.py -p <partner_port_name> -c qa
-$ ninja -C out/<partner_port_name>_qa crashpad_database_util
+$ gn gen out/<partner_port_name>_qa --args='target_platform="<partner_port_name>" build_type="qa"'
+$ ninja -C out/<partner_port_name>_qa native_target/crashpad_database_util
 ```
 2. Remove the existing state for crashpad as it throttles uploads to 1 per hour:
 ```
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn
index bca4126..184ccb2 100644
--- a/starboard/elf_loader/BUILD.gn
+++ b/starboard/elf_loader/BUILD.gn
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
+
 _elf_loader_sources = [
   "dynamic_section.cc",
   "dynamic_section.h",
@@ -88,7 +90,8 @@
   }
 }
 
-if (current_toolchain == starboard_toolchain) {
+# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms.
+if (current_toolchain == starboard_toolchain && !is_host_win) {
   target(starboard_level_final_executable_type, "elf_loader_sandbox") {
     data_deps = [ "//third_party/icu:icudata" ]
     if (cobalt_font_package == "empty") {
@@ -152,31 +155,34 @@
   }
 }
 
-target(gtest_target_type, "elf_loader_test") {
-  testonly = true
-  sources = [ "//starboard/common/test_main.cc" ]
-  deps = [
-    "//starboard",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
-
-  if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
-      target_cpu == "arm64") {
-    sources += [
-      "dynamic_section_test.cc",
-      "elf_header_test.cc",
-      "elf_loader_test.cc",
-      "lz4_file_impl_test.cc",
-      "program_table_test.cc",
-      "relocations_test.cc",
-    ]
-    deps += [
-      ":copy_elf_loader_testdata",
-      ":elf_loader",
+# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms.
+if (!is_host_win) {
+  target(gtest_target_type, "elf_loader_test") {
+    testonly = true
+    sources = [ "//starboard/common/test_main.cc" ]
+    deps = [
+      "//starboard",
+      "//testing/gmock",
+      "//testing/gtest",
     ]
 
-    data_deps = [ ":copy_elf_loader_testdata" ]
+    if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
+        target_cpu == "arm64") {
+      sources += [
+        "dynamic_section_test.cc",
+        "elf_header_test.cc",
+        "elf_loader_test.cc",
+        "lz4_file_impl_test.cc",
+        "program_table_test.cc",
+        "relocations_test.cc",
+      ]
+      deps += [
+        ":copy_elf_loader_testdata",
+        ":elf_loader",
+      ]
+
+      data_deps = [ ":copy_elf_loader_testdata" ]
+    }
   }
 }
 
diff --git a/starboard/elf_loader/sandbox.cc b/starboard/elf_loader/sandbox.cc
index 2f2eb05..d86595e 100644
--- a/starboard/elf_loader/sandbox.cc
+++ b/starboard/elf_loader/sandbox.cc
@@ -58,7 +58,7 @@
   GetEvergreenInfo(&evergreen_info);
   if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad(
           evergreen_info)) {
-    SB_LOG(ERROR) << "Could not send Cobalt library information into Crashapd.";
+    SB_LOG(ERROR) << "Could not send Cobalt library information into Crashpad.";
   } else {
     SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad.";
   }
diff --git a/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
index c4403ea..d3474e2 100644
--- a/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
@@ -27,8 +27,10 @@
     ":sabi_flags",
     "//starboard/evergreen/arm/shared/platform_configuration",
   ]
-  ldflags = [
-    "-Wl,-m",
-    "-Wl,armelf",
-  ]
+  if (sb_is_evergreen) {
+    ldflags = [
+      "-Wl,-m",
+      "-Wl,armelf",
+    ]
+  }
 }
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn
index 2340c86..3bb69ba 100644
--- a/starboard/evergreen/shared/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -13,69 +13,30 @@
 # limitations under the License.
 
 config("platform_configuration") {
-  ldflags = [
-    "-fuse-ld=lld",
-    "-Wl,--build-id",
-    "-Wl,--gc-sections",
-    "-Wl,-X",
-    "-Wl,-v",
-    "-Wl,-eh-frame-hdr",
-    "-Wl,--fini=__cxa_finalize",
-    "-Wl,-shared",
-    "-Wl,-L$clang_base_path",
-    "-Wl,-L/usr/lib",
-    "-Wl,-L/lib",
-    "-Wl,-u GetEvergreenSabiString",
-  ]
+  configs = [ "//starboard/build/config/modular" ]
+  ldflags = []
+  if (sb_is_evergreen) {
+    ldflags += [
+      "-fuse-ld=lld",
+      "-Wl,--build-id",
+      "-Wl,--gc-sections",
+      "-Wl,-X",
+      "-Wl,-v",
+      "-Wl,-eh-frame-hdr",
+      "-Wl,--fini=__cxa_finalize",
+      "-Wl,-shared",
+      "-Wl,-L$clang_base_path",
+      "-Wl,-L/usr/lib",
+      "-Wl,-L/lib",
+      "-Wl,-u GetEvergreenSabiString",
+    ]
+  }
 
   if (sb_is_evergreen) {
     ldflags += [ "-nostdlib" ]
   }
-  cflags = [
-    "-ffunction-sections",
-    "-fdata-sections",
-    "-fPIC",
-    "-nostdlibinc",
-    "-isystem" + rebase_path("//third_party/llvm-project/libcxxabi/include",
-                             root_build_dir),
-    "-isystem" + rebase_path("//third_party/llvm-project/libunwind/include",
-                             root_build_dir),
-    "-isystem" + rebase_path("//third_party/llvm-project/libcxx/include",
-                             root_build_dir),
-    "-isystem" + rebase_path("//third_party/musl/include", root_build_dir),
-    "-isystem" + rebase_path("//third_party/musl/arch/generic", root_build_dir),
-  ]
-
-  cflags_cc = [
-    "-nostdinc++",
-    "-std=c++17",
-  ]
 
   defines = [
-    # Ensure that the Starboardized __external_threading file is included.
-    "_LIBCPP_HAS_THREAD_API_EXTERNAL",
-
-    # Ensure that only the forward declarations and type definitions are included
-    # in __external_threading.
-    "_LIBCPP_HAS_THREAD_LIBRARY_EXTERNAL",
-
-    # Enable GNU extensions to get prototypes like ffsl.
-    "_GNU_SOURCE=1",
-
-    "_LIBCPP_HAS_MUSL_LIBC",
-    "__STDC_FORMAT_MACROS",  # so that we get PRI*
-
-    # File format of the shared object we will generate.
-    "__ELF__",
-
-    # Use scalar portable implementations instead of Clang/GCC vector
-    # extensions in SkVx.h.
-    "SKNX_NO_SIMD",
-
-    # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some
-    # nasty macros (|Status|, for example) that conflict with Chromium base.
-    "MESA_EGL_NO_X11_HEADERS",
-
     # During Evergreen updates the CRX package is kept in-memory, instead of
     # on the file system, before getting unpacked.
     # TODO(b/158043520): we need to make significant customizations to Chromium
@@ -88,150 +49,21 @@
     "IN_MEMORY_UPDATES",
   ]
 
-  if (is_debug) {
-    cflags += [
-      "-O0",
-      "-frtti",
-      "-g",
-    ]
-  } else if (is_devel) {
-    cflags += [
-      "-O2",
-      "-frtti",
-      "-g",
-    ]
-  } else {
-    cflags += [
-      "-gline-tables-only",
-      "-fno-rtti",
-    ]
-  }
-
-  if (is_clang) {
-    cflags += [
-      "-fcolor-diagnostics",
-
-      # Default visibility to hidden, to enable dead stripping.
-      "-fvisibility=hidden",
-
-      # Warns on switches on enums that cover all enum values but
-      # also contain a default: branch. Chrome is full of that.
-      "-Wno-covered-switch-default",
-
-      # protobuf uses hash_map.
-      "-Wno-deprecated",
-
-      "-fno-exceptions",
-
-      # Enable unwind tables used by libunwind for stack traces.
-      "-funwind-tables",
-
-      # Disable usage of frame pointers.
-      "-fomit-frame-pointer",
-
-      # Don"t warn about the "struct foo f = {0};" initialization pattern.
-      "-Wno-missing-field-initializers",
-
-      # Do not warn for implicit sign conversions.
-      "-Wno-sign-conversion",
-
-      "-fno-strict-aliasing",  # See http://crbug.com/32204
-
-      "-Wno-unnamed-type-template-args",
-
-      # Triggered by the COMPILE_ASSERT macro.
-      "-Wno-unused-local-typedef",
-
-      # Do not warn if a function or variable cannot be implicitly
-      # instantiated.
-      "-Wno-undefined-var-template",
-
-      # Do not warn about an implicit exception spec mismatch.
-      "-Wno-implicit-exception-spec-mismatch",
-
-      # It's OK not to use some input parameters.
-      "-Wno-unused-parameter",
-      "-Wno-conversion",
-      "-Wno-bitwise-op-parentheses",
-      "-Wno-shift-op-parentheses",
-      "-Wno-shorten-64-to-32",
-      "-fno-use-cxa-atexit",
-    ]
-  }
-
-  if (is_clang_16) {
-    cflags += [
-      # Do not remove null pointer checks.
-      "-fno-delete-null-pointer-checks",
-    ]
-  }
-
   if (use_asan) {
-    cflags += [
-      "-fsanitize=address",
-      "-fno-omit-frame-pointer",
-    ]
-
     ldflags += [
       "-fsanitize=address",
 
       # Force linking of the helpers in sanitizer_options.cc
       "-Wl,-u_sanitizer_options_link_helper",
     ]
-
-    defines += [ "ADDRESS_SANITIZER" ]
-
-    if (asan_symbolizer_path != "") {
-      defines += [ "ASAN_SYMBOLIZER_PATH=\"${asan_symbolizer_path}\"" ]
-    }
   } else if (use_tsan) {
-    cflags += [
-      "-fsanitize=thread",
-      "-fno-omit-frame-pointer",
-    ]
-
     ldflags += [ "-fsanitize=thread" ]
-
-    defines += [ "THREAD_SANITIZER" ]
   }
 }
 
-config("speed") {
-  cflags = [ "-O2" ]
-}
-
 config("size") {
-  cflags = [ "-Os" ]
-  if (is_qa || is_gold) {
+  configs = [ "//starboard/build/config/modular:size" ]
+  if (sb_is_evergreen && (is_qa || is_gold)) {
     ldflags = [ "-Wl,--icf=safe" ]
   }
 }
-
-config("pedantic_warnings") {
-  cflags = [
-    "-Wall",
-    "-Wextra",
-    "-Wunreachable-code",
-  ]
-}
-
-config("no_pedantic_warnings") {
-  cflags = [
-    # 'this' pointer cannot be NULL...pointer may be assumed
-    # to always convert to true.
-    "-Wno-undefined-bool-conversion",
-
-    # Skia doesn't use overrides.
-    "-Wno-inconsistent-missing-override",
-
-    # Do not warn for implicit type conversions that may change a value.
-    "-Wno-conversion",
-
-    # shifting a negative signed value is undefined
-    "-Wno-shift-negative-value",
-
-    # Width of bit-field exceeds width of its type- value will be truncated
-    "-Wno-bitfield-width",
-    "-Wno-undefined-var-template",
-  ]
-}
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni
index 4327e1d..98bf65f 100644
--- a/starboard/evergreen/shared/platform_configuration/configuration.gni
+++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -29,13 +29,13 @@
 starboard_level_final_executable_type = "shared_library"
 starboard_level_gtest_target_type = "shared_library"
 
-speed_config_path = "//starboard/evergreen/shared/platform_configuration:speed"
+speed_config_path = "//starboard/build/config/modular:speed"
 size_config_path = "//starboard/evergreen/shared/platform_configuration:size"
 
 pedantic_warnings_config_path =
-    "//starboard/evergreen/shared/platform_configuration:pedantic_warnings"
+    "//starboard/build/config/modular:pedantic_warnings"
 no_pedantic_warnings_config_path =
-    "//starboard/evergreen/shared/platform_configuration:no_pedantic_warnings"
+    "//starboard/build/config/modular:no_pedantic_warnings"
 
 cobalt_licenses_platform = "evergreen"
 
diff --git a/starboard/evergreen/x64/platform_configuration/BUILD.gn b/starboard/evergreen/x64/platform_configuration/BUILD.gn
index d49869b..ead85ee 100644
--- a/starboard/evergreen/x64/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/x64/platform_configuration/BUILD.gn
@@ -12,18 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-config("sabi_flags") {
-  cflags = [
-    "-march=x86-64",
-    "-target",
-    "x86_64-unknown-linux-elf",
-  ]
-}
-
 config("platform_configuration") {
   configs = [
-    "//starboard/build/config/sabi",
-    ":sabi_flags",
+    "//starboard/build/config/modular/x64",
     "//starboard/evergreen/shared/platform_configuration",
   ]
 
@@ -31,6 +22,4 @@
     "-Wl,-m",
     "-Wl,elf_x86_64",
   ]
-  cflags = [ "-isystem" +
-             rebase_path("//third_party/musl/arch/x86_64", root_build_dir) ]
 }
diff --git a/starboard/extension/extension_test.cc b/starboard/extension/extension_test.cc
index d32cc84..874cce1 100644
--- a/starboard/extension/extension_test.cc
+++ b/starboard/extension/extension_test.cc
@@ -21,12 +21,14 @@
 #include "starboard/extension/font.h"
 #include "starboard/extension/free_space.h"
 #include "starboard/extension/graphics.h"
+#include "starboard/extension/ifa.h"
 #include "starboard/extension/installation_manager.h"
 #include "starboard/extension/javascript_cache.h"
 #include "starboard/extension/media_session.h"
 #include "starboard/extension/memory_mapped_file.h"
 #include "starboard/extension/platform_info.h"
 #include "starboard/extension/platform_service.h"
+#include "starboard/extension/time_zone.h"
 #include "starboard/extension/updater_notification.h"
 #include "starboard/extension/url_fetcher_observer.h"
 #include "starboard/system.h"
@@ -438,5 +440,46 @@
       << "Extension struct should be a singleton";
 }
 
+TEST(ExtensionTest, TimeZone) {
+  typedef StarboardExtensionTimeZoneApi ExtensionApi;
+  const char* kExtensionName = kStarboardExtensionTimeZoneName;
+
+  const ExtensionApi* extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  if (!extension_api) {
+    return;
+  }
+
+  EXPECT_STREQ(extension_api->name, kExtensionName);
+  EXPECT_EQ(extension_api->version, 1u);
+  EXPECT_NE(extension_api->SetTimeZone, nullptr);
+
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  EXPECT_EQ(second_extension_api, extension_api)
+      << "Extension struct should be a singleton";
+}
+
+TEST(ExtensionTest, Ifa) {
+  typedef StarboardExtensionIfaApi ExtensionApi;
+  const char* kExtensionName = kStarboardExtensionIfaName;
+
+  const ExtensionApi* extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  if (!extension_api) {
+    return;
+  }
+
+  EXPECT_STREQ(extension_api->name, kExtensionName);
+  EXPECT_EQ(extension_api->version, 1u);
+  EXPECT_NE(extension_api->GetAdvertisingId, nullptr);
+  EXPECT_NE(extension_api->GetLimitAdTracking, nullptr);
+
+  const ExtensionApi* second_extension_api =
+      static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+  EXPECT_EQ(second_extension_api, extension_api)
+      << "Extension struct should be a singleton";
+}
+
 }  // namespace extension
 }  // namespace starboard
diff --git a/starboard/extension/ifa.h b/starboard/extension/ifa.h
new file mode 100644
index 0000000..a207049
--- /dev/null
+++ b/starboard/extension/ifa.h
@@ -0,0 +1,58 @@
+// Copyright 2023 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_EXTENSION_IFA_H_
+#define STARBOARD_EXTENSION_IFA_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kStarboardExtensionIfaName "dev.cobalt.extension.Ifa"
+
+typedef struct StarboardExtensionIfaApi {
+  // Name should be the string |kCobaltExtensionIfaName|.
+  // 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.
+
+  // 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.
+  // In Starboard 14 this the value is retrieved through the system
+  // property `kSbSystemPropertyAdvertisingId` defined in
+  // `starboard/system.h`.
+  bool (*GetAdvertisingId)(char* out_value, int value_length);
+
+  // Limit advertising tracking, treated as boolean. Set to nonzero to indicate
+  // a true value. Corresponds to 'lmt' field.
+  // In Starboard 14 this the value is retrieved through the system
+  // property `kSbSystemPropertyLimitAdTracking` defined in
+  // `starboard/system.h`.
+
+  bool (*GetLimitAdTracking)(char* out_value, int value_length);
+
+} CobaltExtensionIfaApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_EXTENSION_IFA_H_
diff --git a/starboard/extension/time_zone.h b/starboard/extension/time_zone.h
new file mode 100644
index 0000000..bfddcea
--- /dev/null
+++ b/starboard/extension/time_zone.h
@@ -0,0 +1,47 @@
+// Copyright 2023 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_EXTENSION_TIME_ZONE_H_
+#define STARBOARD_EXTENSION_TIME_ZONE_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kStarboardExtensionTimeZoneName "dev.starboard.extension.TimeZone"
+
+typedef struct StarboardExtensionTimeZoneApi {
+  // Name should be the string |kStarboardExtensionSetTimeZoneName|.
+  // 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;
+
+  // Sets the current time zone to the specified time zone name.
+  // Note: This function should not be called with a NULL or empty
+  // string. It does not actually change the system clock, so it
+  // will not affect the time displayed on the system clock or
+  // used by other system processes.
+  bool (*SetTimeZone)(const char* time_zone_name);
+
+} StarboardExtensionTimeZoneApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_EXTENSION_TIME_ZONE_H_
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index 57b7908..4b4e3fa 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -66,6 +66,8 @@
     "//starboard/linux/shared/decode_target_internal.cc",
     "//starboard/linux/shared/decode_target_internal.h",
     "//starboard/linux/shared/decode_target_release.cc",
+    "//starboard/linux/shared/ifa.cc",
+    "//starboard/linux/shared/ifa.h",
     "//starboard/linux/shared/media_is_audio_supported.cc",
     "//starboard/linux/shared/media_is_video_supported.cc",
     "//starboard/linux/shared/netlink.cc",
@@ -80,6 +82,8 @@
     "//starboard/linux/shared/system_get_extensions.cc",
     "//starboard/linux/shared/system_get_path.cc",
     "//starboard/linux/shared/system_has_capability.cc",
+    "//starboard/linux/shared/time_zone.cc",
+    "//starboard/linux/shared/time_zone.h",
     "//starboard/shared/alsa/alsa_audio_sink_type.cc",
     "//starboard/shared/alsa/alsa_audio_sink_type.h",
     "//starboard/shared/alsa/alsa_util.cc",
@@ -133,6 +137,7 @@
     "//starboard/shared/linux/thread_get_id.cc",
     "//starboard/shared/linux/thread_get_name.cc",
     "//starboard/shared/linux/thread_set_name.cc",
+    "//starboard/shared/linux/time_zone_get_name.cc",
     "//starboard/shared/nouser/user_get_current.cc",
     "//starboard/shared/nouser/user_get_property.cc",
     "//starboard/shared/nouser/user_get_signed_in.cc",
@@ -217,7 +222,6 @@
     "//starboard/shared/posix/time_get_now.cc",
     "//starboard/shared/posix/time_is_time_thread_now_supported.cc",
     "//starboard/shared/posix/time_zone_get_current.cc",
-    "//starboard/shared/posix/time_zone_get_name.cc",
     "//starboard/shared/pthread/condition_variable_broadcast.cc",
     "//starboard/shared/pthread/condition_variable_create.cc",
     "//starboard/shared/pthread/condition_variable_destroy.cc",
diff --git a/starboard/linux/shared/ifa.cc b/starboard/linux/shared/ifa.cc
new file mode 100644
index 0000000..21b17dc
--- /dev/null
+++ b/starboard/linux/shared/ifa.cc
@@ -0,0 +1,65 @@
+// Copyright 2023 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/linux/shared/ifa.h"
+
+#include "starboard/extension/ifa.h"
+
+#include "starboard/common/string.h"
+#include "starboard/shared/environment.h"
+
+namespace starboard {
+namespace shared {
+
+namespace {
+
+bool CopyStringAndTestIfSuccess(char* out_value,
+                                int value_length,
+                                const char* from_value) {
+  if (strlen(from_value) + 1 > value_length)
+    return false;
+  starboard::strlcpy(out_value, from_value, value_length);
+  return true;
+}
+
+// Definitions of any functions included as components in the extension
+// are added here.
+
+bool GetAdvertisingId(char* out_value, int value_length) {
+  return CopyStringAndTestIfSuccess(
+      out_value, value_length,
+      starboard::GetEnvironment("COBALT_ADVERTISING_ID").c_str());
+}
+
+bool GetLimitAdTracking(char* out_value, int value_length) {
+  return CopyStringAndTestIfSuccess(
+      out_value, value_length,
+      GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str());
+}
+
+const StarboardExtensionIfaApi kIfaApi = {
+    kStarboardExtensionIfaName,
+    1,  // API version that's implemented.
+    &GetAdvertisingId,
+    &GetLimitAdTracking,
+};
+
+}  // namespace
+
+const void* GetIfaApi() {
+  return &kIfaApi;
+}
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/linux/shared/ifa.h
similarity index 61%
rename from starboard/shared/posix/time_zone_get_name.cc
rename to starboard/linux/shared/ifa.h
index 5a1e013..fbe61ab 100644
--- a/starboard/shared/posix/time_zone_get_name.cc
+++ b/starboard/linux/shared/ifa.h
@@ -1,4 +1,4 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+// Copyright 2023 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.
@@ -12,13 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/time_zone.h"
+#ifndef STARBOARD_LINUX_SHARED_IFA_H_
+#define STARBOARD_LINUX_SHARED_IFA_H_
 
-#include <time.h>
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
 
-const char* SbTimeZoneGetName() {
-  // TODO: Using tzname assumes that tzset() has been called at some
-  // point. That should happen as part of Starboard's main loop initialization,
-  // but that doesn't exist yet.
-  return tzname[0];
-}
+const void* GetIfaApi();
+
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_LINUX_SHARED_IFA_H_
diff --git a/starboard/linux/shared/system_get_extensions.cc b/starboard/linux/shared/system_get_extensions.cc
index 4a0b280..efc9169 100644
--- a/starboard/linux/shared/system_get_extensions.cc
+++ b/starboard/linux/shared/system_get_extensions.cc
@@ -20,9 +20,13 @@
 #include "starboard/extension/demuxer.h"
 #include "starboard/extension/enhanced_audio.h"
 #include "starboard/extension/free_space.h"
+#include "starboard/extension/ifa.h"
 #include "starboard/extension/memory_mapped_file.h"
 #include "starboard/extension/platform_service.h"
+#include "starboard/extension/time_zone.h"
+#include "starboard/linux/shared/ifa.h"
 #include "starboard/linux/shared/soft_mic_platform_service.h"
+#include "starboard/linux/shared/time_zone.h"
 #include "starboard/shared/enhanced_audio/enhanced_audio.h"
 #include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
 #include "starboard/shared/posix/free_space.h"
@@ -74,5 +78,13 @@
     return use_ffmpeg_demuxer ? starboard::shared::ffmpeg::GetFFmpegDemuxerApi()
                               : NULL;
   }
+  if (strcmp(name, kStarboardExtensionTimeZoneName) == 0) {
+    return starboard::shared::GetTimeZoneApi();
+  }
+#if SB_API_VERSION < 14
+  if (strcmp(name, kStarboardExtensionIfaName) == 0) {
+    return starboard::shared::GetIfaApi();
+  }
+#endif  // SB_API_VERSION < 14
   return NULL;
 }
diff --git a/starboard/linux/shared/system_get_path.cc b/starboard/linux/shared/system_get_path.cc
index 0f70002..999a016 100644
--- a/starboard/linux/shared/system_get_path.cc
+++ b/starboard/linux/shared/system_get_path.cc
@@ -113,6 +113,19 @@
 }
 #endif
 
+bool GetParentDirectory(char* out_path) {
+  if (!out_path) {
+    return false;
+  }
+  char* last_slash = const_cast<char*>(strrchr(out_path, '/'));
+  if (!last_slash) {
+    return false;
+  }
+
+  *last_slash = '\0';
+  return true;
+}
+
 // Places up to |path_size| - 1 characters of the path to the directory
 // containing the current executable in |out_path|, ensuring it is
 // NULL-terminated. Returns success status. The result being greater than
@@ -122,14 +135,7 @@
   if (!GetExecutablePath(out_path, path_size)) {
     return false;
   }
-
-  char* last_slash = const_cast<char*>(strrchr(out_path, '/'));
-  if (!last_slash) {
-    return false;
-  }
-
-  *last_slash = '\0';
-  return true;
+  return GetParentDirectory(out_path);
 }
 
 // Gets only the name portion of the current executable.
@@ -168,6 +174,11 @@
   if (!GetExecutableDirectory(out_path, path_size)) {
     return false;
   }
+#ifdef USE_COMMON_CONTENT_DIR
+  if (!GetParentDirectory(out_path)) {
+    return false;
+  }
+#endif
   if (starboard::strlcat(out_path, "/content", path_size) >= path_size) {
     return false;
   }
diff --git a/starboard/linux/shared/time_zone.cc b/starboard/linux/shared/time_zone.cc
new file mode 100644
index 0000000..30919af
--- /dev/null
+++ b/starboard/linux/shared/time_zone.cc
@@ -0,0 +1,60 @@
+// Copyright 2023 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/linux/shared/time_zone.h"
+
+#include "starboard/extension/time_zone.h"
+
+#include <stdlib.h>
+#include <time.h>
+#include <cstring>
+
+#include "starboard/common/log.h"
+
+namespace starboard {
+namespace shared {
+
+namespace {
+
+// Definitions of any functions included as components in the extension
+// are added here.
+
+bool SetTimeZone(const char* time_zone_name) {
+  if (time_zone_name == nullptr || strlen(time_zone_name) == 0) {
+    SB_LOG(ERROR) << "Set time zone failed!";
+    SB_LOG(ERROR) << "Time zone name can't be null or empty string.";
+    return false;
+  }
+  if (setenv("TZ", time_zone_name, 1) != 0) {
+    SB_LOG(WARNING) << "Set time zone failed!";
+    return false;
+  }
+  tzset();
+  return true;
+}
+
+const StarboardExtensionTimeZoneApi kTimeZoneApi = {
+    kStarboardExtensionTimeZoneName,
+    1,  // API version that's implemented.
+    &SetTimeZone,
+};
+
+}  // namespace
+
+const void* GetTimeZoneApi() {
+  return &kTimeZoneApi;
+}
+
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/linux/shared/time_zone.h b/starboard/linux/shared/time_zone.h
new file mode 100644
index 0000000..6f063b0
--- /dev/null
+++ b/starboard/linux/shared/time_zone.h
@@ -0,0 +1,27 @@
+// Copyright 2023 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_LINUX_SHARED_TIME_ZONE_H_
+#define STARBOARD_LINUX_SHARED_TIME_ZONE_H_
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+const void* GetTimeZoneApi();
+
+}  // namespace shared
+}  // namespace starboard
+
+#endif  // STARBOARD_LINUX_SHARED_TIME_ZONE_H_
diff --git a/starboard/linux/x64x11/main.cc b/starboard/linux/x64x11/main.cc
index 5cfd197..1929336 100644
--- a/starboard/linux/x64x11/main.cc
+++ b/starboard/linux/x64x11/main.cc
@@ -50,7 +50,6 @@
           : starboard::common::GetCACertificatesPath(evergreen_content_path);
   if (ca_certificates_path.empty()) {
     SB_LOG(ERROR) << "Failed to get CA certificates path";
-    return 1;
   }
 
 #if !SB_IS(MODULAR)
diff --git a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
index ab9cf36..cbdd8af 100644
--- a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
+++ b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
@@ -16,6 +16,7 @@
   if (current_toolchain == default_toolchain && sb_is_modular &&
       !sb_is_evergreen) {
     configs = [ "//starboard/evergreen/x64/platform_configuration" ]
+    ldflags = [ "-Wl,--gc-sections" ]
   } else {
     configs = [
       ":libraries",
diff --git a/starboard/linux/x64x11/toolchain/BUILD.gn b/starboard/linux/x64x11/toolchain/BUILD.gn
index bcee641..79b7991 100644
--- a/starboard/linux/x64x11/toolchain/BUILD.gn
+++ b/starboard/linux/x64x11/toolchain/BUILD.gn
@@ -13,12 +13,19 @@
 # limitations under the License.
 
 import("//build/config/clang/clang.gni")
+import("//starboard/build/toolchain/cobalt_toolchains.gni")
 import("//starboard/shared/toolchain/overridable_gcc_toolchain.gni")
 
 overridable_clang_toolchain("starboard") {
   clang_base_path = clang_base_path
 }
 
+cobalt_clang_toolchain("cobalt") {
+  variables = {
+    native_linker_path = "$clang_base_path/bin/clang++"
+  }
+}
+
 overridable_clang_toolchain("target") {
   clang_base_path = clang_base_path
 }
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index e0384a1..053c0c2 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("//starboard/build/config/os_definitions.gni")
+
 _common_loader_app_sources = [
   "loader_app.cc",
   "loader_app_switches.cc",
@@ -247,20 +249,23 @@
   ]
 }
 
-target(gtest_target_type, "installation_manager_test") {
-  testonly = true
-  sources = [
-    "//starboard/common/test_main.cc",
-    "installation_manager_test.cc",
-    "pending_restart_test.cc",
-  ]
-  deps = [
-    ":installation_manager",
-    ":installation_store_proto",
-    ":pending_restart",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms.
+if (!is_host_win) {
+  target(gtest_target_type, "installation_manager_test") {
+    testonly = true
+    sources = [
+      "//starboard/common/test_main.cc",
+      "installation_manager_test.cc",
+      "pending_restart_test.cc",
+    ]
+    deps = [
+      ":installation_manager",
+      ":installation_store_proto",
+      ":pending_restart",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
 
 static_library("slot_management") {
@@ -285,22 +290,25 @@
   }
 }
 
-target(gtest_target_type, "slot_management_test") {
-  testonly = true
-  sources = [
-    "//starboard/common/test_main.cc",
-    "slot_management_test.cc",
-  ]
-  deps = [
-    ":app_key_files",
-    ":drain_file",
-    ":installation_manager",
-    ":installation_store_proto",
-    ":slot_management",
-    "//starboard/elf_loader:sabi_string",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms.
+if (!is_host_win) {
+  target(gtest_target_type, "slot_management_test") {
+    testonly = true
+    sources = [
+      "//starboard/common/test_main.cc",
+      "slot_management_test.cc",
+    ]
+    deps = [
+      ":app_key_files",
+      ":drain_file",
+      ":installation_manager",
+      ":installation_store_proto",
+      ":slot_management",
+      "//starboard/elf_loader:sabi_string",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
 
 static_library("pending_restart") {
@@ -332,15 +340,18 @@
   deps = [ "//starboard" ]
 }
 
-target(gtest_target_type, "reset_evergreen_update_test") {
-  testonly = true
-  sources = [
-    "//starboard/common/test_main.cc",
-    "reset_evergreen_update_test.cc",
-  ]
-  deps = [
-    ":reset_evergreen_update",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+# TODO: b/309493306 - Stop building evergreen targets for all non-evergreen platforms.
+if (!is_host_win) {
+  target(gtest_target_type, "reset_evergreen_update_test") {
+    testonly = true
+    sources = [
+      "//starboard/common/test_main.cc",
+      "reset_evergreen_update_test.cc",
+    ]
+    deps = [
+      ":reset_evergreen_update",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
diff --git a/starboard/loader_app/loader_app.cc b/starboard/loader_app/loader_app.cc
index 95f7ece..93cf9f4 100644
--- a/starboard/loader_app/loader_app.cc
+++ b/starboard/loader_app/loader_app.cc
@@ -143,7 +143,7 @@
   GetEvergreenInfo(&evergreen_info);
   if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad(
           evergreen_info)) {
-    SB_LOG(ERROR) << "Could not send Cobalt library information into Crashapd.";
+    SB_LOG(ERROR) << "Could not send Cobalt library information into Crashpad.";
   } else {
     SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad.";
   }
diff --git a/starboard/loader_app/slot_management.cc b/starboard/loader_app/slot_management.cc
index fc5549a..8b1af98 100644
--- a/starboard/loader_app/slot_management.cc
+++ b/starboard/loader_app/slot_management.cc
@@ -289,7 +289,7 @@
     if (!third_party::crashpad::wrapper::AddEvergreenInfoToCrashpad(
             evergreen_info)) {
       SB_LOG(ERROR)
-          << "Could not send Cobalt library information into Crashapd.";
+          << "Could not send Cobalt library information into Crashpad.";
     } else {
       SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad.";
     }
diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn
index 664751b..3f314cf 100644
--- a/starboard/nplb/BUILD.gn
+++ b/starboard/nplb/BUILD.gn
@@ -271,7 +271,7 @@
     cflags = [ "-Wno-enum-constexpr-conversion" ]
   }
 
-  #  TODO b/296238576 Add these tests for windows based platform modular builds
+  #  TODO: b/297808555 - Add these tests for windows based platform modular builds.
   if (sb_is_modular && !sb_is_evergreen && is_host_win) {
     sources -= [
       "maximum_player_configuration_explorer.cc",
@@ -279,6 +279,7 @@
       "maximum_player_configuration_explorer_test.cc",
       "media_buffer_test.cc",
       "media_set_audio_write_duration_test.cc",
+      "multiple_player_test.cc",
       "player_create_test.cc",
       "player_creation_param_helpers.cc",
       "player_creation_param_helpers.h",
diff --git a/starboard/nplb/media_configuration_test.cc b/starboard/nplb/media_configuration_test.cc
index 07c5937..432b12a 100644
--- a/starboard/nplb/media_configuration_test.cc
+++ b/starboard/nplb/media_configuration_test.cc
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "starboard/media.h"
+
 #include "starboard/nplb/performance_helpers.h"
+#include "starboard/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace starboard {
@@ -25,9 +27,13 @@
 
   const int count_audio_output = SbMediaGetAudioOutputCount();
   for (int i = 0; i < count_audio_output; ++i) {
+    constexpr int kNumberOfCalls = 100;
+    constexpr SbTime kMaxAverageTimePerCall = 500;
+
     SbMediaAudioConfiguration configuration;
-    TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetAudioConfiguration, i,
-                                   &configuration);
+    TEST_PERF_FUNCWITHARGS_EXPLICIT(kNumberOfCalls, kMaxAverageTimePerCall,
+                                    SbMediaGetAudioConfiguration, i,
+                                    &configuration);
   }
 }
 
diff --git a/starboard/nplb/performance_helpers.h b/starboard/nplb/performance_helpers.h
index 62639df..7a9b538 100644
--- a/starboard/nplb/performance_helpers.h
+++ b/starboard/nplb/performance_helpers.h
@@ -38,11 +38,16 @@
   // Measure time pre calls to |f|.
   const SbTimeMonotonic time_start = SbTimeGetMonotonicNow();
 
+  SbLogPriority initial_log_level = starboard::logging::GetMinLogLevel();
+  starboard::logging::SetMinLogLevel(kSbLogPriorityFatal);
+
   // Call |f| |count_calls| times.
   for (int i = 0; i < count_calls; ++i) {
     f(args...);
   }
 
+  starboard::logging::SetMinLogLevel(initial_log_level);
+
   // Measure time post calls to |f|.
   const SbTimeMonotonic time_last = SbTimeGetMonotonicNow();
   const SbTimeMonotonic time_delta = time_last - time_start;
diff --git a/starboard/nplb/player_write_sample_test.cc b/starboard/nplb/player_write_sample_test.cc
index 414a4ea..5bc1653 100644
--- a/starboard/nplb/player_write_sample_test.cc
+++ b/starboard/nplb/player_write_sample_test.cc
@@ -263,11 +263,15 @@
   SbTime current_time_offset = 0;
   int num_of_buffers_per_write =
       player_fixture.ConvertDurationToAudioBufferCount(kDurationPerWrite);
+  int count = 0;
   while (current_time_offset < kDurationToPlay) {
+    const SbTime kDurationToDiscard =
+        count % 2 == 0 ? kSbTimeSecond : kSbTimeMax;
+    count++;
     // Discard from front.
     for (int i = 0; i < kNumberOfBuffersToDiscard; i++) {
       samples.AddAudioSamples(written_buffer_index, 1, current_time_offset,
-                              kSbTimeSecond, 0);
+                              kDurationToDiscard, 0);
     }
 
     samples.AddAudioSamples(written_buffer_index, num_of_buffers_per_write);
@@ -277,7 +281,7 @@
     // Discard from back.
     for (int i = 0; i < kNumberOfBuffersToDiscard; i++) {
       samples.AddAudioSamples(written_buffer_index, 1, current_time_offset, 0,
-                              kSbTimeSecond);
+                              kDurationToDiscard);
     }
   }
   samples.AddAudioEOS();
diff --git a/starboard/nplb/time_zone_get_current_test.cc b/starboard/nplb/time_zone_get_current_test.cc
index 0c136d0..0c2b3b8 100644
--- a/starboard/nplb/time_zone_get_current_test.cc
+++ b/starboard/nplb/time_zone_get_current_test.cc
@@ -12,7 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "starboard/extension/time_zone.h"
 #include "starboard/nplb/time_constants.h"
+#include "starboard/system.h"
 #include "starboard/time_zone.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -34,9 +36,64 @@
   // ... and +24 hours from the Prime Meridian, inclusive
   EXPECT_LE(zone, 24 * 60);
 
-  if (zone == 0) {
-    SB_LOG(WARNING) << "SbTimeZoneGetCurrent() returns 0. This is only correct "
-                       "if the current time zone is the same as UTC";
+  static auto const* time_zone_extension =
+      static_cast<const StarboardExtensionTimeZoneApi*>(
+          SbSystemGetExtension(kStarboardExtensionTimeZoneName));
+  if (time_zone_extension) {
+    ASSERT_STREQ(time_zone_extension->name, kStarboardExtensionTimeZoneName);
+    ASSERT_EQ(time_zone_extension->version, 1u);
+    time_zone_extension->SetTimeZone("UTC");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 0);
+
+    // Atlantic time zone, UTC−04:00
+    time_zone_extension->SetTimeZone("America/Puerto_Rico");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 240);
+
+    // Eastern time zone, UTC−05:00
+    time_zone_extension->SetTimeZone("America/New_York");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 300);
+
+    time_zone_extension->SetTimeZone("US/Eastern");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 300);
+
+    // Central time zone, UTC−06:00
+    time_zone_extension->SetTimeZone("America/Chicago");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 360);
+
+    // Mountain time zone, UTC−07:00
+    time_zone_extension->SetTimeZone("US/Mountain");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 420);
+
+    // Pacific time zone, UTC-08:00
+    time_zone_extension->SetTimeZone("US/Pacific");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 480);
+
+    // Alaska time zone, UTC-09:00
+    time_zone_extension->SetTimeZone("US/Alaska");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 540);
+
+    // Hawaii-Aleutian time zone, UTC-10:00
+    time_zone_extension->SetTimeZone("Pacific/Honolulu");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 600);
+
+    // American Samoa time zone, UTC-11:00
+    time_zone_extension->SetTimeZone("US/Samoa");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, 660);
+
+    // American Samoa time zone, UTC+10:00
+    time_zone_extension->SetTimeZone("Pacific/Guam");
+    zone = SbTimeZoneGetCurrent();
+    EXPECT_EQ(zone, -600);
   }
 }
 
diff --git a/starboard/nplb/time_zone_get_name_test.cc b/starboard/nplb/time_zone_get_name_test.cc
index a3e013c..318cbfc 100644
--- a/starboard/nplb/time_zone_get_name_test.cc
+++ b/starboard/nplb/time_zone_get_name_test.cc
@@ -12,10 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <string.h>
+
+#include "starboard/common/log.h"
+#include "starboard/extension/time_zone.h"
 #include "starboard/nplb/time_constants.h"
+#include "starboard/system.h"
 #include "starboard/time_zone.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using testing::AnyOf;
+using testing::MatchesRegex;
+
 namespace starboard {
 namespace nplb {
 namespace {
@@ -41,6 +50,27 @@
   // ":Pacific/Kiritimati" is the western-most timezone at UTC+14.
 }
 
+TEST(SbTimeZoneGetNameTest, IsIANAFormat) {
+  const char* name = SbTimeZoneGetName();
+  SB_LOG(INFO) << "time zone name: " << name;
+  char cpy[100];
+  snprintf(cpy, sizeof(cpy), "%s", name);
+  char* continent = strtok(cpy, "/");
+  // The time zone ID starts with a Continent or Ocean name.
+  EXPECT_THAT(
+      continent,
+      testing::AnyOf(std::string("Asia"), std::string("America"),
+                     std::string("Africa"), std::string("Europe"),
+                     std::string("Australia"), std::string("Pacific"),
+                     std::string("Atlantic"), std::string("Antarctica"),
+                     // time zone can be "Etc/UTC" if unset(such as on
+                     // CI builders), shouldn't happen in production.
+                     // TODO(b/304351956): Remove Etc after fixing builders.
+                     std::string("Indian"), std::string("Etc")));
+  char* city = strtok(NULL, "/");
+  EXPECT_TRUE(strlen(city) != 0);
+}
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/starboard/raspi/2/BUILD.gn b/starboard/raspi/2/BUILD.gn
index 92dbb16..0b84634 100644
--- a/starboard/raspi/2/BUILD.gn
+++ b/starboard/raspi/2/BUILD.gn
@@ -22,3 +22,12 @@
   configs += [ "//starboard/build/config:starboard_implementation" ]
   public_deps = [ "//starboard/raspi/shared:starboard_platform" ]
 }
+
+if (sb_is_modular) {
+  static_library("starboard_platform_with_main") {
+    check_includes = false
+    sources = [ "//starboard/raspi/shared/main.cc" ]
+    configs += [ "//starboard/build/config:starboard_implementation" ]
+    public_deps = [ ":starboard_platform" ]
+  }
+}
diff --git a/starboard/raspi/2/args.gn b/starboard/raspi/2/args.gn
index 0b190f8..9c29777 100644
--- a/starboard/raspi/2/args.gn
+++ b/starboard/raspi/2/args.gn
@@ -15,4 +15,5 @@
 target_platform = "raspi-2"
 target_os = "linux"
 target_cpu = "arm"
-is_clang = false
+build_with_separate_cobalt_toolchain = true
+use_asan = false
diff --git a/starboard/raspi/2/platform_configuration/BUILD.gn b/starboard/raspi/2/platform_configuration/BUILD.gn
index ef03b60..b1d0422 100644
--- a/starboard/raspi/2/platform_configuration/BUILD.gn
+++ b/starboard/raspi/2/platform_configuration/BUILD.gn
@@ -17,11 +17,16 @@
     "//starboard/build/config/sabi",
     "//starboard/raspi/shared/platform_configuration",
   ]
-  cflags = [
-    "-march=armv7-a",
-    "-mfpu=neon-vfpv4",
-    "-mfloat-abi=hard",
-    "-mcpu=cortex-a8",
-    "-mtune=cortex-a8",
-  ]
+  if (current_toolchain != default_toolchain || !sb_is_modular) {
+    cflags = [
+      "-march=armv7-a",
+      "-mfpu=neon-vfpv4",
+      "-mfloat-abi=hard",
+      "-mcpu=cortex-a8",
+      "-mtune=cortex-a8",
+    ]
+  }
+  if (sb_is_modular && !sb_is_evergreen) {
+    defines = [ "USE_COMMON_CONTENT_DIR" ]
+  }
 }
diff --git a/starboard/raspi/2/platform_configuration/configuration.gni b/starboard/raspi/2/platform_configuration/configuration.gni
index 3b23661..ad65232 100644
--- a/starboard/raspi/2/platform_configuration/configuration.gni
+++ b/starboard/raspi/2/platform_configuration/configuration.gni
@@ -13,10 +13,14 @@
 # limitations under the License.
 
 import("//starboard/raspi/shared/platform_configuration/configuration.gni")
+if (current_toolchain != default_toolchain ||
+    !build_with_separate_cobalt_toolchain) {
+  arm_float_abi = "hard"
 
-arm_float_abi = "hard"
+  sb_evergreen_compatible_use_libunwind = true
+  sb_is_evergreen_compatible = true
+}
 
-sb_evergreen_compatible_use_libunwind = true
-sb_is_evergreen_compatible = true
-
-separate_install_targets_for_bundling = true
+if (!build_with_separate_cobalt_toolchain) {
+  separate_install_targets_for_bundling = true
+}
diff --git a/starboard/raspi/2/skia/BUILD.gn b/starboard/raspi/2/skia/BUILD.gn
index bcb5b92..80f549c 100644
--- a/starboard/raspi/2/skia/BUILD.gn
+++ b/starboard/raspi/2/skia/BUILD.gn
@@ -22,3 +22,9 @@
   configs += [ "//starboard/build/config:starboard_implementation" ]
   public_deps = [ "//starboard/raspi/shared:starboard_platform" ]
 }
+
+if (build_with_separate_cobalt_toolchain) {
+  group("starboard_platform_with_main") {
+    deps = [ "//starboard/raspi/2:starboard_platform_with_main" ]
+  }
+}
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/raspi/2/skia/starboard_loader.cc
similarity index 61%
copy from starboard/shared/posix/time_zone_get_name.cc
copy to starboard/raspi/2/skia/starboard_loader.cc
index 5a1e013..065bfaa 100644
--- a/starboard/shared/posix/time_zone_get_name.cc
+++ b/starboard/raspi/2/skia/starboard_loader.cc
@@ -1,4 +1,4 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+// Copyright 2023 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.
@@ -12,13 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/time_zone.h"
+#include "starboard/event.h"
 
-#include <time.h>
-
-const char* SbTimeZoneGetName() {
-  // TODO: Using tzname assumes that tzset() has been called at some
-  // point. That should happen as part of Starboard's main loop initialization,
-  // but that doesn't exist yet.
-  return tzname[0];
+int main(int argc, char** argv) {
+  return SbRunStarboardMain(argc, argv, SbEventHandle);
 }
diff --git a/starboard/raspi/2/skia/toolchain/BUILD.gn b/starboard/raspi/2/skia/toolchain/BUILD.gn
index 569a254..8524670 100644
--- a/starboard/raspi/2/skia/toolchain/BUILD.gn
+++ b/starboard/raspi/2/skia/toolchain/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("//build/config/clang/clang.gni")
 import("//build/toolchain/gcc_toolchain.gni")
+import("//starboard/build/toolchain/cobalt_toolchains.gni")
 import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni")
 
 gcc_toolchain("target") {
@@ -23,7 +24,27 @@
 
   ar = gcc_toolchain_ar
 
-  tail_lib_dependencies = "-l:libpthread.so.0"
+  tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2"
+
+  toolchain_args = {
+    is_clang = false
+  }
+}
+
+cobalt_clang_toolchain("cobalt") {
+  variables = {
+    native_linker_path = gcc_toolchain_cxx
+  }
+}
+
+gcc_toolchain("starboard") {
+  cc = gcc_toolchain_cc
+  cxx = gcc_toolchain_cxx
+  ld = cxx
+
+  ar = gcc_toolchain_ar
+
+  tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2"
 
   toolchain_args = {
     is_clang = false
diff --git a/starboard/shared/posix/time_zone_get_name.cc b/starboard/raspi/2/starboard_loader.cc
similarity index 61%
copy from starboard/shared/posix/time_zone_get_name.cc
copy to starboard/raspi/2/starboard_loader.cc
index 5a1e013..065bfaa 100644
--- a/starboard/shared/posix/time_zone_get_name.cc
+++ b/starboard/raspi/2/starboard_loader.cc
@@ -1,4 +1,4 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+// Copyright 2023 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.
@@ -12,13 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/time_zone.h"
+#include "starboard/event.h"
 
-#include <time.h>
-
-const char* SbTimeZoneGetName() {
-  // TODO: Using tzname assumes that tzset() has been called at some
-  // point. That should happen as part of Starboard's main loop initialization,
-  // but that doesn't exist yet.
-  return tzname[0];
+int main(int argc, char** argv) {
+  return SbRunStarboardMain(argc, argv, SbEventHandle);
 }
diff --git a/starboard/raspi/2/toolchain/BUILD.gn b/starboard/raspi/2/toolchain/BUILD.gn
index dcce760..2c49b20 100644
--- a/starboard/raspi/2/toolchain/BUILD.gn
+++ b/starboard/raspi/2/toolchain/BUILD.gn
@@ -13,8 +13,30 @@
 # limitations under the License.
 
 import("//build/toolchain/gcc_toolchain.gni")
+import("//starboard/build/toolchain/cobalt_toolchains.gni")
 import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni")
 
+cobalt_clang_toolchain("cobalt") {
+  variables = {
+    native_linker_path = gcc_toolchain_cxx
+  }
+}
+
+gcc_toolchain("starboard") {
+  cc = gcc_toolchain_cc
+  cxx = gcc_toolchain_cxx
+  ld = cxx
+
+  # We use whatever 'ar' resolves to.
+  ar = gcc_toolchain_ar
+
+  tail_lib_dependencies = "-l:libpthread.so.0 -l:libdl.so.2"
+
+  toolchain_args = {
+    is_clang = false
+  }
+}
+
 gcc_toolchain("target") {
   cc = gcc_toolchain_cc
   cxx = gcc_toolchain_cxx
diff --git a/starboard/raspi/shared/BUILD.gn b/starboard/raspi/shared/BUILD.gn
index a81b614..39ca20e 100644
--- a/starboard/raspi/shared/BUILD.gn
+++ b/starboard/raspi/shared/BUILD.gn
@@ -35,13 +35,14 @@
     "//starboard/linux/shared/system_get_connection_type.cc",
     "//starboard/linux/shared/system_get_path.cc",
     "//starboard/linux/shared/system_has_capability.cc",
+    "//starboard/linux/shared/time_zone.cc",
+    "//starboard/linux/shared/time_zone.h",
     "//starboard/raspi/shared/application_dispmanx.cc",
     "//starboard/raspi/shared/audio_sink_type_dispatcher.cc",
     "//starboard/raspi/shared/dispmanx_util.cc",
     "//starboard/raspi/shared/dispmanx_util.h",
     "//starboard/raspi/shared/graphics.cc",
     "//starboard/raspi/shared/graphics.h",
-    "//starboard/raspi/shared/main.cc",
     "//starboard/raspi/shared/media_is_video_supported.cc",
     "//starboard/raspi/shared/open_max/decode_target_create.cc",
     "//starboard/raspi/shared/open_max/decode_target_create.h",
@@ -112,6 +113,7 @@
     "//starboard/shared/linux/thread_get_id.cc",
     "//starboard/shared/linux/thread_get_name.cc",
     "//starboard/shared/linux/thread_set_name.cc",
+    "//starboard/shared/linux/time_zone_get_name.cc",
     "//starboard/shared/nouser/user_get_current.cc",
     "//starboard/shared/nouser/user_get_property.cc",
     "//starboard/shared/nouser/user_get_signed_in.cc",
@@ -188,7 +190,6 @@
     "//starboard/shared/posix/time_get_now.cc",
     "//starboard/shared/posix/time_is_time_thread_now_supported.cc",
     "//starboard/shared/posix/time_zone_get_current.cc",
-    "//starboard/shared/posix/time_zone_get_name.cc",
     "//starboard/shared/pthread/condition_variable_broadcast.cc",
     "//starboard/shared/pthread/condition_variable_create.cc",
     "//starboard/shared/pthread/condition_variable_destroy.cc",
@@ -333,10 +334,15 @@
     "//starboard/shared/stub/window_set_on_screen_keyboard_keep_focus.cc",
     "//starboard/shared/stub/window_show_on_screen_keyboard.cc",
     "//starboard/shared/stub/window_update_on_screen_keyboard_suggestions.cc",
+    "run_starboard_main.cc",
   ]
 
   sources += common_player_sources
 
+  if (!sb_is_modular) {
+    sources += [ "//starboard/raspi/shared/main.cc" ]
+  }
+
   configs += [ "//starboard/build/config:starboard_implementation" ]
 
   public_deps = [
@@ -378,17 +384,20 @@
   public_deps = [ "//starboard/elf_loader:evergreen_info" ]
 }
 
-target(gtest_target_type, "starboard_platform_tests") {
-  testonly = true
+if (current_toolchain == starboard_toolchain) {
+  target(starboard_level_gtest_target_type, "starboard_platform_tests") {
+    build_loader = false
+    testonly = true
 
-  sources = player_tests_sources + [ "//starboard/common/test_main.cc" ]
+    sources = player_tests_sources + [ "//starboard/common/test_main.cc" ]
 
-  configs += [ "//starboard/build/config:starboard_implementation" ]
+    configs += [ "//starboard/build/config:starboard_implementation" ]
 
-  deps = [
-    "//starboard",
-    "//starboard/shared/starboard/player/filter/testing:test_util",
-    "//testing/gmock",
-    "//testing/gtest",
-  ]
+    deps = [
+      "//starboard:starboard_with_main",
+      "//starboard/shared/starboard/player/filter/testing:test_util",
+      "//testing/gmock",
+      "//testing/gtest",
+    ]
+  }
 }
diff --git a/starboard/raspi/shared/install_target.gni b/starboard/raspi/shared/install_target.gni
index 90ac725..7413840 100644
--- a/starboard/raspi/shared/install_target.gni
+++ b/starboard/raspi/shared/install_target.gni
@@ -21,7 +21,7 @@
   # subdir and install content subdir.
   if (invoker.type == "executable") {
     # install_subdir = "bin"
-    install_subdir = ""
+    install_subdir = installable_target_name
     source_name = installable_target_name
   } else if (invoker.type == "shared_library") {
     install_subdir = "lib"
@@ -29,28 +29,37 @@
   } else {
     assert(false, "You can only install an executable or shared library.")
   }
+  output = "$sb_install_output_dir/$install_subdir/$source_name"
+  input = "$root_out_dir/$source_name"
 
-  action(target_name) {
-    forward_variables_from(invoker, [ "testonly" ])
+  if (is_gold || cobalt_fastbuild) {
+    action(target_name) {
+      forward_variables_from(invoker, [ "testonly" ])
 
-    script = "//starboard/build/run_bash.py"
+      script = "//starboard/build/run_bash.py"
 
-    strip_executable = gcc_toolchain_strip
-    inputs = [
-      strip_executable,
-      "$root_out_dir/$source_name",
-    ]
+      strip_executable = gcc_toolchain_strip
+      inputs = [ input ]
 
-    deps = invoker.deps
-    deps += [ ":$installable_target_name" ]
+      deps = invoker.deps
+      deps += [ invoker.installable_target_dep ]
 
-    outputs = [ "$sb_install_output_dir/$install_subdir/$installable_target_name/$source_name" ]
+      outputs = [ output ]
 
-    args = [
-      rebase_path(strip_executable, root_build_dir),
-      "-o",
-      rebase_path(outputs[0], root_out_dir),
-      rebase_path("$root_out_dir/$source_name", root_out_dir),
-    ]
+      args = [
+        rebase_path(strip_executable, root_build_dir),
+        "-o",
+        rebase_path(outputs[0], root_build_dir),
+        rebase_path(inputs[0], root_build_dir),
+      ]
+    }
+  } else {
+    copy(target_name) {
+      forward_variables_from(invoker, [ "testonly" ])
+      sources = [ input ]
+      outputs = [ output ]
+      deps = invoker.deps
+      deps += [ invoker.installable_target_dep ]
+    }
   }
 }
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py
index 6aad68d..455467b 100644
--- a/starboard/raspi/shared/launcher.py
+++ b/starboard/raspi/shared/launcher.py
@@ -30,6 +30,10 @@
 from starboard.raspi.shared import retry
 
 
+class TargetPathError(ValueError):
+  pass
+
+
 # pylint: disable=unused-argument
 def _sigint_or_sigterm_handler(signum, frame):
   """Clean up and exit with status |signum|.
@@ -83,8 +87,8 @@
   _PROMPT_WAIT_MAX_RETRIES = 5
   # Wait up to 10 seconds for the password prompt from the raspi
   _PEXPECT_PASSWORD_TIMEOUT_MAX_RETRIES = 10
-  # Wait up to 900 seconds for new output from the raspi
-  _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 900
+  # Wait up to 600 seconds for new output from the raspi
+  _PEXPECT_READLINE_TIMEOUT_MAX_RETRIES = 600
   # Delay between subsequent SSH commands
   _INTER_COMMAND_DELAY_SECONDS = 1.5
 
@@ -126,31 +130,43 @@
 
     self.last_run_pexpect_cmd = ''
 
+  def _GetAndCheckTestFile(self, target_name):
+    # TODO(b/218889313): This should reference the bin/ subdir when that's
+    # used.
+    test_dir = os.path.join(self.out_directory, 'install', target_name)
+    test_file = target_name
+    test_path = os.path.join(test_dir, test_file)
+
+    if not os.path.isfile(test_path):
+      raise TargetPathError(f'TargetPath ({test_path}) must be a file.')
+    return test_file
+
+  def _GetAndCheckTestFileWithFallback(self):
+    try:
+      return self._GetAndCheckTestFile(self.target_name + '_loader')
+    except TargetPathError:
+      # For starboard level test targets like player_filter_tests built as an
+      # executable, return the target name.
+      return self._GetAndCheckTestFile(self.target_name)
+
   def _InitPexpectCommands(self):
     """Initializes all of the pexpect commands needed for running the test."""
 
     # Ensure no trailing slashes
     self.out_directory = self.out_directory.rstrip('/')
 
-    # TODO(b/218889313): This should reference the bin/ subdir when that's
-    # used.
-    test_dir = os.path.join(self.out_directory, 'install', self.target_name)
-    test_file = self.target_name
-
-    test_path = os.path.join(test_dir, test_file)
-    if not os.path.isfile(test_path):
-      raise ValueError(f'TargetPath ({test_path}) must be a file.')
+    test_file = self._GetAndCheckTestFileWithFallback()
 
     raspi_user_hostname = Launcher._RASPI_USERNAME + '@' + self.device_id
 
     # Use the basename of the out directory as a common directory on the device
     # so content can be reused for several targets w/o re-syncing for each one.
     raspi_test_dir = os.path.basename(self.out_directory)
-    raspi_test_path = os.path.join(raspi_test_dir, test_file)
+    raspi_test_path = os.path.join(raspi_test_dir, test_file, test_file)
 
     # rsync command setup
-    options = '-avzLhc'
-    source = test_dir + '/'
+    options = '-avzLh'
+    source = os.path.join(self.out_directory, 'install') + '/'
     destination = f'{raspi_user_hostname}:~/{raspi_test_dir}/'
     self.rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
 
@@ -242,28 +258,25 @@
 
   def _PexpectReadLines(self):
     """Reads all lines from the pexpect process."""
-    # pylint: disable=unnecessary-lambda
-    @retry.retry(
-        exceptions=Launcher._RETRY_EXCEPTIONS,
-        retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES,
-        backoff=lambda: self.shutdown_initiated.is_set(),
-        wrap_exceptions=False)
-    def _readloop():
-      while True:
-        # Sanitize the line to remove ansi color codes.
-        line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub(
-            '', self.pexpect_process.readline())
-        self.output_file.flush()
-        if not line:
-          return
-        # Check for the test complete tag. It will be followed by either a
-        # success or failure tag.
-        if line.startswith(self.test_complete_tag):
-          if line.find(self.test_success_tag) != -1:
-            self.return_value = 0
-          return
-
-    _readloop()
+    while True:
+      # pylint: disable=unnecessary-lambda
+      line = retry.with_retry(
+          self.pexpect_process.readline,
+          exceptions=Launcher._RETRY_EXCEPTIONS,
+          retries=Launcher._PEXPECT_READLINE_TIMEOUT_MAX_RETRIES,
+          backoff=lambda: self.shutdown_initiated.is_set(),
+          wrap_exceptions=False)
+      # Sanitize the line to remove ansi color codes.
+      line = Launcher._PEXPECT_SANITIZE_LINE_RE.sub('', line)
+      self.output_file.flush()
+      if not line:
+        return
+      # Check for the test complete tag. It will be followed by either a
+      # success or failure tag.
+      if line.startswith(self.test_complete_tag):
+        if line.find(self.test_success_tag) != -1:
+          self.return_value = 0
+        return
 
   def _Sleep(self, val):
     self._PexpectSendLine(f'sleep {val};echo {Launcher._SSH_SLEEP_SIGNAL}')
diff --git a/starboard/raspi/shared/main.cc b/starboard/raspi/shared/main.cc
index e0aa5bf..b07e33e 100644
--- a/starboard/raspi/shared/main.cc
+++ b/starboard/raspi/shared/main.cc
@@ -49,7 +49,6 @@
           : starboard::common::GetCACertificatesPath(evergreen_content_path);
   if (ca_certificates_path.empty()) {
     SB_LOG(ERROR) << "Failed to get CA certificates path";
-    return 1;
   }
 
   bool start_handler_at_crash =
@@ -72,11 +71,3 @@
   starboard::shared::signal::UninstallCrashSignalHandlers();
   return result;
 }
-
-#if SB_API_VERSION >= 15
-int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) {
-  starboard::raspi::shared::ApplicationDispmanx application(callback);
-  int result = application.Run(argc, argv);
-  return result;
-}
-#endif  // SB_API_VERSION >= 15
diff --git a/starboard/raspi/shared/platform_configuration/BUILD.gn b/starboard/raspi/shared/platform_configuration/BUILD.gn
index b1c930e..e2c7f03 100644
--- a/starboard/raspi/shared/platform_configuration/BUILD.gn
+++ b/starboard/raspi/shared/platform_configuration/BUILD.gn
@@ -16,6 +16,39 @@
   raspi_home = getenv("RASPI_HOME")
 }
 
+config("common_flags") {
+  ldflags = [
+    "--sysroot=$raspi_home/busterroot",
+
+    # This is a quirk of Raspbian, these are required to link any GL-related
+    # libraries.
+    "-L$raspi_home/busterroot/opt/vc/lib",
+    "-Wl,-rpath=$raspi_home/busterroot/opt/vc/lib",
+    "-L$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
+    "-Wl,-rpath=$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
+    "-L$raspi_home/busterroot/lib/arm-linux-gnueabihf",
+    "-Wl,-rpath=$raspi_home/busterroot/lib/arm-linux-gnueabihf",
+
+    # Cleanup unused sections
+    "-Wl,-gc-sections",
+    "-Wl,--unresolved-symbols=ignore-all",
+  ]
+  libs = [
+    "asound",
+    "rt",
+    "openmaxil",
+    "bcm_host",
+    "vcos",
+    "vchiq_arm",
+    "brcmGLESv2",
+    "brcmEGL",
+
+    # Static libs must be last, to avoid __dlopen linker errors
+    "EGL_static",
+    "GLESv2_static",
+  ]
+}
+
 config("compiler_flags") {
   cflags = []
   cflags_c = []
@@ -38,23 +71,6 @@
     cflags += [ "-Wno-unused-but-set-variable" ]
   }
 
-  ldflags += [
-    "--sysroot=$raspi_home/busterroot",
-
-    # This is a quirk of Raspbian, these are required to link any GL-related
-    # libraries.
-    "-L$raspi_home/busterroot/opt/vc/lib",
-    "-Wl,-rpath=$raspi_home/busterroot/opt/vc/lib",
-    "-L$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
-    "-Wl,-rpath=$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
-    "-L$raspi_home/busterroot/lib/arm-linux-gnueabihf",
-    "-Wl,-rpath=$raspi_home/busterroot/lib/arm-linux-gnueabihf",
-
-    # Cleanup unused sections
-    "-Wl,-gc-sections",
-    "-Wl,--unresolved-symbols=ignore-in-shared-libs",
-  ]
-
   cflags += [
     # Generated by code in the raspi/shared/open_max.
     "-Wno-sign-compare",
@@ -121,27 +137,16 @@
 }
 
 config("platform_configuration") {
-  libs = [
-    "asound",
-    "dl",
-    "pthread",
-    "rt",
-    "openmaxil",
-    "bcm_host",
-    "vcos",
-    "vchiq_arm",
-    "brcmGLESv2",
-    "brcmEGL",
+  configs = [ ":common_flags" ]
+  if (current_toolchain == default_toolchain && sb_is_modular) {
+    configs += [ "//starboard/evergreen/arm/hardfp/platform_configuration" ]
+  } else {
+    configs +=
+        [ "//starboard/raspi/shared/platform_configuration:compiler_flags" ]
 
-    # Static libs must be last, to avoid __dlopen linker errors
-    "EGL_static",
-    "GLESv2_static",
-  ]
-
-  configs = [ "//starboard/raspi/shared/platform_configuration:compiler_flags" ]
-
-  if (is_debug || is_devel) {
-    configs += [ "//build/config/compiler:rtti" ]
+    if (is_debug || is_devel) {
+      configs += [ "//build/config/compiler:rtti" ]
+    }
   }
 }
 
diff --git a/starboard/raspi/shared/platform_configuration/configuration.gni b/starboard/raspi/shared/platform_configuration/configuration.gni
index bd06d18..b1e053e 100644
--- a/starboard/raspi/shared/platform_configuration/configuration.gni
+++ b/starboard/raspi/shared/platform_configuration/configuration.gni
@@ -12,26 +12,36 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//starboard/build/config/base_configuration.gni")
+if (current_toolchain == default_toolchain &&
+    build_with_separate_cobalt_toolchain) {
+  import(
+      "//starboard/evergreen/arm/softfp/platform_configuration/configuration.gni")
 
-arm_float_abi = "hard"
+  platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests_install($starboard_toolchain)"
+  cobalt_font_package = "standard"
+} else {
+  import("//starboard/build/config/base_configuration.gni")
 
-sb_static_contents_output_data_dir = "$root_out_dir/content"
+  arm_float_abi = "hard"
 
-no_pedantic_warnings_config_path =
-    "//starboard/raspi/shared/platform_configuration:no_pedantic_warnings"
-pedantic_warnings_config_path =
-    "//starboard/raspi/shared/platform_configuration:pedantic_warnings"
-sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
+  sb_static_contents_output_data_dir = "$root_out_dir/content"
 
-platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests"
+  no_pedantic_warnings_config_path =
+      "//starboard/raspi/shared/platform_configuration:no_pedantic_warnings"
+  pedantic_warnings_config_path =
+      "//starboard/raspi/shared/platform_configuration:pedantic_warnings"
+  sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
 
+  platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests_install($starboard_toolchain)"
+
+  speed_config_path = "//starboard/raspi/shared/platform_configuration:speed"
+  size_config_path = "//starboard/raspi/shared/platform_configuration:size"
+
+  # TODO(b/219073252): Enable -fno-exceptions and don't mix it with -fexceptions.
+  enable_exceptions_override = true
+
+  v8_enable_webassembly = true
+
+  is_raspi = true
+}
 install_target_path = "//starboard/raspi/shared/install_target.gni"
-
-speed_config_path = "//starboard/raspi/shared/platform_configuration:speed"
-size_config_path = "//starboard/raspi/shared/platform_configuration:size"
-
-# TODO(b/219073252): Enable -fno-exceptions and don't mix it with -fexceptions.
-enable_exceptions_override = true
-
-v8_enable_webassembly = true
diff --git a/starboard/raspi/shared/run_starboard_main.cc b/starboard/raspi/shared/run_starboard_main.cc
new file mode 100644
index 0000000..9e957af
--- /dev/null
+++ b/starboard/raspi/shared/run_starboard_main.cc
@@ -0,0 +1,24 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/event.h"
+#include "starboard/raspi/shared/application_dispmanx.h"
+
+#if SB_API_VERSION >= 15
+int SbRunStarboardMain(int argc, char** argv, SbEventHandleCallback callback) {
+  starboard::raspi::shared::ApplicationDispmanx application(callback);
+  int result = application.Run(argc, argv);
+  return result;
+}
+#endif  // SB_API_VERSION >= 15
diff --git a/starboard/raspi/shared/system_get_extensions.cc b/starboard/raspi/shared/system_get_extensions.cc
index bd7854d..511fb79 100644
--- a/starboard/raspi/shared/system_get_extensions.cc
+++ b/starboard/raspi/shared/system_get_extensions.cc
@@ -18,11 +18,13 @@
 #include "starboard/extension/configuration.h"
 #include "starboard/extension/crash_handler.h"
 #include "starboard/extension/graphics.h"
+#include "starboard/extension/time_zone.h"
 #include "starboard/shared/starboard/crash_handler.h"
 #if SB_IS(EVERGREEN_COMPATIBLE)
 #include "starboard/elf_loader/evergreen_config.h"
 #endif
 
+#include "starboard/linux/shared/time_zone.h"
 #include "starboard/raspi/shared/configuration.h"
 #include "starboard/raspi/shared/graphics.h"
 
@@ -48,5 +50,8 @@
   if (strcmp(name, kCobaltExtensionCrashHandlerName) == 0) {
     return starboard::common::GetCrashHandlerApi();
   }
+  if (strcmp(name, kStarboardExtensionTimeZoneName) == 0) {
+    return starboard::shared::GetTimeZoneApi();
+  }
   return NULL;
 }
diff --git a/starboard/raspi/shared/test_filters.py b/starboard/raspi/shared/test_filters.py
index d031a5d..21a428f 100644
--- a/starboard/raspi/shared/test_filters.py
+++ b/starboard/raspi/shared/test_filters.py
@@ -16,6 +16,7 @@
 from starboard.tools.testing import test_filter
 
 # pylint: disable=line-too-long
+
 _FILTERED_TESTS = {
     'nplb': [
         'SbAudioSinkTest.*',
diff --git a/starboard/shared/ffmpeg/BUILD.gn b/starboard/shared/ffmpeg/BUILD.gn
index ec30571..5ad4693 100644
--- a/starboard/shared/ffmpeg/BUILD.gn
+++ b/starboard/shared/ffmpeg/BUILD.gn
@@ -18,18 +18,29 @@
   "ffmpeg_common.h",
   "ffmpeg_demuxer_impl.cc",
   "ffmpeg_demuxer_impl.h",
-  "ffmpeg_video_decoder_impl.cc",
-  "ffmpeg_video_decoder_impl.h",
 ]
 
+if (!defined(is_raspi)) {
+  ffmpeg_specialization_sources += [
+    # TODO(b/291783511): This code is unimplemented on certain platforms.
+    "ffmpeg_video_decoder_impl.cc",
+    "ffmpeg_video_decoder_impl.h",
+  ]
+}
+
 static_library("ffmpeg_dynamic_load") {
   check_includes = false
   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",
   ]
+  if (!defined(is_raspi)) {
+    sources += [
+      # TODO(b/291783511): This code is unimplemented on certain platforms.
+      "ffmpeg_dynamic_load_video_decoder_impl.cc",
+    ]
+  }
   public_deps = [
     ":ffmpeg.57.107.100",
     ":ffmpeg.58.35.100",
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
index 4cf444b..1086f3f 100644
--- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -140,14 +140,17 @@
   SB_DCHECK(output_cb_);
   SB_CHECK(codec_context_ != NULL);
 
-  const auto& input_buffer = input_buffers[0];
-
   Schedule(consumed_cb);
 
+  if (input_buffers.empty() || !input_buffers[0]) {
+    SB_LOG(ERROR) << "No input buffer to decode.";
+    return;
+  }
   if (stream_ended_) {
     SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
     return;
   }
+  const auto& input_buffer = input_buffers[0];
 
   AVPacket packet;
   ffmpeg_->av_init_packet(&packet);
diff --git a/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc b/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc
index 693c133..1df5b8b 100644
--- a/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc
+++ b/starboard/shared/libfdkaac/fdk_aac_audio_decoder.cc
@@ -52,6 +52,10 @@
   SB_DCHECK(output_cb_);
   SB_DCHECK(decoder_ != NULL);
 
+  if (input_buffers.empty() || !input_buffers[0]) {
+    SB_LOG(ERROR) << "No input buffer to decode.";
+    return;
+  }
   if (stream_ended_) {
     SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
     return;
diff --git a/starboard/shared/linux/time_zone_get_name.cc b/starboard/shared/linux/time_zone_get_name.cc
new file mode 100644
index 0000000..4b1819e
--- /dev/null
+++ b/starboard/shared/linux/time_zone_get_name.cc
@@ -0,0 +1,75 @@
+// Copyright 2015 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 <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "starboard/common/log.h"
+#include "starboard/time_zone.h"
+
+#define TZDEFAULT "/etc/localtime"
+#define TZZONEINFOTAIL "/zoneinfo/"
+#define isNonDigit(ch) (ch < '0' || '9' < ch)
+
+static char gTimeZoneBuffer[PATH_MAX];
+static char* gTimeZoneBufferPtr = NULL;
+
+static bool isValidOlsonID(const char* id) {
+  int32_t idx = 0;
+
+  /* Determine if this is something like Iceland (Olson ID)
+  or AST4ADT (non-Olson ID) */
+  while (id[idx] && isNonDigit(id[idx]) && id[idx] != ',') {
+    idx++;
+  }
+
+  /* If we went through the whole string, then it might be okay.
+  The timezone is sometimes set to "CST-7CDT", "CST6CDT5,J129,J131/19:30",
+  "GRNLNDST3GRNLNDDT" or similar, so we cannot use it.
+  The rest of the time it could be an Olson ID. George */
+  return static_cast<bool>(id[idx] == 0 || strcmp(id, "PST8PDT") == 0 ||
+                           strcmp(id, "MST7MDT") == 0 ||
+                           strcmp(id, "CST6CDT") == 0 ||
+                           strcmp(id, "EST5EDT") == 0);
+}
+
+// Similar to how ICU::putil.cpp gets IANA(Olsen) timezone ID.
+const char* SbTimeZoneGetName() {
+  /*
+  This is a trick to look at the name of the link to get the Olson ID
+  because the tzfile contents is underspecified.
+  This isn't guaranteed to work because it may not be a symlink.
+  But this is production-tested solution for most versions of Linux.
+  */
+
+  if (gTimeZoneBufferPtr == NULL) {
+    int32_t ret = (int32_t)readlink(TZDEFAULT, gTimeZoneBuffer,
+                                    sizeof(gTimeZoneBuffer) - 1);
+    if (0 < ret) {
+      int32_t tzZoneInfoTailLen = strlen(TZZONEINFOTAIL);
+      gTimeZoneBuffer[ret] = 0;
+      char* tzZoneInfoTailPtr = strstr(gTimeZoneBuffer, TZZONEINFOTAIL);
+
+      if (tzZoneInfoTailPtr != NULL &&
+          isValidOlsonID(tzZoneInfoTailPtr + tzZoneInfoTailLen)) {
+        return (gTimeZoneBufferPtr = tzZoneInfoTailPtr + tzZoneInfoTailLen);
+      }
+    }
+    SB_NOTREACHED();
+    return "";
+  } else {
+    return gTimeZoneBufferPtr;
+  }
+}
diff --git a/starboard/shared/starboard/media/codec_util.cc b/starboard/shared/starboard/media/codec_util.cc
index 1c2c1b3..ff3b325 100644
--- a/starboard/shared/starboard/media/codec_util.cc
+++ b/starboard/shared/starboard/media/codec_util.cc
@@ -72,7 +72,8 @@
   return !(*this == that);
 }
 
-SbMediaAudioCodec GetAudioCodecFromString(const char* codec) {
+SbMediaAudioCodec GetAudioCodecFromString(const char* codec,
+                                          const char* subtype) {
 #if SB_API_VERSION < 15
   const bool kCheckAc3Audio = kSbHasAc3Audio;
 #else
@@ -105,7 +106,9 @@
   }
   // For WAV, the "codecs" parameter of a MIME type refers to the WAVE format
   // ID, where 1 represents PCM: https://datatracker.ietf.org/doc/html/rfc2361
-  if (strcmp(codec, "1") == 0) {
+  const bool is_wav =
+      strcmp(subtype, "wav") == 0 || strcmp(subtype, "wave") == 0;
+  if (is_wav && strcmp(codec, "1") == 0) {
     return kSbMediaAudioCodecPcm;
   }
 #endif  // SB_API_VERSION >= 14
diff --git a/starboard/shared/starboard/media/codec_util.h b/starboard/shared/starboard/media/codec_util.h
index 11f3102..b6f6b61 100644
--- a/starboard/shared/starboard/media/codec_util.h
+++ b/starboard/shared/starboard/media/codec_util.h
@@ -64,7 +64,11 @@
   optional<AvcParameterSets> avc_parameter_sets_;
 };
 
-SbMediaAudioCodec GetAudioCodecFromString(const char* codec);
+// Attempts to determine an SbMediaAudioCodec from |codec|, returning
+// kSbMediaAudioCodecNone if no match is found. |subtype| may be checked in
+// cases of ambiguous codec strings.
+SbMediaAudioCodec GetAudioCodecFromString(const char* codec,
+                                          const char* subtype);
 
 }  // namespace media
 }  // namespace starboard
diff --git a/starboard/shared/starboard/media/codec_util_test.cc b/starboard/shared/starboard/media/codec_util_test.cc
index 2a334be..a57d769 100644
--- a/starboard/shared/starboard/media/codec_util_test.cc
+++ b/starboard/shared/starboard/media/codec_util_test.cc
@@ -16,6 +16,7 @@
 
 #include <vector>
 
+#include "starboard/media.h"
 #include "starboard/shared/starboard/media/avc_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -214,6 +215,60 @@
   EXPECT_FALSE(config_h264 == config_vp9);
 }
 
+TEST(CodecUtilTest, ParsesAacCodecs) {
+  EXPECT_EQ(GetAudioCodecFromString("mp4a.40.2", ""), kSbMediaAudioCodecAac);
+  EXPECT_EQ(GetAudioCodecFromString("mp4a.40.5", ""), kSbMediaAudioCodecAac);
+}
+
+#if SB_API_VERSION < 15
+const bool kCheckAc3Audio = kSbHasAc3Audio;
+#else
+const bool kCheckAc3Audio = true;
+#endif  // SB_API_VERSION < 15
+
+TEST(CodecUtilTest, ParsesAc3CodecIfEnabled) {
+  EXPECT_EQ(GetAudioCodecFromString("ac-3", ""),
+            kCheckAc3Audio ? kSbMediaAudioCodecAc3 : kSbMediaAudioCodecNone);
+}
+
+TEST(CodecUtilTest, ParsesEac3CodecIfEnabled) {
+  EXPECT_EQ(GetAudioCodecFromString("ec-3", ""),
+            kCheckAc3Audio ? kSbMediaAudioCodecEac3 : kSbMediaAudioCodecNone);
+}
+
+TEST(CodecUtilTest, ParsesOpusCodec) {
+  EXPECT_EQ(GetAudioCodecFromString("opus", ""), kSbMediaAudioCodecOpus);
+}
+
+TEST(CodecUtilTest, ParsesVorbisCodec) {
+  EXPECT_EQ(GetAudioCodecFromString("vorbis", ""), kSbMediaAudioCodecVorbis);
+}
+
+#if SB_API_VERSION >= 14
+TEST(CodecUtilTest, ParsesMp3Codecs) {
+  EXPECT_EQ(GetAudioCodecFromString("mp3", ""), kSbMediaAudioCodecMp3);
+  EXPECT_EQ(GetAudioCodecFromString("mp4a.69", ""), kSbMediaAudioCodecMp3);
+  EXPECT_EQ(GetAudioCodecFromString("mp4a.6B", ""), kSbMediaAudioCodecMp3);
+}
+
+TEST(CodecUtilTest, ParsesFlacCodec) {
+  EXPECT_EQ(GetAudioCodecFromString("flac", ""), kSbMediaAudioCodecFlac);
+}
+
+TEST(CodecUtilTest, ParsesPcmCodecForWav) {
+  EXPECT_EQ(GetAudioCodecFromString("1", "wav"), kSbMediaAudioCodecPcm);
+  EXPECT_EQ(GetAudioCodecFromString("1", "wave"), kSbMediaAudioCodecPcm);
+}
+
+TEST(CodecUtilTest, DoesNotParse1AsPcmForNonWavSubtypes) {
+  EXPECT_EQ(GetAudioCodecFromString("1", ""), kSbMediaAudioCodecNone);
+  EXPECT_EQ(GetAudioCodecFromString("1", "mp4"), kSbMediaAudioCodecNone);
+  EXPECT_EQ(GetAudioCodecFromString("1", "mp3"), kSbMediaAudioCodecNone);
+  EXPECT_EQ(GetAudioCodecFromString("1", "mpeg"), kSbMediaAudioCodecNone);
+  EXPECT_EQ(GetAudioCodecFromString("1", "webm"), kSbMediaAudioCodecNone);
+}
+#endif  // SB_API_VERSION >= 14
+
 }  // namespace
 }  // namespace media
 }  // namespace starboard
diff --git a/starboard/shared/starboard/media/media_tests.gni b/starboard/shared/starboard/media/media_tests.gni
index 4777863..269a60c 100644
--- a/starboard/shared/starboard/media/media_tests.gni
+++ b/starboard/shared/starboard/media/media_tests.gni
@@ -18,6 +18,7 @@
   "//starboard/shared/starboard/media/media_util_test.cc",
   "//starboard/shared/starboard/media/mime_type_test.cc",
   "//starboard/shared/starboard/media/mime_util_test.cc",
+  "//starboard/shared/starboard/media/parsed_mime_info_test.cc",
   "//starboard/shared/starboard/media/video_capabilities_test.cc",
   "//starboard/shared/starboard/media/vp9_util_test.cc",
 ]
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc
index 0ef4b2d..52650f8 100644
--- a/starboard/shared/starboard/media/mime_util.cc
+++ b/starboard/shared/starboard/media/mime_util.cc
@@ -37,23 +37,19 @@
 // Use SbMediaGetAudioConfiguration() to check if the platform can support
 // |channels|.
 bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) {
-  // TODO(b/284140486, b/297426689): Consider removing the call to
-  // `SbMediaGetAudioOutputCount()` completely as the loop will be terminated
-  // once `SbMediaGetAudioConfiguration()` returns false.
-  int count = SbMediaGetAudioOutputCount();
-
-  for (int output_index = 0; output_index < count; ++output_index) {
-    SbMediaAudioConfiguration configuration;
-    if (!SbMediaGetAudioConfiguration(output_index, &configuration)) {
-      break;
-    }
-
+  // SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The
+  // limit here is to avoid infinite loop and also match
+  // SbPlayerBridge::GetAudioConfigurations().
+  const int kMaxAudioConfigurations = 32;
+  int output_index = 0;
+  SbMediaAudioConfiguration configuration;
+  while (output_index < kMaxAudioConfigurations &&
+         SbMediaGetAudioConfiguration(output_index++, &configuration)) {
     if (configuration.coding_type == coding_type &&
         configuration.number_of_channels >= channels) {
       return true;
     }
   }
-
   return false;
 }
 
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc
index e61cb84..ae2e147 100644
--- a/starboard/shared/starboard/media/parsed_mime_info.cc
+++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -101,7 +101,8 @@
   SB_DCHECK(mime_type_.is_valid());
   SB_DCHECK(!has_audio_info());
 
-  SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str());
+  SbMediaAudioCodec audio_codec =
+      GetAudioCodecFromString(codec.c_str(), mime_type_.subtype().c_str());
   if (audio_codec == kSbMediaAudioCodecNone) {
     return false;
   }
diff --git a/starboard/shared/starboard/media/parsed_mime_info_test.cc b/starboard/shared/starboard/media/parsed_mime_info_test.cc
new file mode 100644
index 0000000..94c1f7b
--- /dev/null
+++ b/starboard/shared/starboard/media/parsed_mime_info_test.cc
@@ -0,0 +1,98 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/parsed_mime_info.h"
+
+#include "starboard/media.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+namespace {
+
+#if SB_API_VERSION < 15
+const bool kCheckAc3Audio = kSbHasAc3Audio;
+#else
+const bool kCheckAc3Audio = true;
+#endif  // SB_API_VERSION < 15
+
+TEST(ParsedMimeInfoTest, ParsesAacLowComplexityCodec) {
+  ParsedMimeInfo mime_info("audio/mp4; codecs=\"mp4a.40.2\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAac);
+}
+
+TEST(ParsedMimeInfoTest, ParsesAacHighEfficiencyCodec) {
+  ParsedMimeInfo mime_info("audio/mp4; codecs=\"mp4a.40.5\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAac);
+}
+
+TEST(ParsedMimeInfoTest, ParsesAc3Codec) {
+  ParsedMimeInfo mime_info("audio/mp4; codecs=\"ac-3\"");
+  ASSERT_EQ(mime_info.has_audio_info(), kCheckAc3Audio);
+
+  if (kCheckAc3Audio) {
+    EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecAc3);
+  }
+}
+
+TEST(ParsedMimeInfoTest, ParsesEac3Codec) {
+  ParsedMimeInfo mime_info("audio/mp4; codecs=\"ec-3\"");
+  ASSERT_EQ(mime_info.has_audio_info(), kCheckAc3Audio);
+
+  if (kCheckAc3Audio) {
+    EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecEac3);
+  }
+}
+
+TEST(ParsedMimeInfoTest, ParsesOpusCodec) {
+  ParsedMimeInfo mime_info("audio/webm; codecs=\"opus\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecOpus);
+}
+
+TEST(ParsedMimeInfoTest, ParsesVorbisCodec) {
+  ParsedMimeInfo mime_info("audio/webm; codecs=\"vorbis\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecVorbis);
+}
+
+#if SB_API_VERSION >= 14
+TEST(ParsedMimeInfoTest, ParsesMp3Codec) {
+  ParsedMimeInfo mime_info("audio/mpeg; codecs=\"mp3\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecMp3);
+}
+
+TEST(ParsedMimeInfoTest, ParsesFlacCodec) {
+  ParsedMimeInfo mime_info("audio/ogg; codecs=\"flac\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecFlac);
+}
+
+TEST(ParsedMimeInfoTest, ParsesPcmCodec) {
+  ParsedMimeInfo mime_info("audio/wav; codecs=\"1\"");
+  ASSERT_TRUE(mime_info.has_audio_info());
+  EXPECT_EQ(mime_info.audio_info().codec, kSbMediaAudioCodecPcm);
+}
+#endif  // SB_API_VERSION >= 14
+
+}  // namespace
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/starboard/player/decoded_audio_internal.cc b/starboard/shared/starboard/player/decoded_audio_internal.cc
index 2834bc2..0d90fe9 100644
--- a/starboard/shared/starboard/player/decoded_audio_internal.cc
+++ b/starboard/shared/starboard/player/decoded_audio_internal.cc
@@ -142,17 +142,26 @@
   SB_DCHECK(discarded_duration_from_back >= 0);
   SB_DCHECK(storage_type() == kSbMediaAudioFrameStorageTypeInterleaved);
 
-  const auto bytes_per_frame = GetBytesPerSample(sample_type()) * channels_;
-  auto discarded_frames_from_front =
-      AudioDurationToFrames(discarded_duration_from_front, sample_rate);
+  if (discarded_duration_from_front == 0 && discarded_duration_from_back == 0) {
+    return;
+  }
 
-  discarded_frames_from_front = std::min(discarded_frames_from_front, frames());
+  const auto bytes_per_frame = GetBytesPerSample(sample_type()) * channels_;
+  int current_frames = frames();
+  int discarded_frames_from_front =
+      (discarded_duration_from_front >=
+       AudioFramesToDuration(current_frames, sample_rate))
+          ? current_frames
+          : AudioDurationToFrames(discarded_duration_from_front, sample_rate);
   offset_in_bytes_ += bytes_per_frame * discarded_frames_from_front;
   size_in_bytes_ -= bytes_per_frame * discarded_frames_from_front;
 
-  auto discarded_frames_from_back =
-      AudioDurationToFrames(discarded_duration_from_back, sample_rate);
-  discarded_frames_from_back = std::min(discarded_frames_from_back, frames());
+  current_frames = frames();
+  int discarded_frames_from_back =
+      (discarded_duration_from_back >=
+       AudioFramesToDuration(current_frames, sample_rate))
+          ? current_frames
+          : AudioDurationToFrames(discarded_duration_from_back, sample_rate);
   size_in_bytes_ -= bytes_per_frame * discarded_frames_from_back;
 }
 
diff --git a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
index b1ad4fb..b8f2c36 100644
--- a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
+++ b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
@@ -43,8 +43,12 @@
 void AudioFrameDiscarder::AdjustForDiscardedDurations(
     int sample_rate,
     scoped_refptr<DecodedAudio>* decoded_audio) {
-  SB_DCHECK(decoded_audio);
-  SB_DCHECK(*decoded_audio);
+  if (!decoded_audio || !*decoded_audio) {
+    SB_LOG(ERROR) << "No input buffer to adjust.";
+    SB_DCHECK(decoded_audio);
+    SB_DCHECK(*decoded_audio);
+    return;
+  }
 
   InputBufferInfo input_info;
   {
diff --git a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index 4a1713e..0816518 100644
--- a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -39,6 +39,9 @@
 using std::placeholders::_1;
 using std::placeholders::_2;
 
+typedef shared::starboard::player::PlayerWorker::Handler::HandlerResult
+    HandlerResult;
+
 // TODO: Make this configurable inside SbPlayerCreate().
 const SbTimeMonotonic kUpdateInterval = 200 * kSbTimeMillisecond;
 
@@ -89,7 +92,7 @@
   update_job_ = std::bind(&FilterBasedPlayerWorkerHandler::Update, this);
 }
 
-bool FilterBasedPlayerWorkerHandler::Init(
+HandlerResult FilterBasedPlayerWorkerHandler::Init(
     SbPlayer player,
     UpdateMediaInfoCB update_media_info_cb,
     GetPlayerStateCB get_player_state_cb,
@@ -126,11 +129,10 @@
       SB_LOG(ERROR) << "Audio channels requested " << required_audio_channels
                     << ", but currently supported less than or equal to "
                     << supported_audio_channels;
-      OnError(
-          kSbPlayerErrorCapabilityChanged,
+      std::string error_message =
           FormatString("Required channel %d is greater than maximum channel %d",
-                       required_audio_channels, supported_audio_channels));
-      return false;
+                       required_audio_channels, supported_audio_channels);
+      return HandlerResult{false, error_message};
     }
   }
 
@@ -140,14 +142,14 @@
 
   {
     ::starboard::ScopedLock lock(player_components_existence_mutex_);
-    std::string error_message;
-    player_components_ =
-        factory->CreateComponents(creation_parameters, &error_message);
+    std::string components_error_message;
+    player_components_ = factory->CreateComponents(creation_parameters,
+                                                   &components_error_message);
     if (!player_components_) {
-      OnError(kSbPlayerErrorDecode,
-              FormatString("Failed to create player components with error: %s",
-                           error_message.c_str()));
-      return false;
+      std::string error_message =
+          FormatString("Failed to create player components with error: %s.",
+                       components_error_message.c_str());
+      return HandlerResult{false, error_message};
     }
     media_time_provider_ = player_components_->GetMediaTimeProvider();
     audio_renderer_ = player_components_->GetAudioRenderer();
@@ -187,16 +189,17 @@
 
   update_job_token_ = Schedule(update_job_, kUpdateInterval);
 
-  return true;
+  return HandlerResult{true};
 }
 
-bool FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time, int ticket) {
+HandlerResult FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time,
+                                                   int ticket) {
   SB_DCHECK(BelongsToCurrentThread());
 
   SB_LOG(INFO) << "Seek to " << seek_to_time << ", and media time provider is "
                << media_time_provider_;
   if (!media_time_provider_) {
-    return false;
+    return HandlerResult{false, "Invalid media time provider"};
   }
 
   if (seek_to_time < 0) {
@@ -211,10 +214,10 @@
   media_time_provider_->Seek(seek_to_time);
   audio_prerolled_ = false;
   video_prerolled_ = false;
-  return true;
+  return HandlerResult{true};
 }
 
-bool FilterBasedPlayerWorkerHandler::WriteSamples(
+HandlerResult FilterBasedPlayerWorkerHandler::WriteSamples(
     const InputBuffers& input_buffers,
     int* samples_written) {
   SB_DCHECK(!input_buffers.empty());
@@ -227,19 +230,19 @@
   *samples_written = 0;
   if (input_buffers.front()->sample_type() == kSbMediaTypeAudio) {
     if (!audio_renderer_) {
-      return false;
+      return HandlerResult{false, "Invalid audio renderer."};
     }
 
     if (audio_renderer_->IsEndOfStreamWritten()) {
       SB_LOG(WARNING) << "Try to write audio sample after EOS is reached";
     } else {
       if (!audio_renderer_->CanAcceptMoreData()) {
-        return true;
+        return HandlerResult{true};
       }
       for (const auto& input_buffer : input_buffers) {
         if (input_buffer->drm_info()) {
           if (!SbDrmSystemIsValid(drm_system_)) {
-            return false;
+            return HandlerResult{false, "Invalid DRM system."};
           }
           DumpInputHash(input_buffer);
           SbDrmSystemPrivate::DecryptStatus decrypt_status =
@@ -250,10 +253,10 @@
                   InputBuffers(input_buffers.begin(),
                                input_buffers.begin() + *samples_written));
             }
-            return true;
+            return HandlerResult{true};
           }
           if (decrypt_status == SbDrmSystemPrivate::kFailure) {
-            return false;
+            return HandlerResult{false, "Sample decryption failure."};
           }
         }
         DumpInputHash(input_buffer);
@@ -265,19 +268,19 @@
     SB_DCHECK(input_buffers.front()->sample_type() == kSbMediaTypeVideo);
 
     if (!video_renderer_) {
-      return false;
+      return HandlerResult{false, "Invalid video renderer."};
     }
 
     if (video_renderer_->IsEndOfStreamWritten()) {
       SB_LOG(WARNING) << "Try to write video sample after EOS is reached";
     } else {
       if (!video_renderer_->CanAcceptMoreData()) {
-        return true;
+        return HandlerResult{true};
       }
       for (const auto& input_buffer : input_buffers) {
         if (input_buffer->drm_info()) {
           if (!SbDrmSystemIsValid(drm_system_)) {
-            return false;
+            return HandlerResult{false, "Invalid DRM system."};
           }
           DumpInputHash(input_buffer);
           SbDrmSystemPrivate::DecryptStatus decrypt_status =
@@ -288,10 +291,10 @@
                   InputBuffers(input_buffers.begin(),
                                input_buffers.begin() + *samples_written));
             }
-            return true;
+            return HandlerResult{true};
           }
           if (decrypt_status == SbDrmSystemPrivate::kFailure) {
-            return false;
+            return HandlerResult{false, "Sample decryption failure."};
           }
         }
         DumpInputHash(input_buffer);
@@ -301,16 +304,17 @@
     }
   }
 
-  return true;
+  return HandlerResult{true};
 }
 
-bool FilterBasedPlayerWorkerHandler::WriteEndOfStream(SbMediaType sample_type) {
+HandlerResult FilterBasedPlayerWorkerHandler::WriteEndOfStream(
+    SbMediaType sample_type) {
   SB_DCHECK(BelongsToCurrentThread());
 
   if (sample_type == kSbMediaTypeAudio) {
     if (!audio_renderer_) {
       SB_LOG(INFO) << "Audio EOS enqueued when renderer is NULL.";
-      return false;
+      return HandlerResult{false, "Audio EOS enqueued when renderer is NULL."};
     }
     if (audio_renderer_->IsEndOfStreamWritten()) {
       SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued";
@@ -321,7 +325,7 @@
   } else {
     if (!video_renderer_) {
       SB_LOG(INFO) << "Video EOS enqueued when renderer is NULL.";
-      return false;
+      return HandlerResult{false, "Video EOS enqueued when renderer is NULL."};
     }
     if (video_renderer_->IsEndOfStreamWritten()) {
       SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued";
@@ -331,17 +335,17 @@
     }
   }
 
-  return true;
+  return HandlerResult{true};
 }
 
-bool FilterBasedPlayerWorkerHandler::SetPause(bool pause) {
+HandlerResult FilterBasedPlayerWorkerHandler::SetPause(bool pause) {
   SB_DCHECK(BelongsToCurrentThread());
 
   SB_LOG(INFO) << "Set pause from " << paused_ << " to " << pause
                << ", and media time provider is " << media_time_provider_;
 
   if (!media_time_provider_) {
-    return false;
+    return HandlerResult{false, "Invalid media time provider."};
   }
 
   paused_ = pause;
@@ -351,11 +355,12 @@
   } else {
     media_time_provider_->Play();
   }
-
-  return true;
+  Update();
+  return HandlerResult{true};
 }
 
-bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
+HandlerResult FilterBasedPlayerWorkerHandler::SetPlaybackRate(
+    double playback_rate) {
   SB_DCHECK(BelongsToCurrentThread());
 
   SB_LOG(INFO) << "Set playback rate from " << playback_rate_ << " to "
@@ -365,11 +370,12 @@
   playback_rate_ = playback_rate;
 
   if (!media_time_provider_) {
-    return false;
+    return HandlerResult{false, "Invalid media time provider."};
   }
 
   media_time_provider_->SetPlaybackRate(playback_rate_);
-  return true;
+  Update();
+  return HandlerResult{true};
 }
 
 void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
@@ -384,7 +390,7 @@
   }
 }
 
-bool FilterBasedPlayerWorkerHandler::SetBounds(const Bounds& bounds) {
+HandlerResult FilterBasedPlayerWorkerHandler::SetBounds(const Bounds& bounds) {
   SB_DCHECK(BelongsToCurrentThread());
 
   if (memcmp(&bounds_, &bounds, sizeof(bounds_)) != 0) {
@@ -407,7 +413,7 @@
     }
   }
 
-  return true;
+  return HandlerResult{true};
 }
 
 void FilterBasedPlayerWorkerHandler::OnError(SbPlayerError error,
@@ -500,6 +506,7 @@
     update_media_info_cb_(media_time, dropped_frames, !is_underflow);
   }
 
+  RemoveJobByToken(update_job_token_);
   update_job_token_ = Schedule(update_job_, kUpdateInterval);
 }
 
diff --git a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
index 6f3ed97..e553774 100644
--- a/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
+++ b/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -48,19 +48,19 @@
       SbDecodeTargetGraphicsContextProvider* provider);
 
  private:
-  bool Init(SbPlayer player,
-            UpdateMediaInfoCB update_media_info_cb,
-            GetPlayerStateCB get_player_state_cb,
-            UpdatePlayerStateCB update_player_state_cb,
-            UpdatePlayerErrorCB update_player_error_cb) override;
-  bool Seek(SbTime seek_to_time, int ticket) override;
-  bool WriteSamples(const InputBuffers& input_buffers,
-                    int* samples_written) override;
-  bool WriteEndOfStream(SbMediaType sample_type) override;
-  bool SetPause(bool pause) override;
-  bool SetPlaybackRate(double playback_rate) override;
+  HandlerResult Init(SbPlayer player,
+                     UpdateMediaInfoCB update_media_info_cb,
+                     GetPlayerStateCB get_player_state_cb,
+                     UpdatePlayerStateCB update_player_state_cb,
+                     UpdatePlayerErrorCB update_player_error_cb) override;
+  HandlerResult Seek(SbTime seek_to_time, int ticket) override;
+  HandlerResult WriteSamples(const InputBuffers& input_buffers,
+                             int* samples_written) override;
+  HandlerResult WriteEndOfStream(SbMediaType sample_type) override;
+  HandlerResult SetPause(bool pause) override;
+  HandlerResult SetPlaybackRate(double playback_rate) override;
   void SetVolume(double volume) override;
-  bool SetBounds(const Bounds& bounds) override;
+  HandlerResult SetBounds(const Bounds& bounds) override;
   void Stop() override;
 
   void Update();
diff --git a/starboard/shared/starboard/player/filter/stub_audio_decoder.cc b/starboard/shared/starboard/player/filter/stub_audio_decoder.cc
index 2f4a7b4..39b5017 100644
--- a/starboard/shared/starboard/player/filter/stub_audio_decoder.cc
+++ b/starboard/shared/starboard/player/filter/stub_audio_decoder.cc
@@ -158,11 +158,12 @@
   for (const auto& input_buffer : input_buffers) {
     DecodeOneBuffer(input_buffer);
   }
-  decoder_thread_->job_queue()->Schedule(consumed_cb);
+  Schedule(consumed_cb);
 }
 
 void StubAudioDecoder::DecodeOneBuffer(
     const scoped_refptr<InputBuffer>& input_buffer) {
+  SB_DCHECK(decoder_thread_->job_queue()->BelongsToCurrentThread());
   const int kMaxInputBeforeMultipleDecodedAudios = 4;
 
   if (last_input_buffer_) {
@@ -189,7 +190,7 @@
     if (total_input_count_ % kMaxInputBeforeMultipleDecodedAudios != 0) {
       ScopedLock lock(decoded_audios_mutex_);
       decoded_audios_.push(decoded_audio);
-      decoder_thread_->job_queue()->Schedule(output_cb_);
+      Schedule(output_cb_);
     } else {
       // Divide the content of `decoded_audio` as multiple DecodedAudio objects
       // to ensure that the user of AudioDecoders works with output in
@@ -230,7 +231,7 @@
 
         ScopedLock lock(decoded_audios_mutex_);
         decoded_audios_.push(current_decoded_audio);
-        decoder_thread_->job_queue()->Schedule(output_cb_);
+        Schedule(output_cb_);
       }
     }
   }
@@ -274,11 +275,11 @@
 
     ScopedLock lock(decoded_audios_mutex_);
     decoded_audios_.push(decoded_audio);
-    decoder_thread_->job_queue()->Schedule(output_cb_);
+    Schedule(output_cb_);
   }
   ScopedLock lock(decoded_audios_mutex_);
   decoded_audios_.push(new DecodedAudio());
-  decoder_thread_->job_queue()->Schedule(output_cb_);
+  Schedule(output_cb_);
 }
 
 }  // namespace filter
diff --git a/starboard/shared/starboard/player/filter/testing/BUILD.gn b/starboard/shared/starboard/player/filter/testing/BUILD.gn
index 9d6f6b9..d1cb5be 100644
--- a/starboard/shared/starboard/player/filter/testing/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/testing/BUILD.gn
@@ -49,41 +49,41 @@
     data_deps =
         [ "//starboard/shared/starboard/player:player_download_test_data" ]
   }
-}
 
-if (host_os != "win" && current_toolchain == starboard_toolchain) {
-  target(final_executable_type, "player_filter_benchmarks") {
+  if (host_os != "win") {
+    target(final_executable_type, "player_filter_benchmarks") {
+      testonly = true
+
+      sources = [
+        "//starboard/common/benchmark_main.cc",
+        "audio_decoder_benchmark.cc",
+      ]
+
+      public_deps = [
+        ":test_util",
+        "//third_party/google_benchmark",
+      ]
+
+      deps = cobalt_platform_dependencies
+    }
+  }
+
+  static_library("test_util") {
     testonly = true
 
     sources = [
-      "//starboard/common/benchmark_main.cc",
-      "audio_decoder_benchmark.cc",
+      "test_util.cc",
+      "test_util.h",
     ]
 
+    public_configs = [ "//starboard/build/config:starboard_implementation" ]
+
     public_deps = [
-      ":test_util",
-      "//third_party/google_benchmark",
+      "//starboard",
+      "//starboard/shared/starboard/media:media_util",
+      "//starboard/shared/starboard/player:player_download_test_data",
+      "//starboard/shared/starboard/player:video_dmp",
+      "//testing/gtest",
     ]
-
-    deps = cobalt_platform_dependencies
   }
 }
-
-static_library("test_util") {
-  testonly = true
-
-  sources = [
-    "test_util.cc",
-    "test_util.h",
-  ]
-
-  public_configs = [ "//starboard/build/config:starboard_implementation" ]
-
-  public_deps = [
-    "//starboard",
-    "//starboard/shared/starboard/media:media_util",
-    "//starboard/shared/starboard/player:player_download_test_data",
-    "//starboard/shared/starboard/player:video_dmp",
-    "//testing/gtest",
-  ]
-}
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc
index 25ad281..39ae17d 100644
--- a/starboard/shared/starboard/player/filter/testing/test_util.cc
+++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -181,14 +181,32 @@
       const auto& video_stream_info = dmp_reader.video_stream_info();
       const std::string video_mime = dmp_reader.video_mime_type();
       const MimeType video_mime_type(video_mime.c_str());
-      if (SbMediaIsVideoSupported(
-              dmp_reader.video_codec(),
-              video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8,
-              kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified,
-              kSbMediaMatrixIdUnspecified, video_stream_info.frame_width,
-              video_stream_info.frame_height, dmp_reader.video_bitrate(),
-              dmp_reader.video_fps(), false)) {
-        test_params.push_back(std::make_tuple(filename, output_mode));
+      // SbMediaIsVideoSupported may return false for gpu based decoder that in
+      // fact supports av1 or/and vp9 because the system can make async
+      // initialization at startup.
+      // To minimize probability of false negative we check result few times
+      static bool decoder_has_been_checked_once = false;
+      int counter = 5;
+      const SbMediaVideoCodec video_codec = dmp_reader.video_codec();
+      bool need_to_check_with_wait = video_codec == kSbMediaVideoCodecAv1 ||
+                                     video_codec == kSbMediaVideoCodecVp9;
+      do {
+        if (SbMediaIsVideoSupported(
+                video_codec, video_mime.size() > 0 ? &video_mime_type : nullptr,
+                -1, -1, 8, kSbMediaPrimaryIdUnspecified,
+                kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified,
+                video_stream_info.frame_width, video_stream_info.frame_height,
+                dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) {
+          test_params.push_back(std::make_tuple(filename, output_mode));
+          break;
+        } else if (need_to_check_with_wait && !decoder_has_been_checked_once) {
+          SbThreadSleep(kSbTimeSecond);
+        } else {
+          break;
+        }
+      } while (--counter);
+      if (need_to_check_with_wait) {
+        decoder_has_been_checked_once = true;
       }
     }
   }
diff --git a/starboard/shared/starboard/player/player_worker.cc b/starboard/shared/starboard/player/player_worker.cc
index 5ee9bc0..09947e8 100644
--- a/starboard/shared/starboard/player/player_worker.cc
+++ b/starboard/shared/starboard/player/player_worker.cc
@@ -33,6 +33,9 @@
 using std::placeholders::_2;
 using std::placeholders::_3;
 
+typedef shared::starboard::player::PlayerWorker::Handler::HandlerResult
+    HandlerResult;
+
 #ifdef SB_MEDIA_PLAYER_THREAD_STACK_SIZE
 const int kPlayerStackSize = SB_MEDIA_PLAYER_THREAD_STACK_SIZE;
 #else   // SB_MEDIA_PLAYER_THREAD_STACK_SIZE
@@ -158,9 +161,16 @@
 }
 
 void PlayerWorker::UpdatePlayerError(SbPlayerError error,
+                                     HandlerResult result,
                                      const std::string& error_message) {
+  SB_DCHECK(!result.success);
+  std::string complete_error_message = error_message;
+  if (!result.error_message.empty()) {
+    complete_error_message += " Error: " + result.error_message;
+  }
+
   SB_LOG(WARNING) << "Encountered player error " << error
-                  << " with message: " << error_message;
+                  << " with message: " << complete_error_message;
   // Only report the first error.
   if (error_occurred_.exchange(true)) {
     return;
@@ -168,7 +178,7 @@
   if (!player_error_func_) {
     return;
   }
-  player_error_func_(player_, context_, error, error_message.c_str());
+  player_error_func_(player_, context_, error, complete_error_message.c_str());
 }
 
 // static
@@ -197,17 +207,18 @@
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
   Handler::UpdatePlayerErrorCB update_player_error_cb;
-  update_player_error_cb =
-      std::bind(&PlayerWorker::UpdatePlayerError, this, _1, _2);
-  if (handler_->Init(
-          player_, std::bind(&PlayerWorker::UpdateMediaInfo, this, _1, _2, _3),
-          std::bind(&PlayerWorker::player_state, this),
-          std::bind(&PlayerWorker::UpdatePlayerState, this, _1),
-          update_player_error_cb)) {
+  update_player_error_cb = std::bind(&PlayerWorker::UpdatePlayerError, this, _1,
+                                     HandlerResult{false}, _2);
+  HandlerResult result = handler_->Init(
+      player_, std::bind(&PlayerWorker::UpdateMediaInfo, this, _1, _2, _3),
+      std::bind(&PlayerWorker::player_state, this),
+      std::bind(&PlayerWorker::UpdatePlayerState, this, _1),
+      update_player_error_cb);
+  if (result.success) {
     UpdatePlayerState(kSbPlayerStateInitialized);
   } else {
-    UpdatePlayerError(kSbPlayerErrorDecode,
-                      "Failed to initialize PlayerWorker with unknown error.");
+    UpdatePlayerError(kSbPlayerErrorDecode, result,
+                      "Failed to initialize PlayerWorker.");
   }
 }
 
@@ -232,8 +243,9 @@
   pending_audio_buffers_.clear();
   pending_video_buffers_.clear();
 
-  if (!handler_->Seek(seek_to_time, ticket)) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed seek.");
+  HandlerResult result = handler_->Seek(seek_to_time, ticket);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed seek.");
     return;
   }
 
@@ -273,9 +285,10 @@
     SB_DCHECK(pending_video_buffers_.empty());
   }
   int samples_written;
-  bool result = handler_->WriteSamples(input_buffers, &samples_written);
-  if (!result) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed to write sample.");
+  HandlerResult result =
+      handler_->WriteSamples(input_buffers, &samples_written);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to write sample.");
     return;
   }
   if (samples_written == input_buffers.size()) {
@@ -341,31 +354,37 @@
     SB_DCHECK(pending_video_buffers_.empty());
   }
 
-  if (!handler_->WriteEndOfStream(sample_type)) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed to write end of stream.");
+  HandlerResult result = handler_->WriteEndOfStream(sample_type);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result,
+                      "Failed to write end of stream.");
   }
 }
 
 void PlayerWorker::DoSetBounds(Bounds bounds) {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
-  if (!handler_->SetBounds(bounds)) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set bounds");
+  HandlerResult result = handler_->SetBounds(bounds);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to set bounds.");
   }
 }
 
 void PlayerWorker::DoSetPause(bool pause) {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
-  if (!handler_->SetPause(pause)) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set pause.");
+  HandlerResult result = handler_->SetPause(pause);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result, "Failed to set pause.");
   }
 }
 
 void PlayerWorker::DoSetPlaybackRate(double playback_rate) {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
-  if (!handler_->SetPlaybackRate(playback_rate)) {
-    UpdatePlayerError(kSbPlayerErrorDecode, "Failed to set playback rate.");
+  HandlerResult result = handler_->SetPlaybackRate(playback_rate);
+  if (!result.success) {
+    UpdatePlayerError(kSbPlayerErrorDecode, result,
+                      "Failed to set playback rate.");
   }
 }
 
diff --git a/starboard/shared/starboard/player/player_worker.h b/starboard/shared/starboard/player/player_worker.h
index cf27fb3..96bae3d 100644
--- a/starboard/shared/starboard/player/player_worker.h
+++ b/starboard/shared/starboard/player/player_worker.h
@@ -63,6 +63,13 @@
   // All functions of this class will be called from the JobQueue thread.
   class Handler {
    public:
+    // Stores the success status of Handler operations. If |success| is false,
+    // |error_message| may be set with details of the error.
+    struct HandlerResult {
+      bool success;
+      std::string error_message;
+    };
+
     typedef PlayerWorker::Bounds Bounds;
     typedef ::starboard::shared::starboard::player::InputBuffer InputBuffer;
     typedef ::starboard::shared::starboard::player::InputBuffers InputBuffers;
@@ -79,22 +86,23 @@
     Handler() = default;
     virtual ~Handler() {}
 
-    // All the following functions return false to signal a fatal error.  The
-    // event processing loop in PlayerWorker will terminate in this case.
-    virtual bool Init(SbPlayer player,
-                      UpdateMediaInfoCB update_media_info_cb,
-                      GetPlayerStateCB get_player_state_cb,
-                      UpdatePlayerStateCB update_player_state_cb,
-                      UpdatePlayerErrorCB update_player_error_cb) = 0;
-    virtual bool Seek(SbTime seek_to_time, int ticket) = 0;
-    virtual bool WriteSamples(const InputBuffers& input_buffers,
-                              int* samples_written) = 0;
-    virtual bool WriteEndOfStream(SbMediaType sample_type) = 0;
-    virtual bool SetPause(bool pause) = 0;
-    virtual bool SetPlaybackRate(double playback_rate) = 0;
+    // All the following functions set |HandlerResult.success| to false to
+    // signal a fatal error. The event processing loop in PlayerWorker will
+    // terminate in this case.
+    virtual HandlerResult Init(SbPlayer player,
+                               UpdateMediaInfoCB update_media_info_cb,
+                               GetPlayerStateCB get_player_state_cb,
+                               UpdatePlayerStateCB update_player_state_cb,
+                               UpdatePlayerErrorCB update_player_error_cb) = 0;
+    virtual HandlerResult Seek(SbTime seek_to_time, int ticket) = 0;
+    virtual HandlerResult WriteSamples(const InputBuffers& input_buffers,
+                                       int* samples_written) = 0;
+    virtual HandlerResult WriteEndOfStream(SbMediaType sample_type) = 0;
+    virtual HandlerResult SetPause(bool pause) = 0;
+    virtual HandlerResult SetPlaybackRate(double playback_rate) = 0;
     virtual void SetVolume(double volume) = 0;
 
-    virtual bool SetBounds(const Bounds& bounds) = 0;
+    virtual HandlerResult SetBounds(const Bounds& bounds) = 0;
 
     // Once this function returns, all processing on the Handler and related
     // objects has to be stopped.  The JobQueue will be destroyed immediately
@@ -187,7 +195,9 @@
 
   SbPlayerState player_state() const { return player_state_; }
   void UpdatePlayerState(SbPlayerState player_state);
-  void UpdatePlayerError(SbPlayerError error, const std::string& message);
+  void UpdatePlayerError(SbPlayerError error,
+                         Handler::HandlerResult result,
+                         const std::string& message);
 
   static void* ThreadEntryPoint(void* context);
   void RunLoop();
diff --git a/starboard/shared/uwp/application_uwp.cc b/starboard/shared/uwp/application_uwp.cc
index c1192f5..b0f18dc 100644
--- a/starboard/shared/uwp/application_uwp.cc
+++ b/starboard/shared/uwp/application_uwp.cc
@@ -660,7 +660,8 @@
         TryAddCommandArgsFromStarboardFile(&args_);
         CommandLine cmd_line(args_);
         if (cmd_line.HasSwitch(kNetArgsCommandSwitchWait)) {
-          SbTime timeout = kSbTimeSecond * 2;
+          // Wait for net args is flaky and needs extended wait time on Xbox.
+          SbTime timeout = kSbTimeSecond * 30;
           std::string val = cmd_line.GetSwitchValue(kNetArgsCommandSwitchWait);
           if (!val.empty()) {
             timeout = atoi(val.c_str());
diff --git a/starboard/shared/uwp/extended_resources_manager.cc b/starboard/shared/uwp/extended_resources_manager.cc
index 5b0c78b..21d2821 100644
--- a/starboard/shared/uwp/extended_resources_manager.cc
+++ b/starboard/shared/uwp/extended_resources_manager.cc
@@ -26,7 +26,7 @@
 #include "starboard/time.h"
 #include "starboard/xb1/shared/internal_shims.h"
 #if defined(INTERNAL_BUILD)
-#include "internal/starboard/xb1/av1_video_decoder.h"
+#include "internal/starboard/xb1/dav1d_video_decoder.h"
 #include "internal/starboard/xb1/vpx_video_decoder.h"
 #include "third_party/internal/libvpx_xb1/libvpx/d3dx12.h"
 #endif  // defined(INTERNAL_BUILD)
@@ -41,12 +41,36 @@
 using ::starboard::shared::starboard::media::MimeSupportabilityCache;
 using Windows::Foundation::Metadata::ApiInformation;
 #if defined(INTERNAL_BUILD)
-using ::starboard::xb1::shared::Av1VideoDecoder;
+using ::starboard::xb1::shared::Dav1dVideoDecoder;
+using ::starboard::xb1::shared::GpuVideoDecoderBase;
 using ::starboard::xb1::shared::VpxVideoDecoder;
 #endif  // defined(INTERNAL_BUILD)
 
 const SbTime kReleaseTimeout = kSbTimeSecond;
 
+// kFrameBuffersPoolMemorySize is the size of gpu memory heap for common use
+// by vpx & av1 sw decoders.
+// This value must be greater then max(av1_min_value, vpx_min_value), where
+// av1_min_value & vpx_min_value are minimal required memory size for sw av1 &
+// vpx decoders.
+//
+// Vpx sw decoder needs 13 internal frame buffers for work and at least
+// 8 buffers for preroll.
+// The size of fb is 13762560 for 4K SDR and 12976128 for 2K HDR
+// So, vpx decoder needs minimum  13762560 * (13 + preroll_size) = 289013760
+// bytes.
+//
+// Av1 sw decoder needs 13 internal buffers and 8 buffers for preroll.
+// The size of fb is 5996544 for 2K SDR and 11993088 for 2K HDR
+// av1 decoder needs minimum  11993088 * (13 + preroll_size) = 251854848 bytes.
+//
+// So, the value 289013760 is minimal for reliable decoders working.
+//
+// To make playback more smooth it is better to increase the output queue size
+// up to 30-50 frames, but it should not exceed memory budgetd.
+// So, the value of 440 Mb looks as compromise.
+const uint64_t kFrameBuffersPoolMemorySize = 440 * 1024 * 1024;
+
 bool IsExtendedResourceModeRequired() {
   if (!::starboard::xb1::shared::CanAcquire()) {
     return false;
@@ -150,6 +174,7 @@
 
 bool ExtendedResourcesManager::GetD3D12Objects(
     Microsoft::WRL::ComPtr<ID3D12Device>* device,
+    Microsoft::WRL::ComPtr<ID3D12Heap>* buffer_heap,
     void** command_queue) {
   if (HasNonrecoverableFailure()) {
     SB_LOG(WARNING) << "The D3D12 device has encountered a nonrecoverable "
@@ -184,8 +209,8 @@
   D3D12_HEAP_PROPERTIES prop = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
   D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(1024 * 1024);
   HRESULT result = d3d12device_->CreateCommittedResource(
-      &prop, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COPY_DEST,
-      nullptr, IID_PPV_ARGS(&res));
+      &prop, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr,
+      IID_PPV_ARGS(&res));
   if (result != S_OK) {
     SB_LOG(WARNING) << "The D3D12 device is not in a good state, can not use "
                        "GPU based decoders.";
@@ -196,11 +221,25 @@
 
   *device = d3d12device_;
   *command_queue = d3d12queue_.Get();
+  *buffer_heap = d3d12FrameBuffersHeap_.Get();
   return true;
 }
 
 bool ExtendedResourcesManager::GetD3D12ObjectsInternal() {
   if (!d3d12device_) {
+    UINT dxgiFactoryFlags = 0;
+#if defined(_DEBUG)
+    {
+      // This can help to debug DX issues. If something goes wrong in DX,
+      // Debug Layer outputs detailed log
+      ComPtr<ID3D12Debug> debugController;
+      HRESULT hr = D3D12GetDebugInterface(IID_PPV_ARGS(&debugController));
+      if (SUCCEEDED(hr)) {
+        debugController->EnableDebugLayer();
+      }
+    }
+#endif
+
     if (FAILED(D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0,
                                  IID_PPV_ARGS(&d3d12device_)))) {
       // GPU based vp9 decoding will be temporarily disabled.
@@ -221,8 +260,26 @@
     }
     SB_DCHECK(d3d12queue_);
   }
+  if (!d3d12FrameBuffersHeap_) {
+    D3D12_HEAP_DESC heap_desc;
+    heap_desc.SizeInBytes = kFrameBuffersPoolMemorySize;
+    heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
+    heap_desc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
+    heap_desc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
+    heap_desc.Properties.CreationNodeMask = 0;
+    heap_desc.Properties.VisibleNodeMask = 0;
+    heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
+    heap_desc.Flags = D3D12_HEAP_FLAG_NONE;
 
-  return d3d12device_ && d3d12queue_;
+    if (FAILED(d3d12device_->CreateHeap(
+            &heap_desc, IID_PPV_ARGS(&d3d12FrameBuffersHeap_)))) {
+      SB_LOG(WARNING) << "Failed to create d3d12 buffer.";
+      return false;
+    }
+    SB_DCHECK(d3d12FrameBuffersHeap_);
+  }
+
+  return d3d12device_ && d3d12queue_ && d3d12FrameBuffersHeap_;
 }
 
 bool ExtendedResourcesManager::AcquireExtendedResourcesInternal() {
@@ -335,7 +392,7 @@
                          "shader compile.";
       return;
     }
-    if (Av1VideoDecoder::CompileShaders(d3d12device_, d3d12queue_.Get())) {
+    if (Dav1dVideoDecoder::CompileShaders(d3d12device_)) {
       is_av1_shader_compiled_ = true;
       SB_LOG(INFO) << "Gpu based AV1 decoder finished compiling its shaders.";
     } else {
@@ -352,7 +409,8 @@
       return;
     }
 
-    if (VpxVideoDecoder::CompileShaders(d3d12device_, d3d12queue_.Get())) {
+    if (VpxVideoDecoder::CompileShaders(d3d12device_, d3d12FrameBuffersHeap_,
+                                        d3d12queue_.Get())) {
       is_vp9_shader_compiled_ = true;
       SB_LOG(INFO) << "Gpu based VP9 decoder finished compiling its shaders.";
     } else {
@@ -372,10 +430,6 @@
 
 void ExtendedResourcesManager::ReleaseExtendedResourcesInternal() {
   SB_DCHECK(thread_checker_.CalledOnValidThread());
-#if defined(INTERNAL_BUILD)
-  Av1VideoDecoder::ClearFrameBufferPool();
-#endif  // defined(INTERNAL_BUILD)
-
   ScopedLock scoped_lock(mutex_);
   if (!is_extended_resources_acquired_.load()) {
     SB_LOG(INFO) << "Extended resources hasn't been acquired,"
@@ -410,7 +464,7 @@
           SB_LOG(INFO) << "CreateEvent() failed with " << GetLastError();
         }
 #if defined(INTERNAL_BUILD)
-        Av1VideoDecoder::ReleaseShaders();
+        Dav1dVideoDecoder::ReleaseShaders();
         VpxVideoDecoder::ReleaseShaders();
 #endif  // #if defined(INTERNAL_BUILD)
         is_av1_shader_compiled_ = false;
@@ -418,27 +472,41 @@
       } else {
         SB_LOG(INFO) << "CreateFence() failed with " << hr;
       }
+#if defined(INTERNAL_BUILD)
+      // Clear frame buffers used for rendering queue
+      GpuVideoDecoderBase::ClearFrameBuffersPool();
+#endif  // #if defined(INTERNAL_BUILD)
     }
 
     if (d3d12queue_) {
 #if !defined(COBALT_BUILD_TYPE_GOLD)
       d3d12queue_->AddRef();
       ULONG reference_count = d3d12queue_->Release();
-      SB_DLOG(INFO) << "Reference count of |d3d12queue_| is "
-                    << reference_count;
+      SB_LOG(INFO) << "Reference count of |d3d12queue_| is " << reference_count;
 #endif
       d3d12queue_.Reset();
     }
 
+    if (d3d12FrameBuffersHeap_) {
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+      d3d12FrameBuffersHeap_->AddRef();
+      ULONG reference_count = d3d12FrameBuffersHeap_->Release();
+      SB_LOG(INFO) << "Reference count of |d3d12FrameBuffersHeap_| is "
+                   << reference_count;
+#endif
+      d3d12FrameBuffersHeap_.Reset();
+    }
+
     if (d3d12device_) {
 #if !defined(COBALT_BUILD_TYPE_GOLD)
       d3d12device_->AddRef();
       ULONG reference_count = d3d12device_->Release();
-      SB_DLOG(INFO) << "Reference count of |d3d12device_| is "
-                    << reference_count;
+      SB_LOG(INFO) << "Reference count of |d3d12device_| is "
+                   << reference_count;
 #endif
       d3d12device_.Reset();
     }
+
   } catch (const std::exception& e) {
     SB_LOG(ERROR) << "Exception on releasing extended resources: " << e.what();
     OnNonrecoverableFailure();
diff --git a/starboard/shared/uwp/extended_resources_manager.h b/starboard/shared/uwp/extended_resources_manager.h
index fbd8a5b..54b966f 100644
--- a/starboard/shared/uwp/extended_resources_manager.h
+++ b/starboard/shared/uwp/extended_resources_manager.h
@@ -47,8 +47,10 @@
   void ReleaseExtendedResources();
   void Quit();
 
-  // Returns true when the d3d12 device and command queue can be used.
+  // Returns true when the d3d12 device, buffer heap
+  // and command queue can be used.
   bool GetD3D12Objects(Microsoft::WRL::ComPtr<ID3D12Device>* device,
+                       Microsoft::WRL::ComPtr<ID3D12Heap>* buffer_heap,
                        void** command_queue);
 
   bool IsGpuDecoderReady() const {
@@ -91,6 +93,8 @@
   Queue<Event> event_queue_;
   Microsoft::WRL::ComPtr<ID3D12Device> d3d12device_;
   Microsoft::WRL::ComPtr<ID3D12CommandQueue> d3d12queue_;
+  // heap for frame buffers (for the decoder and output queue) memory allocation
+  Microsoft::WRL::ComPtr<ID3D12Heap> d3d12FrameBuffersHeap_;
 
   // This is set to true when a release of extended resources is requested.
   // Anything delaying the release should be expedited when this is set.
diff --git a/starboard/shared/uwp/player_components_factory.cc b/starboard/shared/uwp/player_components_factory.cc
index 6963151..c8511be 100644
--- a/starboard/shared/uwp/player_components_factory.cc
+++ b/starboard/shared/uwp/player_components_factory.cc
@@ -43,7 +43,7 @@
 #include "starboard/xb1/shared/video_decoder_uwp.h"
 
 #if defined(INTERNAL_BUILD)
-#include "internal/starboard/xb1/av1_video_decoder.h"
+#include "internal/starboard/xb1/dav1d_video_decoder.h"
 #include "internal/starboard/xb1/vpx_video_decoder.h"
 #endif  // defined(INTERNAL_BUILD)
 
@@ -236,9 +236,10 @@
     SB_DCHECK(output_mode == kSbPlayerOutputModeDecodeToTexture);
 
     Microsoft::WRL::ComPtr<ID3D12Device> d3d12device;
+    Microsoft::WRL::ComPtr<ID3D12Heap> d3d12buffer_heap;
     void* d3d12queue = nullptr;
     if (!uwp::ExtendedResourcesManager::GetInstance()->GetD3D12Objects(
-            &d3d12device, &d3d12queue)) {
+            &d3d12device, &d3d12buffer_heap, &d3d12queue)) {
       // Somehow extended resources get lost.  Returns directly to trigger an
       // error to the player.
       *error_message =
@@ -248,24 +249,25 @@
       return false;
     }
     SB_DCHECK(d3d12device);
+    SB_DCHECK(d3d12buffer_heap);
     SB_DCHECK(d3d12queue);
 
 #if defined(INTERNAL_BUILD)
     using GpuVp9VideoDecoder = ::starboard::xb1::shared::VpxVideoDecoder;
-    using GpuAv1VideoDecoder = ::starboard::xb1::shared::Av1VideoDecoder;
+    using GpuAv1VideoDecoder = ::starboard::xb1::shared::Dav1dVideoDecoder;
 
     if (video_codec == kSbMediaVideoCodecVp9) {
       video_decoder->reset(new GpuVp9VideoDecoder(
           creation_parameters.decode_target_graphics_context_provider(),
           creation_parameters.video_stream_info(), is_hdr_video, d3d12device,
-          d3d12queue));
+          d3d12buffer_heap, d3d12queue));
     }
 
     if (video_codec == kSbMediaVideoCodecAv1) {
       video_decoder->reset(new GpuAv1VideoDecoder(
           creation_parameters.decode_target_graphics_context_provider(),
           creation_parameters.video_stream_info(), is_hdr_video, d3d12device,
-          d3d12queue));
+          d3d12buffer_heap, d3d12queue));
     }
 #endif  // defined(INTERNAL_BUILD)
 
diff --git a/starboard/shared/uwp/time_zone_get_name.cc b/starboard/shared/uwp/time_zone_get_name.cc
new file mode 100644
index 0000000..58fc8b9
--- /dev/null
+++ b/starboard/shared/uwp/time_zone_get_name.cc
@@ -0,0 +1,45 @@
+// Copyright 2023 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/time_zone.h"

+

+#include <Windows.h>

+#include <string>

+

+#include "starboard/once.h"

+#include "starboard/shared/win32/wchar_utils.h"

+

+namespace {

+class TimeZoneString {

+ public:

+  static TimeZoneString* Get();

+  const char* value() const { return value_.c_str(); }

+

+ private:

+  TimeZoneString() {

+    Windows::Globalization::Calendar ^ calendar =

+        ref new Windows::Globalization::Calendar();

+    Platform::String ^ time_zone = calendar->GetTimeZone();

+    value_ = starboard::shared::win32::platformStringToString(time_zone);

+  }

+  std::string value_;

+};

+

+SB_ONCE_INITIALIZE_FUNCTION(TimeZoneString, TimeZoneString::Get);

+}  // namespace.

+

+const char* SbTimeZoneGetName() {

+  const char* output = TimeZoneString::Get()->value();

+  return output;

+}

diff --git a/starboard/shared/uwp/wasapi_audio_sink.cc b/starboard/shared/uwp/wasapi_audio_sink.cc
index 2beffa1..d8f5990 100644
--- a/starboard/shared/uwp/wasapi_audio_sink.cc
+++ b/starboard/shared/uwp/wasapi_audio_sink.cc
@@ -97,9 +97,9 @@
     return false;

   }

 

-  hr = device_->Activate(

-      IID_IAudioEndpointVolume, CLSCTX_ALL, NULL,

-      reinterpret_cast<void**>(audio_endpoint_volume_.GetAddressOf()));

+  hr = audio_client_->GetService(

+      IID_ISimpleAudioVolume,

+      reinterpret_cast<void**>(audio_volume_.GetAddressOf()));

   if (hr != S_OK) {

     SB_LOG(ERROR) << "Failed to initialize volume handler, error code: "

                   << std::hex << hr;

@@ -294,7 +294,7 @@
   }

   double volume = volume_.load();

   if (current_volume_ != volume) {

-    hr = audio_endpoint_volume_->SetMasterVolumeLevelScalar(volume, NULL);

+    hr = audio_volume_->SetMasterVolume(volume, NULL);

     CHECK_HRESULT_OK(hr);

     current_volume_ = volume;

   }

diff --git a/starboard/shared/uwp/wasapi_audio_sink.h b/starboard/shared/uwp/wasapi_audio_sink.h
index 86863a4..3d94f53 100644
--- a/starboard/shared/uwp/wasapi_audio_sink.h
+++ b/starboard/shared/uwp/wasapi_audio_sink.h
@@ -16,11 +16,9 @@
 #define STARBOARD_SHARED_UWP_WASAPI_AUDIO_SINK_H_

 

 #include <Audioclient.h>

-#include <endpointvolume.h>

 #include <mmdeviceapi.h>

 #include <wrl\client.h>

 

-#include <atomic>

 #include <functional>

 #include <queue>

 

@@ -119,7 +117,6 @@
 };

 

 const IID IID_IAudioClock = __uuidof(IAudioClock);

-const IID IID_IAudioEndpointVolume = __uuidof(IAudioEndpointVolume);

 const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);

 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);

 const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume);

@@ -168,7 +165,7 @@
   Microsoft::WRL::ComPtr<IMMDevice> device_;

   Microsoft::WRL::ComPtr<IAudioClient3> audio_client_;

   Microsoft::WRL::ComPtr<IAudioRenderClient> render_client_;

-  Microsoft::WRL::ComPtr<IAudioEndpointVolume> audio_endpoint_volume_;

+  Microsoft::WRL::ComPtr<ISimpleAudioVolume> audio_volume_;

 

   Mutex audio_clock_mutex_;

   Microsoft::WRL::ComPtr<IAudioClock> audio_clock_;

diff --git a/starboard/shared/widevine/drm_system_widevine.cc b/starboard/shared/widevine/drm_system_widevine.cc
index 08729a8..3320eba 100644
--- a/starboard/shared/widevine/drm_system_widevine.cc
+++ b/starboard/shared/widevine/drm_system_widevine.cc
@@ -157,9 +157,10 @@
 SB_ONCE_INITIALIZE_FUNCTION(Mutex, GetInitializationMutex);
 
 void EnsureWidevineCdmIsInitialized(const std::string& company_name,
-                                    const std::string& model_name) {
+                                    const std::string& model_name,
+                                    WidevineStorage* storage) {
+  SB_DCHECK(storage) << "|storage| is NULL.";
   static WidevineClock s_clock;
-  static WidevineStorage s_storage(GetWidevineStoragePath());
   static WidevineTimer s_timer;
   static bool s_initialized = false;
 
@@ -190,7 +191,7 @@
   log_level = wv3cdm::kSilent;
 #endif  // COBALT_BUILD_TYPE_GOLD
   wv3cdm::Status status =
-      wv3cdm::initialize(wv3cdm::kNoSecureOutput, client_info, &s_storage,
+      wv3cdm::initialize(wv3cdm::kNoSecureOutput, client_info, storage,
                          &s_clock, &s_timer, log_level);
   SB_DCHECK(status == wv3cdm::kSuccess);
   s_initialized = true;
@@ -235,9 +236,10 @@
   }
 #endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
-  EnsureWidevineCdmIsInitialized(company_name, model_name);
+  static WidevineStorage s_storage(GetWidevineStoragePath());
+  EnsureWidevineCdmIsInitialized(company_name, model_name, &s_storage);
   const bool kEnablePrivacyMode = true;
-  cdm_.reset(wv3cdm::create(this, NULL, kEnablePrivacyMode));
+  cdm_.reset(wv3cdm::create(this, &s_storage, kEnablePrivacyMode));
   SB_DCHECK(cdm_);
 
   // Get cert scope and pass to widevine.
diff --git a/starboard/shared/win32/dx_context_video_decoder.cc b/starboard/shared/win32/dx_context_video_decoder.cc
index 1ca7403..b576659 100644
--- a/starboard/shared/win32/dx_context_video_decoder.cc
+++ b/starboard/shared/win32/dx_context_video_decoder.cc
@@ -45,7 +45,7 @@
   query_display(display, EGL_DEVICE_EXT, &egl_device);
   SB_DCHECK(egl_device != 0);
 
-  intptr_t device;
+  intptr_t device = 0;
   query_device(reinterpret_cast<EGLDeviceEXT>(egl_device),
                EGL_D3D11_DEVICE_ANGLE, &device);
 
diff --git a/starboard/shared/win32/test_filters.py b/starboard/shared/win32/test_filters.py
index 2619466..352adbb 100644
--- a/starboard/shared/win32/test_filters.py
+++ b/starboard/shared/win32/test_filters.py
@@ -15,7 +15,15 @@
 
 from starboard.tools.testing import test_filter
 
-_FILTERED_TESTS = {}
+_FILTERED_TESTS = {
+    'nplb': [
+        # Windows uses a special time zone format that ICU accepts, so we don't
+        # enforce IANA.
+        # TODO(b/304335954): Re-enable the test for UWP after fixing DST
+        # implementation.
+        'SbTimeZoneGetNameTest.IsIANAFormat',
+    ],
+}
 
 
 class TestFilters(object):
diff --git a/starboard/shared/win32/time_zone_get_name.cc b/starboard/shared/win32/time_zone_get_name.cc
index bc92e3b..9c31522 100644
--- a/starboard/shared/win32/time_zone_get_name.cc
+++ b/starboard/shared/win32/time_zone_get_name.cc
@@ -28,6 +28,10 @@
   const char* value() const { return value_.c_str(); }
 
  private:
+  // Returns a string representing a time zone name, e.g. "EST" for Eastern
+  // Standard Time or "PDT" for Pacific Daylight Time. There isn't a native way
+  // to convert these to IANA name format on Windows without UWP, so we're
+  // making use of GetDynamicTimeZoneInformation for now.
   TimeZoneString() {
     DYNAMIC_TIME_ZONE_INFORMATION time_zone_info;
     DWORD zone_id = GetDynamicTimeZoneInformation(&time_zone_info);
diff --git a/starboard/shared/win32/video_decoder.cc b/starboard/shared/win32/video_decoder.cc
index b69cdaf..f889019 100644
--- a/starboard/shared/win32/video_decoder.cc
+++ b/starboard/shared/win32/video_decoder.cc
@@ -194,6 +194,10 @@
   HardwareDecoderContext hardware_context = GetDirectXForHardwareDecoding();
   d3d_device_ = hardware_context.dx_device_out;
   device_manager_ = hardware_context.dxgi_device_manager_out;
+  if (!d3d_device_ || !device_manager_) {
+    return;
+  }
+
   HRESULT hr = d3d_device_.As(&video_device_);
   if (FAILED(hr)) {
     return;
diff --git a/starboard/time_zone.h b/starboard/time_zone.h
index ce9e424..d140338 100644
--- a/starboard/time_zone.h
+++ b/starboard/time_zone.h
@@ -29,18 +29,17 @@
 // The number of minutes west of the Greenwich Prime Meridian, NOT including
 // Daylight Savings Time adjustments.
 //
-// For example: PST/PDT is 480 minutes (28800 seconds, 8 hours).
+// For example: America/Los_Angeles is 480 minutes (28800 seconds, 8 hours).
 typedef int SbTimeZone;
 
 // Gets the system's current SbTimeZone in minutes.
 SB_EXPORT SbTimeZone SbTimeZoneGetCurrent();
 
-// Gets a string representation of the current timezone. Note that the string
-// representation can either be standard or daylight saving time. The output
-// can be of the form:
-//   1) A three-letter abbreviation such as "PST" or "PDT" (preferred).
-//   2) A time zone identifier such as "America/Los_Angeles"
-//   3) An un-abbreviated name such as "Pacific Standard Time".
+// Gets a string representation of the current timezone. The format should be
+// in the IANA format https://data.iana.org/time-zones/theory.html#naming .
+// Names normally have the form AREA/LOCATION, where AREA is a continent or
+// ocean, and LOCATION is a specific location within the area.
+// Typical names are 'Africa/Cairo', 'America/New_York', and 'Pacific/Honolulu'.
 SB_EXPORT const char* SbTimeZoneGetName();
 
 #ifdef __cplusplus
diff --git a/starboard/tools/abstract_launcher.py b/starboard/tools/abstract_launcher.py
index 9928039..d57589e 100644
--- a/starboard/tools/abstract_launcher.py
+++ b/starboard/tools/abstract_launcher.py
@@ -18,6 +18,7 @@
 import abc
 import os
 import sys
+from enum import IntEnum
 
 from starboard.tools import build
 from starboard.tools import paths
@@ -78,7 +79,7 @@
     RuntimeError: The platform does not exist, or there is no project root.
   """
 
-  #  Creates launcher for provided platform if the platform has a valid port
+  #  Creates launcher for provided platform if the platform has a valid port.
   launcher_module = _GetLauncherForPlatform(platform_name)
 
   if not launcher_module:
@@ -97,8 +98,51 @@
       **kwargs)
 
 
+class TargetStatus(IntEnum):
+  """Represents status of the target run and its return code availability."""
+
+  # Target exited normally. Return code is available.
+  OK = 0
+
+  # Target exited normally. Return code is not available.
+  NA = 1
+
+  # Target crashed.
+  CRASH = 2
+
+  # Target not started.
+  NOT_STARTED = 3
+
+  @classmethod
+  def ToString(cls, status):
+    return [
+        "SUCCEEDED", "SUCCEEDED", "FAILED (CRASHED)", "FAILED (NOT STARTED)"
+    ][status]
+
+
 class AbstractLauncher(object):
-  """Class that specifies all required behavior for Cobalt app launchers."""
+  """
+  Class that specifies all required behavior for Cobalt app launchers.
+  The following is definition of extended interface. Implementation is optional.
+  Functions:
+    - InitDevice: a function to be called before any other performance
+      on the target device.
+    - RebootDevice: a function, used to reboot the target device.
+    - Run2: a function to run a target on device. Must be implemented
+      to support extended interface. The target must have been deployed
+      on the device, if required.
+      Returns:
+        a tuple of ReturnCodeStatus and, target return code if available,
+        else 0.
+    - Deploy: creates a package (if required) and deploys it on the target
+      device.
+    - CheckPackageIsDeployed: a function, which returns True, if the target
+      is deployed on the device, False otherwise. Must be implemented,
+      if Deploy is implemented.
+    - Kill: request the target OS to kill the target process. Must be
+      implemented.
+    - SendStop: sends stop signal to the launcher's executable.
+  """
 
   __metaclass__ = abc.ABCMeta
 
@@ -143,30 +187,32 @@
 
     self.test_result_xml_path = kwargs.get("test_result_xml_path", None)
 
+  def HasExtendedInterface(self) -> bool:
+    return hasattr(self, "Run2")
+
   @abc.abstractmethod
   def Run(self):
-    """Runs the launcher's executable.
+    """Runs an underlying application. Supports launching target
+      executable on target device, or target executable directly.
+      Implementation is platform specific.
 
     Must be implemented in subclasses.
 
     Returns:
-      The return code from the launcher's executable.
+      The return code from the underlying application, if available,
+      else, 0 in case of normal completion of the underlying application,
+      otherwise 1.
     """
     pass
 
   @abc.abstractmethod
-  def Kill(self):
-    """Kills the launcher. Must be implemented in subclasses."""
-    pass
-
-  @abc.abstractmethod
   def GetDeviceIp(self):
     """Gets the device IP. Must be implemented in subclasses."""
     pass
 
   @abc.abstractmethod
   def GetDeviceOutputPath(self):
-    """Writable path where test targets can output files"""
+    """Writable path where test targets can output files."""
     pass
 
   def SupportsSuspendResume(self):
@@ -219,14 +265,6 @@
     """
     raise RuntimeError("Freeze not supported for this platform.")
 
-  def SendStop(self):
-    """sends stop signal to the launcher's executable.
-
-    Raises:
-      RuntimeError: Stop signal not supported on platform.
-    """
-    raise RuntimeError("Stop not supported for this platform.")
-
   def SupportsDeepLink(self):
     return False
 
@@ -290,7 +328,7 @@
 
     The default path returned by this method takes the form of:
 
-      "/path/to/out/<platform>_<config>/target_name"
+      "/path/to/out/<platform>_<config>/target_name".
 
     Returns:
       The path to an executable target.
@@ -302,7 +340,7 @@
 
     The default path returned by this method takes the form of:
 
-      "/path/to/out/<platform>_<config>/install/target_name"
+      "/path/to/out/<platform>_<config>/install/target_name".
 
     Returns:
       The path to an executable target.
diff --git a/starboard/tools/testing/test_filter.py b/starboard/tools/testing/test_filter.py
index 6c2af98..6a787b0 100644
--- a/starboard/tools/testing/test_filter.py
+++ b/starboard/tools/testing/test_filter.py
@@ -19,6 +19,13 @@
 FILTER_ALL = 'FILTER_ALL'
 DISABLE_TESTING = 'DISABLE_TESTING'
 
+EVERGREEN_ONLY_TESTS = {
+    'elf_loader_test': {FILTER_ALL},
+    'installation_manager_test': {FILTER_ALL},
+    'reset_evergreen_update_test': {FILTER_ALL},
+    'slot_management_test': {FILTER_ALL},
+}
+
 
 class TestFilter(object):
   """Container for data used to filter out a unit test.
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index 8ae6f49..0fb343a 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -29,6 +29,7 @@
 from six.moves import cStringIO as StringIO
 from starboard.build import clang
 from starboard.tools import abstract_launcher
+from starboard.tools.abstract_launcher import TargetStatus
 from starboard.tools import build
 from starboard.tools import command_line
 from starboard.tools import paths
@@ -160,14 +161,33 @@
   communicate, and for the main thread to shut them down.
   """
 
-  def __init__(self, launcher):
+  def __init__(self, launcher, skip_init):
     self.launcher = launcher
-    self.runner_thread = threading.Thread(target=self._Run)
 
     self.return_code_lock = threading.Lock()
     self.return_code = 1
+    self.run_test = None
+    self.skip_init = skip_init
 
   def Start(self):
+    if self.launcher.HasExtendedInterface():
+      if not self.skip_init:
+        if hasattr(self.launcher, "InitDevice"):
+          self.launcher.InitDevice()
+        if hasattr(self.launcher, "Deploy"):
+          assert hasattr(self.launcher, "CheckPackageIsDeployed")
+          if abstract_launcher.ARG_NOINSTALL not in self.launcher.launcher_args:
+            self.launcher.Deploy()
+          if not self.launcher.CheckPackageIsDeployed():
+            raise IOError(
+                "The target application is not installed on the device.")
+
+      self.run_test = self.launcher.Run2
+
+    else:
+      self.run_test = lambda: (TargetStatus.OK, self.launcher.Run())
+
+    self.runner_thread = threading.Thread(target=self._Run)
     self.runner_thread.start()
 
   def Kill(self):
@@ -183,12 +203,12 @@
   def Join(self):
     self.runner_thread.join()
 
-  def _Run(self):
-    """Runs the launcher, and assigns a return code."""
-    return_code = 1
+  def _Run(self) -> None:
+    """Runs the launcher, and assigns a status and a return code."""
+    return_code = TargetStatus.NOT_STARTED, 0
     try:
       logging.info("Running launcher")
-      return_code = self.launcher.Run()
+      return_code = self.run_test()
       logging.info("Finished running launcher")
     except Exception:  # pylint: disable=broad-except
       sys.stderr.write(f"Error while running {self.launcher.target_name}:\n")
@@ -259,6 +279,7 @@
     self.xml_output_dir = xml_output_dir
     self.log_xml_results = log_xml_results
     self.threads = []
+    self.is_initialized = False
 
     _EnsureBuildDirectoryExists(self.out_directory)
     _VerifyConfig(self._platform_config,
@@ -273,6 +294,7 @@
 
     # If a particular test binary has been provided, configure only that one.
     logging.info("Getting test targets")
+
     if specified_targets:
       self.test_targets = self._GetSpecifiedTestTargets(specified_targets)
     else:
@@ -392,11 +414,11 @@
         env_variables[test] = test_env
     return env_variables
 
-  def _RunTest(self,
-               target_name,
-               test_name=None,
-               shard_index=None,
-               shard_count=None):
+  def RunTest(self,
+              target_name,
+              test_name=None,
+              shard_index=None,
+              shard_count=None):
     """Runs a specific target or test and collects the output.
 
     Args:
@@ -497,7 +519,7 @@
     logging.info("Launcher initialized")
 
     test_reader = TestLineReader(read_pipe)
-    test_launcher = TestLauncher(launcher)
+    test_launcher = TestLauncher(launcher, self.is_initialized)
 
     self.threads.append(test_launcher)
     self.threads.append(test_reader)
@@ -524,6 +546,7 @@
     # Wait for the launcher to exit then close the write pipe, which will
     # cause the reader to exit.
     test_launcher.Join()
+    self.is_initialized = True
     write_pipe.close()
 
     # Only after closing the write pipe, wait for the reader to exit.
@@ -533,10 +556,10 @@
     output = test_reader.GetLines()
 
     self.threads = []
-    return self._CollectTestResults(output, target_name,
-                                    test_launcher.GetReturnCode())
+    return (target_name, *self._CollectTestResults(output),
+            *test_launcher.GetReturnCode())
 
-  def _CollectTestResults(self, results, target_name, return_code):
+  def _CollectTestResults(self, results):
     """Collects passing and failing tests for one test binary.
 
     Args:
@@ -570,8 +593,7 @@
         # Descriptions of all failed tests appear after this line
         failed_tests = self._CollectFailedTests(results[idx + 1:])
 
-    return (target_name, total_count, passed_count, failed_count, failed_tests,
-            return_code)
+    return (total_count, passed_count, failed_count, failed_tests)
 
   def _CollectFailedTests(self, lines):
     """Collects the names of all failed tests.
@@ -630,7 +652,8 @@
       passed_count = result_set[2]
       failed_count = result_set[3]
       failed_tests = result_set[4]
-      return_code = result_set[5]
+      return_code_status = result_set[5]
+      return_code = result_set[6]
       actual_failed_tests = []
       flaky_failed_tests = []
       filtered_tests = self._GetFilteredTestList(target_name)
@@ -660,7 +683,7 @@
           for retry in range(_FLAKY_RETRY_LIMIT):
             # Sometimes the returned test "name" includes information about the
             # parameter that was passed to it. This needs to be stripped off.
-            retry_result = self._RunTest(target_name, test_case.split(",")[0])
+            retry_result = self.RunTest(target_name, test_case.split(",")[0])
             print()  # Explicit print for empty formatting line.
             if retry_result[2] == 1:
               flaky_passed_tests.append(test_case)
@@ -676,24 +699,26 @@
       else:
         logging.info("")  # formatting newline.
 
-      test_status = "SUCCEEDED"
+      test_status = TargetStatus.ToString(return_code_status)
 
       all_flaky_tests_succeeded = initial_flaky_failed_count == len(
           flaky_passed_tests) and initial_flaky_failed_count != 0
 
       # Always mark as FAILED if we have a non-zero return code, or failing
       # test.
-      if ((return_code != 0 and not all_flaky_tests_succeeded) or
-          actual_failed_count > 0 or flaky_failed_count > 0):
+      if ((return_code_status == TargetStatus.OK and return_code != 0 and
+           not all_flaky_tests_succeeded) or actual_failed_count > 0 or
+          flaky_failed_count > 0):
         error = True
-        test_status = "FAILED"
         failed_test_groups.append(target_name)
         # Be specific about the cause of failure if it was caused due to crash
         # upon exit. Normal Gtest failures have return_code = 1; test crashes
         # yield different return codes (e.g. segfault has return_code = 11).
         if (return_code != 1 and actual_failed_count == 0 and
             flaky_failed_count == 0):
-          test_status = "FAILED (CRASHED)"
+          test_status = "FAILED (ISSUE)"
+        else:
+          test_status = "FAILED"
 
       logging.info("%s: %s.", target_name, test_status)
       if return_code != 0 and run_count == 0 and filtered_count == 0:
@@ -808,12 +833,12 @@
         if run_action == ShardingTestConfig.RUN_FULL_TEST:
           logging.info("SHARD %d RUNS TEST %s (full)", self.shard_index,
                        test_target)
-          results.append(self._RunTest(test_target))
+          results.append(self.RunTest(test_target))
         elif run_action == ShardingTestConfig.RUN_PARTIAL_TEST:
           logging.info("SHARD %d RUNS TEST %s (%d of %d)", self.shard_index,
                        test_target, sub_shard_index + 1, sub_shard_count)
           results.append(
-              self._RunTest(
+              self.RunTest(
                   test_target,
                   shard_index=sub_shard_index,
                   shard_count=sub_shard_count))
@@ -822,7 +847,7 @@
           logging.info("SHARD %d SKIP TEST %s", self.shard_index, test_target)
       else:
         # Run all tests and cases serially. No sharding enabled.
-        results.append(self._RunTest(test_target))
+        results.append(self.RunTest(test_target))
     return self._ProcessAllTestResults(results)
 
   def GenerateCoverageReport(self):
@@ -957,6 +982,7 @@
     launcher_args.append(abstract_launcher.ARG_DRYRUN)
 
   logging.info("Initializing test runner")
+
   runner = TestRunner(args.platform, args.config, args.loader_platform,
                       args.loader_config, args.device_id, args.target_name,
                       target_params, args.out_directory,
@@ -991,7 +1017,16 @@
       return 1
 
   if args.run:
-    run_success = runner.RunAllTests()
+    if isinstance(args.target_name, list) and len(args.target_name) == 1:
+      r = runner.RunTest(args.target_name[0])
+      if r[5] == TargetStatus.OK:
+        return r[6]
+      elif r[5] == TargetStatus.NA:
+        return 0
+      else:
+        return 1
+    else:
+      run_success = runner.RunAllTests()
 
   runner.GenerateCoverageReport()
 
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn
index 9501d8f..a99e7e8 100644
--- a/starboard/win/shared/BUILD.gn
+++ b/starboard/win/shared/BUILD.gn
@@ -30,16 +30,18 @@
 
   cflags = [
     "/EHsc",  # C++ exceptions (required with /ZW)
-  ]
-
-  cflags += [
     "/FU${msvc_path}/lib/x86/store/references/platform.winmd",
     "/FU${windows_sdk_path}/References/$wdk_version/Windows.Foundation.FoundationContract/4.0.0.0/Windows.Foundation.FoundationContract.winmd",
     "/FU${windows_sdk_path}/References/$wdk_version/Windows.Foundation.UniversalApiContract/14.0.0.0/Windows.Foundation.UniversalApiContract.winmd",
     "/FU${windows_sdk_path}/References/$wdk_version/Windows.UI.ViewManagement.ViewManagementViewScalingContract/1.0.0.0/Windows.UI.ViewManagement.ViewManagementViewScalingContract.winmd",
-    "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.ApplicationResourcesContract/2.0.0.0/Windows.Xbox.ApplicationResourcesContract.winmd",
-    "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract/1.0.0.0/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract.winmd",
   ]
+
+  if (is_internal_build) {
+    cflags += [
+      "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.ApplicationResourcesContract/2.0.0.0/Windows.Xbox.ApplicationResourcesContract.winmd",
+      "/FU${windows_sdk_path}/References/$wdk_version/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract/1.0.0.0/Windows.Xbox.Security.ApplicationSpecificDeviceAuthenticationContract.winmd",
+    ]
+  }
 }
 
 static_library("starboard_platform") {
@@ -304,7 +306,6 @@
     "//starboard/shared/win32/time_get_now.cc",
     "//starboard/shared/win32/time_utils.h",
     "//starboard/shared/win32/time_zone_get_current.cc",
-    "//starboard/shared/win32/time_zone_get_name.cc",
     "//starboard/shared/win32/video_decoder.cc",
     "//starboard/shared/win32/video_decoder.h",
     "//starboard/shared/win32/wasapi_include.h",
diff --git a/starboard/win/win32/BUILD.gn b/starboard/win/win32/BUILD.gn
index e458622..746db84 100644
--- a/starboard/win/win32/BUILD.gn
+++ b/starboard/win/win32/BUILD.gn
@@ -59,6 +59,7 @@
     "//starboard/shared/win32/system_get_used_cpu_memory.cc",
     "//starboard/shared/win32/system_raise_platform_error.cc",
     "//starboard/shared/win32/system_symbolize.cc",
+    "//starboard/shared/win32/time_zone_get_name.cc",
     "//starboard/shared/win32/window_create.cc",
     "//starboard/shared/win32/window_destroy.cc",
     "//starboard/shared/win32/window_get_platform_handle.cc",
diff --git a/starboard/win/win32/test_filters.py b/starboard/win/win32/test_filters.py
index cf8621f..98be41f 100644
--- a/starboard/win/win32/test_filters.py
+++ b/starboard/win/win32/test_filters.py
@@ -87,6 +87,7 @@
       return [test_filter.DISABLE_TESTING]
     else:
       filters = super().GetTestFilters()
+      _FILTERED_TESTS.update(test_filter.EVERGREEN_ONLY_TESTS)
       for target, tests in _FILTERED_TESTS.items():
         filters.extend(test_filter.TestFilter(target, test) for test in tests)
       if os.environ.get('EXPERIMENTAL_CI', '0') == '1':
diff --git a/starboard/xb1/BUILD.gn b/starboard/xb1/BUILD.gn
index 5a54c16..e4137bc 100644
--- a/starboard/xb1/BUILD.gn
+++ b/starboard/xb1/BUILD.gn
@@ -176,6 +176,10 @@
     "//starboard/shared/win32/media_get_max_buffer_capacity.cc",
     "//starboard/shared/win32/media_transform.cc",
     "//starboard/shared/win32/media_transform.h",
+
+    # TODO (b/304335954): Use uwp implementation for correct IANA name once
+    # daylight savings offset is fixed.
+    "//starboard/shared/win32/time_zone_get_name.cc",
     "//starboard/shared/win32/video_decoder.cc",
     "//starboard/shared/win32/video_decoder.h",
     "//starboard/shared/win32/win32_audio_decoder.cc",
@@ -213,8 +217,8 @@
   if (is_internal_build) {
     sources += [
       "//internal/starboard/shared/uwp/keys.cc",
-      "//internal/starboard/xb1/av1_video_decoder.cc",
-      "//internal/starboard/xb1/av1_video_decoder.h",
+      "//internal/starboard/xb1/dav1d_video_decoder.cc",
+      "//internal/starboard/xb1/dav1d_video_decoder.h",
       "//internal/starboard/xb1/drm_create_system.cc",
       "//internal/starboard/xb1/internal_shims.cc",
       "//internal/starboard/xb1/media_is_supported.cc",
@@ -234,7 +238,7 @@
       "//starboard/shared/widevine:oemcrypto",
       "//third_party/internal/ce_cdm/cdm:widevine_cdm_core",
       "//third_party/internal/ce_cdm/cdm:widevine_ce_cdm_static",
-      "//third_party/internal/libav1_xb1",
+      "//third_party/internal/dav1d_gpu/xb1:dav1d_xb1",
       "//third_party/internal/libvpx_xb1",
     ]
   } else {
diff --git a/starboard/xb1/launcher.py b/starboard/xb1/launcher.py
index 9be3550..583aa3b 100644
--- a/starboard/xb1/launcher.py
+++ b/starboard/xb1/launcher.py
@@ -62,12 +62,7 @@
 
   # All other functions are automatically delegated using this function.
   def __getattr__(self, fname):
-
-    def method(*args):
-      f = getattr(self.delegate, fname)
-      return f(*args)
-
-    return method
+    return getattr(self.delegate, fname)
 
   def GetDeviceIp(self):
     """Gets the device IP. TODO: Implement."""
diff --git a/starboard/xb1/platform_configuration/BUILD.gn b/starboard/xb1/platform_configuration/BUILD.gn
index 68b7862..db2a0b2 100644
--- a/starboard/xb1/platform_configuration/BUILD.gn
+++ b/starboard/xb1/platform_configuration/BUILD.gn
@@ -59,7 +59,9 @@
     ]
   }
 
-  ldflags += [ "/DEBUG:FASTLINK" ]
+  if (is_qa || is_gold || !cobalt_fastbuild) {
+    ldflags += [ "/DEBUG:FASTLINK" ]
+  }
 
   ldflags += [ "/NODEFAULTLIB" ]
   arflags += [ "/NODEFAULTLIB" ]
diff --git a/starboard/xb1/shared/gpu_base_video_decoder.cc b/starboard/xb1/shared/gpu_base_video_decoder.cc
index 32749b1..84bab91 100644
--- a/starboard/xb1/shared/gpu_base_video_decoder.cc
+++ b/starboard/xb1/shared/gpu_base_video_decoder.cc
@@ -16,7 +16,9 @@
 

 #include <d3d11_1.h>

 #include <wrl/client.h>

+#include <algorithm>

 

+#include "starboard/once.h"

 #include "starboard/shared/uwp/application_uwp.h"

 #include "starboard/shared/uwp/async_utils.h"

 #include "starboard/shared/uwp/decoder_utils.h"

@@ -53,11 +55,78 @@
 constexpr int kMaxNumberOfPendingBuffers = 8;

 // Limit the cached presenting images.

 constexpr int kNumberOfCachedPresentingImage = 3;

+// The number of frame buffers in decoder

+constexpr int kNumOutputFrameBuffers = 7;

 

 const char kDecoderThreadName[] = "gpu_video_decoder_thread";

-

 }  // namespace

 

+class GpuFrameBufferPool {

+ public:

+  HRESULT AllocateFrameBuffers(

+      uint16_t width,

+      uint16_t height,

+      DXGI_FORMAT dxgi_format,

+      Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device,

+      Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device) {

+    HRESULT hr;

+    uint16_t number_of_buffers = kNumOutputFrameBuffers;

+    if (!frame_buffers_.empty()) {

+      auto& buffer = frame_buffers_.front();

+      D3D11_TEXTURE2D_DESC desc;

+      buffer->texture(0)->GetDesc(&desc);

+      if (desc.Format != dxgi_format || buffer->width() < width ||

+          buffer->height() < height ||

+          d3d11_device.Get() != buffer->device11().Get() ||

+          d3d12_device.Get() != buffer->device12().Get()) {

+        frame_buffers_.clear();

+      }

+    }

+    if (frame_buffers_.empty()) {

+      frame_buffers_.reserve(number_of_buffers);

+      while (number_of_buffers--) {

+        GpuVideoDecoderBase::GpuFrameBuffer* gpu_fb =

+            new GpuVideoDecoderBase::GpuFrameBuffer(width, height, dxgi_format,

+                                                    d3d11_device, d3d12_device);

+        hr = gpu_fb->CreateTextures();

+        if (FAILED(hr)) {

+          frame_buffers_.clear();

+          return hr;

+        }

+        frame_buffers_.emplace_back(gpu_fb);

+      }

+    }

+    return S_OK;

+  }

+

+  GpuVideoDecoderBase::GpuFrameBuffer* GetFreeBuffer() {

+    SB_DCHECK(!frame_buffers_.empty());

+    auto iter = std::find_if(

+        frame_buffers_.begin(), frame_buffers_.end(),

+        [](const auto& frame_buffer) { return frame_buffer->HasOneRef(); });

+    if (iter == frame_buffers_.end())

+      return nullptr;

+    else

+      return iter->get();

+  }

+

+  bool CheckIfAllBuffersAreReleased() {

+    for (auto&& frame_buffer : frame_buffers_) {

+      if (!frame_buffer->HasOneRef())

+        return false;

+    }

+    return true;

+  }

+

+  void Clear() { frame_buffers_.clear(); }

+

+ private:

+  std::vector<scoped_refptr<GpuVideoDecoderBase::GpuFrameBuffer>>

+      frame_buffers_;

+};

+

+SB_ONCE_INITIALIZE_FUNCTION(GpuFrameBufferPool, GetGpuFrameBufferPool);

+

 class GpuVideoDecoderBase::GPUDecodeTargetPrivate

     : public SbDecodeTargetPrivate {

  public:

@@ -85,7 +154,6 @@
     info.is_opaque = true;

     info.width = image->width();

     info.height = image->height();

-

     GLuint gl_textures_yuv[kNumberOfPlanes] = {};

     glGenTextures(kNumberOfPlanes, gl_textures_yuv);

     SB_DCHECK(glGetError() == GL_NO_ERROR);

@@ -160,19 +228,109 @@
   void* egl_config_;

 };

 

+GpuVideoDecoderBase::GpuFrameBuffer::GpuFrameBuffer(

+    uint16_t width,

+    uint16_t height,

+    DXGI_FORMAT dxgi_format,

+    Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device,

+    Microsoft::WRL::ComPtr<ID3D12Device> d3d12_device)

+    : d3d11_device_(d3d11_device), d3d12_device_(d3d12_device) {

+  SB_DCHECK(d3d11_device_);

+  SB_DCHECK(d3d12_device_);

+

+  texture_desc_.Format = dxgi_format;

+  texture_desc_.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

+  texture_desc_.DepthOrArraySize = 1;

+  texture_desc_.MipLevels = 1;

+  texture_desc_.SampleDesc.Count = 1;

+  texture_desc_.SampleDesc.Quality = 0;

+  texture_desc_.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;

+  texture_desc_.Layout = D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE;

+

+  width_ = width;

+  height_ = height;

+}

+

+HRESULT GpuVideoDecoderBase::GpuFrameBuffer::CreateTextures() {

+  const D3D12_HEAP_PROPERTIES kHeapPropertyTypeDefault = {

+      D3D12_HEAP_TYPE_DEFAULT, D3D12_CPU_PAGE_PROPERTY_UNKNOWN,

+      D3D12_MEMORY_POOL_UNKNOWN, 1, 1};

+  HRESULT hr = E_FAIL;

+  for (unsigned int i = 0; i < kNumberOfPlanes; i++) {

+    const int subsampling = i > 0;

+    const int plane_width =

+        (texture_desc_.Format == DXGI_FORMAT_R10G10B10A2_UNORM)

+            ? (((width_ + subsampling) >> subsampling) + 2) / 3

+            : ((width_ + subsampling) >> subsampling);

+    const int plane_height = (height_ + subsampling) >> subsampling;

+

+    // Create interop resources.

+    texture_desc_.Width = plane_width;

+    texture_desc_.Height = plane_height;

+    hr = d3d12_device_->CreateCommittedResource(

+        &kHeapPropertyTypeDefault, D3D12_HEAP_FLAG_SHARED, &texture_desc_,

+        D3D12_RESOURCE_STATE_RENDER_TARGET, 0,

+        IID_PPV_ARGS(&d3d12_resources_[i]));

+    SB_DCHECK(SUCCEEDED(hr));

+    if (FAILED(hr)) {

+      return hr;

+    }

+

+    // Lowering the priority of texture reduces the amount of texture

+    // thrashing when the Xbox attempts to transfer textures to faster

+    // memory as it become more reluctant to be moved.

+    Microsoft::WRL::ComPtr<ID3D12Device1> d3d12_device1;

+    if (SUCCEEDED(d3d12_device_.As(&d3d12_device1)) && d3d12_device1) {

+      Microsoft::WRL::ComPtr<ID3D12Pageable> d3d12_pageable;

+      if (SUCCEEDED(d3d12_resources_[i].As(&d3d12_pageable)) &&

+          d3d12_pageable) {

+        D3D12_RESIDENCY_PRIORITY priority = D3D12_RESIDENCY_PRIORITY_LOW;

+        hr = d3d12_device1->SetResidencyPriority(

+            1, d3d12_pageable.GetAddressOf(), &priority);

+        SB_DCHECK(SUCCEEDED(hr));

+        if (FAILED(hr)) {

+          return hr;

+        }

+      }

+    }

+

+    HANDLE interop_handle = 0;

+    hr = d3d12_device_->CreateSharedHandle(d3d12_resources_[i].Get(), 0,

+                                           GENERIC_ALL, NULL, &interop_handle);

+    SB_DCHECK(SUCCEEDED(hr));

+    if (FAILED(hr)) {

+      return hr;

+    }

+    hr = d3d11_device_->OpenSharedResource1(interop_handle,

+                                            IID_PPV_ARGS(&d3d11_textures_[i]));

+    SB_DCHECK(SUCCEEDED(hr));

+    if (FAILED(hr)) {

+      return hr;

+    }

+    CloseHandle(interop_handle);

+  }

+  return S_OK;

+}

+

 GpuVideoDecoderBase::GpuVideoDecoderBase(

     SbDecodeTargetGraphicsContextProvider*

         decode_target_graphics_context_provider,

     const VideoStreamInfo& video_stream_info,

     bool is_hdr_video,

+    bool is_10x3_preferred,

     const ComPtr<ID3D12Device>& d3d12_device,

+    const ComPtr<ID3D12Heap> d3d12OutputPoolBufferHeap,

     void* d3d12_queue)

     : decode_target_context_runner_(decode_target_graphics_context_provider),

       is_hdr_video_(is_hdr_video),

+      is_10x3_preferred_(is_10x3_preferred),

       d3d12_device_(d3d12_device),

-      d3d12_queue_(d3d12_queue) {

+      d3d12_queue_(d3d12_queue),

+      d3d12FrameBuffersHeap_(d3d12OutputPoolBufferHeap),

+      frame_buffers_condition_(frame_buffers_mutex_) {

   SB_DCHECK(d3d12_device_);

   SB_DCHECK(d3d12_queue_);

+  SB_DCHECK(d3d12FrameBuffersHeap_);

 

   egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);

   EGLint attribute_list[] = {EGL_SURFACE_TYPE,  // this must be first

@@ -215,6 +373,7 @@
   SB_DCHECK(written_inputs_.empty());

   SB_DCHECK(output_queue_.empty());

   SB_DCHECK(decoder_behavior_.load() == kDecodingStopped);

+  SB_DCHECK(GetGpuFrameBufferPool()->CheckIfAllBuffersAreReleased());

   // All presenting decode targets should be released.

   SB_DCHECK(presenting_decode_targets_.empty());

 

@@ -236,9 +395,18 @@
   error_cb_ = error_cb;

 }

 

+size_t GpuVideoDecoderBase::GetPrerollFrameCount() const {

+  // The underlying decoder has its own output queue. We notify the underlying

+  // decoder to preroll frames once we receive first needed frame. Then the

+  // underlying decoder will delay outputs until it has enough prerolled frames.

+  // When we receive the second output frame, the underlying decoder should

+  // already have enough prerolled frames in its own output queue. So, we always

+  // return 2 here.

+  return 2;

+}

+

 size_t GpuVideoDecoderBase::GetMaxNumberOfCachedFrames() const {

-  return GetMaxNumberOfCachedFramesInternal() -

-         number_of_presenting_decode_targets_;

+  return GetMaxNumberOfCachedFramesInternal() + kNumOutputFrameBuffers;

 }

 

 void GpuVideoDecoderBase::WriteInputBuffers(const InputBuffers& input_buffers) {

@@ -265,6 +433,7 @@
   {

     ScopedLock pending_inputs_lock(pending_inputs_mutex_);

     pending_inputs_.push_back(input_buffer);

+

     needs_more_input = pending_inputs_.size() < kMaxNumberOfPendingBuffers;

   }

   decoder_behavior_.store(kDecodingFrames);

@@ -309,7 +478,10 @@
     decoder_thread_.reset();

   }

   pending_inputs_.clear();

-  written_inputs_.clear();

+  {

+    ScopedLock input_queue_lock(written_inputs_mutex_);

+    written_inputs_.clear();

+  }

   // Release all frames after decoder thread is destroyed.

   decoder_status_cb_(kReleaseAllFrames, nullptr);

   {

@@ -369,30 +541,26 @@
   return decoder_thread_->job_queue()->BelongsToCurrentThread();

 }

 

-void GpuVideoDecoderBase::OnOutputRetrieved(

+int GpuVideoDecoderBase::OnOutputRetrieved(

     const scoped_refptr<DecodedImage>& image) {

   SB_DCHECK(decoder_thread_);

   SB_DCHECK(decoder_status_cb_);

   SB_DCHECK(image);

 

   if (decoder_behavior_.load() == kResettingDecoder || error_occured_) {

-    return;

-  }

-

-  if (!BelongsToDecoderThread()) {

-    decoder_thread_->job_queue()->Schedule(

-        std::bind(&GpuVideoDecoderBase::OnOutputRetrieved, this, image));

-    return;

+    return 0;

   }

 

   SbTime timestamp = image->timestamp();

-  const auto iter = FindByTimestamp(written_inputs_, timestamp);

-  SB_DCHECK(iter != written_inputs_.cend());

-  if (is_hdr_video_) {

-    image->AttachColorMetadata((*iter)->video_stream_info().color_metadata);

+  {

+    ScopedLock input_queue_lock(written_inputs_mutex_);

+    const auto iter = FindByTimestamp(written_inputs_, timestamp);

+    SB_DCHECK(iter != written_inputs_.cend());

+    if (is_hdr_video_) {

+      image->AttachColorMetadata((*iter)->video_stream_info().color_metadata);

+    }

+    written_inputs_.erase(iter);

   }

-  written_inputs_.erase(iter);

-

   scoped_refptr<VideoFrameImpl> frame(new VideoFrameImpl(

       timestamp, std::bind(&GpuVideoDecoderBase::DeleteVideoFrame, this,

                            std::placeholders::_1)));

@@ -400,10 +568,21 @@
       decoder_behavior_.load() == kEndingStream ? kBufferFull : kNeedMoreInput,

       frame);

 

+  // The underlying decoder relies on the return value of OnOutputRetrieved() to

+  // determine stream preroll status. The underlying decoder will start

+  // prorolling at the first time it receives 1 from OnOutputRetrieved(). In

+  // other words, if OnOutputRetrieved() returns 1, the underlying decoder will

+  // delay next output until it has enough prerolled frames inside the

+  // underlying decoder.

   if (!frame->HasOneRef()) {

     ScopedLock output_queue_lock(output_queue_mutex_);

     output_queue_.push_back(image);

+    if (is_waiting_frame_after_drain_) {

+      is_waiting_frame_after_drain_ = false;

+      return 1;

+    }

   }

+  return 0;

 }

 

 void GpuVideoDecoderBase::OnDecoderDrained() {

@@ -412,9 +591,7 @@
   SB_DCHECK(decoder_behavior_.load() == kEndingStream ||

             decoder_behavior_.load() == kResettingDecoder);

 

-  if (decoder_behavior_.load() == kResettingDecoder || error_occured_) {

-    return;

-  }

+  is_waiting_frame_after_drain_ = true;

 

   if (!BelongsToDecoderThread()) {

     decoder_thread_->job_queue()->Schedule(

@@ -422,7 +599,6 @@
     return;

   }

 

-  SB_DCHECK(written_inputs_.empty());

   if (decoder_behavior_.load() == kEndingStream) {

     decoder_status_cb_(kBufferFull, VideoFrame::CreateEOSFrame());

   }

@@ -445,16 +621,6 @@
   }

 }

 

-bool GpuVideoDecoderBase::IsCacheFull() {

-  SB_DCHECK(decoder_thread_);

-  SB_DCHECK(BelongsToDecoderThread());

-

-  ScopedLock output_queue_lock(output_queue_mutex_);

-  return written_inputs_.size() + output_queue_.size() +

-             number_of_presenting_decode_targets_ >=

-         GetMaxNumberOfCachedFramesInternal();

-}

-

 void GpuVideoDecoderBase::DecodeOneBuffer() {

   SB_DCHECK(decoder_thread_);

   SB_DCHECK(BelongsToDecoderThread());

@@ -463,20 +629,28 @@
     return;

   }

 

-  if (IsCacheFull()) {

-    decoder_thread_->job_queue()->Schedule(

-        std::bind(&GpuVideoDecoderBase::DecodeOneBuffer, this),

-        kSbTimeMillisecond);

-    return;

-  }

-

+  // Both decoders av1 & vp9 return decoded frames in separate thread,

+  // so there isn't danger of deadlock in DecodeOneBuffer() and there isn't

+  // necessity of IsCacheFull call

+  scoped_refptr<InputBuffer> input = 0;

+  bool needs_more_input = false;

   {

     ScopedLock pending_inputs_lock(pending_inputs_mutex_);

     SB_DCHECK(!pending_inputs_.empty());

-    written_inputs_.push_back(pending_inputs_.front());

+    input = pending_inputs_.front();

     pending_inputs_.pop_front();

+    if (pending_inputs_.size() < kMaxNumberOfPendingBuffers) {

+      needs_more_input = true;

+    }

   }

-  DecodeInternal(written_inputs_.back());

+  {

+    ScopedLock input_queue_lock(written_inputs_mutex_);

+    written_inputs_.push_back(input);

+  }

+  if (needs_more_input) {

+    decoder_status_cb_(kNeedMoreInput, nullptr);

+  }

+  DecodeInternal(input);

 }

 

 void GpuVideoDecoderBase::DecodeEndOfStream() {

@@ -509,6 +683,9 @@
   if (!is_drain_decoder_called_) {

     is_drain_decoder_called_ = true;

     DrainDecoderInternal();

+    // DrainDecoderInternal is sync command, after it finished, we can be sure

+    // that drain really completed.

+    OnDecoderDrained();

   }

 }

 

@@ -558,6 +735,62 @@
   number_of_presenting_decode_targets_ = 0;

 }

 

+HRESULT GpuVideoDecoderBase::AllocateFrameBuffers(uint16_t width,

+                                                  uint16_t height) {

+  HRESULT hr = S_OK;

+  DXGI_FORMAT dxgi_format =

+      is_hdr_video_ ? (is_10x3_preferred_ ? DXGI_FORMAT_R10G10B10A2_UNORM

+                                          : DXGI_FORMAT_R16_UNORM)

+                    : DXGI_FORMAT_R8_UNORM;

+  return GetGpuFrameBufferPool()->AllocateFrameBuffers(

+      width, height, dxgi_format, d3d11_device_, d3d12_device_);

+}

+

+void GpuVideoDecoderBase::ReleaseFrameBuffer(GpuFrameBuffer* frame_buffer) {

+  SB_DCHECK(frame_buffer);

+  ScopedLock lock(frame_buffers_mutex_);

+  frame_buffer->Release();

+  SB_DCHECK(frame_buffer->HasOneRef());

+  frame_buffers_condition_.Signal();

+}

+

+void GpuVideoDecoderBase::ClearFrameBuffersPool() {