Import Cobalt 24.lts.40.1032940
diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml
index 28616c4..927ec0b 100644
--- a/.github/actions/build/action.yaml
+++ b/.github/actions/build/action.yaml
@@ -17,14 +17,14 @@
       run: |
         set -x
         env
-        if [ -z ${COBALT_BOOTLOADER+x} ]; then
+        if [ -z ${COBALT_EVERGREEN_LOADER+x} ]; then
           BUILD_PLATFORM=${{ matrix.target_platform }}
           BUILD_TARGET=all
           if [[ "${{matrix.config}}" =~ ^(qa|gold)$ ]]; then
             BUILD_TARGET=default
           fi
         else
-          BUILD_PLATFORM=${COBALT_BOOTLOADER}
+          BUILD_PLATFORM=${COBALT_EVERGREEN_LOADER}
           BUILD_TARGET='loader_app_install elf_loader_sandbox_install native_target/crashpad_handler'
         fi
         # GitHub Runners have home set to /github/home.
diff --git a/.github/actions/docker/action.yaml b/.github/actions/docker/action.yaml
index 0c27851..7a1073a 100644
--- a/.github/actions/docker/action.yaml
+++ b/.github/actions/docker/action.yaml
@@ -16,7 +16,7 @@
       shell: bash
     - name: Get docker file changes
       id: changed-files
-      uses: tj-actions/changed-files@8953e851a137075e59e84b5c15fbeb3617e82f15 # v32.1.1
+      uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v44
       with:
         files_ignore: third_party/**
         files: |
@@ -38,7 +38,7 @@
         set -x
         docker_tag="${{ steps.meta.outputs.tags }}"
         docker_tag="${docker_tag%.1[+,-]}"
-        echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_ENV
+        echo "DOCKER_TAG=${docker_tag}" | head -n 1 >> $GITHUB_ENV
       shell: bash
     # We need to set docker tag properly for pull requests.  In those scenarios where no docker related files
     # were changed we need to use an existing image (e.g. main).  In cases where docker image is rebuilt we have
diff --git a/.github/actions/docker_win/action.yaml b/.github/actions/docker_win/action.yaml
index a082e1e..ad7be9b 100644
--- a/.github/actions/docker_win/action.yaml
+++ b/.github/actions/docker_win/action.yaml
@@ -12,7 +12,7 @@
       shell: bash
     - name: Get docker file changes
       id: changed-files
-      uses: tj-actions/changed-files@8953e851a137075e59e84b5c15fbeb3617e82f15 # v32.1.1
+      uses: tj-actions/changed-files@2d756ea4c53f7f6b397767d8723b3a10a9f35bf2 # v44
       with:
         files_ignore: third_party/**
         files: |
@@ -33,7 +33,7 @@
         set -x
         docker_tag="${{ steps.meta.outputs.tags }}"
         docker_tag="${docker_tag%.1[+,-]}"
-        echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_ENV
+        echo "DOCKER_TAG=${docker_tag}" | head -n 1 >> $GITHUB_ENV
       shell: bash
     # We need to set docker tag properly for pull requests.  In those scenarios where no docker related files
     # were changed we need to use an existing image (e.g. main).  In cases where docker image is rebuilt we have
diff --git a/.github/actions/gn/action.yaml b/.github/actions/gn/action.yaml
index 7afac03..ba02701 100644
--- a/.github/actions/gn/action.yaml
+++ b/.github/actions/gn/action.yaml
@@ -22,13 +22,13 @@
       run: |
         set -x
         extra_arguments="${{matrix.extra_gn_arguments}}"
-        if [ -z ${COBALT_BOOTLOADER+x} ]; then
+        if [ -z ${COBALT_EVERGREEN_LOADER+x} ]; then
           BUILD_PLATFORM=${{ matrix.target_platform }}
         else
-          BUILD_PLATFORM=${COBALT_BOOTLOADER}
-          if [ ! -z "${{matrix.bootloader_extra_gn_arguments}}" ]
+          BUILD_PLATFORM=${COBALT_EVERGREEN_LOADER}
+          if [ ! -z "${{matrix.evergreen_loader_extra_gn_arguments}}" ]
           then
-            extra_arguments="${{matrix.bootloader_extra_gn_arguments}}"
+            extra_arguments="${{matrix.evergreen_loader_extra_gn_arguments}}"
           fi
         fi
         gn gen $GITHUB_WORKSPACE/out/${BUILD_PLATFORM}_${{matrix.config}} --args="target_platform=\"${BUILD_PLATFORM}\" ${{matrix.sb_api_version}} ${{matrix.target_os}} ${{matrix.target_cpu}} ${extra_arguments} is_internal_build=false build_type=\"${{matrix.config}}\""
diff --git a/.github/actions/on_device_tests/action.yaml b/.github/actions/on_device_tests/action.yaml
index 7b9ef1e..c981288 100644
--- a/.github/actions/on_device_tests/action.yaml
+++ b/.github/actions/on_device_tests/action.yaml
@@ -23,9 +23,9 @@
         echo "WORKFLOW=${WORKFLOW}" >> $GITHUB_ENV
 
         # Boot loader env
-        if [ "${COBALT_BOOTLOADER}" != "null" ]; then
+        if [ "${COBALT_EVERGREEN_LOADER}" != "null" ]; then
           echo "LOADER_CONFIG=${{ matrix.config }}" >> $GITHUB_ENV
-          echo "LOADER_PLATFORM=${COBALT_BOOTLOADER}" >> $GITHUB_ENV
+          echo "LOADER_PLATFORM=${COBALT_EVERGREEN_LOADER}" >> $GITHUB_ENV
         fi
 
         # Dimension env
diff --git a/.github/actions/on_host_test/action.yaml b/.github/actions/on_host_test/action.yaml
index a10a8bf..c751de0 100644
--- a/.github/actions/on_host_test/action.yaml
+++ b/.github/actions/on_host_test/action.yaml
@@ -39,21 +39,21 @@
         fi
         python3 ${GITHUB_WORKSPACE}/tools/create_archive.py -x -s ${GITHUB_WORKSPACE}/out/tmp/${{matrix.platform}}_${{matrix.config}}.${ARCHIVE_EXTENSION} -d ${GITHUB_WORKSPACE}/out ${parallel}
         rm -rf ${GITHUB_WORKSPACE}/out/tmp
-    - name: Download Bootloader Archive
-      if: ${{ env.COBALT_BOOTLOADER != null && env.COBALT_BOOTLOADER != 'null' }}
+    - name: Download Evergreen loader Archive
+      if: ${{ env.COBALT_EVERGREEN_LOADER != null && env.COBALT_EVERGREEN_LOADER != 'null' }}
       shell: bash
       env:
         WORKFLOW: ${{ github.workflow }}
       run: |
         set -x
         PROJECT_NAME=$(gcloud config get-value project)
-        gsutil cp gs://${PROJECT_NAME}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} ${GITHUB_WORKSPACE}/out/tmp/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION}
-    - name: Extract Bootloader Archive
-      if: ${{ env.COBALT_BOOTLOADER != null && env.COBALT_BOOTLOADER != 'null' }}
+        gsutil cp gs://${PROJECT_NAME}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/${COBALT_EVERGREEN_LOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} ${GITHUB_WORKSPACE}/out/tmp/${COBALT_EVERGREEN_LOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION}
+    - name: Extract Evergreen loader Archive
+      if: ${{ env.COBALT_EVERGREEN_LOADER != null && env.COBALT_EVERGREEN_LOADER != 'null' }}
       shell: bash
       run: |
         set -x
-        python3 ${GITHUB_WORKSPACE}/tools/create_archive.py -x -s ${GITHUB_WORKSPACE}/out/tmp/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} -d ${GITHUB_WORKSPACE}/out --parallel
+        python3 ${GITHUB_WORKSPACE}/tools/create_archive.py -x -s ${GITHUB_WORKSPACE}/out/tmp/${COBALT_EVERGREEN_LOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} -d ${GITHUB_WORKSPACE}/out --parallel
         rm -rf ${GITHUB_WORKSPACE}/out/tmp
     - name: Set Env Variables
       shell: bash
@@ -66,8 +66,8 @@
       run: |
         set -x
         loader_args=''
-        if [ "${COBALT_BOOTLOADER}" != "null" ]; then
-          loader_args="--loader_platform ${COBALT_BOOTLOADER} --loader_config ${{matrix.config}}"
+        if [ "${COBALT_EVERGREEN_LOADER}" != "null" ]; then
+          loader_args="--loader_platform ${COBALT_EVERGREEN_LOADER} --loader_config ${{matrix.config}}"
         fi
         if [[ "${{matrix.shard}}" == 'integration' ]]; then
           xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 $GITHUB_WORKSPACE/cobalt/black_box_tests/black_box_tests.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} ${loader_args}
diff --git a/.github/actions/upload_test_artifacts/action.yaml b/.github/actions/upload_test_artifacts/action.yaml
index d2923eb..33eaa06 100644
--- a/.github/actions/upload_test_artifacts/action.yaml
+++ b/.github/actions/upload_test_artifacts/action.yaml
@@ -18,13 +18,13 @@
       run: |
         set -x
         project_name=$(gcloud config get-value project)
-        if [ -z ${COBALT_BOOTLOADER+x} ]
+        if [ -z ${COBALT_EVERGREEN_LOADER+x} ]
         then
           PLATFORM=${{matrix.platform}}
           echo "TARGET_PLATFORM=${{matrix.target_platform}}" >> $GITHUB_ENV
         else
-          PLATFORM=${COBALT_BOOTLOADER}
-          echo "TARGET_PLATFORM=${COBALT_BOOTLOADER}" >> $GITHUB_ENV
+          PLATFORM=${COBALT_EVERGREEN_LOADER}
+          echo "TARGET_PLATFORM=${COBALT_EVERGREEN_LOADER}" >> $GITHUB_ENV
         fi
 
         if [ "${{ inputs.type }}" == 'ondevice' ]
@@ -56,9 +56,9 @@
         if [ "${{ inputs.type }}" == 'ondevice' ]
         then
           outdir="$GITHUB_WORKSPACE/out/${{matrix.target_platform}}_${{matrix.config}}"
-          if [ -n "${COBALT_BOOTLOADER}" ]
+          if [ -n "${COBALT_EVERGREEN_LOADER}" ]
           then
-            outdir="${outdir} $GITHUB_WORKSPACE/out/${COBALT_BOOTLOADER}_${{matrix.config}}"
+            outdir="${outdir} $GITHUB_WORKSPACE/out/${COBALT_EVERGREEN_LOADER}_${{matrix.config}}"
           fi
           python3 $GITHUB_WORKSPACE/tools/create_archive.py --test_infra -d ${{env.ARCHIVE_FILE}} -s ${outdir}
         elif [ "${{ inputs.type }}" == 'onhost' ]
diff --git a/.github/config/evergreen-arm-hardfp.json b/.github/config/evergreen-arm-hardfp.json
index 8067c72..13b3cd3 100644
--- a/.github/config/evergreen-arm-hardfp.json
+++ b/.github/config/evergreen-arm-hardfp.json
@@ -1,6 +1,6 @@
 {
   "docker_service": "build-raspi",
-  "bootloader": "raspi-2",
+  "evergreen_loader": "raspi-2",
   "on_device_test": {
     "enabled": true,
     "tests": [
@@ -25,7 +25,7 @@
       "target_platform":"evergreen-arm-hardfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "bootloader_extra_gn_arguments": "use_asan=false is_clang=false",
+      "evergreen_loader_extra_gn_arguments": "use_asan=false is_clang=false",
       "dimension": "release_version=regex:10.*"
     },
     {
@@ -34,7 +34,7 @@
       "target_platform":"evergreen-arm-hardfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "evergreen_loader_extra_gn_arguments":"use_asan=false is_clang=false",
       "sb_api_version": "sb_api_version=15",
       "dimension": "release_version=regex:10.*"
     },
@@ -44,7 +44,7 @@
       "target_platform":"evergreen-arm-hardfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "evergreen_loader_extra_gn_arguments":"use_asan=false is_clang=false",
       "sb_api_version": "sb_api_version=14",
       "dimension": "release_version=regex:10.*"
     },
@@ -54,7 +54,7 @@
       "target_platform":"evergreen-arm-hardfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "evergreen_loader_extra_gn_arguments":"use_asan=false is_clang=false",
       "sb_api_version": "sb_api_version=13",
       "dimension": "release_version=regex:10.*"
     }
diff --git a/.github/config/evergreen-x64.json b/.github/config/evergreen-x64.json
index b95a318..0f18b57 100644
--- a/.github/config/evergreen-x64.json
+++ b/.github/config/evergreen-x64.json
@@ -1,7 +1,7 @@
 {
   "docker_service": "build-linux-evergreen",
   "on_host_test": true,
-  "bootloader": "linux-x64x11",
+  "evergreen_loader": "linux-x64x11",
   "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
   "platforms": [
     "evergreen-x64",
diff --git a/.github/config/linux.json b/.github/config/linux.json
index 79ceb0b..e1efe4b 100644
--- a/.github/config/linux.json
+++ b/.github/config/linux.json
@@ -1,5 +1,6 @@
 {
   "docker_service": "build-linux",
+  "evergreen_loader": "linux-x64x11",
   "on_host_test": true,
   "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
   "platforms": [
diff --git a/.github/workflows/android_24.lts.1+.yaml b/.github/workflows/android_24.lts.1+.yaml
index cec9f87..708c0c1 100644
--- a/.github/workflows/android_24.lts.1+.yaml
+++ b/.github/workflows/android_24.lts.1+.yaml
@@ -1,6 +1,8 @@
 name: android_24.lts.1+
 
 on:
+  release:
+    types: [prereleased, released, published]
   pull_request:
     types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
@@ -28,6 +30,7 @@
     with:
       platform: android-arm64
       nightly: ${{ github.event.inputs.nightly }}
+      keep_artifacts: cobalt.apk
   android-x86:
     uses: ./.github/workflows/main.yaml
     permissions:
@@ -36,6 +39,7 @@
     with:
       platform: android-x86
       nightly: ${{ github.event.inputs.nightly }}
+      keep_artifacts: cobalt.apk
   android-arm:
     uses: ./.github/workflows/main.yaml
     permissions:
@@ -44,3 +48,59 @@
     with:
       platform: android-arm
       nightly: ${{ github.event.inputs.nightly }}
+      keep_artifacts: cobalt.apk
+
+  upload-release-artifacts:
+    runs-on: ubuntu-latest
+    needs: [ android-arm, android-arm64, android-x86 ]
+    permissions:
+      actions: write
+      contents: write
+    steps:
+      - name: Download arm-gold apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-arm-gold
+          path: arm-gold
+      - name: Download arm-qa apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-arm-qa
+          path: arm-qa
+      - name: Download arm64-gold apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-arm64-gold
+          path: arm64-gold
+      - name: Download arm64-qa apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-arm64-gold
+          path: arm64-qa
+      - name: Download x86-gold apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-x86-gold
+          path: x86-gold
+      - name: Download x86-qa apk
+        uses: actions/download-artifact@v4
+        with:
+          name: android-x86-qa
+          path: x86-qa
+      - name: 'Upload Android APKs'
+        uses: actions/upload-artifact@v4
+        with:
+          name: Android APKs
+          path: ./*
+          retention-days: 90
+          compression-level: 0 # We expect kept artifacts to be already compressed
+          if-no-files-found: error
+      - name: Make a release zip
+        run: zip -0 -r "Android APKs.zip" . -i '*.apk'
+      - name: Upload the APKs to a release
+        if: ${{ github.event_name == 'release' }}
+        uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # v2.9.0
+        with:
+          repo_token: ${{ secrets.GITHUB_TOKEN }}
+          file: "Android APKs.zip"
+          overwrite: true
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 9ccebb8..ba5f880 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -29,6 +29,11 @@
         required: false
         type: boolean
         default: false
+      keep_artifacts:
+        description: 'Which artifacts to keep for releases'
+        required: false
+        type: string
+        default: ''
 
 # Global env vars.
 env:
@@ -100,8 +105,8 @@
         run: echo "on_host_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_host_test')" >> $GITHUB_ENV
       - id: set-on-host-test-shards
         run: echo "on_host_test_shards=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.on_host_test_shards')" >> $GITHUB_ENV
-      - id: set-on-host-test-bootloader
-        run: echo "bootloader=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.bootloader')" >> $GITHUB_ENV
+      - id: set-on-host-test-evergreen-loader
+        run: echo "evergreen_loader=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.evergreen_loader')" >> $GITHUB_ENV
       - id: set-docker-service
         run: |
           echo "docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -r '.docker_service')" >> $GITHUB_ENV
@@ -113,7 +118,7 @@
       on_device_test_attempts: ${{ env.on_device_test_attempts }}
       on_host_test: ${{ env.on_host_test }}
       on_host_test_shards: ${{ env.on_host_test_shards }}
-      bootloader: ${{ env.bootloader }}
+      evergreen_loader: ${{ env.evergreen_loader }}
       docker_service: ${{ env.docker_service }}
 
   # Builds, tags, and pushes Cobalt docker build images to ghr.
@@ -144,7 +149,7 @@
         id: set-docker-tag-output
         run: |
           echo $DOCKER_TAG
-          echo "docker_tag=$DOCKER_TAG"  >> $GITHUB_ENV
+          echo "docker_tag=$DOCKER_TAG" | head -n 1  >> $GITHUB_ENV
     outputs:
       docker_tag: ${{env.docker_tag}}
 
@@ -210,34 +215,43 @@
         uses: ./.github/actions/gn
       - name: Build Cobalt
         uses: ./.github/actions/build
+      - name: 'Upload Artifact'
+        uses: actions/upload-artifact@v4
+        if: inputs.keep_artifacts
+        with:
+          name: ${{ matrix.platform }}-${{ matrix.config }}
+          path: out/${{ matrix.platform }}_${{ matrix.config }}/${{ inputs.keep_artifacts }}
+          retention-days: 7
+          compression-level: 0 # We expect kept artifacts to be already compressed
+          if-no-files-found: error
       - name: Run API Leak Detector
         uses: ./.github/actions/api_leak_detector
         if: inputs.run_api_leak_detector
         with:
           relative_manifest_path: ${{ inputs.leak_manifest_filename }}
-      - name: Upload Nightly Artifacts
-        if: ${{ ( inputs.nightly == 'true' || github.event_name == 'schedule' ) && matrix.config != 'debug' }}
-        uses: ./.github/actions/upload_nightly_artifacts
       - name: Upload On Host Test Artifacts
         if: ${{ matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true' }}
         uses: ./.github/actions/upload_test_artifacts
         with:
           type: onhost
           os: linux
-      # For some reason passing needs.initialize.outputs.bootloader as parameter to build
+      # For some reason passing needs.initialize.outputs.evergreen_loader as parameter to build
       # action didn't work, so instead we set an env var.
-      - name: Set bootloader config
-        if: ${{ needs.initialize.outputs.bootloader != 'null' }}
-        run: echo "COBALT_BOOTLOADER=${{needs.initialize.outputs.bootloader}}" >> $GITHUB_ENV
-      # Build bootloader for on-host tests if necessary.
-      - name: Bootloader GN
-        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' }}
+      - name: Set Evergreen loader config
+        if: ${{ needs.initialize.outputs.evergreen_loader != 'null' }}
+        run: echo "COBALT_EVERGREEN_LOADER=${{needs.initialize.outputs.evergreen_loader}}" >> $GITHUB_ENV
+      # Build Evergreen loader for on-host tests if necessary.
+      - name: Evergreen loader GN
+        if: ${{ needs.initialize.outputs.evergreen_loader != 'null' && ( matrix.config == 'devel' || matrix.config == 'qa' ) }}
         uses: ./.github/actions/gn
-      - name: Build Bootloader
-        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' }}
+      - name: Build Evergreen loader
+        if: ${{ needs.initialize.outputs.evergreen_loader != 'null' && ( matrix.config == 'devel' || matrix.config == 'qa' ) }}
         uses: ./.github/actions/build
-      - name: Upload Bootloader On Host Test Artifacts
-        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true'}}
+      - name: Upload Nightly Artifacts
+        if: ${{ ( inputs.nightly == 'true' || github.event_name == 'schedule' ) && matrix.config != 'debug' }}
+        uses: ./.github/actions/upload_nightly_artifacts
+      - name: Upload Evergreen loader On Host Test Artifacts
+        if: ${{ needs.initialize.outputs.evergreen_loader != 'null' && matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true'}}
         uses: ./.github/actions/upload_test_artifacts
         with:
           type: onhost
@@ -279,7 +293,7 @@
         shard: ${{ fromJson(needs.initialize.outputs.on_device_test).tests }}
         include: ${{ fromJson(needs.initialize.outputs.includes) }}
     env:
-      COBALT_BOOTLOADER: ${{ needs.initialize.outputs.bootloader }}
+      COBALT_EVERGREEN_LOADER: ${{ needs.initialize.outputs.evergreen_loader }}
       ON_DEVICE_TEST_ATTEMPTS: ${{ needs.initialize.outputs.on_device_test_attempts }}
       MODULAR_BUILD: ${{ inputs.modular && 1 || 0 }}
     steps:
@@ -311,7 +325,7 @@
       # For some reason tests complaining about HOME set to /github/home
       # with permission denied error.
       HOME: /root
-      COBALT_BOOTLOADER: ${{needs.initialize.outputs.bootloader}}
+      COBALT_EVERGREEN_LOADER: ${{needs.initialize.outputs.evergreen_loader}}
       MODULAR_BUILD: ${{ inputs.modular && 1 || 0 }}
     steps:
       - name: Checkout
diff --git a/.gitignore b/.gitignore
index 9a5a5ca..31136b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@
 /venv/*
 _certs/
 .coverage
+compile_commands.json
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index 92fe9dc..d6acce9 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -40,6 +40,7 @@
     "//cobalt/renderer/sandbox:scaling_text_sandbox",
     "//cobalt/speech/sandbox:speech_sandbox",
     "//cobalt/ui_navigation/scroll_engine:scroll_engine_tests",
+    "//cobalt/watchdog:watchdog_test",
     "//cobalt/web:web_test",
     "//cobalt/web_animations:web_animations_test",
     "//cobalt/webdriver:webdriver_test",
diff --git a/cobalt/base/tokens.h b/cobalt/base/tokens.h
index 0b82b64..908ce2d 100644
--- a/cobalt/base/tokens.h
+++ b/cobalt/base/tokens.h
@@ -116,6 +116,7 @@
     MacroOpWithNameOnly(resourcetimingbufferfull)                    \
     MacroOpWithNameOnly(result)                                      \
     MacroOpWithNameOnly(resume)                                      \
+    MacroOpWithNameOnly(samplebufferfull)                            \
     MacroOpWithNameOnly(scroll)                                      \
     MacroOpWithNameOnly(securitypolicyviolation)                     \
     MacroOpWithNameOnly(seeked)                                      \
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index 7dc6fd7..58e042f 100755
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -77,7 +77,9 @@
     'default_site_can_load',
     'disable_eval_with_csp',
     'h5vcc_storage_write_verify_test',
+    'h5vcc_watchdog_api_test',
     'http_cache',
+    'javascript_profiler',
     'persistent_cookie',
     'scroll',
     'service_worker_add_to_cache_test',
diff --git a/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.html b/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.html
new file mode 100644
index 0000000..620107c
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <title>h5vcc logEvent() API test</title>
+</head>
+<body>
+<script src='black_box_js_test_utils.js'></script>
+<script src='h5vcc_logevent_api_test.js'></script>
+</body>
+</html>
diff --git a/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.js b/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.js
new file mode 100644
index 0000000..74e40a6
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/h5vcc_logevent_api_test.js
@@ -0,0 +1,124 @@
+// Copyright 2024 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';
+
+function failTest() {
+  notReached();
+  onEndTest();
+}
+
+function canCallH5vccLogEventTest() {
+  if (!h5vcc.crashLog) {
+    console.log("h5vcc.crashLog does not exist");
+    return;
+  }
+
+  if (!h5vcc.crashLog.logEvent) {
+    console.log("h5vcc.crashLog.logEvent does not exist");
+    return;
+  }
+
+  h5vcc.crashLog.logEvent("test-frame");
+}
+
+function canCallH5vccGetLogTraceTest() {
+  h5vcc.crashLog.logEvent("frame");
+}
+
+function getLogTraceReturnsLastLoggedEventTest() {
+  if (!h5vcc.crashLog) {
+    console.log("h5vcc.crashLog does not exist");
+    return;
+  }
+
+  if (!h5vcc.crashLog.logEvent) {
+    console.log("h5vcc.crashLog.logEvent does not exist");
+    return;
+  }
+
+  h5vcc.crashLog.logEvent(
+      "yt.ui.uix.Abc.efG_ (http://www.youtube.com/yts/jsbin/a-b-c.js:14923:34)");
+  h5vcc.crashLog.logEvent(
+      "yy.ttt.Aaa.b_ (http://www.youtube.com/yts/jsbin/a-b-c.js:123:45)");
+  h5vcc.crashLog.logEvent(
+      "yy.ttt.Ccc.d_ (http://www.youtube.com/yts/jsbin/a-b-c.js:678:90)");
+
+  let logTrace = h5vcc.crashLog.getLogTrace();
+  if (logTrace[logTrace.length - 1]
+      !== "yy.ttt.Ccc.d_ (http://www.youtube.com/yts/jsbin/a-b-c.js:678:90)") {
+    failTest();
+  }
+}
+
+function clearLogWorks() {
+  if (!h5vcc.crashLog.clearLog) {
+    console.log("h5vcc.crashLog.clearLog does not exist");
+    return;
+  }
+
+  h5vcc.crashLog.clearLog();
+
+  h5vcc.crashLog.logEvent("1");
+  h5vcc.crashLog.logEvent("2");
+  h5vcc.crashLog.logEvent("3");
+
+  h5vcc.crashLog.clearLog();
+
+  h5vcc.crashLog.logEvent("4");
+  h5vcc.crashLog.logEvent("5");
+
+  let logTrace = h5vcc.crashLog.getLogTrace();
+
+  if (logTrace.length !== 2) {
+    failTest();
+  }
+
+  if (logTrace[0] !== "4") {
+    failTest();
+  }
+  if (logTrace[1] !== "5") {
+    failTest();
+  }
+}
+
+function canReadLogtraceFromWatchdogViolation() {
+  h5vcc.crashLog.register("test-client", "", "started", 500, 0, 'all',);
+  h5vcc.crashLog.ping("test-client", "");
+
+  h5vcc.crashLog.clearLog();
+  h5vcc.crashLog.logEvent("1");
+  h5vcc.crashLog.logEvent("2");
+
+  // sleep to generate violation
+  let start = Date.now();
+  while ((Date.now() - start) < 2000) {
+  }
+
+  let violationsJson = h5vcc.crashLog.getWatchdogViolations();
+  if (!violationsJson) {
+    failTest();
+  }
+
+  let violations = JSON.parse(violationsJson);
+  if (violations['test-client']['violations'][0]['logTrace'].length !== 2) {
+    failTest();
+  }
+}
+
+canCallH5vccLogEventTest();
+canCallH5vccGetLogTraceTest();
+getLogTraceReturnsLastLoggedEventTest();
+clearLogWorks();
+canReadLogtraceFromWatchdogViolation();
+onEndTest();
diff --git a/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.html b/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.html
new file mode 100644
index 0000000..b04734e
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <title>h5vcc Watchdog API test</title>
+</head>
+<body>
+<script src='black_box_js_test_utils.js'></script>
+<script src='h5vcc_watchdog_violation_test.js'></script>
+</body>
+</html>
diff --git a/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.js b/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.js
new file mode 100644
index 0000000..13e113f
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/h5vcc_watchdog_violation_test.js
@@ -0,0 +1,102 @@
+// Copyright 2024 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';
+
+function failTest() {
+  notReached();
+  onEndTest();
+}
+
+function doBusyLoopFor(msToSleep) {
+  let start = Date.now();
+  while ((Date.now() - start) < msToSleep) {
+  }
+}
+
+function canDetectViolation() {
+  h5vcc.crashLog.register('test-client', '', 'started', 500, 0, 'all');
+  // clear whatever violations are in the cache
+  h5vcc.crashLog.getWatchdogViolations();
+
+  h5vcc.crashLog.ping('test-client', '');
+  doBusyLoopFor(2000)
+
+  let violationsJson = h5vcc.crashLog.getWatchdogViolations();
+  if (!violationsJson) {
+    failTest();
+  }
+
+  let violations = JSON.parse(violationsJson);
+  if (violations['test-client']['violations'].length !== 1) {
+    failTest();
+  }
+}
+
+function reportsRepitativeViolations() {
+  h5vcc.crashLog.register('test-client', '', 'started', 500, 0, 'all');
+  // clear whatever violations are in the cache
+  h5vcc.crashLog.getWatchdogViolations();
+
+  h5vcc.crashLog.ping('test-client', '');
+  doBusyLoopFor(2000);
+  h5vcc.crashLog.ping('test-client', '');
+  doBusyLoopFor(2000);
+
+  let violationsJson = h5vcc.crashLog.getWatchdogViolations();
+  if (!violationsJson) {
+    failTest();
+  }
+
+  let violations = JSON.parse(violationsJson);
+  if (violations['test-client']['violations'].length !== 2) {
+    failTest();
+  }
+}
+
+function reportsLogtraceWithViolations() {
+  h5vcc.crashLog.register('test-client', '', 'started', 500, 0, 'all');
+  // clear whatever violations are in the cache
+  h5vcc.crashLog.getWatchdogViolations();
+  h5vcc.crashLog.logEvent('frame_1');
+  h5vcc.crashLog.logEvent('frame_2');
+
+  h5vcc.crashLog.ping('test-client', '');
+  h5vcc.crashLog.logEvent('frame_3');
+  h5vcc.crashLog.logEvent('frame_4');
+
+  doBusyLoopFor(2000);
+
+  let violationsJson = h5vcc.crashLog.getWatchdogViolations();
+  if (!violationsJson) {
+    failTest();
+  }
+
+  let violations = JSON.parse(violationsJson);
+  let logTrace = violations['test-client']['violations'][0]['logTrace'];
+  if (logTrace.length !== 4) {
+    failTest();
+
+  }
+
+  if (logTrace[0] !== 'frame_1' || logTrace[1] !== 'frame_2' || logTrace[2]
+      !== 'frame_3' || logTrace[3] !== 'frame_4') {
+    failTest();
+  }
+}
+
+canDetectViolation();
+reportsRepitativeViolations();
+reportsLogtraceWithViolations();
+
+onEndTest();
diff --git a/cobalt/black_box_tests/testdata/javascript_profiler.html b/cobalt/black_box_tests/testdata/javascript_profiler.html
new file mode 100644
index 0000000..f268510
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/javascript_profiler.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+
+<head>
+  <title>JavaScript Profiler Test</title>
+  <script async src="black_box_js_test_utils.js"></script>
+  <script>
+
+    const testPrimeProfiler = async () => {
+      const profiler = new Profiler({ sampleInterval: 10 /**ms**/, maxBufferSize: 10 /**number of samples*/ });
+
+      // sleep 10 seconds
+      await new Promise(resolve => setTimeout(resolve, 10000 /**ms**/));
+
+      // stop & check
+      const trace = await profiler.stop();
+      assertTrue(trace.samples.length > 0, "expected some stack traces");
+    };
+
+    const testSampleBufferFullProfiler = async () => {
+      const promise = new Promise((resolve, reject) => {
+        const profiler = new Profiler({ maxBufferSize: 1, sampleInterval: 10 });
+        profiler.addEventListener("samplebufferfull", () => {
+          profiler.stop().then(() => { resolve(true); });
+        });
+      });
+      const timeoutPromise = new Promise((resolve) => {
+        setTimeout(() => {
+          resolve(false);
+        }, 60000);
+      });
+
+      const result = await Promise.race([timeoutPromise, promise]);
+      assertTrue(result, "expected sample buffer to be full");
+    };
+
+    /**
+      * Creates a new Profiler and then deletes it before the sample buffer is full.
+      * Expects the SampleBuffer to be full and the callback to be executed.
+    */
+    const testAbruptGarbageCollection = async () => {
+      const profilerPromise = new Promise((resolve) => {
+        window.profiler = new Profiler({ maxBufferSize: 100, sampleInterval: 10 });
+        delete window.profiler; // simulates the effects of GC.
+        resolve(true);
+      });
+
+      const survivePromise = new Promise((resolve) => {
+        setTimeout(() => {
+          resolve(true);
+        }, 10000);
+      });
+
+      // must survive for 10 seconds
+      const result = await Promise.all([survivePromise, profilerPromise]);
+      assertTrue(result.every(p => p), "expected cobalt to survive 10 sec if GC collects profiler");
+    };
+
+    window.addEventListener("load", async () => {
+      setupFinished();
+      // Test Case 1: Prime Profiler
+      if (window.location.search.includes("mode=testPrimeProfiler")) {
+        console.log("testPrimeProfiler");
+        await testPrimeProfiler();
+      }
+      // Test Case 2: SampleBufferFull Profiler
+      else if (window.location.search.includes("mode=testSampleBufferFullProfiler")) {
+        console.log("testSampleBufferFullProfiler");
+        await testSampleBufferFullProfiler();
+      }
+      // Test Case 3: GC Profiler
+      else if (window.location.search.includes("mode=testAbruptGarbageCollection")) {
+        console.log("testAbruptGarbageCollection");
+        await testAbruptGarbageCollection();
+      }
+      else {
+        console.log("Running all tests");
+        await testPrimeProfiler();
+        await testSampleBufferFullProfiler();
+        await testAbruptGarbageCollection();
+      }
+      console.log("DONE");
+      onEndTest();
+    });
+  </script>
+</head>
+
+<body>
+</body>
diff --git a/cobalt/black_box_tests/tests/h5vcc_logevent_api_test.py b/cobalt/black_box_tests/tests/h5vcc_logevent_api_test.py
new file mode 100644
index 0000000..339b6bd
--- /dev/null
+++ b/cobalt/black_box_tests/tests/h5vcc_logevent_api_test.py
@@ -0,0 +1,31 @@
+# Copyright 2024 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
+"""Test H5vcc logEvent() API"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class H5vccLogEventApiTest(black_box_tests.BlackBoxTestCase):
+
+  def test_h5vcc_logevent_api_test(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(file_name='testdata/h5vcc_logevent_api_test.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForActiveElement()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/h5vcc_watchdog_api_test.py b/cobalt/black_box_tests/tests/h5vcc_watchdog_api_test.py
new file mode 100644
index 0000000..1e388a1
--- /dev/null
+++ b/cobalt/black_box_tests/tests/h5vcc_watchdog_api_test.py
@@ -0,0 +1,35 @@
+# Copyright 2024 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
+"""Test H5vcc logEvent() API"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from cobalt.tools.automated_testing import webdriver_utils
+
+keys = webdriver_utils.import_selenium_module('webdriver.common.keys')
+
+
+class H5vccWatchdogApiTest(black_box_tests.BlackBoxTestCase):
+
+  def test_h5vcc_watchdog_api_test(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/h5vcc_watchdog_violation_test.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/javascript_profiler.py b/cobalt/black_box_tests/tests/javascript_profiler.py
new file mode 100644
index 0000000..33dabc7
--- /dev/null
+++ b/cobalt/black_box_tests/tests/javascript_profiler.py
@@ -0,0 +1,40 @@
+# Copyright 2024 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.
+"""Tests if Cobalt client page can use window.Profiler."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class JavaScriptProfilerTest(black_box_tests.BlackBoxTestCase):
+  """Ensure that the client can declare a `window.Profiler` object."""
+
+  def test_javascript_profiler_prime_profiler(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      modes = [
+          'testPrimeProfiler',
+          'testSampleBufferFullProfiler',
+          'testAbruptGarbageCollection',
+      ]
+      for mode in modes:
+        url = server.GetURL(
+            file_name=f'testdata/javascript_profiler.html?mode={mode}')
+        with self.CreateCobaltRunner(url=url) as runner:
+          runner.WaitForJSTestsSetup()
+          self.assertTrue(runner.JSTestsSucceeded(),
+                          f'JavaScript profiler failed at case mode="{mode}".')
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index f44b2b2..f168f1b 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -1962,6 +1962,11 @@
 
   FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_,
                     Unfreeze(GetResourceProvider(), timestamp));
+#if defined(DIAL_SERVER)
+  if (network_module_) {
+    network_module_->RestartDialService();
+  }
+#endif
 }
 
 void BrowserModule::OnMaybeFreeze() {
diff --git a/cobalt/demos/content/enable-background-playback/enable-background-playback-test.html b/cobalt/demos/content/enable-background-playback/enable-background-playback-test.html
deleted file mode 100644
index a00216b..0000000
--- a/cobalt/demos/content/enable-background-playback/enable-background-playback-test.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-
-<head>
-    <title>Test Enable Background Playback</title>
-</head>
-
-<body style="color: white;">
-    <script>
-    function main() {
-        if (window.h5vcc && window.h5vcc.settings) {
-            h5vcc.settings.set('Media.BackgroundPlaybackEnabled', 1);
-        }
-    }
-    main();
-    </script>
-</body>
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index 17617fa..6cafc64 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -408,6 +408,7 @@
     "rule_matching_test.cc",
     "screen_test.cc",
     "serializer_test.cc",
+    "source_buffer_metrics_test.cc",
     "storage_area_test.cc",
     "text_test.cc",
     "time_ranges_test.cc",
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index 45a49ca..ade0694 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -644,21 +644,16 @@
   event_queue_.Enqueue(event);
 }
 
-std::string HTMLMediaElement::h5vcc_audio_connectors(
-    script::ExceptionState* exception_state) const {
+std::string HTMLMediaElement::h5vcc_audio_connectors() const {
 #if SB_API_VERSION >= 15
   if (!player_) {
-    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
-                             exception_state);
-    return std::string();
+    return "";
   }
 
   std::vector<std::string> configs = player_->GetAudioConnectors();
   return base::JoinString(configs, ";");
 #else   // SB_API_VERSION >= 15
-  web::DOMException::Raise(web::DOMException::kNotSupportedErr,
-                           exception_state);
-  return std::string();
+  return "";
 #endif  // SB_API_VERSION >= 15
 }
 
diff --git a/cobalt/dom/html_media_element.h b/cobalt/dom/html_media_element.h
index 559c72a..f4eefeb 100644
--- a/cobalt/dom/html_media_element.h
+++ b/cobalt/dom/html_media_element.h
@@ -149,8 +149,7 @@
   // Returns semicolon separated names of audio connectors, like
   // "hdmi;bluetooth".
   // TODO(b/267678497): The current interface is tentative, to be refined.
-  std::string h5vcc_audio_connectors(
-      script::ExceptionState* exception_state) const;
+  std::string h5vcc_audio_connectors() const;
 
   // Set max video capabilities.
   void SetMaxVideoCapabilities(const std::string& max_video_capabilities,
diff --git a/cobalt/dom/html_media_element.idl b/cobalt/dom/html_media_element.idl
index 401060a..f3b7917 100644
--- a/cobalt/dom/html_media_element.idl
+++ b/cobalt/dom/html_media_element.idl
@@ -63,8 +63,7 @@
   attribute boolean muted;
 
   // non standard, semicolon separated names of audio connectors, like
-  // "hdmi;bluetooth".  It raises `NotSupportedError` on apps doesn't support
-  // this feature, or `InvalidStateError` if there isn't an active playback.
+  // "hdmi;bluetooth".
   // TODO(b/267678497): The current interface is tentative, to be refined.
-  [RaisesException] readonly attribute DOMString h5vccAudioConnectors;
+  readonly attribute DOMString h5vccAudioConnectors;
 };
diff --git a/cobalt/dom/media_error.idl b/cobalt/dom/media_error.idl
index d6a184a..ae67581 100644
--- a/cobalt/dom/media_error.idl
+++ b/cobalt/dom/media_error.idl
@@ -20,8 +20,8 @@
   const unsigned short MEDIA_ERR_DECODE = 3;
   const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
   const unsigned short MEDIA_ERR_ENCRYPTED = 5;
-  // The underlying playback capability has changed, the app should treat this
-  // as a transient error and retry playback.
+  // Deprecated. Capability change errors are reported with
+  // code MEDIA_ERR_DECODE.
   const unsigned short MEDIA_ERR_CAPABILITY_CHANGED = 1000;
 
   readonly attribute unsigned short code;
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 1ad5a59..087ae14 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -58,6 +58,7 @@
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/media_settings.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/dom/source_buffer_metrics.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/dom_exception.h"
 #include "cobalt/web/web_settings.h"
@@ -591,7 +592,7 @@
     return false;
   }
 
-  metrics_.StartTracking();
+  metrics_.StartTracking(SourceBufferMetricsAction::PREPARE_APPEND);
   media_source_->OpenIfInEndedState();
 
   double current_time = media_source_->GetMediaElement()->current_time(NULL);
@@ -600,11 +601,11 @@
           new_data_size + evict_extra_in_bytes_)) {
     web::DOMException::Raise(web::DOMException::kQuotaExceededErr,
                              exception_state);
-    metrics_.EndTracking(0);
+    metrics_.EndTracking(SourceBufferMetricsAction::PREPARE_APPEND, 0);
     return false;
   }
 
-  metrics_.EndTracking(0);
+  metrics_.EndTracking(SourceBufferMetricsAction::PREPARE_APPEND, 0);
   return true;
 }
 
diff --git a/cobalt/dom/source_buffer_algorithm.cc b/cobalt/dom/source_buffer_algorithm.cc
index b55e9cd..732e463 100644
--- a/cobalt/dom/source_buffer_algorithm.cc
+++ b/cobalt/dom/source_buffer_algorithm.cc
@@ -20,6 +20,7 @@
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/dom/source_buffer_metrics.h"
 
 namespace cobalt {
 namespace dom {
@@ -67,12 +68,13 @@
   TRACE_EVENT1("cobalt::dom", "SourceBufferAppendAlgorithm::Process()",
                "append_size", append_size);
 
-  metrics_->StartTracking();
+  metrics_->StartTracking(SourceBufferMetricsAction::APPEND_BUFFER);
   succeeded_ = chunk_demuxer_->AppendData(
       id_, buffer_, append_size, append_window_start_, append_window_end_,
       &timestamp_offset_);
   update_timestamp_offset_cb_.Run(timestamp_offset_);
-  metrics_->EndTracking(succeeded_ ? append_size : 0);
+  metrics_->EndTracking(SourceBufferMetricsAction::APPEND_BUFFER,
+                        succeeded_ ? append_size : 0);
 
   if (succeeded_) {
     buffer_ += append_size;
diff --git a/cobalt/dom/source_buffer_metrics.cc b/cobalt/dom/source_buffer_metrics.cc
index 82aed8d..237cede 100644
--- a/cobalt/dom/source_buffer_metrics.cc
+++ b/cobalt/dom/source_buffer_metrics.cc
@@ -17,6 +17,8 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
 #include "cobalt/base/statistics.h"
 #include "starboard/common/string.h"
 #include "starboard/once.h"
@@ -26,7 +28,6 @@
 namespace dom {
 
 #if !defined(COBALT_BUILD_TYPE_GOLD)
-
 namespace {
 
 int GetBandwidth(std::size_t size, SbTimeMonotonic duration) {
@@ -59,19 +60,29 @@
 }  // namespace
 SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance);
 
-void SourceBufferMetrics::StartTracking() {
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+void SourceBufferMetrics::StartTracking(SourceBufferMetricsAction action) {
   if (!is_primary_video_) {
     return;
   }
 
   DCHECK(!is_tracking_);
+  DCHECK(current_action_ == SourceBufferMetricsAction::NO_ACTION);
+  DCHECK(action != SourceBufferMetricsAction::NO_ACTION);
+
   is_tracking_ = true;
-  wall_start_time_ = SbTimeGetMonotonicNow();
+  current_action_ = action;
+  wall_start_time_ = clock_->NowTicks();
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
   thread_start_time_ =
       SbTimeIsTimeThreadNowSupported() ? SbTimeGetMonotonicThreadNow() : -1;
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
-void SourceBufferMetrics::EndTracking(std::size_t size_appended) {
+void SourceBufferMetrics::EndTracking(SourceBufferMetricsAction action,
+                                      std::size_t size_appended) {
   if (!is_primary_video_) {
     return;
   }
@@ -79,18 +90,27 @@
   DCHECK(is_tracking_);
   is_tracking_ = false;
 
-  SbTimeMonotonic wall_duration = SbTimeGetMonotonicNow() - wall_start_time_;
+  DCHECK(action == current_action_);
+  current_action_ = SourceBufferMetricsAction::NO_ACTION;
+
+  base::TimeDelta wall_duration = clock_->NowTicks() - wall_start_time_;
+
+  RecordTelemetry(action, wall_duration);
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
   SbTimeMonotonic thread_duration =
       SbTimeIsTimeThreadNowSupported()
           ? SbTimeGetMonotonicThreadNow() - thread_start_time_
           : 0;
-  total_wall_time_ += wall_duration;
+
+  total_wall_time_ += wall_duration.InMicroseconds();
   total_thread_time_ += thread_duration;
 
   total_size_ += size_appended;
 
   if (size_appended > 0) {
-    int wall_bandwidth = GetBandwidth(size_appended, wall_duration);
+    int wall_bandwidth =
+        GetBandwidth(size_appended, wall_duration.InMicroseconds());
     int thread_bandwidth = SbTimeIsTimeThreadNowSupported()
                                ? GetBandwidth(size_appended, thread_duration)
                                : 0;
@@ -100,8 +120,35 @@
     max_thread_bandwidth_ = std::max(max_thread_bandwidth_, thread_bandwidth);
     min_thread_bandwidth_ = std::min(min_thread_bandwidth_, thread_bandwidth);
   }
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
+void SourceBufferMetrics::RecordTelemetry(
+    SourceBufferMetricsAction action, const base::TimeDelta& action_duration) {
+  switch (action) {
+    case SourceBufferMetricsAction::PREPARE_APPEND:
+      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+          "Cobalt.Media.SourceBuffer.PrepareAppend.Timing", action_duration,
+          base::TimeDelta::FromMicroseconds(1),
+          base::TimeDelta::FromMilliseconds(25), 50);
+      break;
+    case SourceBufferMetricsAction::APPEND_BUFFER:
+      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+          "Cobalt.Media.SourceBuffer.AppendBuffer.Timing", action_duration,
+          base::TimeDelta::FromMicroseconds(1),
+          base::TimeDelta::FromMilliseconds(25), 50);
+      break;
+    default:
+      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+          "Cobalt.Media.SourceBuffer.Other.Timing", action_duration,
+          base::TimeDelta::FromMicroseconds(1),
+          base::TimeDelta::FromMilliseconds(25), 50);
+      break;
+  }
+}
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+
 void SourceBufferMetrics::PrintCurrentMetricsAndUpdateAccumulatedMetrics() {
   if (!is_primary_video_) {
     return;
diff --git a/cobalt/dom/source_buffer_metrics.h b/cobalt/dom/source_buffer_metrics.h
index 766ebac..89e5c10 100644
--- a/cobalt/dom/source_buffer_metrics.h
+++ b/cobalt/dom/source_buffer_metrics.h
@@ -15,47 +15,56 @@
 #ifndef COBALT_DOM_SOURCE_BUFFER_METRICS_H_
 #define COBALT_DOM_SOURCE_BUFFER_METRICS_H_
 
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "starboard/time.h"
 
 namespace cobalt {
 namespace dom {
 
-#if defined(COBALT_BUILD_TYPE_GOLD)
-
-class SourceBufferMetrics {
- public:
-  explicit SourceBufferMetrics(bool is_primary_video) {}
-  ~SourceBufferMetrics() = default;
-
-  void StartTracking() {}
-  void EndTracking(size_t size_appended) {}
-  void PrintCurrentMetricsAndUpdateAccumulatedMetrics() {}
-  void PrintAccumulatedMetrics() {}
+enum class SourceBufferMetricsAction : uint8_t {
+  NO_ACTION,
+  PREPARE_APPEND,
+  APPEND_BUFFER,
 };
 
-#else  // defined(COBALT_BUILD_TYPE_GOLD)
-
 class SourceBufferMetrics {
  public:
-  explicit SourceBufferMetrics(bool is_primary_video)
-      : is_primary_video_(is_primary_video) {}
+  SourceBufferMetrics(
+      bool is_primary_video,
+      const base::TickClock* clock = base::DefaultTickClock::GetInstance())
+      : is_primary_video_(is_primary_video), clock_(clock) {}
   ~SourceBufferMetrics() = default;
 
-  void StartTracking();
-  void EndTracking(size_t size_appended);
+  void StartTracking(SourceBufferMetricsAction action);
+  void EndTracking(SourceBufferMetricsAction action, size_t size_appended);
+
+#if defined(COBALT_BUILD_TYPE_GOLD)
+  void PrintCurrentMetricsAndUpdateAccumulatedMetrics() {}
+  void PrintAccumulatedMetrics() {}
+#else   // defined(COBALT_BUILD_TYPE_GOLD)
   void PrintCurrentMetricsAndUpdateAccumulatedMetrics();
   void PrintAccumulatedMetrics();
+#endif  // defined(COBALT_BUILD_TYPE_GOLD)
 
  private:
   SourceBufferMetrics(const SourceBufferMetrics&) = delete;
   SourceBufferMetrics& operator=(const SourceBufferMetrics&) = delete;
 
-  SbTimeMonotonic wall_start_time_ = 0;
-  SbTimeMonotonic thread_start_time_ = 0;
+  void RecordTelemetry(SourceBufferMetricsAction action,
+                       const base::TimeDelta& action_duration);
+
+  base::TimeTicks wall_start_time_;
 
   const bool is_primary_video_;
   bool is_tracking_ = false;
+  SourceBufferMetricsAction current_action_ =
+      SourceBufferMetricsAction::NO_ACTION;
+  const base::TickClock* clock_;
 
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  SbTimeMonotonic thread_start_time_ = 0;
   size_t total_size_ = 0;
   SbTime total_thread_time_ = 0;
   SbTime total_wall_time_ = 0;
@@ -63,10 +72,9 @@
   int min_thread_bandwidth_ = INT_MAX;
   int max_wall_bandwidth_ = 0;
   int min_wall_bandwidth_ = INT_MAX;
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 };
 
-#endif  // defined(COBALT_BUILD_TYPE_GOLD)
-
 }  // namespace dom
 }  // namespace cobalt
 
diff --git a/cobalt/dom/source_buffer_metrics_test.cc b/cobalt/dom/source_buffer_metrics_test.cc
new file mode 100644
index 0000000..3f036e6
--- /dev/null
+++ b/cobalt/dom/source_buffer_metrics_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2024 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/dom/source_buffer_metrics.h"
+
+#include <memory>
+#include <string>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+namespace {
+constexpr char kUmaPrefix[] = "Cobalt.Media.SourceBuffer.";
+
+class SourceBufferMetricsTest : public ::testing::Test {
+ protected:
+  SourceBufferMetricsTest()
+      : metrics_(std::make_unique<SourceBufferMetrics>(
+            /*is_primary_video=*/true, &clock_)) {}
+
+  void SetUp() override { clock_.SetNowTicks(base::TimeTicks()); }
+
+  base::HistogramTester histogram_tester_;
+  base::SimpleTestTickClock clock_;
+
+  std::unique_ptr<SourceBufferMetrics> metrics_;
+};
+
+TEST_F(SourceBufferMetricsTest, RecordsPrepareAppendTelemetry) {
+  metrics_->StartTracking(SourceBufferMetricsAction::PREPARE_APPEND);
+
+  clock_.Advance(base::TimeDelta::FromMicroseconds(100));
+  metrics_->EndTracking(SourceBufferMetricsAction::PREPARE_APPEND, 1234);
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "PrepareAppend.Timing", 100, 1);
+}
+
+TEST_F(SourceBufferMetricsTest, RecordsAppendBufferTelemetry) {
+  metrics_->StartTracking(SourceBufferMetricsAction::APPEND_BUFFER);
+
+  clock_.Advance(base::TimeDelta::FromMicroseconds(100));
+  metrics_->EndTracking(SourceBufferMetricsAction::APPEND_BUFFER, 1234);
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "AppendBuffer.Timing", 100, 1);
+}
+
+}  // namespace
+}  // namespace dom
+}  // namespace cobalt
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index 40fccd4..cd17423 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -100,7 +100,7 @@
       "dial/dial_server.h",
     ]
     deps += [
-      "//net:cobalt_dial_server",
+      "//cobalt/network:cobalt_dial_server",
       "//net:http_server",
     ]
   }
diff --git a/cobalt/h5vcc/dial/dial_http_response.h b/cobalt/h5vcc/dial/dial_http_response.h
index 9adbc34..eb95999 100644
--- a/cobalt/h5vcc/dial/dial_http_response.h
+++ b/cobalt/h5vcc/dial/dial_http_response.h
@@ -19,8 +19,9 @@
 #include <string>
 #include <vector>
 
+#include "cobalt/network/dial/dial_service_handler.h"
 #include "cobalt/script/wrappable.h"
-#include "net/dial/dial_service_handler.h"
+#include "net/server/http_server_response_info.h"
 
 namespace cobalt {
 namespace h5vcc {
diff --git a/cobalt/h5vcc/dial/dial_server.cc b/cobalt/h5vcc/dial/dial_server.cc
index 9bb528d..52b60d1 100644
--- a/cobalt/h5vcc/dial/dial_server.cc
+++ b/cobalt/h5vcc/dial/dial_server.cc
@@ -23,10 +23,10 @@
 
 #include "base/strings/string_util.h"
 #include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/network/dial/dial_service_handler.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
-#include "net/dial/dial_service_handler.h"
 #include "net/server/http_server_request_info.h"
 
 namespace cobalt {
@@ -48,7 +48,7 @@
 
 }  // namespace
 
-class DialServer::ServiceHandler : public net::DialServiceHandler {
+class DialServer::ServiceHandler : public cobalt::network::DialServiceHandler {
  public:
   ServiceHandler(const base::WeakPtr<DialServer>& dial_server,
                  const std::string& service_name);
diff --git a/cobalt/h5vcc/dial/dial_server.h b/cobalt/h5vcc/dial/dial_server.h
index 20e89ad..af86564 100644
--- a/cobalt/h5vcc/dial/dial_server.h
+++ b/cobalt/h5vcc/dial/dial_server.h
@@ -26,11 +26,11 @@
 #include "cobalt/h5vcc/dial/dial_http_request.h"
 #include "cobalt/h5vcc/dial/dial_http_response.h"
 #include "cobalt/h5vcc/script_callback_wrapper.h"
+#include "cobalt/network/dial/dial_service.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
-#include "net/dial/dial_service.h"
 
 namespace cobalt {
 namespace h5vcc {
@@ -81,7 +81,7 @@
   THREAD_CHECKER(thread_checker_);
   CallbackMap callback_map_[kMethodCount];
 
-  scoped_refptr<net::DialServiceProxy> dial_service_proxy_;
+  scoped_refptr<network::DialServiceProxy> dial_service_proxy_;
   scoped_refptr<ServiceHandler> service_handler_;
 
   DISALLOW_COPY_AND_ASSIGN(DialServer);
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index 39c545c..eba3ded 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -226,5 +226,46 @@
   if (watchdog) watchdog->SetPersistentSettingWatchdogCrash(can_trigger_crash);
 }
 
+bool H5vccCrashLog::GetPersistentSettingLogtraceEnable() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) return watchdog->GetPersistentSettingLogtraceEnable();
+  return false;
+}
+
+void H5vccCrashLog::SetPersistentSettingLogtraceEnable(bool enable_logtrace) {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) watchdog->SetPersistentSettingLogtraceEnable(enable_logtrace);
+}
+
+bool H5vccCrashLog::LogEvent(const std::string& event) {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (!watchdog) {
+    return false;
+  }
+
+  return watchdog->LogEvent(event);
+}
+
+script::Sequence<std::string> H5vccCrashLog::GetLogTrace() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+
+  script::Sequence<std::string> sequence;
+  if (watchdog) {
+    std::vector<std::string> logTrace = watchdog->GetLogTrace();
+    for (std::size_t i = 0; i < logTrace.size(); ++i) {
+      sequence.push_back(logTrace[i]);
+    }
+  }
+
+  return sequence;
+}
+
+void H5vccCrashLog::ClearLog() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) {
+    watchdog->ClearLog();
+  }
+}
+
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index e01d23e..def1e67 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -91,6 +91,16 @@
 
   void SetPersistentSettingWatchdogCrash(bool can_trigger_crash);
 
+  bool GetPersistentSettingLogtraceEnable();
+
+  void SetPersistentSettingLogtraceEnable(bool enable_logtrace);
+
+  bool LogEvent(const std::string& event);
+
+  script::Sequence<std::string> GetLogTrace();
+
+  void ClearLog();
+
   DEFINE_WRAPPABLE_TYPE(H5vccCrashLog);
 
  private:
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index e60e0ab..98e1b81 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -108,4 +108,27 @@
   // Sets a persistent Watchdog setting that determines whether or not a
   // Watchdog violation will trigger a crash.
   void setPersistentSettingWatchdogCrash(boolean can_trigger_crash);
+
+  // Gets a persistent setting that determines whether or not
+  // events emitted by logEvent() are saved in Cobalt buffer.
+  boolean getPersistentSettingLogtraceEnable();
+
+  // Sets a persistent setting that determines whether or not
+  // events emitted by logEvent() are saved in Cobalt buffer.
+  void setPersistentSettingLogtraceEnable(boolean enable_logtrace);
+
+  // Appends a string event to a ring buffer. These log events can be appended
+  // from JS code. When watchdog violation is created, a snapshot of
+  // that buffer is attached to a violation. Identical sequential events
+  // are de-duplicated.
+  // Max event length is 256. Ring buffer size is 128.
+  boolean logEvent(DOMString event);
+
+  // Returns a snapshot (a "logTrace") of ring buffer of log events.
+  // This can be used to read a logTrace at arbitrary moment,
+  // without any violation.
+  sequence<DOMString> getLogTrace();
+
+  // Clears the ring buffer of log events.
+  void clearLog();
 };
diff --git a/cobalt/js_profiler/BUILD.gn b/cobalt/js_profiler/BUILD.gn
index 2a025d1..026c220 100644
--- a/cobalt/js_profiler/BUILD.gn
+++ b/cobalt/js_profiler/BUILD.gn
@@ -16,6 +16,8 @@
   sources = [
     "profiler.cc",
     "profiler.h",
+    "profiler_group.cc",
+    "profiler_group.h",
     "profiler_trace_builder.cc",
     "profiler_trace_builder.h",
     "profiler_trace_wrapper.h",
diff --git a/cobalt/js_profiler/js_profiler_test.cc b/cobalt/js_profiler/js_profiler_test.cc
index 98bb584..c2c52fa 100644
--- a/cobalt/js_profiler/js_profiler_test.cc
+++ b/cobalt/js_profiler/js_profiler_test.cc
@@ -35,6 +35,10 @@
  public:
   ProfilerTest() {}
 
+  void CollectGarbage() {
+    window_.web_context()->javascript_engine()->CollectGarbage();
+  }
+
  protected:
   dom::testing::StubWindow window_;
   StrictMock<script::testing::MockExceptionState> exception_state_;
@@ -111,5 +115,42 @@
   EXPECT_TRUE(EvaluateScript("Profiler", &result));
   EXPECT_EQ(result, "function Profiler() { [native code] }");
 }
+
+TEST_F(ProfilerTest, ProfilerGroupDisposesOfCpuProfiler) {
+  v8::HandleScope scope(web::get_isolate(window_.environment_settings()));
+  ProfilerInitOptions init_options;
+  init_options.set_sample_interval(10);
+  init_options.set_max_buffer_size(1);
+
+  auto profiler_group = ProfilerGroup::From(window_.environment_settings());
+  EXPECT_FALSE(profiler_group->active());
+  EXPECT_EQ(profiler_group->num_active_profilers(), 0);
+
+  scoped_refptr<Profiler> profiler_(new Profiler(
+      window_.environment_settings(), init_options, &exception_state_));
+  EXPECT_EQ(profiler_group->num_active_profilers(), 1);
+  EXPECT_TRUE(profiler_group->active());
+
+  auto promise = profiler_->Stop(window_.environment_settings());
+  EXPECT_EQ(profiler_->stopped(), true);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(profiler_group->num_active_profilers(), 0);
+  EXPECT_FALSE(profiler_group->active());
+}
+
+TEST_F(ProfilerTest, ProfilerCanBeCancelled) {
+  v8::HandleScope scope(web::get_isolate(window_.environment_settings()));
+  ProfilerInitOptions init_options;
+  init_options.set_sample_interval(10);
+  init_options.set_max_buffer_size(1000);
+
+  scoped_refptr<Profiler> profiler_(new Profiler(
+      window_.environment_settings(), init_options, &exception_state_));
+
+  profiler_->Cancel();
+  EXPECT_EQ(profiler_->stopped(), true);
+}
+
 }  // namespace js_profiler
 }  // namespace cobalt
diff --git a/cobalt/js_profiler/profiler.cc b/cobalt/js_profiler/profiler.cc
index c380aed..21e4cdd 100644
--- a/cobalt/js_profiler/profiler.cc
+++ b/cobalt/js_profiler/profiler.cc
@@ -14,45 +14,27 @@
 
 #include "cobalt/js_profiler/profiler.h"
 
-#include <iostream>
 #include <limits>
 #include <memory>
 #include <string>
 #include <utility>
 
-#include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/js_profiler/profiler_trace_builder.h"
+#include "base/logging.h"
 #include "cobalt/js_profiler/profiler_trace_wrapper.h"
 #include "cobalt/web/cache_utils.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/dom_exception.h"
-#include "cobalt/web/environment_settings.h"
 #include "cobalt/web/environment_settings_helper.h"
 
-namespace {
-v8::Local<v8::String> toV8String(v8::Isolate* isolate,
-                                 const std::string& string) {
-  if (string.empty()) return v8::String::Empty(isolate);
-  return v8::String::NewFromUtf8(isolate, string.c_str(),
-                                 v8::NewStringType::kNormal, string.length())
-      .ToLocalChecked();
-}
-}  // namespace
-
 namespace cobalt {
 namespace js_profiler {
 
-volatile uint32_t s_lastProfileId = 0;
-
-static constexpr int kBaseSampleIntervalMs = 10;
-
 Profiler::Profiler(script::EnvironmentSettings* settings,
                    ProfilerInitOptions options,
                    script::ExceptionState* exception_state)
-    : cobalt::web::EventTarget(settings),
-      stopped_(false),
-      time_origin_{base::TimeTicks::Now()} {
-  profiler_id_ = nextProfileId();
+    : stopped_(false), time_origin_{base::TimeTicks::Now()} {
+  profiler_group_ = ProfilerGroup::From(settings);
+  profiler_id_ = profiler_group_->NextProfilerId();
 
   const base::TimeDelta sample_interval =
       base::Milliseconds(options.sample_interval());
@@ -66,21 +48,19 @@
 
   int effective_sample_interval_ms =
       static_cast<int>(sample_interval.InMilliseconds());
-  if (effective_sample_interval_ms % kBaseSampleIntervalMs != 0 ||
+  if (effective_sample_interval_ms % Profiler::kBaseSampleIntervalMs != 0 ||
       effective_sample_interval_ms == 0) {
     effective_sample_interval_ms +=
-        (kBaseSampleIntervalMs -
-         effective_sample_interval_ms % kBaseSampleIntervalMs);
+        (Profiler::kBaseSampleIntervalMs -
+         effective_sample_interval_ms % Profiler::kBaseSampleIntervalMs);
   }
   sample_interval_ = effective_sample_interval_ms;
 
-  auto isolate = web::get_isolate(settings);
-
-  auto status = ImplProfilingStart(
-      profiler_id_,
+  SB_LOG(INFO) << "[PROFILER] START " + profiler_id_;
+  auto status = profiler_group_->ProfilerStart(
+      this, settings,
       v8::CpuProfilingOptions(v8::kLeafNodeLineNumbers,
-                              options.max_buffer_size(), sample_interval_us),
-      settings);
+                              options.max_buffer_size(), sample_interval_us));
 
   if (status == v8::CpuProfilingStatus::kAlreadyStarted) {
     web::DOMException::Raise(web::DOMException::kInvalidStateErr,
@@ -91,46 +71,31 @@
   }
 }
 
-Profiler::~Profiler() {
-  if (cpu_profiler_) {
-    cpu_profiler_->Dispose();
-    cpu_profiler_ = nullptr;
+void Profiler::AddEventListener(
+    script::EnvironmentSettings* environment_settings, const std::string& name,
+    const Profiler::SampleBufferFullCallbackHolder& holder) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (name != base::Tokens::samplebufferfull()) {
+    return;
   }
+  auto* global_wrappable = web::get_global_wrappable(environment_settings);
+  SampleBufferFullCallbackReference* token_callback =
+      new SampleBufferFullCallbackReference(global_wrappable, holder);
+  listeners_.push_back(
+      std::unique_ptr<SampleBufferFullCallbackReference>(token_callback));
 }
 
-v8::CpuProfilingStatus Profiler::ImplProfilingStart(
-    std::string profiler_id, v8::CpuProfilingOptions options,
-    script::EnvironmentSettings* settings) {
-  auto isolate = web::get_isolate(settings);
-  cpu_profiler_ = v8::CpuProfiler::New(isolate);
-  cpu_profiler_->SetSamplingInterval(kBaseSampleIntervalMs *
-                                     base::Time::kMicrosecondsPerMillisecond);
-  return cpu_profiler_->StartProfiling(
-      toV8String(isolate, profiler_id), options,
-      std::make_unique<ProfilerMaxSamplesDelegate>(this));
-}
-
-std::string Profiler::nextProfileId() {
-  s_lastProfileId++;
-  return "cobalt::profiler[" + std::to_string(s_lastProfileId) + "]";
-}
-
-void Profiler::PerformStop(
-    script::EnvironmentSettings* environment_settings,
-    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
-    base::TimeTicks time_origin, std::string profiler_id) {
-  auto isolate = web::get_isolate(environment_settings);
-  auto profile =
-      cpu_profiler_->StopProfiling(toV8String(isolate, profiler_id_));
-  auto trace = ProfilerTraceBuilder::FromProfile(profile, time_origin_);
-  scoped_refptr<ProfilerTraceWrapper> result(new ProfilerTraceWrapper(trace));
-  cpu_profiler_->Dispose();
-  cpu_profiler_ = nullptr;
-  promise_reference->value().Resolve(result);
+void Profiler::DispatchSampleBufferFullEvent() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  for (auto it = listeners_.begin(); it != listeners_.end(); ++it) {
+    (*it)->value().Run();
+  }
+  listeners_.clear();
 }
 
 Profiler::ProfilerTracePromise Profiler::Stop(
     script::EnvironmentSettings* environment_settings) {
+  SB_LOG(INFO) << "[PROFILER] STOPPING " + profiler_id_;
   script::HandlePromiseWrappable promise =
       web::get_script_value_factory(environment_settings)
           ->CreateInterfacePromise<scoped_refptr<ProfilerTraceWrapper>>();
@@ -145,7 +110,7 @@
     context->message_loop()->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(&Profiler::PerformStop, base::Unretained(this),
-                       environment_settings, std::move(promise_reference),
+                       profiler_group_, std::move(promise_reference),
                        std::move(time_origin_), std::move(profiler_id_)));
   } else {
     promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr,
@@ -154,5 +119,23 @@
   return promise;
 }
 
+void Profiler::PerformStop(
+    ProfilerGroup* profiler_group,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
+    base::TimeTicks time_origin, std::string profiler_id) {
+  SB_LOG(INFO) << "[PROFILER] STOPPED " + profiler_id_;
+  auto trace = profiler_group->ProfilerStop(this);
+  scoped_refptr<ProfilerTraceWrapper> result(new ProfilerTraceWrapper(trace));
+  promise_reference->value().Resolve(result);
+}
+
+void Profiler::Cancel() {
+  if (!stopped_) {
+    stopped_ = true;
+    profiler_group_->ProfilerStop(this);
+  }
+  profiler_group_ = nullptr;
+}
+
 }  // namespace js_profiler
 }  // namespace cobalt
diff --git a/cobalt/js_profiler/profiler.h b/cobalt/js_profiler/profiler.h
index a6da1e7..675f60b 100644
--- a/cobalt/js_profiler/profiler.h
+++ b/cobalt/js_profiler/profiler.h
@@ -17,67 +17,78 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
+#include "base/threading/thread_checker.h"
 #include "cobalt/dom/performance_high_resolution_time.h"
+#include "cobalt/js_profiler/profiler_group.h"
 #include "cobalt/js_profiler/profiler_init_options.h"
 #include "cobalt/js_profiler/profiler_trace.h"
+#include "cobalt/script/callback_function.h"
 #include "cobalt/script/promise.h"
+#include "cobalt/script/script_value.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/event_target.h"
-#include "third_party/v8/include/cppgc/member.h"
 #include "third_party/v8/include/v8-profiler.h"
 
 namespace cobalt {
 namespace js_profiler {
 
-class Profiler : public cobalt::web::EventTarget {
+// Forward declaration of ProfilerGroup
+class ProfilerGroup;
+
+// TODO(b/326337485): Profiler should be a subclass of EventTarget.
+class Profiler : public script::Wrappable {
  public:
-  using ProfilerTracePromise = script::HandlePromiseWrappable;
+  static const int kBaseSampleIntervalMs = 10;
+  typedef script::HandlePromiseWrappable ProfilerTracePromise;
+  typedef script::CallbackFunction<void()> SampleBufferFullCallback;
+  typedef script::ScriptValue<SampleBufferFullCallback>
+      SampleBufferFullCallbackHolder;
+  typedef SampleBufferFullCallbackHolder::Reference
+      SampleBufferFullCallbackReference;
 
   Profiler(script::EnvironmentSettings* settings, ProfilerInitOptions options,
            script::ExceptionState* exception_state);
-  ~Profiler();
+  ~Profiler() override = default;
+
+  void AddEventListener(script::EnvironmentSettings* environment_settings,
+                        const std::string& name,
+                        const SampleBufferFullCallbackHolder& listener);
+
+  void DispatchSampleBufferFullEvent();
 
   ProfilerTracePromise Stop(script::EnvironmentSettings* environment_settings);
+  void Cancel();
 
   bool stopped() const { return stopped_; }
 
   dom::DOMHighResTimeStamp sample_interval() const { return sample_interval_; }
+  std::string ProfilerId() const { return profiler_id_; }
+  base::TimeTicks time_origin() const { return time_origin_; }
 
   DEFINE_WRAPPABLE_TYPE(Profiler);
 
-  virtual v8::CpuProfilingStatus ImplProfilingStart(
-      std::string profiler_id, v8::CpuProfilingOptions options,
-      script::EnvironmentSettings* settings);
-
  private:
-  void PerformStop(script::EnvironmentSettings* environment_settings,
+  void PerformStop(ProfilerGroup* profiler_group,
                    std::unique_ptr<script::ValuePromiseWrappable::Reference>
                        promise_reference,
                    base::TimeTicks time_origin, std::string profiler_id);
 
-  std::string nextProfileId();
-
   bool stopped_;
   dom::DOMHighResTimeStamp sample_interval_;
-  v8::CpuProfiler* cpu_profiler_ = nullptr;
   base::TimeTicks time_origin_;
   std::string profiler_id_;
-};
+  ProfilerGroup* profiler_group_;
+  // All samplebufferfull listeners. Prevents GC on callbacks owned by this
+  // object, by binding to global-wrappable.
+  std::vector<std::unique_ptr<SampleBufferFullCallbackReference>> listeners_;
 
-class ProfilerMaxSamplesDelegate : public v8::DiscardedSamplesDelegate {
- public:
-  explicit ProfilerMaxSamplesDelegate(Profiler* profiler)
-      : profiler_(profiler) {}
-  void Notify() override {
-    if (profiler_.Get()) {
-      profiler_->DispatchEvent(new web::Event("samplebufferfull"));
-    }
-  }
+  // Thread checker for the thread that creates this instance.
+  THREAD_CHECKER(thread_checker_);
 
- private:
-  cppgc::WeakMember<Profiler> profiler_;
+  DISALLOW_COPY_AND_ASSIGN(Profiler);
 };
 
 }  // namespace js_profiler
diff --git a/cobalt/js_profiler/profiler.idl b/cobalt/js_profiler/profiler.idl
index 6f37933..0492b00 100644
--- a/cobalt/js_profiler/profiler.idl
+++ b/cobalt/js_profiler/profiler.idl
@@ -20,9 +20,16 @@
   ConstructorCallWith=EnvironmentSettings,
   RaisesException = Constructor,
 ]
-interface Profiler : EventTarget {
+interface Profiler {
   readonly attribute DOMHighResTimeStamp sampleInterval;
   readonly attribute boolean stopped;
 
+  // TODO(b/326337485): This function mocks but does not fully emulate the EventTarget interface. It can
+  // take and call many callbacks as listeners. However, note that this class does not remove listeners
+  // or dispatch events. Use with caution.
+  [CallWith=EnvironmentSettings] void addEventListener(DOMString token, SampleBufferFullCallback listener);
+
   [CallWith=EnvironmentSettings] Promise<ProfilerTraceWrapper> stop();
 };
+
+callback SampleBufferFullCallback = void();
diff --git a/cobalt/js_profiler/profiler_group.cc b/cobalt/js_profiler/profiler_group.cc
new file mode 100644
index 0000000..e52512d
--- /dev/null
+++ b/cobalt/js_profiler/profiler_group.cc
@@ -0,0 +1,138 @@
+// Copyright 2024 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/js_profiler/profiler_group.h"
+
+#include "cobalt/js_profiler/profiler_trace_builder.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings_helper.h"
+
+namespace {
+v8::Local<v8::String> toV8String(v8::Isolate* isolate,
+                                 const std::string& string) {
+  if (string.empty()) return v8::String::Empty(isolate);
+  return v8::String::NewFromUtf8(isolate, string.c_str(),
+                                 v8::NewStringType::kNormal, string.length())
+      .ToLocalChecked();
+}
+}  // namespace
+
+namespace cobalt {
+namespace js_profiler {
+
+ProfilerGroup* ProfilerGroup::From(
+    script::EnvironmentSettings* environment_settings) {
+  web::Context* context = web::get_context(environment_settings);
+  if (!context->profiler_group()) {
+    script::GlobalEnvironment* global_env =
+        web::get_global_environment(environment_settings);
+    context->set_profiler_group(
+        std::make_unique<ProfilerGroup>(global_env->isolate()));
+  }
+  return context->profiler_group();
+}
+
+v8::CpuProfilingStatus ProfilerGroup::ProfilerStart(
+    const scoped_refptr<Profiler>& profiler,
+    script::EnvironmentSettings* settings, v8::CpuProfilingOptions options) {
+  if (!cpu_profiler_) {
+    cpu_profiler_ = v8::CpuProfiler::New(isolate_);
+    cpu_profiler_->SetSamplingInterval(
+        cobalt::js_profiler::Profiler::kBaseSampleIntervalMs *
+        base::Time::kMicrosecondsPerMillisecond);
+  }
+  profilers_.push_back(profiler);
+  num_active_profilers_++;
+  return cpu_profiler_->StartProfiling(
+      toV8String(isolate_, profiler->ProfilerId()), options,
+      std::make_unique<ProfilerMaxSamplesDelegate>(this,
+                                                   profiler->ProfilerId()));
+}
+
+ProfilerTrace ProfilerGroup::ProfilerStop(Profiler* profiler) {
+  auto profile = cpu_profiler_->StopProfiling(
+      toV8String(isolate_, profiler->ProfilerId()));
+  this->PopProfiler(profiler->ProfilerId());
+  auto trace =
+      ProfilerTraceBuilder::FromProfile(profile, profiler->time_origin());
+  if (profile) {
+    profile->Delete();
+  }
+  if (cpu_profiler_ && num_active_profilers_ == 0) {
+    cpu_profiler_->Dispose();
+    cpu_profiler_ = nullptr;
+  }
+  return trace;
+}
+
+std::string ProfilerGroup::NextProfilerId() {
+  auto id = "cobalt::profiler[" + std::to_string(next_profiler_id_) + "]";
+  next_profiler_id_++;
+  return id;
+}
+
+void ProfilerGroup::DispatchSampleBufferFullEvent(std::string profiler_id) {
+  auto profiler = GetProfiler(profiler_id);
+
+  if (profiler) {
+    profiler->DispatchSampleBufferFullEvent();
+  }
+}
+
+Profiler* ProfilerGroup::GetProfiler(std::string profiler_id) {
+  auto profiler =
+      std::find_if(profilers_.begin(), profilers_.end(),
+                   [&profiler_id](const scoped_refptr<Profiler>& profiler) {
+                     return profiler->ProfilerId() == profiler_id;
+                   });
+  if (profiler == profilers_.end()) {
+    return nullptr;
+  }
+  return profiler->get();
+}
+
+void ProfilerGroup::PopProfiler(std::string profiler_id) {
+  auto profiler = std::find_if(profilers_.begin(), profilers_.end(),
+                               [&profiler_id](const Profiler* profiler) {
+                                 return profiler->ProfilerId() == profiler_id;
+                               });
+  if (profiler != profilers_.end()) {
+    profilers_.erase(profiler);
+  }
+  num_active_profilers_--;
+}
+
+void ProfilerGroup::WillDestroyCurrentMessageLoop() {
+  while (!profilers_.empty()) {
+    Profiler* profiler = profilers_[0];
+    DCHECK(profiler);
+    profiler->Cancel();
+    DCHECK(profiler->stopped());
+  }
+
+  DCHECK_EQ(num_active_profilers_, 0);
+  if (cpu_profiler_) {
+    cpu_profiler_->Dispose();
+    cpu_profiler_ = nullptr;
+  }
+}
+
+void ProfilerMaxSamplesDelegate::Notify() {
+  if (profiler_group_.Get()) {
+    profiler_group_->DispatchSampleBufferFullEvent(profiler_id_);
+  }
+}
+
+}  // namespace js_profiler
+}  // namespace cobalt
diff --git a/cobalt/js_profiler/profiler_group.h b/cobalt/js_profiler/profiler_group.h
new file mode 100644
index 0000000..a36a616
--- /dev/null
+++ b/cobalt/js_profiler/profiler_group.h
@@ -0,0 +1,103 @@
+// Copyright 2024 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_JS_PROFILER_PROFILER_GROUP_H_
+#define COBALT_JS_PROFILER_PROFILER_GROUP_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "cobalt/js_profiler/profiler.h"
+#include "cobalt/js_profiler/profiler_trace.h"
+#include "cobalt/script/global_environment.h"
+#include "third_party/v8/include/v8-profiler.h"
+#include "v8/include/cppgc/member.h"
+#include "v8/include/libplatform/libplatform.h"
+#include "v8/include/v8-platform.h"
+#include "v8/include/v8.h"
+
+namespace cobalt {
+namespace js_profiler {
+
+// Forward Declaration of Profiler Class.
+class Profiler;
+
+// A ProfilerGroup represents a set of window.Profiler JS objects sharing an
+// underlying v8::CpuProfiler attached to a common isolate.
+class ProfilerGroup : public base::MessageLoop::DestructionObserver {
+ public:
+  explicit ProfilerGroup(v8::Isolate* isolate) : isolate_(isolate) {}
+
+  ~ProfilerGroup() = default;
+
+  v8::CpuProfilingStatus ProfilerStart(const scoped_refptr<Profiler>& profiler,
+                                       script::EnvironmentSettings* settings,
+                                       v8::CpuProfilingOptions options);
+
+  ProfilerTrace ProfilerStop(Profiler* profiler);
+
+  void ProfilerCancel(Profiler* profiler);
+
+  void DispatchSampleBufferFullEvent(std::string profiler_id);
+
+  static ProfilerGroup* From(script::EnvironmentSettings* environment_settings);
+
+  // Generates an unused string identifier to use for a new profiling session.
+  std::string NextProfilerId();
+
+  // From base::MessageLoop::DestructionObserver.
+  // All active profiling threads must be stopped before discarding this object.
+  void WillDestroyCurrentMessageLoop() override;
+
+  int num_active_profilers() const { return num_active_profilers_; }
+
+  bool active() const { return !!cpu_profiler_; }
+
+ private:
+  Profiler* GetProfiler(std::string profiler_id);
+  void PopProfiler(std::string profiler_id);
+
+  v8::Isolate* isolate_;
+  v8::CpuProfiler* cpu_profiler_ = nullptr;
+
+  int num_active_profilers_ = 0;
+  int next_profiler_id_ = 0;
+
+  // All active profilers, retained from GC.
+  std::vector<scoped_refptr<Profiler>> profilers_;
+};
+
+// ProfilerMaxSamplesDelegate has a notify function that is called when the
+// number of collected samples exceeds the allocated buffer. It is primarily
+// responsible for the "samplebufferfull" event listener on the Profiler class.
+class ProfilerMaxSamplesDelegate : public v8::DiscardedSamplesDelegate {
+ public:
+  explicit ProfilerMaxSamplesDelegate(ProfilerGroup* profiler_group,
+                                      std::string profiler_id)
+      : profiler_group_(profiler_group), profiler_id_(profiler_id) {}
+
+  void Notify() override;
+
+ private:
+  cppgc::WeakMember<ProfilerGroup> profiler_group_;
+  std::string profiler_id_;
+};
+
+}  // namespace js_profiler
+}  // namespace cobalt
+
+#endif  // COBALT_JS_PROFILER_PROFILER_GROUP_H_
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index 9b808f9..c2ee44e 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -178,6 +178,7 @@
 
   sources = [
     "blob_fetcher_test.cc",
+    "fetcher_cache_test.cc",
     "fetcher_factory_test.cc",
     "fetcher_test.h",
     "file_fetcher_test.cc",
@@ -204,6 +205,7 @@
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/ots:ots",
+    "//url",
   ]
 
   data_deps = [ ":copy_loader_test_data" ]
diff --git a/cobalt/loader/fetcher_cache.h b/cobalt/loader/fetcher_cache.h
index 8f6e2b0..de32a1f 100644
--- a/cobalt/loader/fetcher_cache.h
+++ b/cobalt/loader/fetcher_cache.h
@@ -42,6 +42,16 @@
   Loader::FetcherCreator GetFetcherCreator(
       const GURL& url, const Loader::FetcherCreator& real_fetcher_creator);
   void NotifyResourceRequested(const std::string& url);
+  size_t size() const {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    CHECK_EQ(thread_id_, SbThreadGetId());
+    return total_size_;
+  }
+  size_t capacity() const {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    CHECK_EQ(thread_id_, SbThreadGetId());
+    return capacity_;
+  }
 
   // To signal the imminent destruction of this object.  If everything is
   // working as expected, there shouldn't be any other reference of this object,
diff --git a/cobalt/loader/fetcher_cache_test.cc b/cobalt/loader/fetcher_cache_test.cc
new file mode 100644
index 0000000..217ad1a
--- /dev/null
+++ b/cobalt/loader/fetcher_cache_test.cc
@@ -0,0 +1,421 @@
+// Copyright 2024 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/loader/fetcher_cache.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_scheduler/task_scheduler.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "cobalt/loader/fetcher.h"
+#include "net/http/http_response_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace loader {
+namespace {
+
+constexpr int kFetcherCapacity = 1024 * 1024;
+
+class StubFetcher : public Fetcher {
+ public:
+  explicit StubFetcher(Handler* handler) : Fetcher(handler) {}
+  ~StubFetcher() override {}
+
+  void FireResponseStarted(
+      const scoped_refptr<net::HttpResponseHeaders>& headers) {
+    auto response_type = handler()->OnResponseStarted(this, headers);
+    if (response_type == kLoadResponseAbort) {
+      return;
+    }
+  }
+
+  void FireReceived(const char* data, size_t size) {
+    handler()->OnReceived(this, data, size);
+  }
+
+  void FireDone() { handler()->OnDone(this); }
+
+  void FireFetcherProcess(
+      const scoped_refptr<net::HttpResponseHeaders>& headers, const char* data,
+      size_t size) {
+    LoadResponseType response = handler()->OnResponseStarted(this, headers);
+    if (response == kLoadResponseContinue) {
+      handler()->OnReceived(this, data, size);
+      handler()->OnDone(this);
+    }
+  }
+};
+
+class StubFetcherHandler : public Fetcher::Handler {
+ public:
+  // From Fetcher::Handler.
+  void OnReceived(Fetcher* fetcher, const char* data, size_t size) override {}
+  void OnDone(Fetcher* fetcher) override {}
+  void OnError(Fetcher* fetcher, const std::string& error_message) override {}
+};
+
+struct StubFetcherFactory {
+ public:
+  StubFetcherFactory() {}
+  // Way to access the last fetcher created by the fetcher factory.
+  StubFetcher* fetcher;
+
+  std::unique_ptr<Fetcher> Create(Fetcher::Handler* handler) {
+    fetcher = new StubFetcher(handler);
+    return base::WrapUnique<Fetcher>(fetcher);
+  }
+
+  Loader::FetcherCreator GetFetcherCreator() {
+    return base::Bind(&StubFetcherFactory::Create, base::Unretained(this));
+  }
+};
+
+std::unique_ptr<Fetcher> CreateFetcher(Fetcher::Handler* handler) {
+  StubFetcher* fetcher = new StubFetcher(handler);
+  return base::WrapUnique<Fetcher>(fetcher);
+}
+
+}  // namespace
+
+class FetcherCacheTest : public ::testing::Test {
+ protected:
+  class OngoingFetcherFactory {
+   public:
+    OngoingFetcherFactory(const char* name, size_t capacity) {
+      fetcher_cache_ = base::MakeRefCounted<FetcherCache>(name, capacity);
+    }
+    ~OngoingFetcherFactory() {
+      if (fetcher_cache_) {
+        fetcher_cache_->DestroySoon();
+      }
+    }
+
+    size_t size() const { return fetcher_cache_->size(); }
+
+    size_t capacity() const { return fetcher_cache_->capacity(); }
+
+    Loader::FetcherCreator MakeCachedFetcherCreator(
+        const GURL& url, const Loader::FetcherCreator& real_fetcher_creator) {
+      return fetcher_cache_->GetFetcherCreator(url, real_fetcher_creator);
+    }
+
+   private:
+    scoped_refptr<FetcherCache> fetcher_cache_;
+  };
+
+  const std::string data_ = "Test string! Test string! Test!!";
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  OngoingFetcherFactory fetcher_factory_{"cache_name", kFetcherCapacity};
+};
+
+TEST_F(FetcherCacheTest, CtorAndDtor) {
+  EXPECT_EQ(kFetcherCapacity, fetcher_factory_.capacity());
+}
+
+TEST_F(FetcherCacheTest, MakeFetcher) {
+  auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator(
+      GURL("https://example.com"), base::Bind(&CreateFetcher));
+  EXPECT_EQ(0, fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, SunnyDay) {
+  StubFetcherHandler handler;
+  StubFetcherFactory stub_fetcher_factory;
+  auto cb = stub_fetcher_factory.GetFetcherCreator();
+
+  auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator(
+      GURL("https://example.com"), cb);
+  auto fetcher = fetcher_creator.Run(&handler);
+
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory.fetcher),
+                                data_.c_str(), data_.size()));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory.fetcher)));
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, DuplicateFetcherCache) {
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+
+  std::vector<StubFetcherFactory> stub_fetcher_factorys;
+  std::vector<Loader::FetcherCreator> fetcher_creators;
+  std::vector<std::unique_ptr<Fetcher>> fetchers;
+
+  for (size_t i = 0; i < 2; i++) {
+    StubFetcherFactory stub_fetcher_factory;
+    Loader::FetcherCreator fetcher_creator =
+        fetcher_factory_.MakeCachedFetcherCreator(
+            GURL("https://example.com"),
+            stub_fetcher_factory.GetFetcherCreator());
+    std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
+
+    stub_fetcher_factorys.push_back(stub_fetcher_factory);
+    fetcher_creators.push_back(fetcher_creator);
+    fetchers.push_back(std::move(fetcher));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&StubFetcher::FireFetcherProcess,
+                       base::Unretained(stub_fetcher_factorys[i].fetcher),
+                       headers, data_.c_str(), data_.size()));
+  }
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, MultipleFetcherCache) {
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+  const size_t kMaxNumCache = 10;
+
+  std::vector<StubFetcherFactory> stub_fetcher_factorys;
+  std::vector<Loader::FetcherCreator> fetcher_creators;
+  std::vector<std::unique_ptr<Fetcher>> fetchers;
+
+  for (size_t i = 0; i < kMaxNumCache; i++) {
+    StubFetcherFactory stub_fetcher_factory;
+    Loader::FetcherCreator fetcher_creator =
+        fetcher_factory_.MakeCachedFetcherCreator(
+            GURL("https://example.com" + base::NumberToString(i)),
+            stub_fetcher_factory.GetFetcherCreator());
+    std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
+
+    stub_fetcher_factorys.push_back(stub_fetcher_factory);
+    fetcher_creators.push_back(fetcher_creator);
+    fetchers.push_back(std::move(fetcher));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&StubFetcher::FireFetcherProcess,
+                       base::Unretained(stub_fetcher_factorys[i].fetcher),
+                       headers, data_.c_str(), data_.size()));
+  }
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity() * kMaxNumCache, fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, MaxFetcherCache) {
+  const size_t capacity = kFetcherCapacity;
+  const size_t data_capacity = data_.capacity();
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+  // kMaxNumCache = ceil(capacity / data_.capacity()) + 1, this will force
+  // to write a new cache and delete an old cache
+  const size_t kMaxNumCache = 1 + (capacity / data_capacity);
+
+  std::vector<StubFetcherFactory> stub_fetcher_factorys;
+  std::vector<Loader::FetcherCreator> fetcher_creators;
+  std::vector<std::unique_ptr<Fetcher>> fetchers;
+
+  for (size_t i = 0; i < kMaxNumCache; i++) {
+    StubFetcherFactory stub_fetcher_factory;
+    Loader::FetcherCreator fetcher_creator =
+        fetcher_factory_.MakeCachedFetcherCreator(
+            GURL("https://example.com" + base::NumberToString(i)),
+            stub_fetcher_factory.GetFetcherCreator());
+    std::unique_ptr<Fetcher> fetcher = fetcher_creator.Run(&handler);
+
+    stub_fetcher_factorys.push_back(stub_fetcher_factory);
+    fetcher_creators.push_back(fetcher_creator);
+    fetchers.push_back(std::move(fetcher));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&StubFetcher::FireFetcherProcess,
+                       base::Unretained(stub_fetcher_factorys[i].fetcher),
+                       headers, data_.c_str(), data_.size()));
+  }
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_capacity * (kMaxNumCache - 1), fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, DisorderedSameFetcherCache) {
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+
+  StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
+  Loader::FetcherCreator fetcher_creator1 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example.com"),
+          stub_fetcher_factory1.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
+  Loader::FetcherCreator fetcher_creator2 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example.com"),
+          stub_fetcher_factory2.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
+
+  // Task 1: start and receive
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory1.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory1.fetcher),
+                                data_.c_str(), data_.size()));
+  // Task 2: start and receive
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory2.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory2.fetcher),
+                                data_.c_str(), data_.size()));
+  // Task 1: done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory1.fetcher)));
+  // Task 2: done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory2.fetcher)));
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity(), fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, DisorderedDiffFetcherCache) {
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+
+  StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
+  Loader::FetcherCreator fetcher_creator1 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example1.com"),
+          stub_fetcher_factory1.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
+  Loader::FetcherCreator fetcher_creator2 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example2.com"),
+          stub_fetcher_factory2.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
+
+  // Task 1: start and receive
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory1.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory1.fetcher),
+                                data_.c_str(), data_.size()));
+  // Task 2: start and receive
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory2.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory2.fetcher),
+                                data_.c_str(), data_.size()));
+  // Task 1: done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory1.fetcher)));
+  // Task 2: done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory2.fetcher)));
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size());
+}
+
+TEST_F(FetcherCacheTest, DisorderedSlowFetcherCache) {
+  StubFetcherHandler handler;
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
+
+  StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2;
+  Loader::FetcherCreator fetcher_creator1 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example1.com"),
+          stub_fetcher_factory1.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher1 = fetcher_creator1.Run(&handler);
+  Loader::FetcherCreator fetcher_creator2 =
+      fetcher_factory_.MakeCachedFetcherCreator(
+          GURL("https://example2.com"),
+          stub_fetcher_factory2.GetFetcherCreator());
+  std::unique_ptr<Fetcher> fetcher2 = fetcher_creator2.Run(&handler);
+
+  // Task 1: start and receive
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory1.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory1.fetcher),
+                                data_.c_str(), data_.size()));
+  // Task 2: start, receive, and done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireResponseStarted,
+                     base::Unretained(stub_fetcher_factory2.fetcher), headers));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&StubFetcher::FireReceived,
+                                base::Unretained(stub_fetcher_factory2.fetcher),
+                                data_.c_str(), data_.size()));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory2.fetcher)));
+  // Task 1: done
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&StubFetcher::FireDone,
+                     base::Unretained(stub_fetcher_factory1.fetcher)));
+
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size());
+}
+
+}  // namespace loader
+}  // namespace cobalt
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index 11bc212..aa0a6fd 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -120,8 +120,9 @@
   sources = [
     "base/cval_stats_test.cc",
     "base/decoder_buffer_cache_test.cc",
+    "base/format_support_query_metrics_test.cc",
+    "base/metrics_provider_test.cc",
     "file_data_source_test.cc",
-    "media_module_test.cc",
     "progressive/demuxer_extension_wrapper_test.cc",
     "progressive/mock_data_source_reader.h",
     "progressive/mp4_map_unittest.cc",
diff --git a/cobalt/media/base/format_support_query_metrics.cc b/cobalt/media/base/format_support_query_metrics.cc
index 45fb348..20c33c4 100644
--- a/cobalt/media/base/format_support_query_metrics.cc
+++ b/cobalt/media/base/format_support_query_metrics.cc
@@ -17,7 +17,10 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "starboard/common/string.h"
 
 namespace cobalt {
@@ -27,11 +30,23 @@
 
 namespace {
 
-std::string CreateQueryDescription(const char* query_name,
+std::string CreateQueryDescription(FormatSupportQueryAction query_action,
                                    const std::string& mime_type,
                                    const std::string& key_system,
                                    SbMediaSupportType support_type,
-                                   SbTimeMonotonic query_duration) {
+                                   base::TimeDelta query_duration) {
+  auto get_query_name_str = [](FormatSupportQueryAction query_action) {
+    switch (query_action) {
+      case FormatSupportQueryAction::HTML_MEDIA_ELEMENT_CAN_PLAY_TYPE:
+        return "HTMLMediaElement::canPlayType";
+      case FormatSupportQueryAction::MEDIA_SOURCE_IS_TYPE_SUPPORTED:
+        return "MediaSource::isTypeSupported";
+      case FormatSupportQueryAction::UNKNOWN_ACTION:
+      default:
+        return "Unknown Action";
+    }
+  };
+
   auto get_support_type_str = [](SbMediaSupportType support_type) {
     switch (support_type) {
       case kSbMediaSupportTypeNotSupported:
@@ -47,34 +62,68 @@
   };
 
   return starboard::FormatString(
-      "%s(%s%s%s, %" PRId64 " us", query_name, mime_type.c_str(),
+      "%s(%s%s%s, %" PRId64 " us", get_query_name_str(query_action),
+      mime_type.c_str(),
       (key_system.empty() ? ")" : ", " + key_system + ")").c_str(),
-      get_support_type_str(support_type), query_duration);
+      get_support_type_str(support_type), query_duration.InMicroseconds());
 }
 
 }  // namespace
 
 // static
-SbTimeMonotonic FormatSupportQueryMetrics::cached_query_durations_
+base::TimeDelta FormatSupportQueryMetrics::cached_query_durations_
     [kMaxCachedQueryDurations] = {};
 char FormatSupportQueryMetrics::max_query_description_
     [kMaxQueryDescriptionLength] = {};
-SbTimeMonotonic FormatSupportQueryMetrics::max_query_duration_ = 0;
-SbTimeMonotonic FormatSupportQueryMetrics::total_query_duration_ = 0;
+base::TimeDelta FormatSupportQueryMetrics::max_query_duration_ =
+    base::TimeDelta();
+base::TimeDelta FormatSupportQueryMetrics::total_query_duration_ =
+    base::TimeDelta();
 int FormatSupportQueryMetrics::total_num_queries_ = 0;
 
-FormatSupportQueryMetrics::FormatSupportQueryMetrics() {
-  start_time_ = SbTimeGetMonotonicNow();
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+FormatSupportQueryMetrics::FormatSupportQueryMetrics(
+    const base::TickClock* clock)
+    : clock_(clock) {
+  start_time_ = clock->NowTicks();
+}
+
+void FormatSupportQueryMetrics::RecordQueryLatencyUMA(
+    FormatSupportQueryAction query_action, base::TimeDelta action_duration) {
+  switch (query_action) {
+    case FormatSupportQueryAction::MEDIA_SOURCE_IS_TYPE_SUPPORTED: {
+      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+          "Cobalt.Media.MediaSource.IsTypeSupported.Timing", action_duration,
+          base::TimeDelta::FromMicroseconds(1),
+          base::TimeDelta::FromMilliseconds(5), 50);
+      break;
+    }
+    case FormatSupportQueryAction::HTML_MEDIA_ELEMENT_CAN_PLAY_TYPE: {
+      UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+          "Cobalt.Media.HTMLMediaElement.CanPlayType.Timing", action_duration,
+          base::TimeDelta::FromMicroseconds(1),
+          base::TimeDelta::FromMilliseconds(5), 50);
+      break;
+    }
+    case FormatSupportQueryAction::UNKNOWN_ACTION:
+    default:
+      break;
+  }
 }
 
 void FormatSupportQueryMetrics::RecordAndLogQuery(
-    const char* query_name, const std::string& mime_type,
+    FormatSupportQueryAction query_action, const std::string& mime_type,
     const std::string& key_system, SbMediaSupportType support_type) {
-  SbTimeMonotonic query_duration = SbTimeGetMonotonicNow() - start_time_;
+  base::TimeDelta query_duration = clock_->NowTicks() - start_time_;
+
+  RecordQueryLatencyUMA(query_action, query_duration);
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
   total_query_duration_ += query_duration;
 
   std::string query_description = CreateQueryDescription(
-      query_name, mime_type, key_system, support_type, query_duration);
+      query_action, mime_type, key_system, support_type, query_duration);
   LOG(INFO) << query_description;
 
   if (total_num_queries_ < SB_ARRAY_SIZE_INT(cached_query_durations_)) {
@@ -88,10 +137,12 @@
   }
 
   ++total_num_queries_;
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
 // static
 void FormatSupportQueryMetrics::PrintAndResetMetrics() {
+#if !defined(COBALT_BUILD_TYPE_GOLD)
   if (total_num_queries_ == 0) {
     LOG(INFO) << "Format support query metrics:\n\tNumber of queries: 0";
     return;
@@ -116,13 +167,13 @@
             << " us\n\tMedian query time: ~" << get_median()
             << " us\n\tLongest query: " << max_query_description_;
 
-  max_query_description_[0] = 0;
-  max_query_duration_ = 0;
-  total_query_duration_ = 0;
-  total_num_queries_ = 0;
+  max_query_description_[0] = {};
+  max_query_duration_ = {};
+  total_query_duration_ = {};
+  total_num_queries_ = {};
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
-#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
 }  // namespace media
 }  // namespace cobalt
diff --git a/cobalt/media/base/format_support_query_metrics.h b/cobalt/media/base/format_support_query_metrics.h
index 7343e14..518eec9 100644
--- a/cobalt/media/base/format_support_query_metrics.h
+++ b/cobalt/media/base/format_support_query_metrics.h
@@ -17,51 +17,52 @@
 
 #include <string>
 
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "starboard/media.h"
 #include "starboard/time.h"
 
 namespace cobalt {
 namespace media {
 
-#if defined(COBALT_BUILD_TYPE_GOLD)
-
-class FormatSupportQueryMetrics {
- public:
-  FormatSupportQueryMetrics() = default;
-  ~FormatSupportQueryMetrics() = default;
-
-  void RecordAndLogQuery(const char* query_name, const std::string& mime_type,
-                         const std::string& key_system,
-                         SbMediaSupportType support_type) {}
-  static void PrintAndResetMetrics() {}
+enum class FormatSupportQueryAction : uint8_t {
+  UNKNOWN_ACTION,
+  HTML_MEDIA_ELEMENT_CAN_PLAY_TYPE,
+  MEDIA_SOURCE_IS_TYPE_SUPPORTED,
 };
 
-#else  // defined(COBALT_BUILD_TYPE_GOLD)
-
 class FormatSupportQueryMetrics {
  public:
-  FormatSupportQueryMetrics();
+  FormatSupportQueryMetrics(
+      const base::TickClock* clock = base::DefaultTickClock::GetInstance());
   ~FormatSupportQueryMetrics() = default;
 
-  void RecordAndLogQuery(const char* query_name, const std::string& mime_type,
+  void RecordAndLogQuery(FormatSupportQueryAction query_action,
+                         const std::string& mime_type,
                          const std::string& key_system,
                          SbMediaSupportType support_type);
   static void PrintAndResetMetrics();
 
  private:
+  void RecordQueryLatencyUMA(FormatSupportQueryAction query_action,
+                             base::TimeDelta action_duration);
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
   static constexpr int kMaxCachedQueryDurations = 150;
   static constexpr int kMaxQueryDescriptionLength = 350;
 
-  static SbTimeMonotonic cached_query_durations_[kMaxCachedQueryDurations];
+  static base::TimeDelta cached_query_durations_[kMaxCachedQueryDurations];
   static char max_query_description_[kMaxQueryDescriptionLength];
-  static SbTimeMonotonic max_query_duration_;
-  static SbTimeMonotonic total_query_duration_;
+  static base::TimeDelta max_query_duration_;
+  static base::TimeDelta total_query_duration_;
   static int total_num_queries_;
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
-  SbTimeMonotonic start_time_ = 0;
+  base::TimeTicks start_time_;
+  const base::TickClock* clock_;
 };
 
-#endif  // defined(COBALT_BUILD_TYPE_GOLD)
 
 }  // namespace media
 }  // namespace cobalt
diff --git a/cobalt/media/base/format_support_query_metrics_test.cc b/cobalt/media/base/format_support_query_metrics_test.cc
new file mode 100644
index 0000000..cbf26ee
--- /dev/null
+++ b/cobalt/media/base/format_support_query_metrics_test.cc
@@ -0,0 +1,76 @@
+// Copyright 2024 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/format_support_query_metrics.h"
+
+#include <memory>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "starboard/media.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace media {
+namespace {
+
+constexpr char kUmaPrefix[] = "Cobalt.Media.";
+
+class FormatSupportQueryMetricsTest : public ::testing::Test {
+ protected:
+  FormatSupportQueryMetricsTest() : metrics_(&clock_) {}
+
+  void SetUp() override { clock_.SetNowTicks(base::TimeTicks()); }
+
+  base::HistogramTester histogram_tester_;
+  base::SimpleTestTickClock clock_;
+
+  FormatSupportQueryMetrics metrics_;
+};
+
+TEST_F(FormatSupportQueryMetricsTest,
+       ReportsMediaSourceIsTypeSupportedLatencyToUMA) {
+  clock_.Advance(base::TimeDelta::FromMicroseconds(50));
+
+  metrics_.RecordAndLogQuery(
+      FormatSupportQueryAction::MEDIA_SOURCE_IS_TYPE_SUPPORTED, "", "",
+      kSbMediaSupportTypeNotSupported);
+
+  auto counts = histogram_tester_.GetTotalCountsForPrefix(kUmaPrefix);
+  EXPECT_EQ(1, counts.size());
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "MediaSource.IsTypeSupported.Timing", 50, 1);
+}
+
+TEST_F(FormatSupportQueryMetricsTest,
+       ReportsHTMLMediaElementCanPlayTypeLatencyToUMA) {
+  clock_.Advance(base::TimeDelta::FromMicroseconds(50));
+
+  metrics_.RecordAndLogQuery(
+      FormatSupportQueryAction::HTML_MEDIA_ELEMENT_CAN_PLAY_TYPE, "", "",
+      kSbMediaSupportTypeNotSupported);
+
+  auto counts = histogram_tester_.GetTotalCountsForPrefix(kUmaPrefix);
+  EXPECT_EQ(1, counts.size());
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "HTMLMediaElement.CanPlayType.Timing", 50, 1);
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace cobalt
diff --git a/cobalt/media/base/metrics_provider.cc b/cobalt/media/base/metrics_provider.cc
index 510d5a8..beccc24 100644
--- a/cobalt/media/base/metrics_provider.cc
+++ b/cobalt/media/base/metrics_provider.cc
@@ -16,6 +16,8 @@
 
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
 
 namespace cobalt {
 namespace media {
@@ -122,5 +124,44 @@
   return media_info_.has_value();
 }
 
+void MediaMetricsProvider::StartTrackingAction(WebMediaPlayerAction action) {
+  DCHECK(IsInitialized());
+  DCHECK(!IsActionCurrentlyTracked(action));
+  ScopedLock scoped_lock(mutex_);
+
+  tracked_actions_start_times_[action] = clock_->NowTicks();
+}
+
+void MediaMetricsProvider::EndTrackingAction(WebMediaPlayerAction action) {
+  DCHECK(IsInitialized());
+  DCHECK(IsActionCurrentlyTracked(action));
+  ScopedLock scoped_lock(mutex_);
+
+  auto duration = clock_->NowTicks() - tracked_actions_start_times_[action];
+  ReportActionLatencyUMA(action, duration);
+  tracked_actions_start_times_.erase(action);
+}
+
+bool MediaMetricsProvider::IsActionCurrentlyTracked(
+    WebMediaPlayerAction action) {
+  DCHECK(IsInitialized());
+  ScopedLock scoped_lock(mutex_);
+  return tracked_actions_start_times_.find(action) !=
+         tracked_actions_start_times_.end();
+}
+
+void MediaMetricsProvider::ReportActionLatencyUMA(
+    WebMediaPlayerAction action, const base::TimeDelta& action_duration) {
+  switch (action) {
+    case WebMediaPlayerAction::SEEK:
+      UMA_HISTOGRAM_TIMES("Cobalt.Media.WebMediaPlayer.Seek.Timing",
+                          action_duration);
+      break;
+    case WebMediaPlayerAction::UNKNOWN_ACTION:
+    default:
+      break;
+  }
+}
+
 }  // namespace media
 }  // namespace cobalt
diff --git a/cobalt/media/base/metrics_provider.h b/cobalt/media/base/metrics_provider.h
index dff1df7..07675d4 100644
--- a/cobalt/media/base/metrics_provider.h
+++ b/cobalt/media/base/metrics_provider.h
@@ -15,9 +15,15 @@
 #ifndef COBALT_MEDIA_BASE_METRICS_PROVIDER_H_
 #define COBALT_MEDIA_BASE_METRICS_PROVIDER_H_
 
+#include <map>
 #include <string>
 
+#include "base/containers/small_map.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
 #include "starboard/common/mutex.h"
+#include "starboard/types.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"
@@ -32,9 +38,16 @@
 using PipelineStatus = ::media::PipelineStatus;
 using VideoDecoderType = ::media::VideoDecoderType;
 
+enum class WebMediaPlayerAction : uint8_t {
+  UNKNOWN_ACTION,
+  SEEK,
+};
+
 class MediaMetricsProvider {
  public:
-  MediaMetricsProvider() = default;
+  MediaMetricsProvider(
+      const base::TickClock* clock = base::DefaultTickClock::GetInstance())
+      : clock_(clock) {}
   ~MediaMetricsProvider();
 
  private:
@@ -71,11 +84,24 @@
 
   void ReportPipelineUMA();
 
+  // Used to record the latency of an action in the WebMediaPlayer.
+  void StartTrackingAction(WebMediaPlayerAction action);
+  void EndTrackingAction(WebMediaPlayerAction action);
+  bool IsActionCurrentlyTracked(WebMediaPlayerAction action);
+
  private:
   std::string GetUMANameForAVStream(const PipelineInfo& player_info) const;
   bool IsInitialized() const;
 
+  void ReportActionLatencyUMA(WebMediaPlayerAction action,
+                              const base::TimeDelta& action_duration);
+
  private:
+  // Media player action latency data.
+  const base::TickClock* clock_;
+  base::small_map<std::map<WebMediaPlayerAction, base::TimeTicks>, 2>
+      tracked_actions_start_times_;
+
   // UMA pipeline packaged data
   PipelineInfo uma_info_;
 
diff --git a/cobalt/media/base/metrics_provider_test.cc b/cobalt/media/base/metrics_provider_test.cc
new file mode 100644
index 0000000..78a1fe4
--- /dev/null
+++ b/cobalt/media/base/metrics_provider_test.cc
@@ -0,0 +1,70 @@
+// Copyright 2024 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 <memory>
+#include <string>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace media {
+namespace {
+constexpr char kUmaPrefix[] = "Cobalt.Media.";
+
+class MediaMetricsProviderTest : public ::testing::Test {
+ protected:
+  MediaMetricsProviderTest() : metrics_(&clock_) {}
+
+  void SetUp() override { clock_.SetNowTicks(base::TimeTicks()); }
+
+  base::HistogramTester histogram_tester_;
+  base::SimpleTestTickClock clock_;
+
+  MediaMetricsProvider metrics_;
+};
+
+TEST_F(MediaMetricsProviderTest, ReportsSeekLatency) {
+  metrics_.Initialize(true);
+  metrics_.StartTrackingAction(WebMediaPlayerAction::SEEK);
+
+  clock_.Advance(base::TimeDelta::FromMilliseconds(100));
+  metrics_.EndTrackingAction(WebMediaPlayerAction::SEEK);
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "WebMediaPlayer.Seek.Timing", 100, 1);
+}
+
+TEST_F(MediaMetricsProviderTest, SupportsTrackingMultipleActions) {
+  metrics_.Initialize(true);
+  metrics_.StartTrackingAction(WebMediaPlayerAction::SEEK);
+  metrics_.StartTrackingAction(WebMediaPlayerAction::UNKNOWN_ACTION);
+
+  clock_.Advance(base::TimeDelta::FromMilliseconds(100));
+  metrics_.EndTrackingAction(WebMediaPlayerAction::UNKNOWN_ACTION);
+
+  clock_.Advance(base::TimeDelta::FromMilliseconds(1000));
+  metrics_.EndTrackingAction(WebMediaPlayerAction::SEEK);
+
+  histogram_tester_.ExpectUniqueSample(
+      std::string(kUmaPrefix) + "WebMediaPlayer.Seek.Timing", 1100, 1);
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace cobalt
diff --git a/cobalt/media/base/sbplayer_interface.cc b/cobalt/media/base/sbplayer_interface.cc
index df33c90..0c7bb8c 100644
--- a/cobalt/media/base/sbplayer_interface.cc
+++ b/cobalt/media/base/sbplayer_interface.cc
@@ -17,11 +17,33 @@
 #include <string>
 
 #include "base/logging.h"
+#include "starboard/extension/player_configuration.h"
 #include "starboard/system.h"
 
 namespace cobalt {
 namespace media {
 
+void SbPlayerInterface::SetDecodeToTexturePreferred(bool preferred) {
+  const StarboardExtensionPlayerConfigurationApi* extension_api =
+      static_cast<const StarboardExtensionPlayerConfigurationApi*>(
+          SbSystemGetExtension(kStarboardExtensionPlayerConfigurationName));
+  if (!extension_api) {
+    return;
+  }
+
+  DCHECK_EQ(extension_api->name,
+            // Avoid comparing raw string pointers for equal.
+            std::string(kStarboardExtensionPlayerConfigurationName));
+  DCHECK_EQ(extension_api->version, 1u);
+
+  // SetDecodeToTexturePreferred api could be NULL.
+  if (extension_api->SetDecodeToTexturePreferred) {
+    extension_api->SetDecodeToTexturePreferred(preferred);
+  } else {
+    LOG(INFO) << "DecodeToTextureModePreferred is not supported.";
+  }
+}
+
 DefaultSbPlayerInterface::DefaultSbPlayerInterface() {
   const CobaltExtensionEnhancedAudioApi* extension_api =
       static_cast<const CobaltExtensionEnhancedAudioApi*>(
diff --git a/cobalt/media/base/sbplayer_interface.h b/cobalt/media/base/sbplayer_interface.h
index d9a3174..138e287 100644
--- a/cobalt/media/base/sbplayer_interface.h
+++ b/cobalt/media/base/sbplayer_interface.h
@@ -92,6 +92,8 @@
     cval_stats_.Enable(should_enable);
   }
   CValStats cval_stats_;
+
+  void SetDecodeToTexturePreferred(bool preferred);
 };
 
 class DefaultSbPlayerInterface final : public SbPlayerInterface {
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index 994f7b9..7014713 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -391,6 +391,7 @@
   StoreMediaTime(seek_time_);
   retrograde_media_time_counter_ = 0;
   timestamp_of_last_written_audio_ = 0;
+  is_video_eos_written_ = false;
 
 #if SB_HAS(PLAYER_WITH_URL)
   if (is_url_based_) {
@@ -1055,6 +1056,9 @@
   } else {
     for (const auto& buffer : buffers) {
       playback_statistics_.OnVideoAU(buffer);
+      if (buffer->end_of_stream()) {
+        is_video_eos_written_ = true;
+      }
     }
   }
   SetReadInProgress(type, false);
@@ -1096,7 +1100,8 @@
     // after the player has received enough audio for preroll, taking into
     // account that our estimate of playback time might be behind by
     // |kMediaTimeCheckInterval|.
-    if (timestamp_of_last_written_audio_ - seek_time_.ToSbTime() >
+    if (!is_video_eos_written_ &&
+        timestamp_of_last_written_audio_ - seek_time_.ToSbTime() >
         AdjustWriteDurationForPlaybackRate(audio_write_duration_for_preroll_,
                                            playback_rate_)) {
       // The estimated time ahead of playback may be negative if no audio has
diff --git a/cobalt/media/base/sbplayer_pipeline.h b/cobalt/media/base/sbplayer_pipeline.h
index 9b793f5..55611fe 100644
--- a/cobalt/media/base/sbplayer_pipeline.h
+++ b/cobalt/media/base/sbplayer_pipeline.h
@@ -337,6 +337,9 @@
   static const SbTime kMediaTimeCheckInterval = 0.1 * kSbTimeSecond;
   // Timestamp for the last written audio.
   SbTime timestamp_of_last_written_audio_ = 0;
+  // Indicates if video end of stream has been written into the underlying
+  // player.
+  bool is_video_eos_written_ = false;
 
   // Last media time reported by GetMediaTime().
   base::CVal<SbTime> last_media_time_;
diff --git a/cobalt/media/media_module.cc b/cobalt/media/media_module.cc
index 024c660..e079826 100644
--- a/cobalt/media/media_module.cc
+++ b/cobalt/media/media_module.cc
@@ -26,7 +26,6 @@
 #include "base/synchronization/waitable_event.h"
 #include "cobalt/media/base/format_support_query_metrics.h"
 #include "starboard/common/string.h"
-#include "starboard/extension/h5vcc_config.h"
 #include "starboard/media.h"
 #include "starboard/window.h"
 #include "third_party/chromium/media/base/mime_util.h"
@@ -128,8 +127,9 @@
     } else {
       support_type = CanPlayType(mime_type, "");
     }
-    metrics.RecordAndLogQuery("HTMLMediaElement::canPlayType", mime_type, "",
-                              support_type);
+    metrics.RecordAndLogQuery(
+        FormatSupportQueryAction::HTML_MEDIA_ELEMENT_CAN_PLAY_TYPE, mime_type,
+        "", support_type);
     return support_type;
   }
 
@@ -138,8 +138,9 @@
       const std::string& key_system) const override {
     media::FormatSupportQueryMetrics metrics;
     SbMediaSupportType support_type = CanPlayType(mime_type, key_system);
-    metrics.RecordAndLogQuery("MediaSource::IsTypeSupported", mime_type,
-                              key_system, support_type);
+    metrics.RecordAndLogQuery(
+        FormatSupportQueryAction::MEDIA_SOURCE_IS_TYPE_SUPPORTED, mime_type,
+        key_system, support_type);
     return support_type;
   }
 
@@ -199,20 +200,6 @@
     LOG(INFO) << (value ? "Enabling" : "Disabling")
               << " media metrics collection.";
     return true;
-  } else if (name == "BackgroundPlaybackEnabled") {
-    const StarboardExtensionH5vccConfigApi* h5vcc_config_api =
-        static_cast<const StarboardExtensionH5vccConfigApi*>(
-            SbSystemGetExtension(kStarboardExtensionH5vccConfigName));
-    if (h5vcc_config_api &&
-        strcmp(h5vcc_config_api->name, kStarboardExtensionH5vccConfigName) ==
-            0 &&
-        h5vcc_config_api->version >= 1) {
-      bool enable_background_playback = value;
-      LOG(INFO) << "Set BackgroundPlaybackEnabled to "
-                << (enable_background_playback ? "enabled" : "disabled");
-      h5vcc_config_api->EnableBackgroundPlayback(enable_background_playback);
-    }
-    return true;
 #if SB_API_VERSION >= 15
   } else if (name == "AudioWriteDurationLocal" && value > 0) {
     audio_write_duration_local_ = value;
@@ -225,6 +212,11 @@
               << audio_write_duration_remote_;
     return true;
 #endif  // SB_API_VERSION >= 15
+  } else if (name == "PlayerConfiguration.DecodeToTexturePreferred") {
+    sbplayer_interface_->SetDecodeToTexturePreferred(value);
+    LOG(INFO) << "Set DecodeToTexturePreferred to "
+              << (value ? "true" : "false");
+    return true;
   }
 
   return false;
diff --git a/cobalt/media/media_module_test.cc b/cobalt/media/media_module_test.cc
deleted file mode 100644
index af3c93f..0000000
--- a/cobalt/media/media_module_test.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2024 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/media_module.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace cobalt {
-namespace media {
-
-TEST(MediaModuleTest, SetConfiguration) {
-  MediaModule mediaModule(nullptr, nullptr);
-
-  EXPECT_EQ(mediaModule.SetConfiguration("BackgroundPlaybackEnabled", 1), true);
-  EXPECT_EQ(mediaModule.SetConfiguration("BackgroundPlaybackEnabled", 0), true);
-}
-}  // namespace media
-}  // namespace cobalt
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index e773474..6f34375 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -22,6 +22,7 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/instance_counter.h"
 #include "cobalt/media/base/drm_system.h"
+#include "cobalt/media/base/metrics_provider.h"
 #include "cobalt/media/base/sbplayer_pipeline.h"
 #include "cobalt/media/player/web_media_player_proxy.h"
 #include "cobalt/media/progressive/data_source_reader.h"
@@ -362,6 +363,7 @@
     return;
   }
 
+  media_metrics_provider_.StartTrackingAction(WebMediaPlayerAction::SEEK);
   media_log_->AddEvent<::media::MediaLogEvent::kSeek>(seconds);
 
   base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
@@ -637,6 +639,14 @@
     const bool kEosPlayed = false;
     GetClient()->TimeChanged(kEosPlayed);
   }
+
+  // WebMediaPlayerImpl::OnPipelineSeek() is called frequently, more than just
+  // when WebMediaPlayerImpl::Seek() is called. This helps to filter out when
+  // the pipeline seeks without being initiated by WebMediaPlayerImpl.
+  if (media_metrics_provider_.IsActionCurrentlyTracked(
+          WebMediaPlayerAction::SEEK)) {
+    media_metrics_provider_.EndTrackingAction(WebMediaPlayerAction::SEEK);
+  }
 }
 
 void WebMediaPlayerImpl::OnPipelineEnded(::media::PipelineStatus status) {
diff --git a/cobalt/media_session/media_session_client.cc b/cobalt/media_session/media_session_client.cc
index ab740de..d1888a2 100644
--- a/cobalt/media_session/media_session_client.cc
+++ b/cobalt/media_session/media_session_client.cc
@@ -212,6 +212,28 @@
   PostDelayedTaskForMaybeFreezeCallback();
 }
 
+void MediaSessionClient::InvokeAction(
+    const CobaltExtensionMediaSessionAction& action) {
+  std::unique_ptr<CobaltExtensionMediaSessionActionDetails> details(
+      new CobaltExtensionMediaSessionActionDetails());
+  CobaltExtensionMediaSessionActionDetailsInit(details.get(), action);
+  DCHECK(media_session_->task_runner_);
+  media_session_->task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&MediaSessionClient::InvokeActionInternal,
+                                AsWeakPtr(), base::Passed(&details)));
+}
+
+void MediaSessionClient::InvokeAction(
+    const CobaltExtensionMediaSessionActionDetails& details) {
+  std::unique_ptr<CobaltExtensionMediaSessionActionDetails> details_ptr(
+      new CobaltExtensionMediaSessionActionDetails(details));
+  DCHECK(media_session_->task_runner_);
+  media_session_->task_runner_->PostTask(
+      FROM_HERE, base::Bind(&MediaSessionClient::InvokeActionInternal,
+                            AsWeakPtr(), base::Passed(&details_ptr)));
+}
+
+
 void MediaSessionClient::RunMaybeFreezeCallback(int sequence_number) {
   if (sequence_number != sequence_number_) return;
 
@@ -233,14 +255,8 @@
          details->action == kCobaltExtensionMediaSessionActionSeekto);
   DCHECK(!details->fast_seek ||
          details->action == kCobaltExtensionMediaSessionActionSeekto);
-
-  DCHECK(media_session_->task_runner_);
-  if (!media_session_->task_runner_->BelongsToCurrentThread()) {
-    media_session_->task_runner_->PostTask(
-        FROM_HERE, base::Bind(&MediaSessionClient::InvokeActionInternal,
-                              AsWeakPtr(), base::Passed(&details)));
-    return;
-  }
+  CHECK(media_session_->task_runner_);
+  CHECK(media_session_->task_runner_->BelongsToCurrentThread());
 
   MediaSession::ActionMap::iterator it = media_session_->action_map_.find(
       ConvertMediaSessionAction(details->action));
diff --git a/cobalt/media_session/media_session_client.h b/cobalt/media_session/media_session_client.h
index 54817e4..3223f32 100644
--- a/cobalt/media_session/media_session_client.h
+++ b/cobalt/media_session/media_session_client.h
@@ -62,19 +62,10 @@
   // Invokes a given media session action
   // https://wicg.github.io/mediasession/#actions-model
   // Can be invoked from any thread.
-  void InvokeAction(CobaltExtensionMediaSessionAction action) {
-    std::unique_ptr<CobaltExtensionMediaSessionActionDetails> details(
-        new CobaltExtensionMediaSessionActionDetails());
-    CobaltExtensionMediaSessionActionDetailsInit(details.get(), action);
-    InvokeActionInternal(std::move(details));
-  }
+  void InvokeAction(const CobaltExtensionMediaSessionAction& action);
 
   // Invokes a given media session action that takes additional details.
-  void InvokeAction(CobaltExtensionMediaSessionActionDetails details) {
-    std::unique_ptr<CobaltExtensionMediaSessionActionDetails> details_ptr(
-        new CobaltExtensionMediaSessionActionDetails(details));
-    InvokeActionInternal(std::move(details_ptr));
-  }
+  void InvokeAction(const CobaltExtensionMediaSessionActionDetails& details);
 
   // Invoked on the browser thread when any metadata, position state, playback
   // state, or supported session actions change.
diff --git a/cobalt/media_session/media_session_test.cc b/cobalt/media_session/media_session_test.cc
index 43f34ed..a50e6a7 100644
--- a/cobalt/media_session/media_session_test.cc
+++ b/cobalt/media_session/media_session_test.cc
@@ -221,6 +221,7 @@
                    .to_ulong());
   session->mock_session_client()->InvokeAction(
       kCobaltExtensionMediaSessionActionPlay);
+  base::RunLoop().RunUntilIdle();
 
   session->SetActionHandler(kMediaSessionActionPlay, null_holder);
   session->mock_session_client()->WaitForSessionStateChange();
@@ -230,6 +231,7 @@
                    .to_ulong());
   session->mock_session_client()->InvokeAction(
       kCobaltExtensionMediaSessionActionPlay);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 3);
 }
@@ -359,6 +361,7 @@
       &details, kCobaltExtensionMediaSessionActionSeekto);
   details.seek_time = 1.2;
   session->mock_session_client()->InvokeAction(details);
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST(MediaSessionTest, SeekDetails) {
@@ -380,17 +383,20 @@
       .WillOnce(Return(CallbackResult<void>()));
   session->mock_session_client()->InvokeAction(
       kCobaltExtensionMediaSessionActionSeekforward);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekbackward)))
       .WillOnce(Return(CallbackResult<void>()));
   session->mock_session_client()->InvokeAction(
       kCobaltExtensionMediaSessionActionSeekbackward);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_CALL(cf, Run(SeekTime(1.2))).WillOnce(Return(CallbackResult<void>()));
   CobaltExtensionMediaSessionActionDetailsInit(
       &details, kCobaltExtensionMediaSessionActionSeekto);
   details.seek_time = 1.2;
   session->mock_session_client()->InvokeAction(details);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 3.4)))
       .WillOnce(Return(CallbackResult<void>()));
@@ -398,6 +404,7 @@
       &details, kCobaltExtensionMediaSessionActionSeekforward);
   details.seek_offset = 3.4;
   session->mock_session_client()->InvokeAction(details);
+  base::RunLoop().RunUntilIdle();
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 5.6)))
       .WillOnce(Return(CallbackResult<void>()));
@@ -405,6 +412,7 @@
       &details, kCobaltExtensionMediaSessionActionSeekbackward);
   details.seek_offset = 5.6;
   session->mock_session_client()->InvokeAction(details);
+  base::RunLoop().RunUntilIdle();
 
   session->mock_session_client()->WaitForSessionStateChange();
   EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 0);
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index e0ef901..f4a58d2 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -65,7 +65,7 @@
   }
 
   if (enable_in_app_dial) {
-    deps += [ "//net:cobalt_dial_server" ]
+    deps += [ ":cobalt_dial_server" ]
   }
 
   # Enable network logging on all but gold builds.
@@ -109,4 +109,43 @@
     "//testing/gtest",
     "//url",
   ]
+  if (enable_in_app_dial) {
+    sources += [
+      "dial/dial_http_server_unittest.cc",
+      "dial/dial_service_unittest.cc",
+      "dial/dial_test_helpers.h",
+      "dial/dial_udp_server_unittests.cc",
+    ]
+    deps += [
+      ":cobalt_dial_server",
+      "//net",
+      "//net:http_server",
+      "//net:test_support",
+    ]
+  }
+}
+
+if (enable_in_app_dial) {
+  static_library("cobalt_dial_server") {
+    sources = [
+      "dial/dial_http_server.cc",
+      "dial/dial_http_server.h",
+      "dial/dial_service.cc",
+      "dial/dial_service.h",
+      "dial/dial_service_handler.h",
+      "dial/dial_system_config.cc",
+      "dial/dial_system_config.h",
+      "dial/dial_system_config_starboard.cc",
+      "dial/dial_udp_server.cc",
+      "dial/dial_udp_server.h",
+      "dial/dial_udp_socket_factory.cc",
+      "dial/dial_udp_socket_factory.h",
+    ]
+    deps = [
+      "//base:base",
+      "//net",
+      "//net:http_server",
+      "//starboard/common",
+    ]
+  }
 }
diff --git a/net/dial/README.md b/cobalt/network/dial/README.md
similarity index 99%
rename from net/dial/README.md
rename to cobalt/network/dial/README.md
index 4e1584a..42bf485 100644
--- a/net/dial/README.md
+++ b/cobalt/network/dial/README.md
@@ -5,4 +5,4 @@
 This is custom, old code wrritten by the Cobalt team. It supports Cobalt's dial
 server.
 
-This directory used to be in [net of Cobalt's old Chromium source library](https://cobalt.googlesource.com/cobalt/+/cfbb98cb3109970b4360995fc305e4e9f53e35f9/src/net/dial/)
\ No newline at end of file
+This directory used to be in [net of Cobalt's old Chromium source library](https://cobalt.googlesource.com/cobalt/+/cfbb98cb3109970b4360995fc305e4e9f53e35f9/src/net/dial/)
diff --git a/net/dial/dial_http_server.cc b/cobalt/network/dial/dial_http_server.cc
similarity index 80%
rename from net/dial/dial_http_server.cc
rename to cobalt/network/dial/dial_http_server.cc
index 42b8820..3d40df2 100644
--- a/net/dial/dial_http_server.cc
+++ b/cobalt/network/dial/dial_http_server.cc
@@ -1,8 +1,18 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// 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/dial/dial_http_server.h"
+#include "cobalt/network/dial/dial_http_server.h"
 
 #include <memory>
 #include <vector>
@@ -10,19 +20,20 @@
 #include "base/bind.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/trace_event/trace_event.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "cobalt/network/dial/dial_service.h"
+#include "cobalt/network/dial/dial_service_handler.h"
+#include "cobalt/network/dial/dial_system_config.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
-#include "net/dial/dial_service.h"
-#include "net/dial/dial_service_handler.h"
-#include "net/dial/dial_system_config.h"
 #include "net/server/http_connection.h"
 #include "net/server/http_server_request_info.h"
 #include "net/socket/stream_socket.h"
 #include "net/socket/tcp_server_socket.h"
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 namespace {
 const char* kXmlMimeType = "text/xml; charset=\"utf-8\"";
@@ -89,8 +100,8 @@
         ip_addr.value().address().ToString(), ip_addr.value().port(),
         1 /*backlog*/);
   }
-  http_server_.reset(
-      new HttpServer(std::unique_ptr<ServerSocket>(server_socket), this));
+  http_server_.reset(new net::HttpServer(
+      std::unique_ptr<net::ServerSocket>(server_socket), this));
   ConfigureApplicationUrl();
 }
 
@@ -105,14 +116,14 @@
   http_server_.reset();
 }
 
-int DialHttpServer::GetLocalAddress(IPEndPoint* addr) {
+int DialHttpServer::GetLocalAddress(net::IPEndPoint* addr) {
   DCHECK_EQ(task_runner_, base::ThreadTaskRunnerHandle::Get());
   // We want to give second screen the IPv4 address, but we still need to
   // get http_server_'s address for its port number.
   int ret = http_server_->GetLocalAddress(addr);
 
   if (ret != 0) {
-    return ERR_FAILED;
+    return net::ERR_FAILED;
   }
 
   SbSocketAddress local_ip = {0};
@@ -124,21 +135,21 @@
   // DIAL only works with IPv4.
   destination.type = kSbSocketAddressTypeIpv4;
   if (!SbSocketGetInterfaceAddress(&destination, &local_ip, NULL)) {
-    return ERR_FAILED;
+    return net::ERR_FAILED;
   }
   local_ip.port = addr->port();
   LOG_ONCE(INFO) << "In-App DIAL Address http://" << addr->address().ToString()
-               << ":" << addr->port();
+                 << ":" << addr->port();
 
   if (addr->FromSbSocketAddress(&local_ip)) {
-    return OK;
+    return net::OK;
   }
 
-  return ERR_FAILED;
+  return net::ERR_FAILED;
 }
 
 void DialHttpServer::OnHttpRequest(int conn_id,
-                                   const HttpServerRequestInfo& info) {
+                                   const net::HttpServerRequestInfo& info) {
   TRACE_EVENT0("net::dial", "DialHttpServer::OnHttpRequest");
   DCHECK_EQ(task_runner_, base::ThreadTaskRunnerHandle::Get());
   if (info.method == "GET" &&
@@ -155,7 +166,7 @@
                              "Location: %s\r\n"
                              "Content-Length: 0\r\n"
                              "\r\n",
-                             HTTP_FOUND, "Found",
+                             net::HTTP_FOUND, "Found",
                              (application_url() + "YouTube").c_str()),
           kNetworkTrafficAnnotation);
 
@@ -172,8 +183,8 @@
 void DialHttpServer::OnClose(int conn_id) {}
 
 void DialHttpServer::ConfigureApplicationUrl() {
-  IPEndPoint end_point;
-  if (OK != GetLocalAddress(&end_point)) {
+  net::IPEndPoint end_point;
+  if (net::OK != GetLocalAddress(&end_point)) {
     LOG(ERROR) << "Could not get the local URL!";
     return;
   }
@@ -192,8 +203,8 @@
   // For non-Gold builds, append the IP to the friendly name
   // to help differentiate the devices.
   std::string friendly_name_str = system_config->friendly_name();
-  IPEndPoint end_point;
-  if (OK == GetLocalAddress(&end_point)) {
+  net::IPEndPoint end_point;
+  if (net::OK == GetLocalAddress(&end_point)) {
     friendly_name_str += " ";
     friendly_name_str += end_point.ToStringWithoutPort();
   }
@@ -204,7 +215,7 @@
       kDdXmlFormat, friendly_name, system_config->manufacturer_name(),
       system_config->model_name(), system_config->model_uuid());
 
-  HttpServerResponseInfo response_info(HTTP_OK);
+  net::HttpServerResponseInfo response_info(net::HTTP_OK);
   response_info.SetBody(response_body, kXmlMimeType);
   response_info.AddHeader("Application-URL", application_url().c_str());
 
@@ -212,7 +223,7 @@
 }
 
 bool DialHttpServer::DispatchToHandler(int conn_id,
-                                       const HttpServerRequestInfo& info) {
+                                       const net::HttpServerRequestInfo& info) {
   DCHECK_EQ(task_runner_, base::ThreadTaskRunnerHandle::Get());
   // See if DialService has a handler for this request.
   TRACE_EVENT0("net::dial", __FUNCTION__);
@@ -230,8 +241,7 @@
 }
 
 void DialHttpServer::OnReceivedResponse(
-    int conn_id,
-    std::unique_ptr<HttpServerResponseInfo> response) {
+    int conn_id, std::unique_ptr<net::HttpServerResponseInfo> response) {
   if (task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     task_runner_->PostTask(FROM_HERE,
                            base::Bind(&DialHttpServer::OnReceivedResponse, this,
@@ -249,4 +259,5 @@
   }
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_http_server.h b/cobalt/network/dial/dial_http_server.h
new file mode 100644
index 0000000..4490835
--- /dev/null
+++ b/cobalt/network/dial/dial_http_server.h
@@ -0,0 +1,112 @@
+// 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_NETWORK_DIAL_DIAL_HTTP_SERVER_H_
+#define COBALT_NETWORK_DIAL_DIAL_HTTP_SERVER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "cobalt/network/dial/dial_service_handler.h"
+#include "net/base/ip_endpoint.h"
+#include "net/http/http_status_code.h"
+#include "net/server/http_server.h"
+#include "net/server/http_server_request_info.h"
+#include "net/server/http_server_response_info.h"
+
+namespace cobalt {
+namespace network {
+
+class DialService;
+
+// This class is created and owned by DialService and is not meant to be
+// used externally.
+// All functions run on the DialService's message loop, except for
+// the callback AsyncReceivedResponse() which is eventually called
+// by a DialServiceHandler.
+// It's refcounted threadsafe so we can safely bind it to the callback we pass
+// to DialServiceHandler::handleRequest().
+class NET_EXPORT DialHttpServer
+    : public net::HttpServer::Delegate,
+      public base::RefCountedThreadSafe<DialHttpServer> {
+ public:
+  explicit DialHttpServer(DialService* dial_service);
+  void Stop();
+
+  // HttpServer::Delegate implementation
+  void OnConnect(int /*conn_id*/) override{};
+  void OnHttpRequest(int conn_id,
+                     const net::HttpServerRequestInfo& info) override;
+
+  void OnClose(int conn_id) override;
+
+  // Unused HttpServer::Delegate
+  void OnWebSocketRequest(int /*connection_id*/,
+                          const net::HttpServerRequestInfo& /*info*/) override {
+  }
+
+  void OnWebSocketMessage(int /*connection_id*/,
+                          const std::string& /*data*/) override {}
+
+  // Return the formatted application URL
+  std::string application_url() const { return server_url_ + "apps/"; }
+
+  // Return the formatted location URL.
+  std::string location_url() const { return server_url_ + "dd.xml"; }
+
+  // Somewhat similar to HttpServer::GetLocalAddress, but figures out the
+  // network IP address and uses that. The port remains the same.
+  int GetLocalAddress(net::IPEndPoint* addr);
+
+ private:
+  friend class base::RefCountedThreadSafe<DialHttpServer>;
+  virtual ~DialHttpServer();
+
+  void ConfigureApplicationUrl();
+
+  // Send the DIAL Device Description Manifest to the client.
+  void SendDeviceDescriptionManifest(int conn_id);
+
+  // Query DIAL service for a handler for the given request.
+  // Return false if no handler found, true if handleRequest() was issued
+  // to the handler.
+  bool DispatchToHandler(int conn_id, const net::HttpServerRequestInfo& info);
+
+  // Callback from WebKit thread when the HTTP task is complete.
+  // Post the response info to DIAL service thread.
+  void AsyncReceivedResponse(
+      int conn_id, std::unique_ptr<net::HttpServerResponseInfo> response);
+  // Handles DIAL response.
+  void OnReceivedResponse(
+      int conn_id, std::unique_ptr<net::HttpServerResponseInfo> response);
+
+  std::unique_ptr<net::HttpServer> http_server_;
+  std::string server_url_;
+  // DialService owns this object.
+  DialService* dial_service_;
+
+  // Message Loop of the thread that created us. We make sure http server
+  // is only called on the proper thread.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(DialHttpServer);
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_HTTP_SERVER_H_
diff --git a/net/dial/dial_http_server_unittest.cc b/cobalt/network/dial/dial_http_server_unittest.cc
similarity index 79%
rename from net/dial/dial_http_server_unittest.cc
rename to cobalt/network/dial/dial_http_server_unittest.cc
index c0ce217..ea32207 100644
--- a/net/dial/dial_http_server_unittest.cc
+++ b/cobalt/network/dial/dial_http_server_unittest.cc
@@ -1,29 +1,42 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// 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/network/dial/dial_http_server.h"
 
 #include <utility>
-
-#include "dial_http_server.h"
-#include "dial_service_handler.h"
+#include <vector>
 
 #include "base/strings/string_split.h"
 #include "base/test/scoped_task_environment.h"
+#include "cobalt/network/dial/dial_service.h"
+#include "cobalt/network/dial/dial_service_handler.h"
+#include "cobalt/network/dial/dial_test_helpers.h"
 #include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/cert/cert_verifier.h"
 #include "net/cert/ct_policy_enforcer.h"
 #include "net/cert/multi_log_ct_verifier.h"
-#include "net/dial/dial_service.h"
-#include "net/dial/dial_test_helpers.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/http_network_session.h"
 #include "net/http/http_network_transaction.h"
 #include "net/http/http_request_info.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_response_info.h"
+#include "net/http/http_server_properties.h"
 #include "net/http/http_server_properties_impl.h"
 #include "net/http/transport_security_state.h"
 #include "net/proxy_resolution/proxy_resolution_service.h"
@@ -33,8 +46,33 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace net {
+namespace cobalt {
+namespace network {
 
+using ::cobalt::network::DialService;
+using ::net::CertVerifier;
+using ::net::CTPolicyEnforcer;
+using ::net::ERR_IO_PENDING;
+using ::net::HostResolver;
+using ::net::HTTP_FOUND;
+using ::net::HTTP_NOT_FOUND;
+using ::net::HTTP_OK;
+using ::net::HttpNetworkSession;
+using ::net::HttpNetworkTransaction;
+using ::net::HttpRequestInfo;
+using ::net::HttpResponseInfo;
+using ::net::HttpServerProperties;
+using ::net::HttpServerResponseInfo;
+using ::net::IPEndPoint;
+using ::net::MockHostResolver;
+using ::net::MultiLogCTVerifier;
+using ::net::MutableNetworkTrafficAnnotationTag;
+using ::net::NetLogWithSource;
+using ::net::OK;
+using ::net::ProxyResolutionService;
+using ::net::SSLConfigService;
+using ::net::TestCompletionCallback;
+using ::net::TransportSecurityState;
 using ::testing::_;
 using ::testing::DoAll;
 using ::testing::Eq;
@@ -92,31 +130,31 @@
     context.proxy_resolution_service = proxy_resolution_service_ =
         ProxyResolutionService::CreateDirect().release();
     context.http_server_properties = http_server_properties_ =
-        new HttpServerPropertiesImpl();
+        new net::HttpServerPropertiesImpl();
     context.ssl_config_service = ssl_config_service_ =
-        new SSLConfigServiceDefaults();
+        new net::SSLConfigServiceDefaults();
     context.transport_security_state = transport_security_state_ =
         new TransportSecurityState();
     context.cert_transparency_verifier = cert_transparency_verifier_ =
         new MultiLogCTVerifier();
     context.ct_policy_enforcer = ct_policy_enforcer_ =
-        new DefaultCTPolicyEnforcer();
+        new net::DefaultCTPolicyEnforcer();
     context.host_resolver = host_resolver_ = new MockHostResolver();
     context.cert_verifier = cert_verifier_ =
-        CertVerifier::CreateDefault().release();
+        net::CertVerifier::CreateDefault().release();
     session_.reset(new HttpNetworkSession(params, context));
     client_.reset(new HttpNetworkTransaction(net::RequestPriority::MEDIUM,
                                              session_.get()));
   }
 
-  virtual void SetUp() override {
+  void SetUp() override {
     dial_service_.reset(new DialService());
     dial_service_->Register(handler_);
     EXPECT_EQ(OK, dial_service_->http_server()->GetLocalAddress(&addr_));
     InitHttpClientLibrary();
   }
 
-  virtual void TearDown() override {
+  void TearDown() override {
     dial_service_->Deregister(handler_);
     dial_service_.reset(NULL);
 
@@ -152,14 +190,14 @@
     HttpRequestInfo req;
     req.url = GURL("http://" + addr_.ToString() + path);
     req.method = method;
-    req.load_flags = LOAD_BYPASS_PROXY | LOAD_DISABLE_CACHE;
+    req.load_flags = net::LOAD_BYPASS_PROXY | net::LOAD_DISABLE_CACHE;
     req.traffic_annotation =
         MutableNetworkTrafficAnnotationTag(kNetworkTrafficAnnotation);
     return req;
   }
 
   void Capture(const std::string& path,
-               const HttpServerRequestInfo& request,
+               const net::HttpServerRequestInfo& request,
                const DialServiceHandler::CompletionCB& on_completion) {
     if (!test_response_->succeeded_) {
       on_completion.Run(std::unique_ptr<HttpServerResponseInfo>());
@@ -193,11 +231,12 @@
 
     ASSERT_NE(0, content_length);  // if failed, no point continuing.
 
-    scoped_refptr<IOBuffer> buffer(
-        new IOBuffer(static_cast<size_t>(content_length)));
+    scoped_refptr<net::IOBuffer> buffer(
+        new net::IOBuffer(static_cast<size_t>(content_length)));
     TestCompletionCallback callback;
-    int rv = client_->Read(buffer, content_length, callback.callback());
-    if (rv == net::ERR_IO_PENDING) {
+    int rv = client_->Read(buffer, static_cast<int>(content_length),
+                           callback.callback());
+    if (rv == ERR_IO_PENDING) {
       rv = callback.WaitForResult();
     }
 
@@ -226,11 +265,12 @@
   int64 content_length = resp->headers->GetContentLength();
   ASSERT_NE(0, content_length);  // if failed, no point continuing.
 
-  scoped_refptr<IOBuffer> buffer(
-      new IOBuffer(static_cast<size_t>(content_length)));
+  scoped_refptr<net::IOBuffer> buffer(
+      new net::IOBuffer(static_cast<size_t>(content_length)));
   TestCompletionCallback callback;
-  int rv = client_->Read(buffer, content_length, callback.callback());
-  if (rv == net::ERR_IO_PENDING) {
+  int rv = client_->Read(buffer, static_cast<int>(content_length),
+                         callback.callback());
+  if (rv == ERR_IO_PENDING) {
     rv = callback.WaitForResult();
   }
   EXPECT_EQ(content_length, rv);
@@ -266,7 +306,7 @@
       {"POST", "/apps/YouTube", HTTP_NOT_FOUND},
   };
 
-  for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+  for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(tests)); ++i) {
     HttpRequestInfo req = CreateRequest(tests[i].method, tests[i].path);
     const HttpResponseInfo* resp = GetResponse(req);
     ASSERT_TRUE(resp != NULL);
@@ -282,7 +322,7 @@
   test_response_->mime_type_ = "text/plain; charset=\"utf-8\"";
   test_response_->response_code_ = HTTP_OK;
   test_response_->headers_.push_back(
-      std::make_pair<std::string, std::string>("X-Test-Header", "Baz"));
+      std::pair<std::string, std::string>("X-Test-Header", "Baz"));
 
   const HttpRequestInfo& req = CreateRequest("GET", "/apps/Foo/bar");
   EXPECT_CALL(*handler_, HandleRequest(Eq("/bar"), _, _))
@@ -334,4 +374,5 @@
   EXPECT_STRNE("127.0.0.1", addr_.ToStringWithoutPort().c_str());
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/net/dial/dial_service.cc b/cobalt/network/dial/dial_service.cc
similarity index 77%
rename from net/dial/dial_service.cc
rename to cobalt/network/dial/dial_service.cc
index 7da2da5..02052b8 100644
--- a/net/dial/dial_service.cc
+++ b/cobalt/network/dial/dial_service.cc
@@ -1,20 +1,18 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// 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/dial/dial_service.h"
+#include "cobalt/network/dial/dial_service.h"
 
 #include "base/bind.h"
 #include "base/strings/string_piece.h"
@@ -25,7 +23,8 @@
 #include "net/server/http_server_request_info.h"
 #include "net/url_request/url_request.h"
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 namespace {
 const char* kUdpServerAgent = "Cobalt/2.0 UPnP/1.1";
@@ -37,7 +36,7 @@
       new DialUdpServer(http_server_->location_url(), kUdpServerAgent));
 
   // Compute HTTP local address and cache it.
-  IPEndPoint addr;
+  net::IPEndPoint addr;
   if (http_server_->GetLocalAddress(&addr) == net::OK) {
     http_host_address_ = addr.ToString();
   } else {
@@ -90,8 +89,7 @@
 }
 
 scoped_refptr<DialServiceHandler> DialService::GetHandler(
-    const std::string& request_path,
-    std::string* handler_path) {
+    const std::string& request_path, std::string* handler_path) {
   // This function should only be called by DialHttpServer, to find a handler
   // to respond to an incoming request.
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -159,10 +157,18 @@
       FROM_HERE, base::Bind(&DialServiceProxy::OnDeregister, this, handler));
 }
 
+void DialServiceProxy::ReplaceDialService(
+    const base::WeakPtr<DialService>& service) {
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&DialServiceProxy::OnReplaceDialService, this, service));
+}
+
 void DialServiceProxy::OnRegister(
     const scoped_refptr<DialServiceHandler>& handler) {
   if (dial_service_) {
     dial_service_->Register(handler);
+    handlers_.push_back(handler);
   }
 }
 
@@ -170,7 +176,19 @@
     const scoped_refptr<DialServiceHandler>& handler) {
   if (dial_service_) {
     dial_service_->Deregister(handler);
+    handlers_.remove(handler);
   }
 }
 
-}  // namespace net
+void DialServiceProxy::OnReplaceDialService(
+    const base::WeakPtr<DialService>& service) {
+  dial_service_ = service;
+  if (dial_service_) {
+    for (auto handler : handlers_) {
+      dial_service_->Register(handler);
+    }
+  }
+}
+
+}  // namespace network
+}  // namespace cobalt
diff --git a/net/dial/dial_service.h b/cobalt/network/dial/dial_service.h
similarity index 63%
rename from net/dial/dial_service.h
rename to cobalt/network/dial/dial_service.h
index 09a6203..8891b41 100644
--- a/net/dial/dial_service.h
+++ b/cobalt/network/dial/dial_service.h
@@ -1,23 +1,23 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// 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 SRC_DIAL_SERVICE_H_
-#define SRC_DIAL_SERVICE_H_
+#ifndef COBALT_NETWORK_DIAL_DIAL_SERVICE_H_
+#define COBALT_NETWORK_DIAL_DIAL_SERVICE_H_
 
+#include <list>
 #include <map>
+#include <memory>
 #include <string>
 
 #include "base/gtest_prod_util.h"
@@ -26,11 +26,12 @@
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
 #include "base/threading/thread.h"
-#include "net/dial/dial_http_server.h"
-#include "net/dial/dial_service_handler.h"
-#include "net/dial/dial_udp_server.h"
+#include "cobalt/network/dial/dial_http_server.h"
+#include "cobalt/network/dial/dial_service_handler.h"
+#include "cobalt/network/dial/dial_udp_server.h"
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 // DialService is part of an implementation of in-app DIAL.
 // It starts up a UDP server to be used to respond to SSDP discovery requests,
@@ -52,7 +53,7 @@
   const std::string& http_host_address() const;
 
   // Expose the DialHttpServer for unit tests.
-  net::DialHttpServer* http_server() const { return http_server_.get(); }
+  network::DialHttpServer* http_server() const { return http_server_.get(); }
 
  private:
   FRIEND_TEST_ALL_PREFIXES(DialServiceTest, GetHandler);
@@ -61,9 +62,9 @@
   // Called in DialService destructor.
   void Terminate();
 
-  scoped_refptr<net::DialHttpServer> http_server_;
-  std::unique_ptr<net::DialUdpServer> udp_server_;
-  typedef std::map<std::string, scoped_refptr<DialServiceHandler> >
+  scoped_refptr<network::DialHttpServer> http_server_;
+  std::unique_ptr<network::DialUdpServer> udp_server_;
+  typedef std::map<std::string, scoped_refptr<DialServiceHandler>>
       ServiceHandlerMap;
   ServiceHandlerMap handlers_;
   std::string http_host_address_;
@@ -79,9 +80,10 @@
 class NET_EXPORT DialServiceProxy
     : public base::RefCountedThreadSafe<DialServiceProxy> {
  public:
-  DialServiceProxy(const base::WeakPtr<DialService>& dial_service);
+  explicit DialServiceProxy(const base::WeakPtr<DialService>& dial_service);
   void Register(const scoped_refptr<DialServiceHandler>& handler);
   void Deregister(const scoped_refptr<DialServiceHandler>& handler);
+  void ReplaceDialService(const base::WeakPtr<DialService>& service);
   std::string host_address() const { return host_address_; }
 
  private:
@@ -89,16 +91,20 @@
   virtual ~DialServiceProxy();
   void OnRegister(const scoped_refptr<DialServiceHandler>& handler);
   void OnDeregister(const scoped_refptr<DialServiceHandler>& handler);
+  void OnReplaceDialService(const base::WeakPtr<DialService>& service);
 
   base::WeakPtr<DialService> dial_service_;
   std::string host_address_;
 
+  std::list<scoped_refptr<DialServiceHandler>> handlers_;
+
   // Message loop to call DialService methods on.
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   DISALLOW_COPY_AND_ASSIGN(DialServiceProxy);
 };
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
 
-#endif  // SRC_DIAL_SERVICE_H_
+#endif  // COBALT_NETWORK_DIAL_DIAL_SERVICE_H_
diff --git a/cobalt/network/dial/dial_service_handler.h b/cobalt/network/dial/dial_service_handler.h
new file mode 100644
index 0000000..594d350
--- /dev/null
+++ b/cobalt/network/dial/dial_service_handler.h
@@ -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.
+
+#ifndef COBALT_NETWORK_DIAL_DIAL_SERVICE_HANDLER_H_
+#define COBALT_NETWORK_DIAL_DIAL_SERVICE_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "net/http/http_response_headers.h"
+#include "net/server/http_server_request_info.h"
+#include "net/server/http_server_response_info.h"
+
+namespace cobalt {
+namespace network {
+
+// Abstract class that provides a response to a request from the dial server.
+// DialServiceHandlers should be Register()ed with a DialService object.
+class DialServiceHandler
+    : public base::RefCountedThreadSafe<DialServiceHandler> {
+ public:
+  typedef base::Callback<void(
+      std::unique_ptr<net::HttpServerResponseInfo> response)>
+      CompletionCB;
+  // Called by the DialHttpServer to satisfy an incoming request.
+  // It is expected that this will be handled asynchronously and the completion
+  // callback must be called in all cases. If the request is handled
+  // successfully, pass the response the server should send
+  // back and a |result| of true. If the request cannot be handled, pass a NULL
+  // |response| and a |result| of false.
+  virtual void HandleRequest(const std::string& path,
+                             const net::HttpServerRequestInfo& request,
+                             const CompletionCB& completion_cb) = 0;
+  // The name of the DIAL service this handler implements.
+  virtual const std::string& service_name() const = 0;
+
+ protected:
+  friend class base::RefCountedThreadSafe<DialServiceHandler>;
+  virtual ~DialServiceHandler() {}
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_SERVICE_HANDLER_H_
diff --git a/net/dial/dial_service_unittest.cc b/cobalt/network/dial/dial_service_unittest.cc
similarity index 68%
rename from net/dial/dial_service_unittest.cc
rename to cobalt/network/dial/dial_service_unittest.cc
index bf2eba1..0807a2b 100644
--- a/net/dial/dial_service_unittest.cc
+++ b/cobalt/network/dial/dial_service_unittest.cc
@@ -1,30 +1,40 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// 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 "dial_service.h"
+#include "cobalt/network/dial/dial_service.h"
 
 #include "base/bind.h"
 #include "base/message_loop/message_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "net/dial/dial_test_helpers.h"
+#include "cobalt/network/dial/dial_test_helpers.h"
 #include "net/server/http_server_request_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::Return;
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 class DialServiceTest : public testing::Test {
  protected:
   base::test::ScopedTaskEnvironment scoped_task_env_;
   std::unique_ptr<DialService> service_;
 
-  virtual void TearDown() override { service_.reset(); }
+  void TearDown() override { service_.reset(); }
 
   void GetHandlerTest(
-      const std::string& path,
-      const std::string& expected_path,
+      const std::string& path, const std::string& expected_path,
       const scoped_refptr<DialServiceHandler>& expected_handler) {
     std::string ret_path;
     scoped_refptr<DialServiceHandler> ret_handler =
@@ -74,4 +84,5 @@
   service_->Terminate();
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/net/dial/dial_system_config.cc b/cobalt/network/dial/dial_system_config.cc
similarity index 73%
rename from net/dial/dial_system_config.cc
rename to cobalt/network/dial/dial_system_config.cc
index 589c97e..33077aa 100644
--- a/net/dial/dial_system_config.cc
+++ b/cobalt/network/dial/dial_system_config.cc
@@ -1,12 +1,22 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// 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.
 
 // Dial requires some system information exposed to the world, for example
 // the device name, etc. This class lets it be configurable, but throw default
 // values from implementation.
 
-#include "net/dial/dial_system_config.h"
+#include "cobalt/network/dial/dial_system_config.h"
 
 #include <openssl/evp.h>
 
@@ -20,7 +30,8 @@
 #include "starboard/types.h"
 #endif
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 namespace {
 const char* kSecret = "v=8FpigqfcvlM";
@@ -77,4 +88,5 @@
   DCHECK_EQ(22, strlen(s_dial_uuid));
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_system_config.h b/cobalt/network/dial/dial_system_config.h
new file mode 100644
index 0000000..9c17664
--- /dev/null
+++ b/cobalt/network/dial/dial_system_config.h
@@ -0,0 +1,71 @@
+// 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.
+
+// Dial requires some system information exposed to the world, for example
+// the device name, etc. This class lets it be configurable, but throw default
+// values from implementation.
+
+#ifndef COBALT_NETWORK_DIAL_DIAL_SYSTEM_CONFIG_H_
+#define COBALT_NETWORK_DIAL_DIAL_SYSTEM_CONFIG_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+
+namespace cobalt {
+namespace network {
+
+class DialSystemConfig {
+ public:
+  static DialSystemConfig* GetInstance();
+
+  // Stores the pointers to various system info.
+  const char* friendly_name() const { return friendly_name_.c_str(); }
+
+  const char* manufacturer_name() const { return manufacturer_name_.c_str(); }
+
+  const char* model_name() const { return model_name_.c_str(); }
+
+  // Gets the model uuid.
+  const char* model_uuid() const;
+
+ private:
+  DialSystemConfig();
+
+  friend struct base::DefaultSingletonTraits<DialSystemConfig>;
+
+  static void CreateDialUuid();
+
+  // These 4 functions must be defined by a platform-specific source file.
+  static std::string GetFriendlyName();
+  static std::string GetManufacturerName();
+  static std::string GetModelName();
+  static std::string GeneratePlatformUuid();
+
+  static const int kMaxNameSize = 64;
+
+  std::string friendly_name_;
+  std::string manufacturer_name_;
+  std::string model_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(DialSystemConfig);
+  mutable base::Lock lock_;
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_SYSTEM_CONFIG_H_
diff --git a/net/dial/dial_system_config_starboard.cc b/cobalt/network/dial/dial_system_config_starboard.cc
similarity index 93%
rename from net/dial/dial_system_config_starboard.cc
rename to cobalt/network/dial/dial_system_config_starboard.cc
index 7ec70d0..93095e0 100644
--- a/net/dial/dial_system_config_starboard.cc
+++ b/cobalt/network/dial/dial_system_config_starboard.cc
@@ -1,4 +1,4 @@
-// Copyright 2015 Google Inc. 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,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "net/dial/dial_system_config.h"
-
+#include "cobalt/network/dial/dial_system_config.h"
 #include "starboard/common/file.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/system.h"
@@ -23,7 +22,8 @@
 const size_t kUuidSizeBytes = 16;
 }  // namespace
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 // static
 std::string DialSystemConfig::GetFriendlyName() {
@@ -107,4 +107,5 @@
   return uuid;
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_test_helpers.h b/cobalt/network/dial/dial_test_helpers.h
new file mode 100644
index 0000000..1e77f06
--- /dev/null
+++ b/cobalt/network/dial/dial_test_helpers.h
@@ -0,0 +1,46 @@
+// 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_NETWORK_DIAL_DIAL_TEST_HELPERS_H_
+#define COBALT_NETWORK_DIAL_DIAL_TEST_HELPERS_H_
+
+#include <string>
+
+#include "base/message_loop/message_loop.h"
+#include "cobalt/network/dial/dial_service_handler.h"
+#include "net/server/http_server_request_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace network {
+
+class MockServiceHandler : public DialServiceHandler {
+ public:
+  explicit MockServiceHandler(const std::string& service_name)
+      : service_name_(service_name) {}
+  MOCK_METHOD3(HandleRequest, void(const std::string&,
+                                   const net::HttpServerRequestInfo& request,
+                                   const CompletionCB& completion_cb));
+  const std::string& service_name() const override { return service_name_; }
+
+ private:
+  ~MockServiceHandler() {}
+  std::string service_name_;
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_TEST_HELPERS_H_
diff --git a/net/dial/dial_udp_server.cc b/cobalt/network/dial/dial_udp_server.cc
similarity index 67%
rename from net/dial/dial_udp_server.cc
rename to cobalt/network/dial/dial_udp_server.cc
index 7173221..7c1269b 100644
--- a/net/dial/dial_udp_server.cc
+++ b/cobalt/network/dial/dial_udp_server.cc
@@ -1,8 +1,18 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+// 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/dial/dial_udp_server.h"
+#include "cobalt/network/dial/dial_udp_server.h"
 
 #if defined(STARBOARD)
 #include "starboard/client_porting/poem/inet_poem.h"
@@ -19,15 +29,17 @@
 #include "base/logging.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "cobalt/network/dial/dial_system_config.h"
+#include "cobalt/network/dial/dial_udp_socket_factory.h"
 #include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
 #include "net/base/net_string_util.h"
-#include "net/dial/dial_system_config.h"
-#include "net/dial/dial_udp_socket_factory.h"
 #include "net/server/http_server.h"
 #include "net/server/http_server_request_info.h"
 #include "starboard/types.h"
 
-namespace net {
+namespace cobalt {
+namespace network {
 
 namespace {  // anonymous
 
@@ -35,9 +47,9 @@
 const int kReadBufferSize = 50 * 1024;
 
 // Get the INADDR_ANY address.
-IPEndPoint GetAddressForAllInterfaces(unsigned short port) {
+net::IPEndPoint GetAddressForAllInterfaces(unsigned short port) {
 #if defined(STARBOARD)
-  return IPEndPoint::GetForAllInterfaces(port);
+  return net::IPEndPoint::GetForAllInterfaces(port);
 #else
   SockaddrStorage any_addr;
   struct sockaddr_in* in = (struct sockaddr_in*)any_addr.addr;
@@ -45,7 +57,7 @@
   in->sin_port = htons(port);
   in->sin_addr.s_addr = INADDR_ANY;
 
-  IPEndPoint addr;
+  net::IPEndPoint addr;
   ignore_result(addr.FromSockAddr(any_addr.addr, any_addr.addr_len));
   return addr;
 #endif  // !defined(STARBOARD)
@@ -60,7 +72,7 @@
       server_agent_(server_agent),
       thread_("dial_udp_server"),
       is_running_(false),
-      read_buf_(new IOBuffer(kReadBufferSize)) {
+      read_buf_(new net::IOBuffer(kReadBufferSize)) {
   DCHECK(!location_url_.empty());
   DETACH_FROM_THREAD(thread_checker_);
   thread_.StartWithOptions(
@@ -68,9 +80,7 @@
   Start();
 }
 
-DialUdpServer::~DialUdpServer() {
-  Stop();
-}
+DialUdpServer::~DialUdpServer() { Stop(); }
 
 void DialUdpServer::CreateAndBind() {
   DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
@@ -119,44 +129,49 @@
   if (err_code > 0) {
     // RecvFrom can also return the number of received bytes right away as well.
     DidRead(err_code);
-  } else if (err_code != ERR_IO_PENDING) {
-    DCHECK(err_code == OK) << "RecvFrom returned bad error code: " << err_code;
+  } else if (err_code != net::ERR_IO_PENDING) {
+    DCHECK(err_code == net::OK)
+        << "RecvFrom returned bad error code: " << err_code;
   }
   // otherwise, RecvFrom returned -1 and will execute DidRead when any data is
   // received.
 }
 
-void DialUdpServer::DidClose(UDPSocket* server) {}
+void DialUdpServer::DidClose(net::UDPSocket* server) {}
 
 void DialUdpServer::DidRead(int bytes_read) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!socket_ || !read_buf_.get() || !read_buf_->data()) {
-    return;
-  }
-  if (bytes_read <= 0) {
-    LOG(WARNING) << "Dial server socket read error: " << bytes_read;
-    return;
-  }
-  // If M-Search request was valid, send response. Else, keep quiet.
-  if (ParseSearchRequest(std::string(read_buf_->data()))) {
-    auto response = std::make_unique<std::string>();
-    *response = std::move(ConstructSearchResponse());
-    // Using the fake IOBuffer to avoid another copy.
-    scoped_refptr<WrappedIOBuffer> fake_buffer =
-        new WrappedIOBuffer(response->data());
-    // After optimization, some compiler will dereference and get response size
-    // later than passing response.
-    auto response_size = response->size();
-    int result = socket_->SendTo(
-        fake_buffer.get(), response_size, client_address_,
-        base::Bind(&DialUdpServer::WriteComplete, base::Unretained(this),
-                   fake_buffer, base::Passed(&response)));
-    if (result == ERR_IO_PENDING) {
-      // WriteComplete is responsible for posting the next callback to accept
-      // connection.
-      return;
-    } else if (result < 0) {
-      LOG(ERROR) << "UDPSocket SendTo error: " << result;
+    LOG(INFO) << "Dial server socket read error: no socket or buffer";
+  } else {
+    if (bytes_read <= 0) {
+      LOG(WARNING) << "Dial server socket reads no bytes: " << bytes_read;
+    }
+
+    // ParseSearchRequest can be triggered when read bytes is zero, this will
+    // prompt a response that updates the device picker.
+    if (ParseSearchRequest(std::string(read_buf_->data()))) {
+      LOG(INFO) << "Dial server socket parses search request with "
+                << bytes_read << " bytes read";
+      auto response = std::make_unique<std::string>();
+      *response = std::move(ConstructSearchResponse());
+      // Using the fake IOBuffer to avoid another copy.
+      scoped_refptr<net::WrappedIOBuffer> fake_buffer =
+          new net::WrappedIOBuffer(response->data());
+      // After optimization, some compiler will dereference and get response
+      // size later than passing response.
+      auto response_size = response->size();
+      int result = socket_->SendTo(
+          fake_buffer.get(), response_size, client_address_,
+          base::Bind(&DialUdpServer::WriteComplete, base::Unretained(this),
+                     fake_buffer, base::Passed(&response)));
+      if (result == net::ERR_IO_PENDING) {
+        // WriteComplete is responsible for posting the next callback to accept
+        // connection.
+        return;
+      } else if (result < 0) {
+        LOG(ERROR) << "UDPSocket SendTo error: " << result;
+      }
     }
   }
 
@@ -169,9 +184,8 @@
                             base::Unretained(this)));
 }
 
-void DialUdpServer::WriteComplete(scoped_refptr<WrappedIOBuffer>,
-                                  std::unique_ptr<std::string>,
-                                  int rv) {
+void DialUdpServer::WriteComplete(scoped_refptr<net::WrappedIOBuffer>,
+                                  std::unique_ptr<std::string>, int rv) {
   if (rv < 0) {
     LOG(ERROR) << "UDPSocket completion callback error: " << rv;
   }
@@ -182,8 +196,8 @@
 
 // Parse a request to make sure it is a M-Search.
 bool DialUdpServer::ParseSearchRequest(const std::string& request) {
-  HttpServerRequestInfo info;
-  if (!HttpServer::ParseHeaders(request, &info)) {
+  net::HttpServerRequestInfo info;
+  if (!net::HttpServer::ParseHeaders(request, &info)) {
     DVLOG(1) << "Failed parsing SSDP headers: " << request;
     return false;
   }
@@ -207,7 +221,8 @@
   return true;
 }
 
-bool DialUdpServer::IsValidMSearchRequest(const HttpServerRequestInfo& info) {
+bool DialUdpServer::IsValidMSearchRequest(
+    const net::HttpServerRequestInfo& info) {
   if (info.method != "M-SEARCH") {
     DVLOG(1) << "Invalid M-Search: SSDP method incorrect. Received method: "
              << info.method;
@@ -255,4 +270,5 @@
   return std::move(ret);
 }
 
-}  // namespace net
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_udp_server.h b/cobalt/network/dial/dial_udp_server.h
new file mode 100644
index 0000000..d3f5418
--- /dev/null
+++ b/cobalt/network/dial/dial_udp_server.h
@@ -0,0 +1,88 @@
+// 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_NETWORK_DIAL_DIAL_UDP_SERVER_H_
+#define COBALT_NETWORK_DIAL_DIAL_UDP_SERVER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "net/base/net_export.h"
+#include "net/server/http_server_request_info.h"
+#include "net/socket/udp_socket.h"
+
+namespace cobalt {
+namespace network {
+
+class UdpSocketFactory;
+
+class NET_EXPORT DialUdpServer {
+ public:
+  DialUdpServer(const std::string& location_url,
+                const std::string& server_agent);
+  virtual ~DialUdpServer();
+
+  virtual void DidRead(int rv);
+
+  virtual void DidClose(net::UDPSocket* sock);
+
+  void WriteComplete(scoped_refptr<net::WrappedIOBuffer>,
+                     std::unique_ptr<std::string>, int rv);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(DialUdpServerTest, ParseSearchRequest);
+
+  void Start();
+  void Stop();
+
+  // Create the listen socket. Runs on a separate thread.
+  void CreateAndBind();
+  void Shutdown();
+  void AcceptAndProcessConnection();
+
+  // Construct the appropriate search response.
+  std::string ConstructSearchResponse() const;
+
+  // Parse a request to make sure it is a M-Search.
+  static bool ParseSearchRequest(const std::string& request);
+
+  // Is the valid SSDP request a valid M-Search request too ?
+  static bool IsValidMSearchRequest(const net::HttpServerRequestInfo& info);
+
+  std::unique_ptr<net::UDPSocket> socket_;
+  std::unique_ptr<UdpSocketFactory> factory_;
+
+  // Value to pass in LOCATION: header
+  std::string location_url_;
+
+  // Value to pass in SERVER: header
+  std::string server_agent_;
+
+  base::Thread thread_;
+  THREAD_CHECKER(thread_checker_);
+
+  bool is_running_;
+
+  scoped_refptr<net::IOBuffer> read_buf_;
+  net::IPEndPoint client_address_;
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_UDP_SERVER_H_
diff --git a/cobalt/network/dial/dial_udp_server_unittests.cc b/cobalt/network/dial/dial_udp_server_unittests.cc
new file mode 100644
index 0000000..aecdbca
--- /dev/null
+++ b/cobalt/network/dial/dial_udp_server_unittests.cc
@@ -0,0 +1,78 @@
+// 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/network/dial/dial_udp_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace network {
+namespace {
+#define ARRAYSIZE_UNSAFE(a)     \
+  ((sizeof(a) / sizeof(*(a))) / \
+   static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
+}  // namespace
+
+TEST(DialUdpServerTest, ParseSearchRequest) {
+  DialUdpServer server("fake_location", "fake_server_agent");
+  struct TestData {
+    std::string received_data;
+    bool result;
+  } tests[] = {
+      {
+          // Empty request
+          "",
+          false,
+      },
+      {
+          // The smallest correct response
+          "M-SEARCH * HTTP/1.1\r\n"
+          "ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n",
+          true,
+      },
+      {
+          // Incorrect ST-Header
+          "M-SEARCH * HTTP/1.1\r\nST:  xyzfoo1231  \r\n\r\n",
+          false,
+      },
+      {
+          // Missing \r\n
+          "M-SEARCH * HTTP/1.1\r\nST: 345512\r\n",
+          false,
+      },
+      {
+          // Incorrect SSDP method
+          "GET * HTTP/1.1\r\nST: 13\r\n\r\n",
+          false,
+      },
+      {
+          // Incorrect SSDP path
+          "M-SEARCH /path HTTP/1.1\r\nST: 13\r\n\r\n",
+          false,
+      },
+      {
+          // Empty headers
+          "M-SEARCH * HTTP/1.1\r\n\r\n",
+          false,
+      },
+  };
+
+  for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(tests)); ++i) {
+    std::string out_st_request;
+    EXPECT_EQ(tests[i].result,
+              server.ParseSearchRequest(tests[i].received_data));
+  }
+}
+
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_udp_socket_factory.cc b/cobalt/network/dial/dial_udp_socket_factory.cc
new file mode 100644
index 0000000..8e6044f
--- /dev/null
+++ b/cobalt/network/dial/dial_udp_socket_factory.cc
@@ -0,0 +1,69 @@
+// 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/network/dial/dial_udp_socket_factory.h"
+
+#if !defined(STARBOARD)
+#include <arpa/inet.h>
+#endif
+
+#include <memory>
+
+#include "base/logging.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_string_util.h"
+
+#if defined(STARBOARD)
+#include "starboard/common/socket.h"
+#include "starboard/types.h"
+#endif
+
+namespace cobalt {
+namespace network {
+
+std::unique_ptr<net::UDPSocket> UdpSocketFactory::CreateAndBind(
+    const net::IPEndPoint& address) {
+  std::unique_ptr<net::UDPSocket> sock(new net::UDPSocket(
+      net::DatagramSocket::DEFAULT_BIND, nullptr, net::NetLogSource()));
+  if (sock->Open(address.GetFamily()) != net::OK) {
+    return NULL;
+  }
+
+  int set_socket_reuse_success = sock->AllowAddressReuse();
+  if (set_socket_reuse_success != net::OK) {
+    LOG(WARNING) << "AllowAddressReuse failed on DIAL UDP socket with code "
+                 << set_socket_reuse_success;
+  }
+
+  if (sock->Bind(address) != net::OK) {
+    LOG(WARNING) << "DIAL UDP socket failed to bind to address";
+    sock->Close();
+    return NULL;
+  }
+
+  SetupSocketAfterBind(sock.get());
+
+  return sock;
+}
+// Enable Multicast and join group if multicast is enabled.
+void DialUdpSocketFactory::SetupSocketAfterBind(net::UDPSocket* sock) {
+  net::IPAddress address(239, 255, 255, 250);
+  if (sock->JoinGroup(address) != net::OK) {
+    LOG(WARNING) << "JoinGroup failed on DIAL UDP socket.";
+  }
+}
+
+}  // namespace network
+}  // namespace cobalt
diff --git a/cobalt/network/dial/dial_udp_socket_factory.h b/cobalt/network/dial/dial_udp_socket_factory.h
new file mode 100644
index 0000000..6fa018e
--- /dev/null
+++ b/cobalt/network/dial/dial_udp_socket_factory.h
@@ -0,0 +1,46 @@
+// 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_NETWORK_DIAL_DIAL_UDP_SOCKET_FACTORY_H_
+#define COBALT_NETWORK_DIAL_DIAL_UDP_SOCKET_FACTORY_H_
+
+#include <memory>
+
+#include "net/socket/udp_socket.h"
+
+namespace cobalt {
+namespace network {
+
+class UdpSocketFactory {
+ public:
+  std::unique_ptr<net::UDPSocket> CreateAndBind(const net::IPEndPoint& address);
+  virtual ~UdpSocketFactory() {}
+
+ protected:
+  virtual void SetupSocketAfterBind(net::UDPSocket* sock) {}
+};
+
+class DialUdpSocketFactory : public UdpSocketFactory {
+ public:
+  virtual ~DialUdpSocketFactory() {}
+
+ protected:
+  // UdpSocketFactory implementation
+  void SetupSocketAfterBind(net::UDPSocket* sock) override;
+};
+
+}  // namespace network
+}  // namespace cobalt
+
+#endif  // COBALT_NETWORK_DIAL_DIAL_UDP_SOCKET_FACTORY_H_
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc
index 343d7a4..888bbe9 100644
--- a/cobalt/network/network_module.cc
+++ b/cobalt/network/network_module.cc
@@ -223,8 +223,8 @@
   cookie_jar_.reset(new CookieJarImpl(url_request_context_->cookie_store(),
                                       task_runner().get()));
 #if defined(DIAL_SERVER)
-  dial_service_.reset(new net::DialService());
-  dial_service_proxy_ = new net::DialServiceProxy(dial_service_->AsWeakPtr());
+  dial_service_.reset(new DialService());
+  dial_service_proxy_ = new DialServiceProxy(dial_service_->AsWeakPtr());
 #endif
 
   net_poster_.reset(new NetPoster(this));
@@ -232,6 +232,34 @@
   creation_event->Signal();
 }
 
+#if defined(DIAL_SERVER)
+void NetworkModule::RestartDialService() {
+  base::WaitableEvent creation_event(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  // Run Network module startup on IO thread,
+  // so the network delegate and URL request context are
+  // constructed on that thread.
+  task_runner()->PostTask(FROM_HERE,
+                          base::Bind(&NetworkModule::OnRestartDialService,
+                                     base::Unretained(this), &creation_event));
+  // Wait for OnCreate() to run, so we can be sure our members
+  // have been constructed.
+  creation_event.Wait();
+}
+
+void NetworkModule::OnRestartDialService(base::WaitableEvent* creation_event) {
+  // A new DialService instance cannot be created if any already exists
+  // since they will use the same address and it will cause some socket errors.
+  // Destroy existing service first.
+  dial_service_.reset();
+  // Create new dial service
+  dial_service_ = std::make_unique<DialService>();
+  dial_service_proxy_->ReplaceDialService(dial_service_->AsWeakPtr());
+  creation_event->Signal();
+}
+#endif
+
 void NetworkModule::AddClientHintHeaders(
     net::URLFetcher& url_fetcher, ClientHintHeadersCallType call_type) const {
   if (kEnabledClientHintHeaders & call_type) {
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h
index 18ea05c..e00a5f1 100644
--- a/cobalt/network/network_module.h
+++ b/cobalt/network/network_module.h
@@ -36,7 +36,7 @@
 #if defined(DIAL_SERVER)
 // Including this header causes a link error on Windows, since we
 // don't have StreamListenSocket.
-#include "net/dial/dial_service.h"
+#include "cobalt/network/dial/dial_service.h"
 #endif
 #include "net/url_request/http_user_agent_settings.h"
 #include "starboard/common/atomic.h"
@@ -123,7 +123,7 @@
   network_bridge::CookieJar* cookie_jar() const { return cookie_jar_.get(); }
   network_bridge::PostSender GetPostSender() const;
 #if defined(DIAL_SERVER)
-  scoped_refptr<net::DialServiceProxy> dial_service_proxy() const {
+  scoped_refptr<network::DialServiceProxy> dial_service_proxy() const {
     return dial_service_proxy_;
   }
 #endif
@@ -142,6 +142,9 @@
   void StartNetLog();
   base::FilePath StopNetLog();
 
+#if defined(DIAL_SERVER)
+  void RestartDialService();
+#endif
 
  private:
   void Initialize(const std::string& user_agent_string,
@@ -159,8 +162,9 @@
   std::unique_ptr<net::HttpUserAgentSettings> http_user_agent_settings_;
   std::unique_ptr<network_bridge::CookieJar> cookie_jar_;
 #if defined(DIAL_SERVER)
-  std::unique_ptr<net::DialService> dial_service_;
-  scoped_refptr<net::DialServiceProxy> dial_service_proxy_;
+  void OnRestartDialService(base::WaitableEvent* creation_event);
+  std::unique_ptr<network::DialService> dial_service_;
+  scoped_refptr<network::DialServiceProxy> dial_service_proxy_;
 #endif
   std::unique_ptr<network_bridge::NetPoster> net_poster_;
 
diff --git a/cobalt/site/docker/Dockerfile b/cobalt/site/docker/Dockerfile
index b22bc09..1acba94 100644
--- a/cobalt/site/docker/Dockerfile
+++ b/cobalt/site/docker/Dockerfile
@@ -13,11 +13,12 @@
 # limitations under the License.
 
 ARG FROM_IMAGE
-FROM ${FROM_IMAGE:-gcr.io/cloud-marketplace-containers/google/debian10}
+FROM ${FROM_IMAGE:-gcr.io/cloud-marketplace-containers/google/debian11}
 
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
         curl \
+        ca-certificates \
         doxygen \
         git \
         python3 \
diff --git a/cobalt/site/docs/communication.md b/cobalt/site/docs/communication.md
index fc26c2b..7410841 100644
--- a/cobalt/site/docs/communication.md
+++ b/cobalt/site/docs/communication.md
@@ -20,29 +20,13 @@
 
 [View all Open Cobalt Bugs](https://issuetracker.google.com/issues?q=componentid:181120%20status:open)
 
-
 *You will need a valid Google account to post. You can [create a Google
 account](https://accounts.google.com/SignUpWithoutGmail) with a non-gmail
 address (say, your work email address).*
 
-
-## Asking Technical Questions
-
-For programming related technical questions about Starboard or Cobalt, we
-encourage you to search through and post questions on [Stack
-Overflow](https://stackoverflow.com) (SO). The team actively monitors for
-Cobalt-tagged questions on SO.
-
-Stack Overflow has guidelines about what kinds of questions are acceptable. In
-particular, they should be about some aspect of **coding**, and not questions
-about schedules, future plans, certifications and so on. Additionally, they
-should not be bug reports or feature requests, those should be filed on our
-public Issue Tracker.
-
-[View recent Cobalt SO Questions](https://stackoverflow.com/questions/tagged/cobalt)
-
-[Ask a Cobalt Question on SO](https://stackoverflow.com/questions/ask?tags=cobalt)
-
+We also encourage contributions by sending pull requests on Github, especially
+if the bugfix or feature addition is small, well tested, and [follows
+our contribution guidelines](https://github.com/youtube/cobalt/blob/main/CONTRIBUTING.md).
 
 ## Asking about Schedules, Discussing Features, Announcements, and Miscellany
 
@@ -51,6 +35,13 @@
 that doesn't fit into the above categories, asking about it on cobalt-dev is a
 reasonable course of action.
 
+Use this mailing list for asking related technical questions about Starboard or
+Cobalt. We encourage searching through archives both in mailing list and bug
+tracker for prior questions.
+
+If your issue seems like a bug or is really a feature request, use the Issue
+Tracker to file it.
+
 [cobalt-dev@googlegroups.com](cobalt-dev@googlegroups.com)
 
 [cobalt-dev web interface](https://groups.google.com/forum/#!forum/cobalt-dev)
diff --git a/cobalt/version.h b/cobalt/version.h
index 8d80aef..b550b97 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "24.lts.30"
+#define COBALT_VERSION "24.lts.40"
 
 #endif  // COBALT_VERSION_H_
diff --git a/cobalt/watchdog/BUILD.gn b/cobalt/watchdog/BUILD.gn
index 131d334..92bb7f3 100644
--- a/cobalt/watchdog/BUILD.gn
+++ b/cobalt/watchdog/BUILD.gn
@@ -14,6 +14,8 @@
 
 static_library("watchdog") {
   sources = [
+    "instrumentation_log.cc",
+    "instrumentation_log.h",
     "watchdog.cc",
     "watchdog.h",
   ]
@@ -29,7 +31,10 @@
 target(gtest_target_type, "watchdog_test") {
   testonly = true
 
-  sources = [ "watchdog_test.cc" ]
+  sources = [
+    "instrumentation_log_test.cc",
+    "watchdog_test.cc",
+  ]
 
   deps = [
     ":watchdog",
diff --git a/cobalt/watchdog/instrumentation_log.cc b/cobalt/watchdog/instrumentation_log.cc
new file mode 100644
index 0000000..7eccf91
--- /dev/null
+++ b/cobalt/watchdog/instrumentation_log.cc
@@ -0,0 +1,63 @@
+// Copyright 2024 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/watchdog/instrumentation_log.h"
+
+#include <string>
+#include <vector>
+
+namespace cobalt {
+namespace watchdog {
+
+bool InstrumentationLog::LogEvent(const std::string& event) {
+  if (event.length() > kMaxEventLenBytes) {
+    SB_DLOG(ERROR) << "[Watchdog] Log event exceeds max: " << kMaxEventLenBytes;
+    return false;
+  }
+
+  starboard::ScopedLock scoped_lock(buffer_mutex_);
+  buffer_.SaveToBuffer(event);
+
+  return true;
+}
+
+std::vector<std::string> InstrumentationLog::GetLogTrace() {
+  std::vector<std::string> traceEvents;
+
+  starboard::ScopedLock scoped_lock(buffer_mutex_);
+  for (auto it = buffer_.Begin(); it; ++it) {
+    traceEvents.push_back(**it);
+  }
+
+  return traceEvents;
+}
+
+base::Value InstrumentationLog::GetLogTraceAsValue() {
+  base::Value log_trace_value = base::Value(base::Value::Type::LIST);
+
+  starboard::ScopedLock scoped_lock(buffer_mutex_);
+  for (auto it = buffer_.Begin(); it; ++it) {
+    log_trace_value.GetList().emplace_back(**it);
+  }
+
+  return log_trace_value;
+}
+
+void InstrumentationLog::ClearLog() {
+  starboard::ScopedLock scoped_lock(buffer_mutex_);
+  buffer_.Clear();
+}
+
+}  // namespace watchdog
+}  // namespace cobalt
diff --git a/cobalt/watchdog/instrumentation_log.h b/cobalt/watchdog/instrumentation_log.h
new file mode 100644
index 0000000..599a198
--- /dev/null
+++ b/cobalt/watchdog/instrumentation_log.h
@@ -0,0 +1,59 @@
+// Copyright 2024 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_WATCHDOG_INSTRUMENTATION_LOG_H_
+#define COBALT_WATCHDOG_INSTRUMENTATION_LOG_H_
+
+#include <string>
+#include <vector>
+
+#include "base/containers/ring_buffer.h"
+#include "base/values.h"
+#include "starboard/common/mutex.h"
+
+namespace cobalt {
+namespace watchdog {
+
+constexpr int kBufferSize = 128;
+constexpr int kMaxEventLenBytes = 256;
+
+// Wrapper class on top of base::RingBuffer for tracking log events emitted
+// through logEvent() h5vcc API. There's an optimization: identical sequential
+// events added back to back are folded into single event.
+class InstrumentationLog {
+ public:
+  // Append a single event to the end of the buffer.
+  bool LogEvent(const std::string& event);
+
+  // Returns a snapshot of the ring buffer.
+  // Vector of kBufferSize strings at max.
+  std::vector<std::string> GetLogTrace();
+
+  // Same as GetLogTrace() but converted to base::Value for json serialization.
+  base::Value GetLogTraceAsValue();
+
+  // Empty the ring buffer.
+  void ClearLog();
+
+ private:
+  base::RingBuffer<std::string, kBufferSize> buffer_;
+
+  // Mutex to guard buffer operations. E.g. LogEvent() called from h5vcc API
+  // handler and getLogTrace() called from watchdog monitoring thread.
+  starboard::Mutex buffer_mutex_;
+};
+
+}  // namespace watchdog
+}  // namespace cobalt
+#endif  // COBALT_WATCHDOG_INSTRUMENTATION_LOG_H_
diff --git a/cobalt/watchdog/instrumentation_log_test.cc b/cobalt/watchdog/instrumentation_log_test.cc
new file mode 100644
index 0000000..1f77de0
--- /dev/null
+++ b/cobalt/watchdog/instrumentation_log_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2024 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/watchdog//instrumentation_log.h"
+
+#include <set>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "starboard/common/file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace watchdog {
+
+class InstrumentationLogTest : public testing::Test {
+ protected:
+  InstrumentationLog* instrumentation_log_;
+};
+
+TEST_F(InstrumentationLogTest, CanCallLogEvent) {
+  InstrumentationLog log;
+  log.LogEvent("abc");
+
+  ASSERT_EQ(log.GetLogTrace().size(), 1);
+}
+
+TEST_F(InstrumentationLogTest, LogEventReturnsFalseOnMaxLenExceed) {
+  InstrumentationLog log;
+
+  std::string maxLenEvent(kMaxEventLenBytes, 'a');
+  ASSERT_TRUE(log.LogEvent(maxLenEvent));
+
+  std::string exceedingLenEvent(kMaxEventLenBytes + 1, 'a');
+  ASSERT_FALSE(log.LogEvent(exceedingLenEvent));
+}
+
+TEST_F(InstrumentationLogTest, GetLogTraceReturnsEventsInOrder) {
+  InstrumentationLog log;
+
+  for (int i = 0; i < kBufferSize; i++) {
+    log.LogEvent(std::to_string(i));
+  }
+
+  log.LogEvent("1");
+  log.LogEvent("2");
+  log.LogEvent("3");
+  log.LogEvent("4");
+  log.LogEvent("5");
+
+  ASSERT_EQ(log.GetLogTrace().at(kBufferSize - 1 - 4), "1");
+  ASSERT_EQ(log.GetLogTrace().at(kBufferSize - 1 - 3), "2");
+  ASSERT_EQ(log.GetLogTrace().at(kBufferSize - 1 - 2), "3");
+  ASSERT_EQ(log.GetLogTrace().at(kBufferSize - 1 - 1), "4");
+  ASSERT_EQ(log.GetLogTrace().at(kBufferSize - 1), "5");
+}
+
+TEST_F(InstrumentationLogTest, GetLogTraceCanReturnDuplicateEvents) {
+  InstrumentationLog log;
+
+  log.LogEvent("1");
+  log.LogEvent("1");
+  log.LogEvent("1");
+
+  ASSERT_EQ(log.GetLogTrace().at(0), "1");
+  ASSERT_EQ(log.GetLogTrace().at(1), "1");
+  ASSERT_EQ(log.GetLogTrace().at(2), "1");
+}
+
+TEST_F(InstrumentationLogTest, CanGetEmptyTraceAsValue) {
+  InstrumentationLog log;
+
+  ASSERT_EQ(log.GetLogTraceAsValue().GetList().size(), 0);
+}
+
+TEST_F(InstrumentationLogTest, CanGetLogTraceAsValue) {
+  InstrumentationLog log;
+
+  log.LogEvent("1");
+  log.LogEvent("2");
+  log.LogEvent("3");
+
+  ASSERT_EQ(log.GetLogTraceAsValue().GetList().at(0).GetString(), "1");
+  ASSERT_EQ(log.GetLogTraceAsValue().GetList().at(1).GetString(), "2");
+  ASSERT_EQ(log.GetLogTraceAsValue().GetList().at(2).GetString(), "3");
+}
+
+TEST_F(InstrumentationLogTest, CanClearLog) {
+  InstrumentationLog log;
+
+  log.LogEvent("1");
+  log.LogEvent("2");
+  log.LogEvent("3");
+
+  ASSERT_EQ(log.GetLogTrace().size(), 3);
+
+  log.ClearLog();
+  ASSERT_EQ(log.GetLogTrace().size(), 0);
+}
+
+TEST_F(InstrumentationLogTest, CanClearEmptyLog) {
+  InstrumentationLog log;
+  ASSERT_EQ(log.GetLogTrace().size(), 0);
+
+  log.ClearLog();
+  ASSERT_EQ(log.GetLogTrace().size(), 0);
+}
+
+}  // namespace watchdog
+}  // namespace cobalt
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index f328ec1..6906883 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -49,19 +49,6 @@
 // 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
-// stub except that persistent settings can still be get/set. Requires a
-// restart to take effect.
-const char kPersistentSettingWatchdogEnable[] =
-    "kPersistentSettingWatchdogEnable";
-const bool kDefaultSettingWatchdogEnable = true;
-// Persistent setting name and default setting for the boolean that controls
-// whether or not a Watchdog violation will trigger a crash.
-const char kPersistentSettingWatchdogCrash[] =
-    "kPersistentSettingWatchdogCrash";
-const bool kDefaultSettingWatchdogCrash = false;
-
 }  // namespace
 
 bool Watchdog::Initialize(
@@ -76,6 +63,7 @@
     std::string watchdog_file_name, int64_t watchdog_monitor_frequency) {
   persistent_settings_ = persistent_settings;
   is_disabled_ = !GetPersistentSettingWatchdogEnable();
+  is_logtrace_disabled_ = !GetPersistentSettingLogtraceEnable();
 
   if (is_disabled_) return true;
 
@@ -269,9 +257,10 @@
 void Watchdog::UpdateViolationsMap(void* context, Client* client,
                                    SbTimeMonotonic time_delta) {
   // Gets violation dictionary with key client name from violations map.
+  Watchdog* watchdog_instance = static_cast<Watchdog*>(context);
+
   base::Value* violation_dict =
-      (static_cast<Watchdog*>(context)->GetViolationsMap())
-          ->FindKey(client->name);
+      (watchdog_instance->GetViolationsMap())->FindKey(client->name);
 
   // Checks if new unique violation.
   bool new_violation = false;
@@ -317,14 +306,18 @@
         base::Value(std::to_string(
             (time_delta - client->time_interval_microseconds) / 1000)));
     base::Value registered_clients(base::Value::Type::LIST);
-    for (auto& it : static_cast<Watchdog*>(context)->client_map_) {
+    for (auto& it : watchdog_instance->client_map_) {
       registered_clients.GetList().emplace_back(base::Value(it.first));
     }
-    for (auto& it : static_cast<Watchdog*>(context)->client_list_) {
+    for (auto& it : watchdog_instance->client_list_) {
       registered_clients.GetList().emplace_back(base::Value(it->name));
     }
     violation.SetKey("registeredClients", registered_clients.Clone());
 
+    violation.SetKey(
+        "logTrace",
+        watchdog_instance->instrumentation_log_.GetLogTraceAsValue());
+
     // Adds new violation to violations map.
     if (violation_dict == nullptr) {
       base::Value dict(base::Value::Type::DICTIONARY);
@@ -332,7 +325,7 @@
       base::Value list(base::Value::Type::LIST);
       list.GetList().emplace_back(violation.Clone());
       dict.SetKey("violations", list.Clone());
-      (static_cast<Watchdog*>(context)->GetViolationsMap())
+      (watchdog_instance->GetViolationsMap())
           ->SetKey(client->name, dict.Clone());
     } else {
       base::Value* violations = violation_dict->FindKey("violations");
@@ -350,11 +343,10 @@
         "violationDurationMilliseconds",
         base::Value(std::to_string(violation_duration + (time_delta / 1000))));
   }
-  static_cast<Watchdog*>(context)->pending_write_ = true;
+  watchdog_instance->pending_write_ = true;
 
   int violations_count = 0;
-  for (const auto& it :
-       (static_cast<Watchdog*>(context)->GetViolationsMap())->DictItems()) {
+  for (const auto& it : (watchdog_instance->GetViolationsMap())->DictItems()) {
     base::Value& violation_dict = it.second;
     base::Value* violations = violation_dict.FindKey("violations");
     violations_count += violations->GetList().size();
@@ -762,6 +754,47 @@
       std::make_unique<base::Value>(can_trigger_crash));
 }
 
+bool Watchdog::LogEvent(const std::string& event) {
+  if (is_logtrace_disabled_) {
+    return true;
+  }
+
+  return instrumentation_log_.LogEvent(event);
+}
+
+std::vector<std::string> Watchdog::GetLogTrace() {
+  if (is_logtrace_disabled_) {
+    return {};
+  }
+
+  return instrumentation_log_.GetLogTrace();
+}
+
+void Watchdog::ClearLog() {
+  if (is_logtrace_disabled_) {
+    return;
+  }
+
+  instrumentation_log_.ClearLog();
+}
+
+bool Watchdog::GetPersistentSettingLogtraceEnable() {
+  if (!persistent_settings_) return kDefaultSettingLogtraceEnable;
+
+  // Gets the boolean that controls whether or not LogTrace is enabled.
+  return persistent_settings_->GetPersistentSettingAsBool(
+      kPersistentSettingLogtraceEnable, kDefaultSettingLogtraceEnable);
+}
+
+void Watchdog::SetPersistentSettingLogtraceEnable(bool enable_logtrace) {
+  if (!persistent_settings_) return;
+
+  // Sets the boolean that controls whether or not LogTrace is enabled.
+  persistent_settings_->SetPersistentSetting(
+      kPersistentSettingLogtraceEnable,
+      std::make_unique<base::Value>(enable_logtrace));
+}
+
 #if defined(_DEBUG)
 // Sleeps threads for Watchdog debugging.
 void Watchdog::MaybeInjectDebugDelay(const std::string& name) {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index aed2ec1..caaf90b 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -23,6 +23,7 @@
 #include "base/values.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
+#include "cobalt/watchdog/instrumentation_log.h"
 #include "cobalt/watchdog/singleton.h"
 #include "starboard/common/atomic.h"
 #include "starboard/common/condition_variable.h"
@@ -33,6 +34,28 @@
 namespace cobalt {
 namespace watchdog {
 
+// Persistent setting name and default setting for the boolean that controls
+// whether or not Watchdog is enabled. When disabled, Watchdog behaves like a
+// stub except that persistent settings can still be get/set. Requires a
+// restart to take effect.
+constexpr char kPersistentSettingWatchdogEnable[] =
+    "kPersistentSettingWatchdogEnable";
+constexpr bool kDefaultSettingWatchdogEnable = true;
+
+// Persistent setting name and default setting for the boolean that controls
+// whether or not a Watchdog violation will trigger a crash.
+constexpr char kPersistentSettingWatchdogCrash[] =
+    "kPersistentSettingWatchdogCrash";
+constexpr bool kDefaultSettingWatchdogCrash = false;
+
+// Persistent setting name and default setting for the boolean that controls
+// whether or not LogTrace API is enabled. When disabled, all LogTrace methods
+// behave like a stub except for persistent settings itself. Requires a
+// restart to take effect.
+constexpr char kPersistentSettingLogtraceEnable[] =
+    "kPersistentSettingLogtraceEnable";
+constexpr bool kDefaultSettingLogtraceEnable = true;
+
 // Client to monitor
 typedef struct Client {
   std::string name;
@@ -108,6 +131,13 @@
   bool GetPersistentSettingWatchdogCrash();
   void SetPersistentSettingWatchdogCrash(bool can_trigger_crash);
 
+  // LogTrace API. See instrumentation_log.h for more information.
+  bool LogEvent(const std::string& event);
+  std::vector<std::string> GetLogTrace();
+  void ClearLog();
+  bool GetPersistentSettingLogtraceEnable();
+  void SetPersistentSettingLogtraceEnable(bool enable_logtrace);
+
 #if defined(_DEBUG)
   // Sleeps threads based off of environment variables for Watchdog debugging.
   void MaybeInjectDebugDelay(const std::string& name);
@@ -174,6 +204,11 @@
       starboard::ConditionVariable(mutex_);
   // The frequency in microseconds of monitor loops.
   int64_t watchdog_monitor_frequency_;
+  // Captures string events emitted from Kabuki via logEvent() h5vcc API.
+  InstrumentationLog instrumentation_log_;
+  // Flag to disable LogTrace API. When disabled, all LogTrace methods behave
+  // like a stub except that the flag itself can still be get/set.
+  bool is_logtrace_disabled_;
 
 #if defined(_DEBUG)
   starboard::Mutex delay_mutex_;
diff --git a/cobalt/watchdog/watchdog_test.cc b/cobalt/watchdog/watchdog_test.cc
index 6668c2a..bd77790 100644
--- a/cobalt/watchdog/watchdog_test.cc
+++ b/cobalt/watchdog/watchdog_test.cc
@@ -19,12 +19,15 @@
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
+#include "base/test/scoped_task_environment.h"
 #include "starboard/common/file.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
 namespace watchdog {
 
+using persistent_storage::PersistentSettings;
+
 namespace {
 
 const char kWatchdogViolationsJson[] = "watchdog_test.json";
@@ -35,7 +38,9 @@
 
 class WatchdogTest : public testing::Test {
  protected:
-  WatchdogTest() {}
+  WatchdogTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::DEFAULT) {}
 
   void SetUp() final {
     watchdog_ = new watchdog::Watchdog();
@@ -48,6 +53,8 @@
     watchdog_->Uninitialize();
     delete watchdog_;
     watchdog_ = nullptr;
+
+    DeletePersistentSettingsFile();
   }
 
   base::Value CreateDummyViolationDict(std::string desc, int begin, int end) {
@@ -79,7 +86,23 @@
     return violation.Clone();
   }
 
+  void DeletePersistentSettingsFile() {
+    std::vector<char> storage_dir(kSbFileMaxPath + 1, 0);
+    SbSystemGetPath(kSbSystemPathCacheDirectory, storage_dir.data(),
+                    kSbFileMaxPath);
+    std::string path =
+        std::string(storage_dir.data()) + kSbFileSepString + kSettingsFileName;
+
+    starboard::SbFileDeleteRecursive(path.c_str(), true);
+  }
+
+  const std::string kSettingsFileName = "test-settings.json";
+
   watchdog::Watchdog* watchdog_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::WaitableEvent task_done_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
 };
 
 TEST_F(WatchdogTest, RedundantRegistersShouldFail) {
@@ -636,5 +659,122 @@
   ASSERT_EQ(watchdog_->GetWatchdogViolations({}, false), "");
 }
 
+TEST_F(WatchdogTest, CanGetLogTrace) {
+  watchdog_->LogEvent("1");
+  watchdog_->LogEvent("2");
+
+  std::vector<std::string> expected = {"1", "2"};
+  ASSERT_EQ(watchdog_->GetLogTrace(), expected);
+}
+
+TEST_F(WatchdogTest, CanClearLog) {
+  watchdog_->LogEvent("1");
+  watchdog_->LogEvent("2");
+
+  watchdog_->ClearLog();
+
+  ASSERT_EQ(watchdog_->GetLogTrace().size(), 0);
+}
+
+TEST_F(WatchdogTest, ViolationContainsLogTrace) {
+  watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted,
+                      kWatchdogMonitorFrequency);
+  watchdog_->Ping("test-name", "test-ping");
+
+  watchdog_->LogEvent("1");
+  watchdog_->LogEvent("2");
+  watchdog_->LogEvent("3");
+
+  SbThreadSleep(kWatchdogSleepDuration);
+
+  std::string json = watchdog_->GetWatchdogViolations();
+  std::unique_ptr<base::Value> violations_map = base::JSONReader::Read(json);
+  base::Value* violations =
+      violations_map->FindKey("test-name")->FindKey("violations");
+  base::Value* logTrace = violations->GetList()[0].FindKey("logTrace");
+
+  ASSERT_EQ(logTrace->GetList().size(), 3);
+}
+
+TEST_F(WatchdogTest, ViolationContainsEmptyLogTrace) {
+  watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted,
+                      kWatchdogMonitorFrequency);
+  watchdog_->Ping("test-name", "test-ping");
+
+  SbThreadSleep(kWatchdogSleepDuration);
+
+  std::string json = watchdog_->GetWatchdogViolations();
+  std::unique_ptr<base::Value> violations_map = base::JSONReader::Read(json);
+  base::Value* violations =
+      violations_map->FindKey("test-name")->FindKey("violations");
+  base::Value* logTrace = violations->GetList()[0].FindKey("logTrace");
+
+  ASSERT_EQ(logTrace->GetList().size(), 0);
+}
+
+TEST_F(WatchdogTest, WatchdogMethodsAreNoopWhenWatchdogIsDisabled) {
+  // init and destroy existing watchdog to re-initialize it later
+  watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted,
+                      kWatchdogMonitorFrequency);
+  TearDown();
+
+  // PersistentSettings doesn't have interface so it's not mockable
+  auto persistent_settings =
+      std::make_unique<PersistentSettings>(kSettingsFileName);
+  persistent_settings->ValidatePersistentSettings();
+
+  base::OnceClosure closure = base::BindOnce(
+      [](base::WaitableEvent* task_done) { task_done->Signal(); }, &task_done_);
+  persistent_settings->SetPersistentSetting(
+      kPersistentSettingWatchdogEnable, std::make_unique<base::Value>(false),
+      std::move(closure), true);
+  task_done_.Wait();
+
+  watchdog_ = new watchdog::Watchdog();
+  watchdog_->InitializeCustom(persistent_settings.get(),
+                              std::string(kWatchdogViolationsJson),
+                              kWatchdogMonitorFrequency);
+
+  ASSERT_TRUE(watchdog_->Register("test-name", "test-desc",
+                                  base::kApplicationStateStarted,
+                                  kWatchdogMonitorFrequency));
+  ASSERT_TRUE(watchdog_->Ping("test-name"));
+  ASSERT_TRUE(watchdog_->PingByClient(nullptr));
+
+  SbThreadSleep(kWatchdogSleepDuration);
+
+  ASSERT_EQ(watchdog_->GetWatchdogViolations(), "");
+  ASSERT_TRUE(watchdog_->Unregister("test-name"));
+  ASSERT_TRUE(watchdog_->Unregister(""));
+}
+
+TEST_F(WatchdogTest, LogtraceMethodsAreNoopWhenLogtraceIsDisabled) {
+  // init and destroy existing watchdog to re-initialize it later
+  watchdog_->Register("test-name", "test-desc", base::kApplicationStateStarted,
+                      kWatchdogMonitorFrequency);
+  TearDown();
+
+  // PersistentSettings doesn't have interface so it's not mockable
+  auto persistent_settings =
+      std::make_unique<PersistentSettings>(kSettingsFileName);
+  persistent_settings->ValidatePersistentSettings();
+
+  base::OnceClosure closure = base::BindOnce(
+      [](base::WaitableEvent* task_done) { task_done->Signal(); }, &task_done_);
+  persistent_settings->SetPersistentSetting(
+      kPersistentSettingLogtraceEnable, std::make_unique<base::Value>(false),
+      std::move(closure), true);
+  task_done_.Wait();
+
+  watchdog_ = new watchdog::Watchdog();
+  watchdog_->InitializeCustom(persistent_settings.get(),
+                              std::string(kWatchdogViolationsJson),
+                              kWatchdogMonitorFrequency);
+
+  ASSERT_TRUE(watchdog_->LogEvent("foo"));
+  ASSERT_EQ(watchdog_->GetLogTrace().size(), 0);
+  ASSERT_NO_FATAL_FAILURE(watchdog_->ClearLog());
+}
+
 }  // namespace watchdog
 }  // namespace cobalt
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index e9f51e4..1c0c551 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -23,6 +23,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/startup_timer.h"
+#include "cobalt/js_profiler/profiler_group.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/script/environment_settings.h"
@@ -97,6 +98,9 @@
   script::ScriptRunner* script_runner() const final {
     return script_runner_.get();
   }
+  js_profiler::ProfilerGroup* profiler_group() const final {
+    return profiler_group_.get();
+  }
   Blob::Registry* blob_registry() const final { return blob_registry_.get(); }
   web::WebSettings* web_settings() const final { return web_settings_; }
   network::NetworkModule* network_module() const final {
@@ -173,6 +177,10 @@
     return active_service_worker_;
   }
 
+  void set_profiler_group(
+      std::unique_ptr<js_profiler::ProfilerGroup> profiler_group) final {
+    profiler_group_ = std::move(profiler_group);
+  }
 
  private:
   // Injects a list of attributes into the Web Context's global object.
@@ -218,6 +226,9 @@
   // Environment Settings object
   std::unique_ptr<EnvironmentSettings> environment_settings_;
 
+  // A ProfilerGroup contains all window.Profiler objects on the isolate.
+  std::unique_ptr<js_profiler::ProfilerGroup> profiler_group_ = nullptr;
+
   // The service worker registration object map.
   //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#environment-settings-object-service-worker-registration-object-map
   std::map<worker::ServiceWorkerRegistrationObject*,
@@ -329,6 +340,7 @@
   blob_registry_.reset();
   script_runner_.reset();
   execution_state_.reset();
+  profiler_group_.reset();
 
   // Ensure that global_environment_ is null before it's destroyed.
   scoped_refptr<script::GlobalEnvironment> global_environment(
diff --git a/cobalt/web/context.h b/cobalt/web/context.h
index b8fc450..cb9a1ad 100644
--- a/cobalt/web/context.h
+++ b/cobalt/web/context.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_WEB_CONTEXT_H_
 #define COBALT_WEB_CONTEXT_H_
 
+#include <memory>
 #include <string>
 
 #include "cobalt/loader/fetcher_factory.h"
@@ -39,6 +40,9 @@
 class ServiceWorkerContext;
 class ServiceWorkerObject;
 }  // namespace worker
+namespace js_profiler {
+class ProfilerGroup;
+}  // namespace js_profiler
 namespace web {
 class WindowOrWorkerGlobalScope;
 
@@ -66,6 +70,7 @@
   virtual web::WebSettings* web_settings() const = 0;
   virtual network::NetworkModule* network_module() const = 0;
   virtual worker::ServiceWorkerContext* service_worker_context() const = 0;
+  virtual js_profiler::ProfilerGroup* profiler_group() const = 0;
 
   virtual const std::string& name() const = 0;
   virtual void SetupEnvironmentSettings(EnvironmentSettings* settings) = 0;
@@ -110,6 +115,10 @@
   active_service_worker() = 0;
   virtual const scoped_refptr<worker::ServiceWorkerObject>&
   active_service_worker() const = 0;
+
+  // https://wicg.github.io/js-self-profiling/
+  virtual void set_profiler_group(
+      std::unique_ptr<js_profiler::ProfilerGroup> profiler_group) = 0;
 };
 
 }  // namespace web
diff --git a/cobalt/web/testing/BUILD.gn b/cobalt/web/testing/BUILD.gn
index 8c67e01..601c3a7 100644
--- a/cobalt/web/testing/BUILD.gn
+++ b/cobalt/web/testing/BUILD.gn
@@ -29,6 +29,7 @@
     "//base/test:test_support",
     "//cobalt/base",
     "//cobalt/browser",
+    "//cobalt/js_profiler",
     "//cobalt/loader",
     "//cobalt/network",
     "//cobalt/script",
diff --git a/cobalt/web/testing/stub_web_context.h b/cobalt/web/testing/stub_web_context.h
index 487101b..7c341e9 100644
--- a/cobalt/web/testing/stub_web_context.h
+++ b/cobalt/web/testing/stub_web_context.h
@@ -17,9 +17,11 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "base/message_loop/message_loop.h"
 #include "base/test/scoped_task_environment.h"
+#include "cobalt/js_profiler/profiler_group.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/global_environment.h"
@@ -53,6 +55,8 @@
     global_environment_ = javascript_engine_->CreateGlobalEnvironment();
     blob_registry_.reset(new Blob::Registry);
     web_settings_.reset(new WebSettingsImpl());
+    profiler_group_.reset(
+        new js_profiler::ProfilerGroup(global_environment_->isolate()));
     network_module_.reset(new network::NetworkModule());
     fetcher_factory_.reset(new loader::FetcherFactory(
         network_module_.get(),
@@ -109,6 +113,10 @@
     DCHECK(network_module_);
     return network_module_.get();
   }
+  js_profiler::ProfilerGroup* profiler_group() const final {
+    DCHECK(profiler_group_);
+    return profiler_group_.get();
+  }
 
   worker::ServiceWorkerContext* service_worker_context() const final {
     NOTREACHED();
@@ -202,6 +210,11 @@
     return service_worker_object_;
   }
 
+  void set_profiler_group(
+      std::unique_ptr<js_profiler::ProfilerGroup> profiler_group) {
+    profiler_group_ = std::move(profiler_group);
+  }
+
   // Other
  private:
   // Name of the web instance.
@@ -217,6 +230,7 @@
 
   std::unique_ptr<WebSettingsImpl> web_settings_;
   std::unique_ptr<network::NetworkModule> network_module_;
+  std::unique_ptr<js_profiler::ProfilerGroup> profiler_group_;
   // Environment Settings object
   std::unique_ptr<EnvironmentSettings> environment_settings_;
   UserAgentPlatformInfo* platform_info_ = nullptr;
diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml
index 9c6aa27..17e964e 100644
--- a/docker-compose-windows.yml
+++ b/docker-compose-windows.yml
@@ -97,6 +97,17 @@
       - cobalt-build-win32-base
     image: cobalt-build-win-win32
 
+  build-xb1:
+    <<: *common-definitions
+    build:
+      context: ./docker/windows/xb1
+      dockerfile: ./Dockerfile
+      args:
+        - FROM_IMAGE=cobalt-build-win-base
+    depends_on:
+      - cobalt-build-win-base
+    image: cobalt-build-xb1
+
   # -----------------------------------------
   # Win32 Platform Images for Building Cobalt
   # -----------------------------------------
@@ -115,6 +126,16 @@
       PLATFORM: win-win32
     image: cobalt-build-win32
 
+  xb1:
+    <<: *common-definitions
+    environment:
+      <<: *shared-build-env
+      PLATFORM: xb1
+      COBALT_CONCURRENT_LINKS: ${COBALT_CONCURRENT_LINKS:-2}
+    image: cobalt-build-xb1
+    depends_on:
+      - build-xb1
+
   runner-win-win32:
     <<: *common-definitions
     build:
@@ -122,6 +143,19 @@
       dockerfile: ./Dockerfile
       args:
         - RUNNER_VERSION=2.294.0
+        - FROM_IMAGE=cobalt-build-win-win32
     depends_on:
       - cobalt-build-win32-base
     image: cobalt-runner-win-win32
+
+  runner-xb1:
+    <<: *common-definitions
+    build:
+      context: ./docker/windows/runner
+      dockerfile: ./Dockerfile
+      args:
+        - RUNNER_VERSION=2.294.0
+        - FROM_IMAGE=cobalt-build-xb1
+    depends_on:
+      - xb1
+    image: cobalt-runner-xb1
diff --git a/docker/linux/android/Dockerfile b/docker/linux/android/Dockerfile
index 0a949a9..5d0d153 100644
--- a/docker/linux/android/Dockerfile
+++ b/docker/linux/android/Dockerfile
@@ -14,8 +14,9 @@
 
 FROM cobalt-build-base
 
+# TODO: b/309157124 - add --no-install-recommends.
 RUN apt update -qqy \
-    && apt install -qqy --no-install-recommends \
+    && apt install -qqy \
         libxml2-dev \
         default-jdk \
         binutils-arm-linux-gnueabi \
@@ -53,7 +54,6 @@
     "extras;android;m2repository" \
     "extras;google;m2repository" \
     "ndk;25.2.9519653" \
-    "patcher;v4" \
     "platforms;android-30" \
     "platforms;android-31" \
     "platform-tools" \
diff --git a/docker/windows/base/visualstudio2022/Dockerfile b/docker/windows/base/visualstudio2022/Dockerfile
index 0d6cdbf..fc9bd3b 100644
--- a/docker/windows/base/visualstudio2022/Dockerfile
+++ b/docker/windows/base/visualstudio2022/Dockerfile
@@ -22,29 +22,13 @@
 SHELL ["powershell", "-ExecutionPolicy", "Unrestricted", "-Command", `
        "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
 
+COPY dockerized_install_visual_studio_2022.ps1 /dockerized_install_visual_studio_2022.ps1
+
 # Pins Visual Studio 2022 release version to 17.4.6 (includes Clang 15.0.1).
 # This should be kept in sync with the VS installer used in the dev workstation
 # setup script in: `tools/install_visual_studio_22.ps1`
 ARG MSVS_INSTALLER_URL="https://download.visualstudio.microsoft.com/download/pr/d1ed8638-9e88-461e-92b7-4e29cc6172c3/38b09fc09ae9e590b73ae6752a0ebfd62579798969041bd341689273b842bc10/vs_BuildTools.exe"
-RUN mkdir C:\TEMP;`
-    Write-Host ('Downloading vs_buildtools.exe');`
-    Invoke-WebRequest -Uri ${env:MSVS_INSTALLER_URL} `
-      -OutFile C:\TEMP\vs_buildtools.exe
-
-RUN Write-Host ('Installing vs_buildtools.exe');`
-    Start-Process C:\TEMP\vs_buildtools.exe -Wait -NoNewWindow`
-        -ArgumentList '--quiet --wait --norestart --nocache`
-        --installPath C:\BuildTools `
-        --add Microsoft.VisualStudio.Component.VC.14.34.17.4.x86.x64  `
-        --add Microsoft.VisualStudio.Component.VC.Llvm.Clang          `
-        --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset   `
-        --add Microsoft.VisualStudio.Component.Windows10SDK.18362';   `
-    Write-Host ('Cleaning up vs_buildtools.exe');`
-    Remove-Item -Force -Recurse ${env:ProgramFiles(x86)}\'Microsoft Visual Studio'\Installer;`
-    Remove-Item -Force -Recurse $env:TEMP\*;`
-    Remove-Item -Force -Recurse $env:ProgramData\'Package Cache'\;`
-    Remove-Item -Force -Recurse C:\BuildTools\Common7\IDE;`
-    Remove-Item C:\TEMP\vs_buildtools.exe
+RUN C:\dockerized_install_visual_studio_2022.ps1 ${env:MSVS_INSTALLER_URL}
 
 ENV VSINSTALLDIR "C:\BuildTools"
 
diff --git a/docker/windows/base/visualstudio2022/dockerized_install_visual_studio_2022.ps1 b/docker/windows/base/visualstudio2022/dockerized_install_visual_studio_2022.ps1
new file mode 100644
index 0000000..638b52a
--- /dev/null
+++ b/docker/windows/base/visualstudio2022/dockerized_install_visual_studio_2022.ps1
@@ -0,0 +1,28 @@
+$MSVS_INSTALLER_URL=$args[0]
+
+mkdir C:\TEMP;
+
+Write-Host ('Downloading vs_buildtools.exe');
+Invoke-WebRequest -Uri $MSVS_INSTALLER_URL -OutFile C:\TEMP\vs_buildtools.exe;
+
+Write-Host ('Installing vs_buildtools.exe');
+Start-Process C:\TEMP\vs_buildtools.exe -Wait -NoNewWindow -ArgumentList @(
+  "--quiet", "--wait", "--norestart", "--nocache", "--installPath",
+  "C:\BuildTools", "--add", "Microsoft.VisualStudio.Component.VC.Llvm.Clang",
+  "--add", "Microsoft.VisualStudio.Component.VC.14.34.17.4.x86.x64",
+  "--add", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset",
+  "--add", "Microsoft.VisualStudio.Component.Windows10SDK.18362");
+
+Write-Host ('Cleaning up installation artifacts');
+$FILES_TO_DELETE = @(
+  "${env:ProgramFiles(x86)}\'Microsoft Visual Studio'\Installer",
+  "C:\TEMP\*",
+  "$env:ProgramData\'Package Cache'",
+  "C:\BuildTools\Common7\IDE",
+  "C:\TEMP\vs_buildtools.exe"
+)
+Foreach ($file_to_delete in $FILES_TO_DELETE) {
+  If (Test-Path $file_to_delete) {
+    Remove-Item -Force -Recurse -Path $file_to_delete
+  }
+}
diff --git a/docker/windows/runner/Dockerfile b/docker/windows/runner/Dockerfile
index a494811..b8aa4ad 100644
--- a/docker/windows/runner/Dockerfile
+++ b/docker/windows/runner/Dockerfile
@@ -11,7 +11,8 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-FROM cobalt-build-win32-base
+ARG FROM_IMAGE
+FROM ${FROM_IMAGE}
 
 SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
 
@@ -20,7 +21,7 @@
 RUN Invoke-WebRequest -Uri 'https://aka.ms/install-powershell.ps1' -OutFile install-powershell.ps1; \
     powershell -ExecutionPolicy Unrestricted -File ./install-powershell.ps1 -AddToPath
 
-RUN Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v$env:RUNNER_VERSION/actions-runner-win-x64-$env:RUNNER_VERSION.zip -OutFile runner.zip; \
+RUN Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v$env:RUNNER_VERSION/actions-runner-win-x64-$env:RUNNER_VERSION.zip -OutFile C:/runner.zip; \
     Expand-Archive -Path C:/runner.zip -DestinationPath C:/actions-runner; \
     Remove-Item -Path C:\runner.zip; \
     setx /M PATH $(${Env:PATH} + \";${Env:ProgramFiles}\Git\bin\")
@@ -31,4 +32,4 @@
 ENV TMP "C:\Users\ContainerAdministrator\AppData\Local\Temp2"
 
 ADD runner.ps1 C:/runner.ps1
-CMD ["pwsh", "-ExecutionPolicy", "Unrestricted", "-File", ".\\runner.ps1"]
+CMD ["pwsh", "-ExecutionPolicy", "Unrestricted", "-File", "C:\\runner.ps1"]
diff --git a/docker/windows/runner/runner.ps1 b/docker/windows/runner/runner.ps1
index 1d30fd3..fa4fb5c 100644
--- a/docker/windows/runner/runner.ps1
+++ b/docker/windows/runner/runner.ps1
@@ -1,2 +1,2 @@
-.\actions-runner\config.cmd --unattended --replace --url https://github.com/${env:RUNNER_REPO} --token $env:RUNNER_TOKEN --name $env:RUNNER_NAME --work $env:RUNNER_WORKDIR;
-.\actions-runner\run.cmd;
+C:\actions-runner\config.cmd --unattended --replace --url https://github.com/${env:RUNNER_REPO} --token $env:RUNNER_TOKEN --name $env:RUNNER_NAME --work $env:RUNNER_WORKDIR --labels $env:RUNNER_LABELS;
+C:\actions-runner\run.cmd;
diff --git a/docker/windows/xb1/Dockerfile b/docker/windows/xb1/Dockerfile
new file mode 100644
index 0000000..e4a7fd5
--- /dev/null
+++ b/docker/windows/xb1/Dockerfile
@@ -0,0 +1,50 @@
+# escape=`
+
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+ARG FROM_IMAGE
+FROM ${FROM_IMAGE}
+
+SHELL ["powershell", "-ExecutionPolicy", "Unrestricted", "-Command", `
+       "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
+
+# Note: Visual Studio is installed as part of cobalt-build-win-base
+
+# Windows SDK
+RUN Invoke-WebRequest -Uri https://go.microsoft.com/fwlink/p/?linkid=2173743 `
+    -OutFile C:\win_sdk_installer.exe ; `
+    Write-Host 'Installing win_sdk_installer.exe ...' ; `
+    C:\win_sdk_installer.exe /ceip off /quiet /features + | Out-Null ; `
+    Write-Host 'Install of win_sdk_installer.exe completed. Cleaning up ...' ; `
+    Remove-Item -Force -Recurse $env:TEMP\* ; `
+    Remove-Item C:\win_sdk_installer.exe
+
+# MSVC sdk
+ARG vs_buildtools_version="15"
+ARG windows_11_sdk_version="22000"
+RUN Write-Host 'Downloading vs_buildtools.exe' ; `
+    Invoke-WebRequest -Uri https://aka.ms/vs/${env:vs_buildtools_version}/release/vs_buildtools.exe `
+                      -OutFile C:\vs_buildtools.exe ; `
+    Write-Host 'Installing vs_buildtools.exe ...' ;`
+    Start-Process -Wait -FilePath C:\vs_buildtools.exe -ArgumentList "--quiet","--wait","--norestart","--nocache","--installPath","C:\BuildTools","--add","Microsoft.VisualStudio.Component.Windows11SDK.${env:windows_11_sdk_version}","--add","Microsoft.VisualStudio.Component.VC.Redist.14.Latest" ; `
+    Write-Host 'Install of vs_buildtools.exe completed. Cleaning up ...' ; `
+    Remove-Item C:\vs_buildtools.exe
+
+
+WORKDIR "C:\code"
+
+CMD python3 cobalt/build/gn.py -p $env:PLATFORM -c $env:CONFIG out/${env:PLATFORM}_${env:CONFIG};`
+    if ($?) {`
+      ninja $env:NINJA_FLAGS -C out/${env:PLATFORM}_$env:CONFIG $env:TARGET;`
+    }
diff --git a/net/BUILD.gn b/net/BUILD.gn
index e978e30..acd3a77 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -4176,17 +4176,6 @@
     ]
   }
 
-  if (enable_in_app_dial) {
-    sources += [
-    # DIAL server is Cobalt custom implementation. This is the wrong place for
-    # it and it should be moved to cobalt/network in the future.
-    "dial/dial_http_server_unittest.cc",
-    "dial/dial_service_unittest.cc",
-    "dial/dial_test_helpers.h",
-    "dial/dial_udp_server_unittests.cc",
-    ]
-  }
-
   # Avoid compiler errors due to conversion from "0x00" to char.
   if (is_win) {
     if (!is_starboard) {
@@ -4204,7 +4193,6 @@
   configs -= [ "//starboard/build/config:size" ]
 
   deps = [
-    # This is needed by dial_http_server in net
     ":http_server",
     ":net",
     ":net_generated_registry_controlled_domains",
@@ -4226,9 +4214,6 @@
     "//url",
     "//url:url_features",
   ]
-  if (enable_in_app_dial) {
-    deps += [ ":cobalt_dial_server" ]
-  }
 
   if (is_starboard) {
     data_deps = [
@@ -4693,27 +4678,3 @@
   ]
 }
 
-if (enable_in_app_dial) {
-static_library("cobalt_dial_server") {
-  sources = [
-    "dial/dial_http_server.cc",
-    "dial/dial_http_server.h",
-    "dial/dial_service.cc",
-    "dial/dial_service.h",
-    "dial/dial_service_handler.h",
-    "dial/dial_system_config.cc",
-    "dial/dial_system_config.h",
-    "dial/dial_system_config_starboard.cc",
-    "dial/dial_udp_server.cc",
-    "dial/dial_udp_server.h",
-    "dial/dial_udp_socket_factory.cc",
-    "dial/dial_udp_socket_factory.h",
-  ]
-  deps = [
-      ":http_server",
-      ":net",
-      "//base:base",
-      "//starboard/common",
-    ]
-  }
-}
diff --git a/net/dial/dial_http_server.h b/net/dial/dial_http_server.h
deleted file mode 100644
index f2d5045..0000000
--- a/net/dial/dial_http_server.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef NET_DIAL_DIAL_HTTP_SERVER_H
-#define NET_DIAL_DIAL_HTTP_SERVER_H
-
-#include <string>
-
-#include "base/callback_forward.h"
-#include "base/memory/ref_counted.h"
-#include "base/single_thread_task_runner.h"
-#include "net/dial/dial_service_handler.h"
-#include "net/http/http_status_code.h"
-#include "net/server/http_server.h"
-#include "net/server/http_server_response_info.h"
-
-namespace net {
-
-class DialService;
-class HttpResponseInfo;
-class IPEndPoint;
-
-// This class is created and owned by DialService and is not meant to be
-// used externally.
-// All functions run on the DialService's message loop, except for
-// the callback AsyncReceivedResponse() which is eventually called
-// by a DialServiceHandler.
-// It's refcounted threadsafe so we can safely bind it to the callback we pass
-// to DialServiceHandler::handleRequest().
-class NET_EXPORT DialHttpServer
-    : public HttpServer::Delegate,
-      public base::RefCountedThreadSafe<DialHttpServer> {
- public:
-  explicit DialHttpServer(DialService* dial_service);
-  void Stop();
-
-  // HttpServer::Delegate implementation
-  virtual void OnConnect(int /*conn_id*/) override{};
-  virtual void OnHttpRequest(int conn_id,
-                             const HttpServerRequestInfo& info) override;
-
-  virtual void OnClose(int conn_id) override;
-
-  // Unused HttpServer::Delegate
-  virtual void OnWebSocketRequest(
-      int /*connection_id*/,
-      const HttpServerRequestInfo& /*info*/) override {}
-
-  virtual void OnWebSocketMessage(int /*connection_id*/,
-                                  const std::string& /*data*/) override {}
-
-  // Return the formatted application URL
-  std::string application_url() const { return server_url_ + "apps/"; }
-
-  // Return the formatted location URL.
-  std::string location_url() const { return server_url_ + "dd.xml"; }
-
-  // Somewhat similar to HttpServer::GetLocalAddress, but figures out the
-  // network IP address and uses that. The port remains the same.
-  int GetLocalAddress(IPEndPoint* addr);
-
- private:
-  friend class base::RefCountedThreadSafe<DialHttpServer>;
-  virtual ~DialHttpServer();
-
-  void ConfigureApplicationUrl();
-
-  // Send the DIAL Device Description Manifest to the client.
-  void SendDeviceDescriptionManifest(int conn_id);
-
-  // Query DIAL service for a handler for the given request.
-  // Return false if no handler found, true if handleRequest() was issued
-  // to the handler.
-  bool DispatchToHandler(int conn_id, const HttpServerRequestInfo& info);
-
-  // Callback from WebKit thread when the HTTP task is complete.
-  // Post the response info to DIAL service thread.
-  void AsyncReceivedResponse(int conn_id,
-                             std::unique_ptr<HttpServerResponseInfo> response);
-  // Handles DIAL response.
-  void OnReceivedResponse(int conn_id,
-                          std::unique_ptr<HttpServerResponseInfo> response);
-
-  std::unique_ptr<HttpServer> http_server_;
-  std::string server_url_;
-  // DialService owns this object.
-  DialService* dial_service_;
-
-  // Message Loop of the thread that created us. We make sure http server
-  // is only called on the proper thread.
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(DialHttpServer);
-};
-
-}  // namespace net
-
-#endif  // NET_DIAL_DIAL_HTTP_SERVER_H
diff --git a/net/dial/dial_service_handler.h b/net/dial/dial_service_handler.h
deleted file mode 100644
index e6afc05..0000000
--- a/net/dial/dial_service_handler.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_DIAL_SERVICE_HANDLER_H_
-#define SRC_DIAL_SERVICE_HANDLER_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/basictypes.h"
-#include "base/callback_forward.h"
-#include "net/http/http_response_headers.h"
-
-namespace net {
-
-class HttpServerRequestInfo;
-
-// DialUdpServer used to define HttpServerResponseInfo itself. The new net
-// have its own HttpServerResponseInfo now and we can use that instead. It also
-// makes connecting with other net components easier.
-class HttpServerResponseInfo;
-
-// Abstract class that provides a response to a request from the dial server.
-// DialServiceHandlers should be Register()ed with a DialService object.
-class DialServiceHandler
-    : public base::RefCountedThreadSafe<DialServiceHandler> {
- public:
-  typedef base::Callback<void(std::unique_ptr<HttpServerResponseInfo> response)>
-      CompletionCB;
-  // Called by the DialHttpServer to satisfy an incoming request.
-  // It is expected that this will be handled asynchronously and the completion
-  // callback must be called in all cases. If the request is handled
-  // successfully, pass the response the server should send
-  // back and a |result| of true. If the request cannot be handled, pass a NULL
-  // |response| and a |result| of false.
-  virtual void HandleRequest(const std::string& path,
-                             const HttpServerRequestInfo& request,
-                             const CompletionCB& completion_cb) = 0;
-  // The name of the DIAL service this handler implements.
-  virtual const std::string& service_name() const = 0;
-
- protected:
-  friend class base::RefCountedThreadSafe<DialServiceHandler>;
-  virtual ~DialServiceHandler() {}
-};
-
-}  // namespace net
-
-#endif  // SRC_DIAL_SERVICE_HANDLER_H_
diff --git a/net/dial/dial_system_config.h b/net/dial/dial_system_config.h
deleted file mode 100644
index bdaae03..0000000
--- a/net/dial/dial_system_config.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Dial requires some system information exposed to the world, for example
-// the device name, etc. This class lets it be configurable, but throw default
-// values from implementation.
-
-#ifndef NET_DIAL_DIAL_SYSTEM_CONFIG_H
-#define NET_DIAL_DIAL_SYSTEM_CONFIG_H
-
-#include <string>
-
-#include "base/basictypes.h"
-#include "base/memory/singleton.h"
-#include "base/synchronization/lock.h"
-
-namespace net {
-
-class DialSystemConfig {
- public:
-  static DialSystemConfig* GetInstance();
-
-  // Stores the pointers to various system info.
-  const char* friendly_name() const { return friendly_name_.c_str(); }
-
-  const char* manufacturer_name() const { return manufacturer_name_.c_str(); }
-
-  const char* model_name() const { return model_name_.c_str(); }
-
-  // Get's the model uuid.
-  const char* model_uuid() const;
-
- private:
-  DialSystemConfig();
-
-  friend struct base::DefaultSingletonTraits<DialSystemConfig>;
-
-  static void CreateDialUuid();
-
-  // These 4 functions must be defined by a platform-specific source file.
-  static std::string GetFriendlyName();
-  static std::string GetManufacturerName();
-  static std::string GetModelName();
-  static std::string GeneratePlatformUuid();
-
-  static const int kMaxNameSize = 64;
-
-  std::string friendly_name_;
-  std::string manufacturer_name_;
-  std::string model_name_;
-
-  DISALLOW_COPY_AND_ASSIGN(DialSystemConfig);
-  mutable base::Lock lock_;
-};
-
-}  // namespace net
-
-#endif  // NET_DIAL_DIAL_SYSTEM_CONFIG_H
diff --git a/net/dial/dial_test_helpers.h b/net/dial/dial_test_helpers.h
deleted file mode 100644
index c84c4ea..0000000
--- a/net/dial/dial_test_helpers.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef NET_DIAL_DIAL_TEST_HELPERS_H
-#define NET_DIAL_DIAL_TEST_HELPERS_H
-
-#include "base/message_loop/message_loop.h"
-#include "net/dial/dial_service_handler.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace net {
-
-class MockServiceHandler : public DialServiceHandler {
- public:
-  MockServiceHandler(const std::string& service_name)
-      : service_name_(service_name) {}
-  MOCK_METHOD3(HandleRequest,
-               void(const std::string&,
-                    const HttpServerRequestInfo& request,
-                    const CompletionCB& completion_cb));
-  const std::string& service_name() const override { return service_name_; }
-
- private:
-  ~MockServiceHandler() {}
-  std::string service_name_;
-};
-
-}  // namespace net
-
-#endif  // NET_DIAL_DIAL_TEST_HELPERS_H
diff --git a/net/dial/dial_udp_server.h b/net/dial/dial_udp_server.h
deleted file mode 100644
index 7af175a..0000000
--- a/net/dial/dial_udp_server.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef NET_DIAL_DIAL_UDP_SERVER_H
-#define NET_DIAL_DIAL_UDP_SERVER_H
-
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/gtest_prod_util.h"
-#include "base/memory/ref_counted.h"
-#include "base/threading/thread.h"
-#include "net/base/net_export.h"
-#include "net/socket/udp_socket.h"
-
-namespace net {
-
-class IPEndPoint;
-class UdpSocketFactory;
-class HttpServerRequestInfo;
-
-class NET_EXPORT DialUdpServer {
- public:
-  DialUdpServer(const std::string& location_url,
-                const std::string& server_agent);
-  virtual ~DialUdpServer();
-
-  virtual void DidRead(int rv);
-
-  virtual void DidClose(UDPSocket* sock);
-
-  void WriteComplete(scoped_refptr<WrappedIOBuffer>,
-                     std::unique_ptr<std::string>,
-                     int rv);
-
- private:
-  FRIEND_TEST_ALL_PREFIXES(DialUdpServerTest, ParseSearchRequest);
-
-  void Start();
-  void Stop();
-
-  // Create the listen socket. Runs on a separate thread.
-  void CreateAndBind();
-  void Shutdown();
-  void AcceptAndProcessConnection();
-
-  // Construct the appropriate search response.
-  std::string ConstructSearchResponse() const;
-
-  // Parse a request to make sure it is a M-Search.
-  static bool ParseSearchRequest(const std::string& request);
-
-  // Is the valid SSDP request a valid M-Search request too ?
-  static bool IsValidMSearchRequest(const HttpServerRequestInfo& info);
-
-  std::unique_ptr<UDPSocket> socket_;
-  std::unique_ptr<UdpSocketFactory> factory_;
-
-  // Value to pass in LOCATION: header
-  std::string location_url_;
-
-  // Value to pass in SERVER: header
-  std::string server_agent_;
-
-  base::Thread thread_;
-  THREAD_CHECKER(thread_checker_);
-
-  bool is_running_;
-
-  scoped_refptr<IOBuffer> read_buf_;
-  IPEndPoint client_address_;
-};
-
-}  // namespace net
-
-#endif  // NET_DIAL_DIAL_UDP_SERVER_H
diff --git a/net/dial/dial_udp_server_unittests.cc b/net/dial/dial_udp_server_unittests.cc
deleted file mode 100644
index cc96933..0000000
--- a/net/dial/dial_udp_server_unittests.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "net/dial/dial_udp_server.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace net {
-namespace {
-#define ARRAYSIZE_UNSAFE(a)     \
-  ((sizeof(a) / sizeof(*(a))) / \
-   static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
-}
-
-TEST(DialUdpServerTest, ParseSearchRequest) {
-  DialUdpServer server("fake_location", "fake_server_agent");
-  struct TestData {
-    std::string received_data;
-    bool result;
-  } tests[] = {
-      {
-          // The smallest correct response
-          "M-SEARCH * HTTP/1.1\r\n"
-          "ST: urn:dial-multiscreen-org:service:dial:1\r\n\r\n",
-          true,
-      },
-      {
-          // Incorrect ST-Header
-          "M-SEARCH * HTTP/1.1\r\nST:  xyzfoo1231  \r\n\r\n", false,
-      },
-      {
-          // Missing \r\n
-          "M-SEARCH * HTTP/1.1\r\nST: 345512\r\n", false,
-      },
-      {
-          // Incorrect SSDP method
-          "GET * HTTP/1.1\r\nST: 13\r\n\r\n", false,
-      },
-      {
-          // Incorrect SSDP path
-          "M-SEARCH /path HTTP/1.1\r\nST: 13\r\n\r\n", false,
-      },
-      {
-          // Empty headers
-          "M-SEARCH * HTTP/1.1\r\n\r\n", false,
-      },
-      {
-          // Empty request
-          "", false,
-      },
-  };
-
-  for (int i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
-    std::string out_st_request;
-    EXPECT_EQ(tests[i].result,
-              server.ParseSearchRequest(tests[i].received_data));
-  }
-}
-
-}  // namespace net
diff --git a/net/dial/dial_udp_socket_factory.cc b/net/dial/dial_udp_socket_factory.cc
deleted file mode 100644
index ddbc23b..0000000
--- a/net/dial/dial_udp_socket_factory.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "net/dial/dial_udp_socket_factory.h"
-
-#if !defined(STARBOARD)
-#include <arpa/inet.h>
-#endif
-
-#include "base/logging.h"
-#include "net/base/ip_endpoint.h"
-#include "net/base/net_string_util.h"
-
-#if defined(STARBOARD)
-#include "starboard/common/socket.h"
-#include "starboard/types.h"
-#endif
-
-namespace net {
-
-std::unique_ptr<UDPSocket> UdpSocketFactory::CreateAndBind(
-    const IPEndPoint& address) {
-  std::unique_ptr<UDPSocket> sock(
-      new UDPSocket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource()));
-  if (sock->Open(address.GetFamily()) != net::OK) {
-    return NULL;
-  }
-
-  int set_socket_reuse_success = sock->AllowAddressReuse();
-  if (set_socket_reuse_success != OK) {
-    LOG(WARNING) << "AllowAddressReuse failed on DIAL UDP socket with code "
-                 << set_socket_reuse_success;
-  }
-
-  if (sock->Bind(address) != net::OK) {
-    LOG(WARNING) << "DIAL UDP socket failed to bind to address";
-    sock->Close();
-    return NULL;
-  }
-
-  SetupSocketAfterBind(sock.get());
-
-  return sock;
-}
-// Enable Multicast and join group if multicast is enabled.
-void DialUdpSocketFactory::SetupSocketAfterBind(UDPSocket* sock) {
-  net::IPAddress address(239, 255, 255, 250);
-  if (sock->JoinGroup(address) != net::OK) {
-    LOG(WARNING) << "JoinGroup failed on DIAL UDP socket.";
-  }
-}
-
-}  // namespace net
diff --git a/net/dial/dial_udp_socket_factory.h b/net/dial/dial_udp_socket_factory.h
deleted file mode 100644
index 25c8ffc..0000000
--- a/net/dial/dial_udp_socket_factory.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef NET_DIAL_DIAL_UDP_SOCKET_FACTORY_H
-#define NET_DIAL_DIAL_UDP_SOCKET_FACTORY_H
-
-#include "net/socket/udp_socket.h"
-
-namespace net {
-
-class IPEndPoint;
-
-class UdpSocketFactory {
- public:
-  std::unique_ptr<UDPSocket> CreateAndBind(const IPEndPoint& address);
-  virtual ~UdpSocketFactory() {}
-
- protected:
-  virtual void SetupSocketAfterBind(UDPSocket* sock) {}
-};
-
-class DialUdpSocketFactory : public UdpSocketFactory {
- public:
-  virtual ~DialUdpSocketFactory() {}
-
- protected:
-  // UdpSocketFactory implementation
-  virtual void SetupSocketAfterBind(UDPSocket* sock) override;
-};
-
-}  // namespace net
-
-#endif  // NET_DIAL_DIAL_UDP_SOCKET_FACTORY_H
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 2a11745..5710475 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
@@ -118,8 +118,6 @@
   private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("America/Los_Angeles");
   private final long timeNanosecondsPerMicrosecond = 1000;
 
-  public static boolean enableBackgroundPlayback = false;
-
   public StarboardBridge(
       Context appContext,
       Holder<Activity> activityHolder,
@@ -191,11 +189,6 @@
   @SuppressWarnings("unused")
   @UsedByNative
   protected void startMediaPlaybackService() {
-    if (!enableBackgroundPlayback) {
-      Log.v(TAG, "Media Playback Service is disabled. Skip startMediaPlaybackService().");
-      return;
-    }
-
     if (cobaltMediaSession == null || !cobaltMediaSession.isActive()) {
       Log.w(TAG, "Do not start a MediaPlaybackService when the MediSsession is null or inactive.");
       return;
@@ -215,18 +208,13 @@
         } else {
           appContext.startService(intent);
         }
-      } catch (RuntimeException e) {
+      } catch (SecurityException e) {
         Log.e(TAG, "Failed to start MediaPlaybackService with intent.", e);
         return;
       }
     } else {
       Log.i(TAG, "Warm start - Restarting the MediaPlaybackService.");
-      try {
-        ((MediaPlaybackService) service).startService();
-      } catch (RuntimeException e) {
-        Log.e(TAG, "Failed to restart MediaPlaybackService.", e);
-        return;
-      }
+      ((MediaPlaybackService) service).startService();
     }
   }
 
@@ -236,12 +224,7 @@
     Service service = serviceHolder.get();
     if (service != null) {
       Log.i(TAG, "Stopping the MediaPlaybackService.");
-      try {
-        ((MediaPlaybackService) service).stopService();
-      } catch (RuntimeException e) {
-        Log.e(TAG, "Failed to stop MediaPlaybackService.", e);
-        return;
-      }
+      ((MediaPlaybackService) service).stopService();
     }
   }
 
@@ -666,6 +649,12 @@
         playbackState, actions, positionMs, speed, title, artist, album, artwork, duration);
   }
 
+  @SuppressWarnings("unused")
+  @UsedByNative
+  public void deactivateMediaSession() {
+    cobaltMediaSession.deactivateMediaSession();
+  }
+
   /** Returns string for kSbSystemPropertyUserAgentAuxField */
   @SuppressWarnings("unused")
   @UsedByNative
@@ -863,11 +852,4 @@
   protected String getBuildFingerprint() {
     return Build.FINGERPRINT;
   }
-
-  @SuppressWarnings("unused")
-  @UsedByNative
-  protected void enableBackgroundPlayback(boolean value) {
-    enableBackgroundPlayback = value;
-    Log.v(TAG, "StarboardBridge set enableBackgroundPlayback: %b", value);
-  }
 }
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 0e02ebc..9aba9c3 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
@@ -60,8 +60,6 @@
       int sampleRate,
       int channelCount,
       int preferredBufferSizeInBytes,
-      boolean enableAudioDeviceCallback,
-      boolean enablePcmContentTypeMovie,
       int tunnelModeAudioSessionId,
       boolean isWebAudio) {
     AudioTrackBridge audioTrackBridge =
@@ -70,7 +68,6 @@
             sampleRate,
             channelCount,
             preferredBufferSizeInBytes,
-            enablePcmContentTypeMovie,
             tunnelModeAudioSessionId,
             isWebAudio);
     if (!audioTrackBridge.isAudioTrackValid()) {
@@ -80,7 +77,7 @@
     audioTrackBridgeList.add(audioTrackBridge);
     hasAudioDeviceChanged.set(false);
 
-    if (hasRegisteredAudioDeviceCallback || !enableAudioDeviceCallback) {
+    if (hasRegisteredAudioDeviceCallback || isWebAudio) {
       return audioTrackBridge;
     }
 
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index b87b9eb..b66c3cc 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -65,7 +65,6 @@
       int sampleRate,
       int channelCount,
       int preferredBufferSizeInBytes,
-      boolean enablePcmContentTypeMovie,
       int tunnelModeAudioSessionId,
       boolean isWebAudio) {
 
@@ -119,7 +118,7 @@
       // TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
       final boolean isSurround =
           sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
-      final boolean useContentTypeMovie = isSurround || (!isWebAudio && enablePcmContentTypeMovie);
+      final boolean useContentTypeMovie = isSurround || !isWebAudio;
       attributes =
           new AudioAttributes.Builder()
               .setContentType(
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
index cd62cf0..2dc78d9 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -31,7 +31,6 @@
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.view.WindowManager;
 import androidx.annotation.RequiresApi;
-import dev.cobalt.coat.StarboardBridge;
 import dev.cobalt.util.Holder;
 import dev.cobalt.util.Log;
 
@@ -130,10 +129,6 @@
   }
 
   public boolean isActive() {
-    if (!StarboardBridge.enableBackgroundPlayback) {
-      return false;
-    }
-
     if (this.mediaSession == null) {
       return false;
     } else {
@@ -259,16 +254,23 @@
       Log.i(TAG, "MediaSession already released");
       return;
     }
-    if (StarboardBridge.enableBackgroundPlayback) {
-      mediaSession.setActive(playbackState != PLAYBACK_STATE_NONE);
-    }
+    mediaSession.setActive(playbackState != PLAYBACK_STATE_NONE);
     if (lifecycleCallback != null) {
       lifecycleCallback.onMediaSessionLifecycle(
           this.mediaSession.isActive(), this.mediaSession.getSessionToken());
     }
     if (deactivating) {
       // Suspending lands here.
-      Log.i(TAG, "MediaSession release");
+      deactivateMediaSession();
+    }
+  }
+
+  public void deactivateMediaSession() {
+    Log.i(TAG, "MediaSession release");
+    if (mediaSession != null) {
+      if (mediaSession.isActive()) {
+        mediaSession.setActive(false);
+      }
       mediaSession.release();
       mediaSession = null;
     }
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 50610e0..fdeab69 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
@@ -541,7 +541,6 @@
       boolean mustSupportHdr,
       boolean mustSupportSoftwareCodec,
       boolean mustSupportTunnelMode,
-      boolean forceImprovedSupportCheck,
       int decoderCacheTtlMs,
       int frameWidth,
       int frameHeight,
@@ -552,8 +551,7 @@
             Locale.US,
             "Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth:"
                 + " %d, frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b,"
-                + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b,"
-                + " forceImprovedSupportCheck: %b",
+                + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b",
             mimeType,
             mustSupportSecure,
             frameWidth,
@@ -562,8 +560,7 @@
             fps,
             mustSupportHdr,
             mustSupportSoftwareCodec,
-            mustSupportTunnelMode,
-            forceImprovedSupportCheck);
+            mustSupportTunnelMode);
     Log.v(TAG, decoderInfo);
     String deviceInfo =
         String.format(
@@ -656,38 +653,20 @@
       Range<Integer> supportedWidths = decoder.supportedWidths;
       Range<Integer> supportedHeights = decoder.supportedHeights;
 
-      // Enable the improved support check based on more specific APIs, like isSizeSupported() or
-      // areSizeAndRateSupported(), for 8k content. These APIs are theoretically more accurate,
-      // but we are unsure about their level of support on various Android TV platforms.
-      final boolean enableImprovedSupportCheck =
-          forceImprovedSupportCheck || (frameWidth > 3840 || frameHeight > 2160);
-      if (enableImprovedSupportCheck) {
-        if (frameWidth != 0 && frameHeight != 0) {
-          if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) {
-            String format = "Rejecting %s, reason: width %s is not compatible with height %d";
-            Log.v(TAG, format, name, frameWidth, frameHeight);
-            continue;
-          }
-        } else if (frameWidth != 0) {
-          if (!supportedWidths.contains(frameWidth)) {
-            String format = "Rejecting %s, reason: supported widths %s does not contain %d";
-            Log.v(TAG, format, name, supportedWidths.toString(), frameWidth);
-            continue;
-          }
-        } else if (frameHeight != 0) {
-          if (!supportedHeights.contains(frameHeight)) {
-            String format = "Rejecting %s, reason: supported heights %s does not contain %d";
-            Log.v(TAG, format, name, supportedHeights.toString(), frameHeight);
-            continue;
-          }
+      if (frameWidth != 0 && frameHeight != 0) {
+        if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) {
+          String format = "Rejecting %s, reason: width %s is not compatible with height %d";
+          Log.v(TAG, format, name, frameWidth, frameHeight);
+          continue;
         }
-      } else {
-        if (frameWidth != 0 && !supportedWidths.contains(frameWidth)) {
+      } else if (frameWidth != 0) {
+        if (!supportedWidths.contains(frameWidth)) {
           String format = "Rejecting %s, reason: supported widths %s does not contain %d";
           Log.v(TAG, format, name, supportedWidths.toString(), frameWidth);
           continue;
         }
-        if (frameHeight != 0 && !supportedHeights.contains(frameHeight)) {
+      } else if (frameHeight != 0) {
+        if (!supportedHeights.contains(frameHeight)) {
           String format = "Rejecting %s, reason: supported heights %s does not contain %d";
           Log.v(TAG, format, name, supportedHeights.toString(), frameHeight);
           continue;
@@ -702,28 +681,20 @@
       }
 
       Range<Integer> supportedFrameRates = videoCapabilities.getSupportedFrameRates();
-      if (enableImprovedSupportCheck) {
-        if (fps != 0) {
-          if (frameHeight != 0 && frameWidth != 0) {
-            if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) {
-              String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-              Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
-              continue;
-            }
-          } else {
-            // At least one of frameHeight or frameWidth is 0
-            if (!supportedFrameRates.contains(fps)) {
-              String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-              Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
-              continue;
-            }
+      if (fps != 0) {
+        if (frameHeight != 0 && frameWidth != 0) {
+          if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) {
+            String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
+            Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
+            continue;
           }
-        }
-      } else {
-        if (fps != 0 && !supportedFrameRates.contains(fps)) {
-          String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
-          Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
-          continue;
+        } else {
+          // At least one of frameHeight or frameWidth is 0
+          if (!supportedFrameRates.contains(fps)) {
+            String format = "Rejecting %s, reason: supported frame rates %s does not contain %d";
+            Log.v(TAG, format, name, supportedFrameRates.toString(), fps);
+            continue;
+          }
         }
       }
 
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn
index 45e7412..a34fdd5 100644
--- a/starboard/android/shared/BUILD.gn
+++ b/starboard/android/shared/BUILD.gn
@@ -166,6 +166,9 @@
     "//starboard/shared/pthread/mutex_destroy.cc",
     "//starboard/shared/pthread/mutex_release.cc",
     "//starboard/shared/pthread/once.cc",
+    "//starboard/shared/pthread/thread_context_get_pointer.cc",
+    "//starboard/shared/pthread/thread_context_internal.cc",
+    "//starboard/shared/pthread/thread_context_internal.h",
     "//starboard/shared/pthread/thread_create_local_key.cc",
     "//starboard/shared/pthread/thread_create_priority.h",
     "//starboard/shared/pthread/thread_destroy_local_key.cc",
@@ -174,6 +177,13 @@
     "//starboard/shared/pthread/thread_get_local_value.cc",
     "//starboard/shared/pthread/thread_is_equal.cc",
     "//starboard/shared/pthread/thread_join.cc",
+    "//starboard/shared/pthread/thread_sampler_create.cc",
+    "//starboard/shared/pthread/thread_sampler_destroy.cc",
+    "//starboard/shared/pthread/thread_sampler_freeze.cc",
+    "//starboard/shared/pthread/thread_sampler_internal.cc",
+    "//starboard/shared/pthread/thread_sampler_internal.h",
+    "//starboard/shared/pthread/thread_sampler_is_supported.cc",
+    "//starboard/shared/pthread/thread_sampler_thaw.cc",
     "//starboard/shared/pthread/thread_set_local_value.cc",
     "//starboard/shared/pthread/thread_yield.cc",
     "//starboard/shared/signal/crash_signals.h",
@@ -250,12 +260,6 @@
     "//starboard/shared/stub/system_request_focus.cc",
     "//starboard/shared/stub/system_request_reveal.cc",
     "//starboard/shared/stub/system_sign_with_certification_secret_key.cc",
-    "//starboard/shared/stub/thread_context_get_pointer.cc",
-    "//starboard/shared/stub/thread_sampler_create.cc",
-    "//starboard/shared/stub/thread_sampler_destroy.cc",
-    "//starboard/shared/stub/thread_sampler_freeze.cc",
-    "//starboard/shared/stub/thread_sampler_is_supported.cc",
-    "//starboard/shared/stub/thread_sampler_thaw.cc",
     "//starboard/shared/stub/ui_nav_get_interface.cc",
     "accessibility_get_caption_settings.cc",
     "accessibility_get_display_settings.cc",
@@ -319,8 +323,6 @@
     "get_home_directory.cc",
     "graphics.cc",
     "graphics.h",
-    "h5vcc_config.cc",
-    "h5vcc_config.h",
     "input_events_generator.cc",
     "input_events_generator.h",
     "jni_env_ext.cc",
diff --git a/starboard/android/shared/android_media_session_client.cc b/starboard/android/shared/android_media_session_client.cc
index 22ade7d..e031ad1 100644
--- a/starboard/android/shared/android_media_session_client.cc
+++ b/starboard/android/shared/android_media_session_client.cc
@@ -172,11 +172,6 @@
   jint playback_state = CobaltExtensionPlaybackStateToPlaybackState(
       session_state.actual_playback_state);
 
-  SbOnce(&once_flag, OnceInit);
-  SbMutexAcquire(&mutex);
-
-  SbMutexRelease(&mutex);
-
   jlong playback_state_actions = MediaSessionActionsToPlaybackStateActions(
       session_state.available_actions);
 
@@ -271,6 +266,9 @@
   g_update_platform_playback_state_callback = NULL;
 
   SbMutexRelease(&mutex);
+
+  JniEnvExt* env = JniEnvExt::Get();
+  env->CallStarboardVoidMethodOrAbort("deactivateMediaSession", "()V");
 }
 
 }  // namespace
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index 8d56fa1..7ebed22 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -100,6 +100,7 @@
       QueueApplication(sb_event_handle_callback),
 #endif  // SB_API_VERSION >= 15
       last_is_accessibility_high_contrast_text_enabled_(false) {
+  handle_system_events_.store(true);
   // Initialize Time Zone early so that local time works correctly.
   // Called once here to help SbTimeZoneGet*Name()
   tzset();
@@ -208,19 +209,23 @@
 
 Event* ApplicationAndroid::WaitForSystemEventWithTimeout(SbTime time) {
   // Limit the polling time in case some non-system event is injected.
-  const int kMaxPollingTimeMillisecond = 10;
+  const int kMaxPollingTimeMillisecond = 1000;
 
   // Convert from microseconds to milliseconds, taking the ceiling value.
   // If we take the floor, or round, then we end up busy looping every time
   // the next event time is less than one millisecond.
-  int timeout_millis = (time + kSbTimeMillisecond - 1) / kSbTimeMillisecond;
+  int timeout_millis =
+      (time <
+       std::min(kSbInt64Max - 1000, 1000 * static_cast<int64_t>(INT_MAX - 1)))
+          ? (time + 1000 - 1) / 1000
+          : INT_MAX;
   int looper_events;
-  int ident = ALooper_pollAll(
+  int ident = ALooper_pollOnce(
       std::min(std::max(timeout_millis, 0), kMaxPollingTimeMillisecond), NULL,
       &looper_events, NULL);
 
   // Ignore new system events while processing one.
-  handle_system_events_ = false;
+  handle_system_events_.store(false);
 
   switch (ident) {
     case kLooperIdAndroidCommand:
@@ -231,7 +236,7 @@
       break;
   }
 
-  handle_system_events_ = true;
+  handle_system_events_.store(true);
 
   // Always return NULL since we already dispatched our own system events.
   return NULL;
diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h
index bca3b1f..ddae6de 100644
--- a/starboard/android/shared/application_android.h
+++ b/starboard/android/shared/application_android.h
@@ -128,7 +128,7 @@
   void OnSuspend() override;
 
   // --- QueueApplication overrides ---
-  bool MayHaveSystemEvents() override { return handle_system_events_; }
+  bool MayHaveSystemEvents() override { return handle_system_events_.load(); }
   Event* WaitForSystemEventWithTimeout(SbTime time) override;
   void WakeSystemEventWait() override;
 
@@ -144,7 +144,7 @@
 
   // In certain situations, the Starboard thread should not try to process new
   // system events (e.g. while one is being processed).
-  bool handle_system_events_ = true;
+  atomic_bool handle_system_events_;
 
   // Synchronization for commands that change availability of Android resources
   // such as the input and/or native_window_.
diff --git a/starboard/android/shared/audio_decoder.cc b/starboard/android/shared/audio_decoder.cc
index 60e7a44..5af0059 100644
--- a/starboard/android/shared/audio_decoder.cc
+++ b/starboard/android/shared/audio_decoder.cc
@@ -70,12 +70,14 @@
 }  // namespace
 
 AudioDecoder::AudioDecoder(const AudioStreamInfo& audio_stream_info,
-                           SbDrmSystem drm_system)
+                           SbDrmSystem drm_system,
+                           bool enable_flush_during_seek)
     : audio_stream_info_(audio_stream_info),
       sample_type_(GetSupportedSampleType()),
       output_sample_rate_(audio_stream_info.samples_per_second),
       output_channel_count_(audio_stream_info.number_of_channels),
-      drm_system_(static_cast<DrmSystem*>(drm_system)) {
+      drm_system_(static_cast<DrmSystem*>(drm_system)),
+      enable_flush_during_seek_(enable_flush_during_seek) {
   if (!InitializeCodec()) {
     SB_LOG(ERROR) << "Failed to initialize audio decoder.";
   }
@@ -167,13 +169,16 @@
   SB_DCHECK(BelongsToCurrentThread());
   SB_DCHECK(output_cb_);
 
-  media_decoder_.reset();
-  audio_frame_discarder_.Reset();
+  // If fail to flush |media_decoder_|, then re-create |media_decoder_|.
+  if (!enable_flush_during_seek_ || !media_decoder_->Flush()) {
+    media_decoder_.reset();
 
-  if (!InitializeCodec()) {
-    // TODO: Communicate this failure to our clients somehow.
-    SB_LOG(ERROR) << "Failed to initialize codec after reset.";
+    if (!InitializeCodec()) {
+      // TODO: Communicate this failure to our clients somehow.
+      SB_LOG(ERROR) << "Failed to initialize codec after reset.";
+    }
   }
+  audio_frame_discarder_.Reset();
 
   consumed_cb_ = nullptr;
 
diff --git a/starboard/android/shared/audio_decoder.h b/starboard/android/shared/audio_decoder.h
index a57f0ff..1ee32d6 100644
--- a/starboard/android/shared/audio_decoder.h
+++ b/starboard/android/shared/audio_decoder.h
@@ -45,7 +45,8 @@
       AudioStreamInfo;
 
   AudioDecoder(const AudioStreamInfo& audio_stream_info,
-               SbDrmSystem drm_system);
+               SbDrmSystem drm_system,
+               bool enable_flush_during_seek);
   ~AudioDecoder() override;
 
   void Initialize(const OutputCB& output_cb, const ErrorCB& error_cb) override;
@@ -77,6 +78,7 @@
 
   const AudioStreamInfo audio_stream_info_;
   const SbMediaAudioSampleType sample_type_;
+  const bool enable_flush_during_seek_;
 
   jint output_sample_rate_;
   jint output_channel_count_;
diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc
index 610baaa..4dbf55e 100644
--- a/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/starboard/android/shared/audio_renderer_passthrough.cc
@@ -75,15 +75,14 @@
 AudioRendererPassthrough::AudioRendererPassthrough(
     const AudioStreamInfo& audio_stream_info,
     SbDrmSystem drm_system,
-    bool enable_audio_device_callback)
-    : audio_stream_info_(audio_stream_info),
-      enable_audio_device_callback_(enable_audio_device_callback) {
+    bool enable_flush_during_seek)
+    : audio_stream_info_(audio_stream_info) {
   SB_DCHECK(audio_stream_info_.codec == kSbMediaAudioCodecAc3 ||
             audio_stream_info_.codec == kSbMediaAudioCodecEac3);
   if (SbDrmSystemIsValid(drm_system)) {
     SB_LOG(INFO) << "Creating AudioDecoder as decryptor.";
-    scoped_ptr<AudioDecoder> audio_decoder(
-        new AudioDecoder(audio_stream_info, drm_system));
+    scoped_ptr<AudioDecoder> audio_decoder(new AudioDecoder(
+        audio_stream_info, drm_system, enable_flush_during_seek));
     if (audio_decoder->is_valid()) {
       decoder_.reset(audio_decoder.release());
     }
@@ -401,7 +400,6 @@
       optional<SbMediaAudioSampleType>(),  // Not required in passthrough mode
       audio_stream_info_.number_of_channels,
       audio_stream_info_.samples_per_second, kPreferredBufferSizeInBytes,
-      enable_audio_device_callback_, false /* enable_pcm_content_type_movie */,
       kTunnelModeAudioSessionId, false /* is_web_audio */));
 
   if (!audio_track_bridge->is_valid()) {
@@ -456,8 +454,7 @@
   SB_DCHECK(error_cb_);
   SB_DCHECK(audio_track_bridge_);
 
-  if (enable_audio_device_callback_ &&
-      audio_track_bridge_->GetAndResetHasAudioDeviceChanged()) {
+  if (audio_track_bridge_->GetAndResetHasAudioDeviceChanged()) {
     SB_LOG(INFO) << "Audio device changed, raising a capability changed error "
                     "to restart playback.";
     error_cb_(kSbPlayerErrorCapabilityChanged,
diff --git a/starboard/android/shared/audio_renderer_passthrough.h b/starboard/android/shared/audio_renderer_passthrough.h
index 7d9a1a3..f7a0ef2 100644
--- a/starboard/android/shared/audio_renderer_passthrough.h
+++ b/starboard/android/shared/audio_renderer_passthrough.h
@@ -55,7 +55,7 @@
 
   AudioRendererPassthrough(const AudioStreamInfo& audio_stream_info,
                            SbDrmSystem drm_system,
-                           bool enable_audio_device_callback);
+                           bool enable_flush_during_seek);
   ~AudioRendererPassthrough() override;
 
   bool is_valid() const { return decoder_ != nullptr; }
@@ -102,9 +102,8 @@
   void OnDecoderConsumed();
   void OnDecoderOutput();
 
-  // The following three variables are set in the ctor.
+  // The following two variables are set in the ctor.
   const AudioStreamInfo audio_stream_info_;
-  const bool enable_audio_device_callback_;
   // The AudioDecoder is used as a decryptor when the stream is encrypted.
   // TODO: Revisit to encapsulate the AudioDecoder as a SbDrmSystemPrivate
   //       instead.  This would need to turn SbDrmSystemPrivate::Decrypt() into
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 1d156b9..a254909 100644
--- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -126,7 +126,7 @@
             GetSampleSize(task.sample_type),
         &MinRequiredFramesTester::UpdateSourceStatusFunc,
         &MinRequiredFramesTester::ConsumeFramesFunc,
-        &MinRequiredFramesTester::ErrorFunc, 0, -1, false, false, false, this);
+        &MinRequiredFramesTester::ErrorFunc, 0, -1, false, this);
     {
       ScopedLock scoped_lock(mutex_);
       wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index f725ed1..dfdf743 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -117,8 +117,6 @@
     SbAudioSinkPrivate::ErrorFunc error_func,
     SbTime start_time,
     int tunnel_mode_audio_session_id,
-    bool enable_audio_device_callback,
-    bool enable_pcm_content_type_movie,
     bool is_web_audio,
     void* context)
     : type_(type),
@@ -142,8 +140,6 @@
               channels,
               sampling_frequency_hz,
               preferred_buffer_size_in_bytes,
-              enable_audio_device_callback,
-              enable_pcm_content_type_movie,
               tunnel_mode_audio_session_id,
               is_web_audio) {
   SB_DCHECK(update_source_status_func_);
@@ -462,17 +458,11 @@
   const SbTime kStartTime = 0;
   // Disable tunnel mode.
   const int kTunnelModeAudioSessionId = -1;
-  // Disable AudioDeviceCallback for WebAudio.
-  const bool kEnableAudioDeviceCallback = false;
   const bool kIsWebAudio = true;
-  // Disable AudioAttributes::CONTENT_TYPE_MOVIE for WebAudio.
-  const bool kEnablePcmContentTypeMovie = false;
   return Create(channels, sampling_frequency_hz, audio_sample_type,
                 audio_frame_storage_type, frame_buffers, frames_per_channel,
                 update_source_status_func, consume_frames_func, error_func,
-                kStartTime, kTunnelModeAudioSessionId,
-                kEnableAudioDeviceCallback, kEnablePcmContentTypeMovie,
-                kIsWebAudio, context);
+                kStartTime, kTunnelModeAudioSessionId, kIsWebAudio, context);
 }
 
 SbAudioSink AudioTrackAudioSinkType::Create(
@@ -487,8 +477,6 @@
     SbAudioSinkPrivate::ErrorFunc error_func,
     SbTime start_media_time,
     int tunnel_mode_audio_session_id,
-    bool enable_audio_device_callback,
-    bool enable_pcm_content_type_movie,
     bool is_web_audio,
     void* context) {
   int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
@@ -500,9 +488,7 @@
       this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
       frames_per_channel, preferred_buffer_size_in_bytes,
       update_source_status_func, consume_frames_func, error_func,
-      start_media_time, tunnel_mode_audio_session_id,
-      enable_audio_device_callback, enable_pcm_content_type_movie, is_web_audio,
-      context);
+      start_media_time, tunnel_mode_audio_session_id, is_web_audio, context);
   if (!audio_sink->IsAudioTrackValid()) {
     SB_DLOG(ERROR)
         << "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h
index b115deb..06bb6db 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -69,8 +69,6 @@
       SbAudioSinkPrivate::ErrorFunc error_func,
       SbTime start_time,
       int tunnel_mode_audio_session_id,
-      bool enable_audio_device_callback,
-      bool enable_pcm_content_type_movie,
       bool is_web_audio,
       void* context);
 
@@ -79,6 +77,8 @@
   }
 
   void Destroy(SbAudioSink audio_sink) override {
+    // TODO(b/330793785): Use audio_sink.flush() instead of re-creating a new
+    // audio_sink.
     if (audio_sink != kSbAudioSinkInvalid && !IsValid(audio_sink)) {
       SB_LOG(WARNING) << "audio_sink is invalid.";
       return;
@@ -115,8 +115,6 @@
       SbAudioSinkPrivate::ErrorFunc error_func,
       SbTime start_media_time,
       int tunnel_mode_audio_session_id,
-      bool enable_audio_device_callback,
-      bool enable_pcm_content_type_movie,
       bool is_web_audio,
       void* context);
   ~AudioTrackAudioSink() override;
diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc
index 3415eeb..e518f74 100644
--- a/starboard/android/shared/audio_track_bridge.cc
+++ b/starboard/android/shared/audio_track_bridge.cc
@@ -39,8 +39,6 @@
                                    int channels,
                                    int sampling_frequency_hz,
                                    int preferred_buffer_size_in_bytes,
-                                   bool enable_audio_device_callback,
-                                   bool enable_pcm_content_type_movie,
                                    int tunnel_mode_audio_session_id,
                                    bool is_web_audio) {
   if (coding_type == kSbMediaAudioCodingTypePcm) {
@@ -65,10 +63,9 @@
           "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
   jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
       j_audio_output_manager.Get(), "createAudioTrackBridge",
-      "(IIIIZZIZ)Ldev/cobalt/media/AudioTrackBridge;",
+      "(IIIIIZ)Ldev/cobalt/media/AudioTrackBridge;",
       GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
-      channels, preferred_buffer_size_in_bytes, enable_audio_device_callback,
-      enable_pcm_content_type_movie, tunnel_mode_audio_session_id,
+      channels, preferred_buffer_size_in_bytes, tunnel_mode_audio_session_id,
       is_web_audio);
   if (!j_audio_track_bridge) {
     // One of the cases that this may hit is when output happened to be switched
diff --git a/starboard/android/shared/audio_track_bridge.h b/starboard/android/shared/audio_track_bridge.h
index b4303ae..5d7aeeb 100644
--- a/starboard/android/shared/audio_track_bridge.h
+++ b/starboard/android/shared/audio_track_bridge.h
@@ -41,8 +41,6 @@
                    int channels,
                    int sampling_frequency_hz,
                    int preferred_buffer_size_in_bytes,
-                   bool enable_audio_device_callback,
-                   bool enable_pcm_content_type_movie,
                    int tunnel_mode_audio_session_id,
                    bool is_web_audio);
   ~AudioTrackBridge();
diff --git a/starboard/android/shared/h5vcc_config.cc b/starboard/android/shared/h5vcc_config.cc
deleted file mode 100644
index 059b417..0000000
--- a/starboard/android/shared/h5vcc_config.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2024 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/extension/h5vcc_config.h"
-#include "starboard/android/shared/h5vcc_config.h"
-#include "starboard/android/shared/jni_env_ext.h"
-#include "starboard/android/shared/jni_utils.h"
-#include "starboard/common/log.h"
-
-namespace starboard {
-namespace android {
-namespace shared {
-
-namespace {
-
-// Definitions of any functions included as components in the extension
-// are added here.
-
-void EnableBackgroundPlayback(bool value) {
-  JniEnvExt* env = JniEnvExt::Get();
-  JniEnvExt::Get()->CallStarboardVoidMethodOrAbort("enableBackgroundPlayback",
-                                                   "(Z)V", (jboolean)value);
-}
-
-const StarboardExtensionH5vccConfigApi kH5vccConfigApi = {
-    kStarboardExtensionH5vccConfigName,
-    1,  // API version that's implemented.
-    &EnableBackgroundPlayback,
-};
-
-}  // namespace
-
-const void* GetH5vccConfigApi() {
-  return &kH5vccConfigApi;
-}
-
-}  // namespace shared
-}  // namespace android
-}  // namespace starboard
diff --git a/starboard/android/shared/h5vcc_config.h b/starboard/android/shared/h5vcc_config.h
deleted file mode 100644
index f2a501c..0000000
--- a/starboard/android/shared/h5vcc_config.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2024 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef STARBOARD_ANDROID_SHARED_H5VCC_CONFIG_H_
-#define STARBOARD_ANDROID_SHARED_H5VCC_CONFIG_H_
-
-namespace starboard {
-namespace android {
-namespace shared {
-
-const void* GetH5vccConfigApi();
-
-}  // namespace shared
-}  // namespace android
-}  // namespace starboard
-
-#endif  // STARBOARD_ANDROID_SHARED_H5VCC_CONFIG_H_
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc
index fe96cc8..c35f29a 100644
--- a/starboard/android/shared/media_capabilities_cache.cc
+++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -373,22 +373,18 @@
   return supported_bitrates_.Contains(bitrate);
 }
 
-bool VideoCodecCapability::AreResolutionAndRateSupported(
-    bool force_improved_support_check,
-    int frame_width,
-    int frame_height,
-    int fps) {
-  if (force_improved_support_check) {
-    if (frame_width != 0 && frame_height != 0 && fps != 0) {
-      return JniEnvExt::Get()->CallBooleanMethodOrAbort(
-                 j_video_capabilities_, "areSizeAndRateSupported", "(IID)Z",
-                 frame_width, frame_height,
-                 static_cast<jdouble>(fps)) == JNI_TRUE;
-    } else if (frame_width != 0 && frame_height != 0) {
-      return JniEnvExt::Get()->CallBooleanMethodOrAbort(
-                 j_video_capabilities_, "isSizeSupported", "(II)Z", frame_width,
-                 frame_height) == JNI_TRUE;
-    }
+bool VideoCodecCapability::AreResolutionAndRateSupported(int frame_width,
+                                                         int frame_height,
+                                                         int fps) {
+  if (frame_width != 0 && frame_height != 0 && fps != 0) {
+    return JniEnvExt::Get()->CallBooleanMethodOrAbort(
+               j_video_capabilities_, "areSizeAndRateSupported", "(IID)Z",
+               frame_width, frame_height,
+               static_cast<jdouble>(fps)) == JNI_TRUE;
+  } else if (frame_width != 0 && frame_height != 0) {
+    return JniEnvExt::Get()->CallBooleanMethodOrAbort(
+               j_video_capabilities_, "isSizeSupported", "(II)Z", frame_width,
+               frame_height) == JNI_TRUE;
   }
   if (frame_width != 0 && !supported_widths_.Contains(frame_width)) {
     return false;
@@ -476,19 +472,16 @@
   return !FindAudioDecoder(mime_type, bitrate).empty();
 }
 
-bool MediaCapabilitiesCache::HasVideoDecoderFor(
-    const std::string& mime_type,
-    bool must_support_secure,
-    bool must_support_hdr,
-    bool must_support_tunnel_mode,
-    bool force_improved_support_check,
-    int frame_width,
-    int frame_height,
-    int bitrate,
-    int fps) {
+bool MediaCapabilitiesCache::HasVideoDecoderFor(const std::string& mime_type,
+                                                bool must_support_secure,
+                                                bool must_support_hdr,
+                                                bool must_support_tunnel_mode,
+                                                int frame_width,
+                                                int frame_height,
+                                                int bitrate,
+                                                int fps) {
   return !FindVideoDecoder(mime_type, must_support_secure, must_support_hdr,
-                           false, must_support_tunnel_mode,
-                           force_improved_support_check, frame_width,
+                           false, must_support_tunnel_mode, frame_width,
                            frame_height, bitrate, fps)
               .empty();
 }
@@ -527,7 +520,6 @@
     bool must_support_hdr,
     bool require_software_codec,
     bool must_support_tunnel_mode,
-    bool force_improved_support_check,
     int frame_width,
     int frame_height,
     int bitrate,
@@ -538,11 +530,10 @@
         env->NewStringStandardUTFOrAbort(mime_type.c_str()));
     jobject j_decoder_name = env->CallStaticObjectMethodOrAbort(
         "dev/cobalt/media/MediaCodecUtil", "findVideoDecoder",
-        "(Ljava/lang/String;ZZZZZIIIII)Ljava/lang/String;", j_mime.Get(),
+        "(Ljava/lang/String;ZZZZIIIII)Ljava/lang/String;", j_mime.Get(),
         must_support_secure, must_support_hdr,
-        false, /* mustSupportSoftwareCodec */
-        must_support_tunnel_mode, force_improved_support_check,
-        -1, /* decoderCacheTtlMs */
+        false,                        /* mustSupportSoftwareCodec */
+        must_support_tunnel_mode, -1, /* decoderCacheTtlMs */
         frame_width, frame_height, bitrate, fps);
     return env->GetStringStandardUTFOrAbort(
         static_cast<jstring>(j_decoder_name));
@@ -579,12 +570,9 @@
       continue;
     }
 
-    bool enable_improved_support_check =
-        force_improved_support_check ||
-        (frame_width > 3840 || frame_height > 2160);
     // Reject if resolution or frame rate is not supported.
-    if (!video_capability->AreResolutionAndRateSupported(
-            enable_improved_support_check, frame_width, frame_height, fps)) {
+    if (!video_capability->AreResolutionAndRateSupported(frame_width,
+                                                         frame_height, fps)) {
       continue;
     }
 
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h
index 656398a..89f0f3f 100644
--- a/starboard/android/shared/media_capabilities_cache.h
+++ b/starboard/android/shared/media_capabilities_cache.h
@@ -96,8 +96,7 @@
   // VideoCapabilities.areSizeAndRateSupported() or
   // VideoCapabilities.isSizeSupported() will be used to check the
   // supportability.
-  bool AreResolutionAndRateSupported(bool force_improved_support_check,
-                                     int frame_width,
+  bool AreResolutionAndRateSupported(int frame_width,
                                      int frame_height,
                                      int fps);
 
@@ -134,7 +133,6 @@
                           bool must_support_secure,
                           bool must_support_hdr,
                           bool must_support_tunnel_mode,
-                          bool force_improved_support_check,
                           int frame_width,
                           int frame_height,
                           int bitrate,
@@ -147,7 +145,6 @@
                                bool must_support_hdr,
                                bool require_software_codec,
                                bool must_support_tunnel_mode,
-                               bool force_improved_support_check,
                                int frame_width,
                                int frame_height,
                                int bitrate,
diff --git a/starboard/android/shared/media_codec_bridge.cc b/starboard/android/shared/media_codec_bridge.cc
index c126fc0..eb14a60 100644
--- a/starboard/android/shared/media_codec_bridge.cc
+++ b/starboard/android/shared/media_codec_bridge.cc
@@ -227,7 +227,6 @@
     bool require_software_codec,
     int tunnel_mode_audio_session_id,
     bool force_big_endian_hdr_metadata,
-    bool force_improved_support_check,
     int max_video_input_size,
     std::string* error_message) {
   SB_DCHECK(error_message);
@@ -249,7 +248,7 @@
   std::string decoder_name =
       MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
           mime, must_support_secure, must_support_hdr, require_software_codec,
-          must_support_tunnel_mode, force_improved_support_check,
+          must_support_tunnel_mode,
           /* frame_width = */ 0,
           /* frame_height = */ 0,
           /* bitrate = */ 0,
@@ -259,7 +258,7 @@
     decoder_name = MediaCapabilitiesCache::GetInstance()->FindVideoDecoder(
         mime, must_support_secure, /* must_support_hdr = */ false,
         require_software_codec, must_support_tunnel_mode,
-        force_improved_support_check, /* frame_width = */ 0,
+        /* frame_width = */ 0,
         /* frame_height = */ 0,
         /* bitrate = */ 0,
         /* fps = */ 0);
@@ -447,6 +446,11 @@
       j_media_codec_bridge_, "setPlaybackRate", "(D)V", playback_rate);
 }
 
+bool MediaCodecBridge::Start() {
+  return JniEnvExt::Get()->CallBooleanMethodOrAbort(j_media_codec_bridge_,
+                                                    "start", "()Z") == JNI_TRUE;
+}
+
 jint MediaCodecBridge::Flush() {
   return JniEnvExt::Get()->CallIntMethodOrAbort(j_media_codec_bridge_, "flush",
                                                 "()I");
diff --git a/starboard/android/shared/media_codec_bridge.h b/starboard/android/shared/media_codec_bridge.h
index ad99b62..a7b3d36 100644
--- a/starboard/android/shared/media_codec_bridge.h
+++ b/starboard/android/shared/media_codec_bridge.h
@@ -173,7 +173,6 @@
       bool require_software_codec,
       int tunnel_mode_audio_session_id,
       bool force_big_endian_hdr_metadata,
-      bool force_improved_support_check,
       int max_video_input_size,
       std::string* error_message);
 
@@ -199,6 +198,7 @@
   void ReleaseOutputBufferAtTimestamp(jint index, jlong render_timestamp_ns);
 
   void SetPlaybackRate(double playback_rate);
+  bool Start();
   jint Flush();
   FrameSize GetOutputSize();
   AudioOutputFormatResult GetAudioOutputFormat();
diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc
index 49c9752..ac3cd8f 100644
--- a/starboard/android/shared/media_decoder.cc
+++ b/starboard/android/shared/media_decoder.cc
@@ -118,7 +118,6 @@
                            const FrameRenderedCB& frame_rendered_cb,
                            int tunnel_mode_audio_session_id,
                            bool force_big_endian_hdr_metadata,
-                           bool force_improved_support_check,
                            int max_video_input_size,
                            std::string* error_message)
     : media_type_(kSbMediaTypeVideo),
@@ -137,8 +136,7 @@
       video_codec, width_hint, height_hint, fps, max_width, max_height, this,
       j_output_surface, j_media_crypto, color_metadata, require_secured_decoder,
       require_software_codec, tunnel_mode_audio_session_id,
-      force_big_endian_hdr_metadata, force_improved_support_check,
-      max_video_input_size, error_message);
+      force_big_endian_hdr_metadata, max_video_input_size, error_message);
   if (!media_codec_bridge_) {
     SB_LOG(ERROR) << "Failed to create video media codec bridge with error: "
                   << *error_message;
@@ -267,6 +265,9 @@
           }
         }
         SB_DCHECK(dequeue_output_results.empty());
+        if (destroying_.load()) {
+          break;
+        }
         CollectPendingData_Locked(&pending_tasks, &input_buffer_indices,
                                   &dequeue_output_results);
       }
@@ -314,6 +315,9 @@
         collect_pending_data = !has_input || !has_output;
       }
 
+      if (destroying_.load()) {
+        break;
+      }
       if (collect_pending_data) {
         ScopedLock scoped_lock(mutex_);
         CollectPendingData_Locked(&pending_tasks, &input_buffer_indices,
@@ -618,6 +622,12 @@
   SB_DCHECK(media_codec_bridge_);
   SB_DCHECK(buffer_index >= 0);
 
+  // TODO(b/291959069): After |decoder_thread_| is destroyed, it may still
+  // receive output buffer, discard this invalid output buffer.
+  if (destroying_.load() || !SbThreadIsValid(decoder_thread_)) {
+    return;
+  }
+
   DequeueOutputResult dequeue_output_result;
   dequeue_output_result.status = 0;
   dequeue_output_result.index = buffer_index;
@@ -646,6 +656,64 @@
   frame_rendered_cb_(frame_timestamp);
 }
 
+bool MediaDecoder::Flush() {
+  // Try to flush if we can, otherwise return |false| to recreate the codec
+  // completely. Flush() is called by `player_worker` thread,
+  // but MediaDecoder is on `audio_decoder` and `video_decoder`
+  // threads, let `player_worker` destroy `audio_decoder` and
+  // `video_decoder` threads to clean up all pending tasks,
+  // and Flush()/Start() |media_codec_bridge_|.
+
+  // 1. Destroy `audio_decoder` and `video_decoder` threads.
+  destroying_.store(true);
+  {
+    ScopedLock scoped_lock(mutex_);
+    condition_variable_.Signal();
+  }
+  if (SbThreadIsValid(decoder_thread_)) {
+    SbThreadJoin(decoder_thread_, NULL);
+    decoder_thread_ = kSbThreadInvalid;
+  }
+
+  // 2. Flush()/Start() |media_codec_bridge_| and clean up pending tasks.
+  if (is_valid()) {
+    // 2.1. Flush() |media_codec_bridge_|.
+    host_->OnFlushing();
+    jint status = media_codec_bridge_->Flush();
+    if (status != MEDIA_CODEC_OK) {
+      SB_LOG(ERROR) << "Failed to flush media codec.";
+      return false;
+    }
+
+    // 2.2. Clean up pending_tasks and input_buffer/output_buffer indices.
+    number_of_pending_tasks_.store(0);
+    pending_tasks_.clear();
+    input_buffer_indices_.clear();
+    dequeue_output_results_.clear();
+    pending_queue_input_buffer_task_ = nullopt_t();
+
+    // 2.3. Add OutputFormatChanged to get current output format after Flush().
+    DequeueOutputResult dequeue_output_result = {};
+    dequeue_output_result.index = -1;
+    dequeue_output_results_.push_back(dequeue_output_result);
+
+    // 2.4. Start() |media_codec_bridge_|. As the codec is configured in
+    // asynchronous mode, call Start() after Flush() has returned to
+    // resume codec operations. After Start(), input_buffer_index should
+    // start with 0.
+    if (!media_codec_bridge_->Start()) {
+      SB_LOG(ERROR) << "Failed to start media codec.";
+      return false;
+    }
+  }
+
+  // 3. Recreate `audio_decoder` and `video_decoder` threads in
+  // WriteInputBuffers().
+  stream_ended_.store(false);
+  destroying_.store(false);
+  return true;
+}
+
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/starboard/android/shared/media_decoder.h b/starboard/android/shared/media_decoder.h
index 23838bd..17a4d10 100644
--- a/starboard/android/shared/media_decoder.h
+++ b/starboard/android/shared/media_decoder.h
@@ -97,7 +97,6 @@
                const FrameRenderedCB& frame_rendered_cb,
                int tunnel_mode_audio_session_id,
                bool force_big_endian_hdr_metadata,
-               bool force_improved_support_check,
                int max_video_input_size,
                std::string* error_message);
   ~MediaDecoder();
@@ -114,6 +113,8 @@
 
   bool is_valid() const { return media_codec_bridge_ != NULL; }
 
+  bool Flush();
+
  private:
   struct Event {
     enum Type {
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc
index 9844406..023cbfc 100644
--- a/starboard/android/shared/media_is_audio_supported.cc
+++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -45,12 +45,6 @@
     if (!mime_type->is_valid()) {
       return false;
     }
-    // Allows for disabling the use of the AudioDeviceCallback API to detect
-    // when audio peripherals are connected. Enabled by default.
-    // (https://developer.android.com/reference/android/media/AudioDeviceCallback)
-    if (!mime_type->ValidateBoolParameter("enableaudiodevicecallback")) {
-      return false;
-    }
 
     // Enables audio passthrough if the codec supports it.
     if (!mime_type->ValidateBoolParameter("audiopassthrough")) {
@@ -58,13 +52,6 @@
     }
     enable_audio_passthrough =
         mime_type->GetParamBoolValue("audiopassthrough", true);
-
-    // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for
-    // non-tunneled playbacks with PCM audio. Enabled by default.
-    // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE)
-    if (!mime_type->ValidateBoolParameter("enablepcmcontenttypemovie")) {
-      return false;
-    }
   }
 
   // Android uses a libopus based opus decoder for clear content, or a platform
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc
index 8965d7c..c6e0462 100644
--- a/starboard/android/shared/media_is_video_supported.cc
+++ b/starboard/android/shared/media_is_video_supported.cc
@@ -51,8 +51,6 @@
   // can play decode-to-texture video just as well as normal video.
 
   bool must_support_tunnel_mode = false;
-  bool force_improved_support_check = true;
-  int decoder_cache_ttl_ms = -1;
   if (mime_type) {
     if (!mime_type->is_valid()) {
       return false;
@@ -79,17 +77,6 @@
       return false;
     }
 
-    // Forces the use of specific Android APIs (isSizeSupported() and
-    // areSizeAndRateSupported()) to determine format support.
-    if (!mime_type->ValidateBoolParameter("forceimprovedsupportcheck")) {
-      return false;
-    }
-    force_improved_support_check =
-        mime_type->GetParamBoolValue("forceimprovedsupportcheck", true);
-
-    decoder_cache_ttl_ms =
-        mime_type->GetParamIntValue("decoder_cache_ttl_ms", -1);
-
     // Disable MediaCapabilitiesCache if "disablecache" option presented.
     if (!mime_type->ValidateBoolParameter("disablecache")) {
       return false;
@@ -126,5 +113,5 @@
 
   return MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
       mime, require_secure_playback, must_support_hdr, must_support_tunnel_mode,
-      force_improved_support_check, frame_width, frame_height, bitrate, fps);
+      frame_width, frame_height, bitrate, fps);
 }
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index 82aca7e..2472789 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -70,13 +70,21 @@
 // video distortion on some platforms.
 constexpr bool kForceResetSurfaceUnderTunnelMode = true;
 
+// By default, Cobalt recreates MediaCodec when Reset() during Seek().
+// Set the following variable to true to force it Flush() MediaCodec
+// during Seek().
+constexpr bool kForceFlushDecoderDuringReset = false;
+
+// By default, Cobalt teardowns AudioDecoder during Reset().
+// Set the following variable to true to force it reset audio decoder
+// during Reset(). This should be enabled with kForceFlushDecoderDuringReset.
+constexpr bool kForceResetAudioDecoder = false;
+
 // This class allows us to force int16 sample type when tunnel mode is enabled.
 class AudioRendererSinkAndroid : public ::starboard::shared::starboard::player::
                                      filter::AudioRendererSinkImpl {
  public:
-  explicit AudioRendererSinkAndroid(bool enable_audio_device_callback,
-                                    bool enable_pcm_content_type_movie,
-                                    int tunnel_mode_audio_session_id = -1)
+  explicit AudioRendererSinkAndroid(int tunnel_mode_audio_session_id = -1)
       : AudioRendererSinkImpl(
             [=](SbTime start_media_time,
                 int channels,
@@ -97,8 +105,7 @@
                   audio_frame_storage_type, frame_buffers,
                   frame_buffers_size_in_frames, update_source_status_func,
                   consume_frames_func, error_func, start_media_time,
-                  tunnel_mode_audio_session_id, enable_audio_device_callback,
-                  enable_pcm_content_type_movie, false, /* is_web_audio */
+                  tunnel_mode_audio_session_id, false, /* is_web_audio */
                   context);
             }),
         tunnel_mode_audio_session_id_(tunnel_mode_audio_session_id) {}
@@ -210,21 +217,13 @@
                                                          error_message);
     }
 
-    bool enable_audio_device_callback = true;
-
     if (!creation_parameters.audio_mime().empty()) {
       MimeType audio_mime_type(creation_parameters.audio_mime());
       if (!audio_mime_type.is_valid() ||
-          !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") ||
           !audio_mime_type.ValidateBoolParameter("audiopassthrough")) {
         return scoped_ptr<PlayerComponents>();
       }
 
-      enable_audio_device_callback =
-          audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
-      SB_LOG(INFO) << "AudioDeviceCallback is "
-                   << (enable_audio_device_callback ? "enabled." : "disabled.");
-
       if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) {
         SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: "
                         "false. Passthrough is disabled.";
@@ -232,12 +231,29 @@
       }
     }
 
+    bool enable_flush_during_seek = false;
+    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone &&
+        !creation_parameters.video_mime().empty()) {
+      MimeType video_mime_type(creation_parameters.video_mime());
+      if (video_mime_type.ValidateBoolParameter("enableflushduringseek")) {
+        enable_flush_during_seek =
+            video_mime_type.GetParamBoolValue("enableflushduringseek", false);
+      }
+    }
+
+    if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) {
+      SB_LOG(INFO)
+          << "`kForceFlushDecoderDuringReset` is set to true, force flushing"
+          << " audio passthrough decoder during Reset().";
+      enable_flush_during_seek = true;
+    }
+
     SB_LOG(INFO) << "Creating passthrough components.";
     // TODO: Enable tunnel mode for passthrough
     scoped_ptr<AudioRendererPassthrough> audio_renderer;
     audio_renderer.reset(new AudioRendererPassthrough(
         creation_parameters.audio_stream_info(),
-        creation_parameters.drm_system(), enable_audio_device_callback));
+        creation_parameters.drm_system(), enable_flush_during_seek));
     if (!audio_renderer->is_valid()) {
       return scoped_ptr<PlayerComponents>();
     }
@@ -255,24 +271,10 @@
       constexpr int kTunnelModeAudioSessionId = -1;
       constexpr bool kForceSecurePipelineUnderTunnelMode = false;
 
-      bool force_improved_support_check = true;
-
-      if (!creation_parameters.video_mime().empty()) {
-        MimeType video_mime_type(creation_parameters.video_mime());
-        if (!video_mime_type.is_valid() ||
-            !video_mime_type.ValidateBoolParameter(
-                "forceimprovedsupportcheck")) {
-          return scoped_ptr<PlayerComponents>();
-        }
-        force_improved_support_check = video_mime_type.GetParamBoolValue(
-            "forceimprovedsupportcheck", true);
-        SB_LOG_IF(INFO, !force_improved_support_check)
-            << "Improved support check is disabled for queries under 4K.";
-      }
-      scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(
-          creation_parameters, kTunnelModeAudioSessionId,
-          kForceSecurePipelineUnderTunnelMode, force_improved_support_check,
-          max_video_input_size, error_message);
+      scoped_ptr<VideoDecoder> video_decoder =
+          CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId,
+                             kForceSecurePipelineUnderTunnelMode,
+                             max_video_input_size, error_message);
       if (video_decoder) {
         using starboard::shared::starboard::player::filter::VideoRendererImpl;
 
@@ -309,8 +311,7 @@
     MimeType audio_mime_type(audio_mime);
     if (!audio_mime.empty()) {
       if (!audio_mime_type.is_valid() ||
-          !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") ||
-          !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) {
+          !audio_mime_type.ValidateBoolParameter("tunnelmode")) {
         *error_message =
             "Invalid audio MIME: '" + std::string(audio_mime) + "'";
         return false;
@@ -325,7 +326,7 @@
     if (!video_mime.empty()) {
       if (!video_mime_type.is_valid() ||
           !video_mime_type.ValidateBoolParameter("tunnelmode") ||
-          !video_mime_type.ValidateBoolParameter("forceimprovedsupportcheck")) {
+          !video_mime_type.ValidateBoolParameter("enableflushduringseek")) {
         *error_message =
             "Invalid video MIME: '" + std::string(video_mime) + "'";
         return false;
@@ -360,17 +361,12 @@
       enable_tunnel_mode = true;
     }
 
-    const bool force_improved_support_check =
-        video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true);
-    SB_LOG_IF(INFO, !force_improved_support_check)
-        << "Improved support check is disabled for queries under 4K.";
     bool force_secure_pipeline_under_tunnel_mode = false;
     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);
+                                &force_secure_pipeline_under_tunnel_mode)) {
+        tunnel_mode_audio_session_id =
+            GenerateAudioSessionId(creation_parameters);
         SB_LOG(INFO) << "Generated tunnel mode audio session id "
                      << tunnel_mode_audio_session_id;
       } else {
@@ -391,13 +387,46 @@
                    << tunnel_mode_audio_session_id << '.';
     }
 
+    bool enable_reset_audio_decoder =
+        video_mime_type.GetParamBoolValue("enableresetaudiodecoder", false);
+    SB_LOG(INFO) << "Reset AudioDecoder during Reset(): "
+                 << (enable_reset_audio_decoder ? "enabled. " : "disabled. ")
+                 << "Video mime parameter \"enableresetaudiodecoder\" value: "
+                 << video_mime_type.GetParamStringValue(
+                        "enableresetaudiodecoder", "<not provided>")
+                 << ".";
+
+    if (kForceResetAudioDecoder && !enable_reset_audio_decoder) {
+      SB_LOG(INFO)
+          << "`kForceResetAudioDecoder` is set to true, force resetting"
+          << " audio decoder during Reset().";
+      enable_reset_audio_decoder = true;
+    }
+
+    bool enable_flush_during_seek =
+        video_mime_type.GetParamBoolValue("enableflushduringseek", false);
+    SB_LOG(INFO) << "Flush MediaCodec during Reset(): "
+                 << (enable_flush_during_seek ? "enabled. " : "disabled. ")
+                 << "Video mime parameter \"enableflushduringseek\" value: "
+                 << video_mime_type.GetParamStringValue("enableflushduringseek",
+                                                        "<not provided>")
+                 << ".";
+
+    if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) {
+      SB_LOG(INFO)
+          << "`kForceFlushDecoderDuringReset` is set to true, force flushing"
+          << " audio decoder during Reset().";
+      enable_flush_during_seek = true;
+    }
+
     if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone) {
       SB_DCHECK(audio_decoder);
       SB_DCHECK(audio_renderer_sink);
 
       using starboard::shared::starboard::media::AudioStreamInfo;
-      auto decoder_creator = [](const AudioStreamInfo& audio_stream_info,
-                                SbDrmSystem drm_system) {
+      auto decoder_creator = [enable_flush_during_seek](
+                                 const AudioStreamInfo& audio_stream_info,
+                                 SbDrmSystem drm_system) {
         bool use_libopus_decoder =
             audio_stream_info.codec == kSbMediaAudioCodecOpus &&
             !SbDrmSystemIsValid(drm_system) && !kForcePlatformOpusDecoder;
@@ -409,8 +438,8 @@
           }
         } else if (audio_stream_info.codec == kSbMediaAudioCodecAac ||
                    audio_stream_info.codec == kSbMediaAudioCodecOpus) {
-          scoped_ptr<AudioDecoder> audio_decoder_impl(
-              new AudioDecoder(audio_stream_info, drm_system));
+          scoped_ptr<AudioDecoder> audio_decoder_impl(new AudioDecoder(
+              audio_stream_info, drm_system, enable_flush_during_seek));
           if (audio_decoder_impl->is_valid()) {
             return audio_decoder_impl.PassAs<AudioDecoderBase>();
           }
@@ -423,29 +452,18 @@
 
       audio_decoder->reset(new AdaptiveAudioDecoder(
           creation_parameters.audio_stream_info(),
-          creation_parameters.drm_system(), decoder_creator));
-
-      bool enable_audio_device_callback =
-          audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
-      SB_LOG(INFO) << "AudioDeviceCallback is "
-                   << (enable_audio_device_callback ? "enabled." : "disabled.");
-      bool enable_pcm_content_type_movie =
-          audio_mime_type.GetParamBoolValue("enablepcmcontenttypemovie", true);
-      SB_LOG(INFO) << "AudioAttributes::CONTENT_TYPE_MOVIE is "
-                   << (enable_pcm_content_type_movie ? "enabled" : "disabled")
-                   << " for non-tunneled PCM audio playback.";
+          creation_parameters.drm_system(), decoder_creator,
+          enable_reset_audio_decoder));
 
       if (tunnel_mode_audio_session_id != -1) {
         *audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink(
-            tunnel_mode_audio_session_id, creation_parameters,
-            enable_audio_device_callback);
+            tunnel_mode_audio_session_id, creation_parameters);
         if (!*audio_renderer_sink) {
           tunnel_mode_audio_session_id = -1;
         }
       }
       if (!*audio_renderer_sink) {
-        audio_renderer_sink->reset(new AudioRendererSinkAndroid(
-            enable_audio_device_callback, enable_pcm_content_type_movie));
+        audio_renderer_sink->reset(new AudioRendererSinkAndroid());
       }
     }
 
@@ -465,10 +483,10 @@
         force_secure_pipeline_under_tunnel_mode = false;
       }
 
-      scoped_ptr<VideoDecoder> video_decoder_impl = CreateVideoDecoder(
-          creation_parameters, tunnel_mode_audio_session_id,
-          force_secure_pipeline_under_tunnel_mode, force_improved_support_check,
-          max_video_input_size, error_message);
+      scoped_ptr<VideoDecoder> video_decoder_impl =
+          CreateVideoDecoder(creation_parameters, tunnel_mode_audio_session_id,
+                             force_secure_pipeline_under_tunnel_mode,
+                             max_video_input_size, error_message);
       if (video_decoder_impl) {
         *video_render_algorithm = video_decoder_impl->GetRenderAlgorithm();
         *video_renderer_sink = video_decoder_impl->GetSink();
@@ -512,11 +530,12 @@
       const CreationParameters& creation_parameters,
       int tunnel_mode_audio_session_id,
       bool force_secure_pipeline_under_tunnel_mode,
-      bool force_improved_support_check,
       int max_video_input_size,
       std::string* error_message) {
     bool force_big_endian_hdr_metadata = false;
-    if (!creation_parameters.video_mime().empty()) {
+    bool enable_flush_during_seek = false;
+    if (creation_parameters.video_codec() != kSbMediaVideoCodecNone &&
+        !creation_parameters.video_mime().empty()) {
       // Use mime param to determine endianness of HDR metadata. If param is
       // missing or invalid it defaults to Little Endian.
       MimeType video_mime_type(creation_parameters.video_mime());
@@ -526,6 +545,16 @@
           video_mime_type.GetParamStringValue("hdrinfoendianness",
                                               /*default=*/"little");
       force_big_endian_hdr_metadata = hdr_info_endianness == "big";
+
+      video_mime_type.ValidateBoolParameter("enableflushduringseek");
+      enable_flush_during_seek =
+          video_mime_type.GetParamBoolValue("enableflushduringseek", false);
+    }
+    if (kForceFlushDecoderDuringReset && !enable_flush_during_seek) {
+      SB_LOG(INFO)
+          << "`kForceFlushDecoderDuringReset` is set to true, force flushing"
+          << " video decoder during Reset().";
+      enable_flush_during_seek = true;
     }
 
     scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder(
@@ -535,7 +564,7 @@
         creation_parameters.max_video_capabilities(),
         tunnel_mode_audio_session_id, force_secure_pipeline_under_tunnel_mode,
         kForceResetSurfaceUnderTunnelMode, force_big_endian_hdr_metadata,
-        force_improved_support_check, max_video_input_size, error_message));
+        max_video_input_size, enable_flush_during_seek, error_message));
     if (creation_parameters.video_codec() == kSbMediaVideoCodecAv1 ||
         video_decoder->is_decoder_created()) {
       return video_decoder.Pass();
@@ -546,8 +575,7 @@
   }
 
   bool IsTunnelModeSupported(const CreationParameters& creation_parameters,
-                             bool* force_secure_pipeline_under_tunnel_mode,
-                             const bool force_improved_support_check) {
+                             bool* force_secure_pipeline_under_tunnel_mode) {
     SB_DCHECK(force_secure_pipeline_under_tunnel_mode);
     *force_secure_pipeline_under_tunnel_mode = false;
 
@@ -588,8 +616,7 @@
 
     bool is_encrypted = !!j_media_crypto;
     if (MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
-            mime, is_encrypted, false, true, force_improved_support_check, 0, 0,
-            0, 0)) {
+            mime, is_encrypted, false, true, 0, 0, 0, 0)) {
       return true;
     }
 
@@ -597,8 +624,7 @@
       const bool kIsEncrypted = true;
       auto support_tunnel_mode_under_secure_pipeline =
           MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor(
-              mime, kIsEncrypted, false, true, force_improved_support_check, 0,
-              0, 0, 0);
+              mime, kIsEncrypted, false, true, 0, 0, 0, 0);
       if (support_tunnel_mode_under_secure_pipeline) {
         *force_secure_pipeline_under_tunnel_mode = true;
         return true;
@@ -610,12 +636,10 @@
     return false;
   }
 
-  int GenerateAudioSessionId(const CreationParameters& creation_parameters,
-                             const bool force_improved_support_check) {
+  int GenerateAudioSessionId(const CreationParameters& creation_parameters) {
     bool force_secure_pipeline_under_tunnel_mode = false;
     SB_DCHECK(IsTunnelModeSupported(creation_parameters,
-                                    &force_secure_pipeline_under_tunnel_mode,
-                                    force_improved_support_check));
+                                    &force_secure_pipeline_under_tunnel_mode));
 
     JniEnvExt* env = JniEnvExt::Get();
     ScopedLocalJavaRef<jobject> j_audio_output_manager(
@@ -637,10 +661,9 @@
 
   scoped_ptr<AudioRendererSink> TryToCreateTunnelModeAudioRendererSink(
       int tunnel_mode_audio_session_id,
-      const CreationParameters& creation_parameters,
-      bool enable_audio_device_callback) {
-    scoped_ptr<AudioRendererSink> audio_sink(new AudioRendererSinkAndroid(
-        enable_audio_device_callback, true, tunnel_mode_audio_session_id));
+      const CreationParameters& creation_parameters) {
+    scoped_ptr<AudioRendererSink> audio_sink(
+        new AudioRendererSinkAndroid(tunnel_mode_audio_session_id));
     // We need to double check if the audio sink can actually be created.
     int max_cached_frames, min_frames_per_append;
     GetAudioRendererParams(creation_parameters, &max_cached_frames,
diff --git a/starboard/android/shared/system_get_extensions.cc b/starboard/android/shared/system_get_extensions.cc
index b7f8958..fed416e 100644
--- a/starboard/android/shared/system_get_extensions.cc
+++ b/starboard/android/shared/system_get_extensions.cc
@@ -17,7 +17,6 @@
 #include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/android/shared/configuration.h"
 #include "starboard/android/shared/graphics.h"
-#include "starboard/android/shared/h5vcc_config.h"
 #include "starboard/android/shared/platform_info.h"
 #include "starboard/android/shared/platform_service.h"
 #include "starboard/android/shared/player_set_max_video_input_size.h"
@@ -32,7 +31,6 @@
 #include "starboard/extension/configuration.h"
 #include "starboard/extension/crash_handler.h"
 #include "starboard/extension/graphics.h"
-#include "starboard/extension/h5vcc_config.h"
 #include "starboard/extension/media_session.h"
 #include "starboard/extension/platform_info.h"
 #include "starboard/extension/platform_service.h"
@@ -75,8 +73,5 @@
   if (strcmp(name, kStarboardExtensionPlayerSetMaxVideoInputSizeName) == 0) {
     return starboard::android::shared::GetPlayerSetMaxVideoInputSizeApi();
   }
-  if (strcmp(name, kStarboardExtensionH5vccConfigName) == 0) {
-    return starboard::android::shared::GetH5vccConfigApi();
-  }
   return NULL;
 }
diff --git a/starboard/android/shared/video_decoder.cc b/starboard/android/shared/video_decoder.cc
index b3fe2bb..dbe824c 100644
--- a/starboard/android/shared/video_decoder.cc
+++ b/starboard/android/shared/video_decoder.cc
@@ -356,8 +356,8 @@
                            bool force_secure_pipeline_under_tunnel_mode,
                            bool force_reset_surface_under_tunnel_mode,
                            bool force_big_endian_hdr_metadata,
-                           bool force_improved_support_check,
                            int max_video_input_size,
+                           bool enable_flush_during_seek,
                            std::string* error_message)
     : video_codec_(video_stream_info.codec),
       drm_system_(static_cast<DrmSystem*>(drm_system)),
@@ -375,8 +375,8 @@
       surface_condition_variable_(surface_destroy_mutex_),
       require_software_codec_(IsSoftwareDecodeRequired(max_video_capabilities)),
       force_big_endian_hdr_metadata_(force_big_endian_hdr_metadata),
-      force_improved_support_check_(force_improved_support_check),
-      number_of_preroll_frames_(kInitialPrerollFrameCount) {
+      number_of_preroll_frames_(kInitialPrerollFrameCount),
+      enable_flush_during_seek_(enable_flush_during_seek) {
   SB_DCHECK(error_message);
 
   if (force_secure_pipeline_under_tunnel_mode) {
@@ -601,16 +601,28 @@
 void VideoDecoder::Reset() {
   SB_DCHECK(BelongsToCurrentThread());
 
-  TeardownCodec();
+  // If fail to flush |media_decoder_|, then re-create |media_decoder_|.
+  if (!enable_flush_during_seek_ || !media_decoder_->Flush()) {
+    TeardownCodec();
+
+    input_buffer_written_ = 0;
+
+    // If the codec is kSbMediaVideoCodecAv1,
+    // set video_fps_ to 0 will call InitializeCodec(),
+    // which we do not need if flush the codec.
+    video_fps_ = 0;
+  }
   CancelPendingJobs();
 
+  // TODO(b/291959069): After flush |media_decoder_|, the output buffers
+  // may have invalid frames. Reset |output_format_| to null here to skip max
+  // output buffers check.
+  decoded_output_frames_ = 0;
+  output_format_ = starboard::nullopt;
+
   tunnel_mode_prerolling_.store(true);
   tunnel_mode_frame_rendered_.store(false);
-  input_buffer_written_ = 0;
-  decoded_output_frames_ = 0;
-  output_format_ = starboard::nullopt;
   end_of_stream_written_ = false;
-  video_fps_ = 0;
   pending_input_buffers_.clear();
 
   // TODO: We rely on VideoRenderAlgorithmTunneled::Seek() to be called inside
@@ -719,7 +731,7 @@
       color_metadata_ ? &*color_metadata_ : nullptr, require_software_codec_,
       std::bind(&VideoDecoder::OnFrameRendered, this, _1),
       tunnel_mode_audio_session_id_, force_big_endian_hdr_metadata_,
-      force_improved_support_check_, max_video_input_size_, error_message));
+      max_video_input_size_, error_message));
   if (media_decoder_->is_valid()) {
     if (error_cb_) {
       media_decoder_->Initialize(
diff --git a/starboard/android/shared/video_decoder.h b/starboard/android/shared/video_decoder.h
index c5fde74..db2750e 100644
--- a/starboard/android/shared/video_decoder.h
+++ b/starboard/android/shared/video_decoder.h
@@ -70,8 +70,8 @@
                bool force_secure_pipeline_under_tunnel_mode,
                bool force_reset_surface_under_tunnel_mode,
                bool force_big_endian_hdr_metadata,
-               bool force_improved_support_check,
                int max_input_size,
+               bool enable_flush_during_seek,
                std::string* error_message);
   ~VideoDecoder() override;
 
@@ -144,10 +144,6 @@
   // the main player and SW decoder for sub players.
   const bool require_software_codec_;
 
-  // Forces the use of specific Android APIs (isSizeSupported() and
-  // areSizeAndRateSupported()) to determine format support.
-  const bool force_improved_support_check_;
-
   // Force endianness of HDR Metadata.
   const bool force_big_endian_hdr_metadata_;
 
@@ -156,6 +152,8 @@
   // Set the maximum size in bytes of an input buffer for video.
   const int max_video_input_size_;
 
+  const bool enable_flush_during_seek_;
+
   // Force resetting the video surface after tunnel mode playback, which
   // prevents video distortion on some devices.
   const bool force_reset_surface_under_tunnel_mode_;
diff --git a/starboard/extension/extension_test.cc b/starboard/extension/extension_test.cc
index 7bbce73..79f0464 100644
--- a/starboard/extension/extension_test.cc
+++ b/starboard/extension/extension_test.cc
@@ -21,7 +21,6 @@
 #include "starboard/extension/font.h"
 #include "starboard/extension/free_space.h"
 #include "starboard/extension/graphics.h"
-#include "starboard/extension/h5vcc_config.h"
 #include "starboard/extension/ifa.h"
 #include "starboard/extension/installation_manager.h"
 #include "starboard/extension/javascript_cache.h"
@@ -29,6 +28,7 @@
 #include "starboard/extension/memory_mapped_file.h"
 #include "starboard/extension/platform_info.h"
 #include "starboard/extension/platform_service.h"
+#include "starboard/extension/player_configuration.h"
 #include "starboard/extension/player_set_max_video_input_size.h"
 #include "starboard/extension/time_zone.h"
 #include "starboard/extension/updater_notification.h"
@@ -504,9 +504,9 @@
       << "Extension struct should be a singleton";
 }
 
-TEST(ExtensionTest, H5vccConfig) {
-  typedef StarboardExtensionH5vccConfigApi ExtensionApi;
-  const char* kExtensionName = kStarboardExtensionH5vccConfigName;
+TEST(ExtensionTest, PlayerConfiguration) {
+  typedef StarboardExtensionPlayerConfigurationApi ExtensionApi;
+  const char* kExtensionName = kStarboardExtensionPlayerConfigurationName;
 
   const ExtensionApi* extension_api =
       static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
@@ -516,12 +516,15 @@
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
   EXPECT_EQ(extension_api->version, 1u);
-  EXPECT_NE(extension_api->EnableBackgroundPlayback, 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";
+  if (extension_api->SetDecodeToTexturePreferred) {
+    extension_api->SetDecodeToTexturePreferred(true);
+    extension_api->SetDecodeToTexturePreferred(false);
+  }
+  if (extension_api->SetTunnelModePreferred) {
+    extension_api->SetTunnelModePreferred(true);
+    extension_api->SetTunnelModePreferred(false);
+  }
 }
 
 }  // namespace extension
diff --git a/starboard/extension/h5vcc_config.h b/starboard/extension/h5vcc_config.h
deleted file mode 100644
index a88a2f3..0000000
--- a/starboard/extension/h5vcc_config.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2024 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_H5VCC_CONFIG_H_
-#define STARBOARD_EXTENSION_H5VCC_CONFIG_H_
-
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define kStarboardExtensionH5vccConfigName "dev.starboard.extension.H5vccConfig"
-
-typedef struct StarboardExtensionH5vccConfigApi {
-  // Name should be the string |kStarboardExtensionH5vccConfigName|.
-  // 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;
-
-  // This API enable the Cobalt background playback mode.
-  void (*EnableBackgroundPlayback)(bool value);
-
-} StarboardExtensionH5vccConfigApi;
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-
-#endif  // STARBOARD_EXTENSION_H5VCC_CONFIG_H_
diff --git a/starboard/extension/player_configuration.h b/starboard/extension/player_configuration.h
new file mode 100644
index 0000000..3770196
--- /dev/null
+++ b/starboard/extension/player_configuration.h
@@ -0,0 +1,54 @@
+// Copyright 2024 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_PLAYER_CONFIGURATION_H_
+#define STARBOARD_EXTENSION_PLAYER_CONFIGURATION_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kStarboardExtensionPlayerConfigurationName \
+  "dev.starboard.extension.PlayerConfiguration"
+
+typedef struct StarboardExtensionPlayerConfigurationApi {
+  // Name should be the string kStarboardExtensionPlayerConfigurationName.
+  // 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.
+
+  // This is used to ask the underlying starboard player prefer to use decode
+  // to texture mode to render video frames when it's available. This function
+  // can be null.
+  // When decode to texture mode is prefereed, SbPlayerGetPreferredOutputMode()
+  // should return kSbPlayerOutputModeDecodeToTexture if it's supported.
+  void (*SetDecodeToTexturePreferred)(bool preferred);
+
+  // This is used to ask the underlying starboard player prefer to use tunnel
+  // mode when it's available. This function can be null.
+  // When tunnel mode is prefereed, the platform should use tunnel mode for
+  // primary player if it's supported.
+  void (*SetTunnelModePreferred)(bool preferred);
+
+} StarboardExtensionPlayerConfigurationApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // STARBOARD_EXTENSION_PLAYER_CONFIGURATION_H_
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index 4b4e3fa..507a764 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -88,8 +88,6 @@
     "//starboard/shared/alsa/alsa_audio_sink_type.h",
     "//starboard/shared/alsa/alsa_util.cc",
     "//starboard/shared/alsa/alsa_util.h",
-    "//starboard/shared/deviceauth/deviceauth_internal.cc",
-    "//starboard/shared/deviceauth/deviceauth_internal.h",
     "//starboard/shared/egl/system_egl.cc",
     "//starboard/shared/gcc/atomic_gcc_public.h",
     "//starboard/shared/gles/system_gles2.cc",
@@ -380,12 +378,14 @@
     "//starboard:starboard_headers_only",
     "//starboard/shared/starboard/player/filter:filter_based_player_sources",
   ]
-  deps = [ "//third_party/boringssl:crypto" ]
+  deps = []
 
   if (is_internal_build) {
     sources += [
       "//internal/starboard/linux/shared/internal/oemcrypto_engine_device_properties_linux.cc",
       "//starboard/linux/shared/drm_create_system.cc",
+      "//starboard/shared/deviceauth/deviceauth_internal.cc",
+      "//starboard/shared/deviceauth/deviceauth_internal.h",
       "//starboard/shared/starboard/drm/drm_close_session.cc",
       "//starboard/shared/starboard/drm/drm_destroy_system.cc",
       "//starboard/shared/starboard/drm/drm_generate_session_update_request.cc",
@@ -404,6 +404,7 @@
     ]
     deps += [
       "//starboard/shared/widevine:oemcrypto",
+      "//third_party/boringssl:crypto",
       "//third_party/internal/ce_cdm/cdm:widevine_ce_cdm_static",
     ]
   } else {
diff --git a/starboard/player.h b/starboard/player.h
index e7771c5..ca7c5f9 100644
--- a/starboard/player.h
+++ b/starboard/player.h
@@ -379,7 +379,6 @@
 //   video will never adapt to resolution higher than 1920x1080 or frame per
 //   second higher than 15 fps. When the maximums are unknown, this will be set
 //   to NULL.
-
 //
 // |sample_deallocator_func|: If not |NULL|, the player calls this function
 //   on an internal thread to free the sample buffers passed into
@@ -419,7 +418,6 @@
 // If |NULL| is passed to any of the callbacks (|sample_deallocator_func|,
 // |decoder_status_func|, |player_status_func|, or |player_error_func| if it
 // applies), then |kSbPlayerInvalid| must be returned.
-
 SB_EXPORT SbPlayer
 SbPlayerCreate(SbWindow window,
                const SbPlayerCreationParam* creation_param,
@@ -489,7 +487,6 @@
 //   when SbPlayerSeek was called. To be very specific, once SbPlayerSeek has
 //   been called with ticket X, a client should ignore all
 //   |SbPlayerDecoderStatusFunc| calls that do not pass in ticket X.
-
 #if SB_API_VERSION >= 15
 SB_EXPORT void SbPlayerSeek(SbPlayer player,
                             SbTime seek_to_timestamp,
@@ -522,7 +519,7 @@
 //   SbPlayerWriteSamples(), so it must be copied if its content will be used
 //   after SbPlayerWriteSamples() returns.
 // |number_of_sample_infos|: Specify the number of samples contained inside
-//   |sample_infos|.  It has to be at least one, and less than the return value
+//   |sample_infos|.  It has to be at least one, and at most the return value
 //   of SbPlayerGetMaximumNumberOfSamplesPerWrite().
 #if SB_API_VERSION >= 15
 SB_EXPORT void SbPlayerWriteSamples(SbPlayer player,
@@ -533,13 +530,13 @@
                                     const SbPlayerSampleInfo* sample_infos,
                                     int number_of_sample_infos);
 
-// Writes a single sample of the given media type to |player|'s input stream.
-// Its data may be passed in via more than one buffers.  The lifetime of
-// |sample_buffers|, |sample_buffer_sizes|, |video_sample_info|, and
-// |sample_drm_info| (as well as member |subsample_mapping| contained inside it)
-// are not guaranteed past the call to SbPlayerWriteSample. That means that
-// before returning, the implementation must synchronously copy any information
-// it wants to retain from those structures.
+// Returns the maximum number of samples that can be written in a single call
+// to SbPlayerWriteSamples(). Returning a value greater than one can improve
+// performance by allowing SbPlayerWriteSamples() to write multiple samples in
+// one call.
+//
+// Note that this feature is currently disabled in Cobalt where
+// SbPlayerWriteSamples() will always be called with one sample.
 //
 // |player|: The player for which the number is retrieved.
 // |sample_type|: The type of sample for which the number is retrieved. See the
diff --git a/starboard/raspi/shared/test_filters.py b/starboard/raspi/shared/test_filters.py
index 21a428f..b0c1734 100644
--- a/starboard/raspi/shared/test_filters.py
+++ b/starboard/raspi/shared/test_filters.py
@@ -39,6 +39,11 @@
 
         # Enable once verified on the platform.
         'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
+
+        # TODO: b/327686141 - Passes locally but fails on github
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/type_ipv6',
     ],
     'player_filter_tests': [
         # The implementations for the raspberry pi (0 and 2) are incomplete
diff --git a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
index 1086f3f..6d25689 100644
--- a/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_audio_decoder_impl.cc
@@ -249,6 +249,11 @@
 void AudioDecoderImpl<FFMPEG>::Reset() {
   SB_DCHECK(BelongsToCurrentThread());
 
+  TeardownCodec();
+  if ((ffmpeg_->specialization_version()) == FFMPEG) {
+    InitializeCodec();
+  }
+
   stream_ended_ = false;
   while (!decoded_audios_.empty()) {
     decoded_audios_.pop();
diff --git a/starboard/shared/opus/opus_audio_decoder.cc b/starboard/shared/opus/opus_audio_decoder.cc
index 4833964..fa8e00a 100644
--- a/starboard/shared/opus/opus_audio_decoder.cc
+++ b/starboard/shared/opus/opus_audio_decoder.cc
@@ -47,31 +47,14 @@
 
 OpusAudioDecoder::OpusAudioDecoder(const AudioStreamInfo& audio_stream_info)
     : audio_stream_info_(audio_stream_info) {
-  int error;
-  int channels = audio_stream_info_.number_of_channels;
-  if (channels > 8 || channels < 1) {
-    SB_LOG(ERROR) << "Can't create decoder with " << channels << " channels";
-    return;
-  }
-
-  decoder_ = opus_multistream_decoder_create(
-      audio_stream_info_.samples_per_second, channels,
-      vorbis_mappings[channels - 1].nb_streams,
-      vorbis_mappings[channels - 1].nb_coupled_streams,
-      vorbis_mappings[channels - 1].mapping, &error);
-  if (error != OPUS_OK) {
-    SB_LOG(ERROR) << "Failed to create decoder with error: "
-                  << opus_strerror(error);
-    decoder_ = NULL;
-    return;
-  }
-  SB_DCHECK(decoder_ != NULL);
+  InitializeCodec();
 }
 
 OpusAudioDecoder::~OpusAudioDecoder() {
-  if (decoder_) {
-    opus_multistream_decoder_destroy(decoder_);
+  if (is_valid()) {
+    opus_multistream_decoder_ctl(decoder_, OPUS_RESET_STATE);
   }
+  TeardownCodec();
 }
 
 void OpusAudioDecoder::Initialize(const OutputCB& output_cb,
@@ -215,6 +198,35 @@
   Schedule(output_cb_);
 }
 
+void OpusAudioDecoder::InitializeCodec() {
+  int error;
+  int channels = audio_stream_info_.number_of_channels;
+  if (channels > 8 || channels < 1) {
+    SB_LOG(ERROR) << "Can't create decoder with " << channels << " channels";
+    return;
+  }
+
+  decoder_ = opus_multistream_decoder_create(
+      audio_stream_info_.samples_per_second, channels,
+      vorbis_mappings[channels - 1].nb_streams,
+      vorbis_mappings[channels - 1].nb_coupled_streams,
+      vorbis_mappings[channels - 1].mapping, &error);
+  if (error != OPUS_OK) {
+    SB_LOG(ERROR) << "Failed to create decoder with error: "
+                  << opus_strerror(error);
+    decoder_ = NULL;
+    return;
+  }
+  SB_DCHECK(decoder_ != NULL);
+}
+
+void OpusAudioDecoder::TeardownCodec() {
+  if (is_valid()) {
+    opus_multistream_decoder_destroy(decoder_);
+    decoder_ = NULL;
+  }
+}
+
 scoped_refptr<OpusAudioDecoder::DecodedAudio> OpusAudioDecoder::Read(
     int* samples_per_second) {
   SB_DCHECK(BelongsToCurrentThread());
@@ -233,6 +245,19 @@
 void OpusAudioDecoder::Reset() {
   SB_DCHECK(BelongsToCurrentThread());
 
+  if (is_valid()) {
+    int error = opus_multistream_decoder_ctl(decoder_, OPUS_RESET_STATE);
+    if (error != OPUS_OK) {
+      SB_LOG(ERROR) << "Failed to reset OpusAudioDecoder with error: "
+                    << opus_strerror(error);
+
+      // If fail to reset opus decoder, re-create it.
+      TeardownCodec();
+      InitializeCodec();
+    }
+  }
+
+  frames_per_au_ = kMaxOpusFramesPerAU;
   stream_ended_ = false;
   while (!decoded_audios_.empty()) {
     decoded_audios_.pop();
diff --git a/starboard/shared/opus/opus_audio_decoder.h b/starboard/shared/opus/opus_audio_decoder.h
index ab3e947..a8d59dc 100644
--- a/starboard/shared/opus/opus_audio_decoder.h
+++ b/starboard/shared/opus/opus_audio_decoder.h
@@ -54,6 +54,8 @@
  private:
   static constexpr int kMinimumBuffersToDecode = 2;
 
+  void InitializeCodec();
+  void TeardownCodec();
   void DecodePendingBuffers();
   bool DecodeInternal(const scoped_refptr<InputBuffer>& input_buffer);
   static const int kMaxOpusFramesPerAU = 9600;
diff --git a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc
index 7e74803..dbc9f17 100644
--- a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc
+++ b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.cc
@@ -43,8 +43,26 @@
   SB_DCHECK(audio_stream_info.codec != kSbMediaAudioCodecNone);
 }
 
+AdaptiveAudioDecoder::AdaptiveAudioDecoder(
+    const media::AudioStreamInfo& audio_stream_info,
+    SbDrmSystem drm_system,
+    const AudioDecoderCreator& audio_decoder_creator,
+    bool enable_reset_audio_decoder,
+    const OutputFormatAdjustmentCallback& output_adjustment_callback)
+    : AdaptiveAudioDecoder(audio_stream_info,
+                           drm_system,
+                           audio_decoder_creator,
+                           output_adjustment_callback) {
+  enable_reset_audio_decoder_ = enable_reset_audio_decoder;
+}
+
 AdaptiveAudioDecoder::~AdaptiveAudioDecoder() {
-  Reset();
+  SB_DCHECK(BelongsToCurrentThread());
+
+  if (audio_decoder_) {
+    TeardownAudioDecoder();
+  }
+  ResetInternal();
 }
 
 void AdaptiveAudioDecoder::Initialize(const OutputCB& output_cb,
@@ -75,6 +93,9 @@
   SB_DCHECK(input_buffers.front()->audio_stream_info().codec !=
             kSbMediaAudioCodecNone);
 
+  if (!first_input_written_) {
+    first_input_written_ = true;
+  }
   if (!audio_decoder_) {
     InitializeAudioDecoder(input_buffers.front()->audio_stream_info());
     if (audio_decoder_) {
@@ -113,7 +134,7 @@
   SB_DCHECK(!pending_consumed_cb_);
 
   stream_ended_ = true;
-  if (audio_decoder_) {
+  if (first_input_written_) {
     audio_decoder_->WriteEndOfStream();
   } else {
     decoded_audios_.push(new DecodedAudio);
@@ -146,17 +167,15 @@
   SB_DCHECK(BelongsToCurrentThread());
 
   if (audio_decoder_) {
-    TeardownAudioDecoder();
+    if (enable_reset_audio_decoder_) {
+      audio_decoder_->Reset();
+      resampler_.reset();
+      channel_mixer_.reset();
+    } else {
+      TeardownAudioDecoder();
+    }
   }
-  CancelPendingJobs();
-  while (!decoded_audios_.empty()) {
-    decoded_audios_.pop();
-  }
-  pending_input_buffers_.clear();
-  pending_consumed_cb_ = nullptr;
-  flushing_ = false;
-  stream_ended_ = false;
-  first_output_received_ = false;
+  ResetInternal();
 }
 
 void AdaptiveAudioDecoder::InitializeAudioDecoder(
@@ -184,6 +203,21 @@
   audio_decoder_.reset();
   resampler_.reset();
   channel_mixer_.reset();
+  first_input_written_ = false;
+}
+
+void AdaptiveAudioDecoder::ResetInternal() {
+  CancelPendingJobs();
+  while (!decoded_audios_.empty()) {
+    decoded_audios_.pop();
+  }
+  pending_input_buffers_.clear();
+  pending_consumed_cb_ = nullptr;
+  flushing_ = false;
+  stream_ended_ = false;
+  first_output_received_ = false;
+  first_input_written_ = false;
+  output_format_checked_ = false;
 }
 
 void AdaptiveAudioDecoder::OnDecoderOutput() {
diff --git a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h
index 2fc9964..4600bd1 100644
--- a/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h
+++ b/starboard/shared/starboard/player/filter/adaptive_audio_decoder_internal.h
@@ -52,6 +52,12 @@
                        const AudioDecoderCreator& audio_decoder_creator,
                        const OutputFormatAdjustmentCallback&
                            output_adjustment_callback = nullptr);
+  AdaptiveAudioDecoder(const media::AudioStreamInfo& audio_stream_info,
+                       SbDrmSystem drm_system,
+                       const AudioDecoderCreator& audio_decoder_creator,
+                       bool enable_reset_audio_decoder,
+                       const OutputFormatAdjustmentCallback&
+                           output_adjustment_callback = nullptr);
   ~AdaptiveAudioDecoder() override;
 
   void Initialize(const OutputCB& output_cb, const ErrorCB& error_cb) override;
@@ -65,6 +71,7 @@
   void InitializeAudioDecoder(const media::AudioStreamInfo& audio_stream_info);
   void TeardownAudioDecoder();
   void OnDecoderOutput();
+  void ResetInternal();
 
   const uint32_t initial_samples_per_second_;
   const SbDrmSystem drm_system_;
@@ -89,6 +96,8 @@
   bool stream_ended_ = false;
   bool first_output_received_ = false;
   bool output_format_checked_ = false;
+  bool first_input_written_ = false;
+  bool enable_reset_audio_decoder_ = false;
 };
 
 }  // namespace filter
diff --git a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
index 4b23321..d1d742a 100644
--- a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
+++ b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -336,14 +336,31 @@
     }
   }
 
-  void ResetDecoder() {
-    audio_decoder_->Reset();
+  void ResetInternal() {
     can_accept_more_input_ = true;
     last_input_buffer_ = nullptr;
     decoded_audios_.clear();
+    written_inputs_.clear();
     eos_written_ = false;
     decoded_audio_sample_rate_ = 0;
     first_output_received_ = false;
+    {
+      ScopedLock scoped_lock(event_queue_mutex_);
+      event_queue_.clear();
+    }
+  }
+
+  void ResetDecoder() {
+    audio_decoder_->Reset();
+    ResetInternal();
+  }
+
+  void RecreateDecoder() {
+    // TODO(b/332955827): Investigate the output difference after flush() codec.
+    audio_decoder_.reset();
+    audio_renderer_sink_.reset();
+    ResetInternal();
+    SetUp();
   }
 
   void WaitForDecodedAudio() {
@@ -361,6 +378,40 @@
     }
   }
 
+  void WaitForDecoderInRunningState() {
+    // Write multiple inputs to avoid reset codec too soon after start.
+    const size_t kMaxNumberOfInputsToWrite = 50;
+    size_t start_index = 0;
+    size_t number_of_inputs_to_write = std::min(
+        kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers());
+
+    // Wait for at least one output available to ensure codec is in running
+    // state.
+    ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
+    ++start_index;
+    --number_of_inputs_to_write;
+
+    while (number_of_inputs_to_write > 0) {
+      Event event = kError;
+      ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event));
+      if (event == kConsumed) {
+        ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
+        ++start_index;
+        --number_of_inputs_to_write;
+        continue;
+      }
+      if (event == kError) {
+        FAIL();
+      }
+      ASSERT_EQ(kOutput, event);
+      scoped_refptr<DecodedAudio> decoded_audio;
+      ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio));
+      ASSERT_TRUE(decoded_audio);
+      ASSERT_FALSE(decoded_audio->is_end_of_stream());
+      break;
+    }
+  }
+
   scoped_refptr<InputBuffer> GetAudioInputBuffer(size_t index) {
     auto input_buffer = testing::GetAudioInputBuffer(&dmp_reader_, index);
     auto iter = invalid_inputs_.find(index);
@@ -724,7 +775,7 @@
        ++number_of_input_to_write) {
     SB_LOG(INFO) << "Testing " << number_of_input_to_write
                  << " access units for partial audio.";
-    ResetDecoder();
+    RecreateDecoder();
 
     // Decode InputBuffers without partial audio and use the output as reference
     for (int i = 0; i < number_of_input_to_write; ++i) {
@@ -766,7 +817,7 @@
                                      decoded_audio_sample_rate_) /
         4;
 
-    ResetDecoder();
+    RecreateDecoder();
 
     for (int i = 0; i < number_of_input_to_write; ++i) {
       SbTime duration_to_discard_from_front = i == 0 ? duration_to_discard : 0;
@@ -817,12 +868,12 @@
         (frames_per_access_unit / 4 - 1) * bytes_per_frame;
     auto reference_search_end = reference_decoded_audio->data() +
                                 reference_decoded_audio->size_in_bytes();
-    auto offset_in_bytes =
-        std::search(reference_search_begin, reference_search_end,
-                    partial_decoded_audio->data(),
-                    partial_decoded_audio->data() +
-                        partial_decoded_audio->size_in_bytes()) -
-        reference_search_begin;
+    auto offset_index = std::search(
+        reference_search_begin, reference_search_end,
+        partial_decoded_audio->data(),
+        partial_decoded_audio->data() + partial_decoded_audio->size_in_bytes());
+    ASSERT_FALSE(offset_index == reference_search_end);
+    auto offset_in_bytes = offset_index - reference_search_begin;
     auto offset_in_frames = offset_in_bytes / bytes_per_frame;
 
     constexpr int kEpsilonInFrames = 2;
@@ -832,6 +883,94 @@
   }
 }
 
+TEST_P(AudioDecoderTest, ResetAfterEndOfStream) {
+  ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+
+  ResetDecoder();
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  ASSERT_TRUE(decoded_audios_.empty());
+}
+
+TEST_P(AudioDecoderTest, ResetAfterEndOfStreamWithSingleInput) {
+  ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_);
+
+  ResetDecoder();
+
+  ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  ASSERT_FALSE(decoded_audios_.empty());
+  ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid());
+
+  int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_);
+  ASSERT_EQ(decoded_audio_frames_before_reset,
+            decoded_audio_frames_after_reset);
+}
+
+TEST_P(AudioDecoderTest, ResetAfterEndOfStreamWithMultipleInputs) {
+  const size_t kMaxNumberOfInputsToWrite = 5;
+  const size_t number_of_inputs_to_write = std::min(
+      kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers());
+
+  ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_);
+
+  ResetDecoder();
+
+  ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  ASSERT_FALSE(decoded_audios_.empty());
+  ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid());
+
+  int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_);
+  ASSERT_EQ(decoded_audio_frames_before_reset,
+            decoded_audio_frames_after_reset);
+}
+
+TEST_P(AudioDecoderTest, ResetInRunningStateWithInput) {
+  const size_t kMaxNumberOfInputsToWrite = 5;
+  const size_t number_of_inputs_to_write = std::min(
+      kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers());
+
+  ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  int decoded_audio_frames_before_reset = GetTotalFrames(decoded_audios_);
+  ResetDecoder();
+
+  WaitForDecoderInRunningState();
+
+  ResetDecoder();
+  ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write));
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  ASSERT_FALSE(decoded_audios_.empty());
+  ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid());
+
+  int decoded_audio_frames_after_reset = GetTotalFrames(decoded_audios_);
+  ASSERT_EQ(decoded_audio_frames_before_reset,
+            decoded_audio_frames_after_reset);
+}
+
+TEST_P(AudioDecoderTest, ResetInRunningStateWithEndOfStream) {
+  WaitForDecoderInRunningState();
+
+  ResetDecoder();
+  WriteEndOfStream();
+  ASSERT_NO_FATAL_FAILURE(DrainOutputs());
+  ASSERT_TRUE(decoded_audios_.empty());
+  ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid());
+}
+
 INSTANTIATE_TEST_CASE_P(
     AudioDecoderTests,
     AudioDecoderTest,
diff --git a/starboard/shared/uwp/application_uwp.cc b/starboard/shared/uwp/application_uwp.cc
index b0f18dc..4e0fd5c 100644
--- a/starboard/shared/uwp/application_uwp.cc
+++ b/starboard/shared/uwp/application_uwp.cc
@@ -143,6 +143,9 @@
     L"True");
 
 const uint32_t kYuv420BitsPerPixelForHdr10Mode = 24;
+const uint32_t kHdr4kRefreshRateMaximum = 60;
+const uint32_t k4kResolutionWidth = 3840;
+const uint32_t k4kResolutionHeight = 2160;
 
 // The number of seconds to wait after requesting application stop. This is used
 // to exit the app when a suspend has been requested. This is a temporary
@@ -986,17 +989,32 @@
 
   preferred_display_mode_hdmi_ = hdmi_display_info->GetCurrentDisplayMode();
   for (auto mode : hdmi_display_info->GetSupportedDisplayModes()) {
-    if (mode->ResolutionWidthInRawPixels ==
-            preferred_display_mode_hdmi_->ResolutionWidthInRawPixels &&
-        mode->ResolutionHeightInRawPixels ==
-            preferred_display_mode_hdmi_->ResolutionHeightInRawPixels &&
-        mode->Is2086MetadataSupported && mode->IsSmpte2084Supported &&
-        mode->BitsPerPixel >= kYuv420BitsPerPixelForHdr10Mode &&
-        mode->ColorSpace == HdmiDisplayColorSpace::BT2020) {
-      if (!preferred_display_mode_hdr_ ||
-          preferred_display_mode_hdr_->RefreshRate < mode->RefreshRate) {
-        preferred_display_mode_hdr_ = mode;
-      }
+    // Check that resolution matches the preferred display mode.
+    if (mode->ResolutionWidthInRawPixels !=
+            preferred_display_mode_hdmi_->ResolutionWidthInRawPixels ||
+        mode->ResolutionHeightInRawPixels !=
+            preferred_display_mode_hdmi_->ResolutionHeightInRawPixels) {
+      continue;
+    }
+    // Verify HDR metadata and transfer function are supported.
+    if (!mode->Is2086MetadataSupported || !mode->IsSmpte2084Supported) {
+      continue;
+    }
+    // Verify we have enough bits per pixel and the correct color space for HDR.
+    if (mode->BitsPerPixel < kYuv420BitsPerPixelForHdr10Mode ||
+        mode->ColorSpace != HdmiDisplayColorSpace::BT2020) {
+      continue;
+    }
+    // We don't serve 4k HDR videos over 60fps, skipping display modes that will
+    // consume more power than needed.
+    if (mode->ResolutionWidthInRawPixels >= k4kResolutionWidth &&
+        mode->ResolutionHeightInRawPixels >= k4kResolutionHeight &&
+        mode->RefreshRate > kHdr4kRefreshRateMaximum) {
+      continue;
+    }
+    if (!preferred_display_mode_hdr_ ||
+        preferred_display_mode_hdr_->RefreshRate < mode->RefreshRate) {
+      preferred_display_mode_hdr_ = mode;
     }
   }
 }
diff --git a/starboard/shared/uwp/extended_resources_manager.cc b/starboard/shared/uwp/extended_resources_manager.cc
index e8a730b..3fbf5ac 100644
--- a/starboard/shared/uwp/extended_resources_manager.cc
+++ b/starboard/shared/uwp/extended_resources_manager.cc
@@ -21,6 +21,7 @@
 #include "starboard/common/string.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/media/mime_supportability_cache.h"
+#include "starboard/shared/uwp/xb1_get_type.h"
 #include "starboard/shared/win32/video_decoder.h"
 #include "starboard/thread.h"
 #include "starboard/time.h"
@@ -69,18 +70,23 @@
 // 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.
 // Compromise value was found out experimentally.
-// 400 Mb leaves enough memory for stable working of the rest system.
-// Just in case to be more sure we reduce this value down to 380 Mb.
-const uint64_t kFrameBuffersPoolMemorySize = 380 * 1024 * 1024;
+// In XBOX ONE S/Base 400 Mb leaves enough memory for stable working of the rest
+// system. Just in case to be more sure we reduce this value down to 380 Mb. In
+// Sereies devices (if to use gpu based decoders) the max available frame size
+// size is 4K instead of 2K. On the other hand the memory budget is greater than
+// for Base/S. So we can use more memory for output queue.
+const uint64_t kFrameBuffersPoolMemorySizeForXB1 = 380 * 1024 * 1024;
+// +8 extra frames 4K HDR DXGI_FORMAT_R10G10B10A2_UNORM
+const uint64_t kFrameBuffersPoolMemorySizeForXBSeries =
+    kFrameBuffersPoolMemorySizeForXB1 + 8 * 16588800;
 
 bool IsExtendedResourceModeRequired() {
   if (!::starboard::xb1::shared::CanAcquire()) {
     return false;
   }
-  bool is_erm_required =
-      !shared::win32::VideoDecoder::IsHardwareVp9DecoderSupported();
-  SB_LOG(INFO) << "Extended resources mode"
-               << (is_erm_required ? " is required." : " isn't required.");
+  // erm is required for all xbox types except kXboxOneX;
+  bool is_erm_required = ::starboard::shared::uwp::GetXboxType() !=
+                         ::starboard::shared::uwp::kXboxOneX;
   return is_erm_required;
 }
 
@@ -174,6 +180,10 @@
   pending_extended_resources_release_.store(true);
 }
 
+void ExtendedResourcesManager::ReleaseBuffersHeap() {
+  d3d12FrameBuffersHeap_.Reset();
+}
+
 bool ExtendedResourcesManager::GetD3D12Objects(
     Microsoft::WRL::ComPtr<ID3D12Device>* device,
     Microsoft::WRL::ComPtr<ID3D12Heap>* buffer_heap,
@@ -263,8 +273,13 @@
     SB_DCHECK(d3d12queue_);
   }
   if (!d3d12FrameBuffersHeap_) {
+    const bool isSeries = ::starboard::shared::uwp::GetXboxType() ==
+                              ::starboard::shared::uwp::kXboxSeriesS ||
+                          ::starboard::shared::uwp::GetXboxType() ==
+                              ::starboard::shared::uwp::kXboxSeriesX;
     D3D12_HEAP_DESC heap_desc;
-    heap_desc.SizeInBytes = kFrameBuffersPoolMemorySize;
+    heap_desc.SizeInBytes = isSeries ? kFrameBuffersPoolMemorySizeForXBSeries
+                                     : kFrameBuffersPoolMemorySizeForXB1;
     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;
diff --git a/starboard/shared/uwp/extended_resources_manager.h b/starboard/shared/uwp/extended_resources_manager.h
index 54b966f..78d3fee 100644
--- a/starboard/shared/uwp/extended_resources_manager.h
+++ b/starboard/shared/uwp/extended_resources_manager.h
@@ -46,6 +46,7 @@
   void AcquireExtendedResources();
   void ReleaseExtendedResources();
   void Quit();
+  void ReleaseBuffersHeap();
 
   // Returns true when the d3d12 device, buffer heap
   // and command queue can be used.
diff --git a/starboard/shared/uwp/media_is_video_supported.cc b/starboard/shared/uwp/media_is_video_supported.cc
index eab4c87..e19e139 100644
--- a/starboard/shared/uwp/media_is_video_supported.cc
+++ b/starboard/shared/uwp/media_is_video_supported.cc
@@ -144,7 +144,7 @@
                                             60);
         hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 3840, 2160,
                                             30);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1920, 1080,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2560, 1440,
                                             60);
         // Vertical video resolutions
         hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
@@ -153,7 +153,7 @@
                                             60);
         hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
                                             30);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1080, 1920,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
                                             60);
         break;
       case starboard::shared::uwp::kXboxSeriesX:
@@ -167,20 +167,20 @@
         // Horizontal video resolutions
         hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 3840, 2160,
                                             30);
-        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 2560, 1440,
+        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 3840, 2160,
                                             60);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2560, 1440,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 3840, 2160,
                                             30);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1920, 1080,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2560, 1440,
                                             60);
         // Vertical video resolutions
         hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
                                             30);
-        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 1440, 2560,
+        hw_decoder_capabilities_.AddSdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
                                             60);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1440, 2560,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 2160, 3840,
                                             30);
-        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1080, 1920,
+        hw_decoder_capabilities_.AddHdrRule(kSbMediaVideoCodecAv1, 1440, 2560,
                                             60);
         break;
       default:
diff --git a/starboard/shared/uwp/player_components_factory.cc b/starboard/shared/uwp/player_components_factory.cc
index c8511be..c29420a 100644
--- a/starboard/shared/uwp/player_components_factory.cc
+++ b/starboard/shared/uwp/player_components_factory.cc
@@ -196,6 +196,7 @@
     }
 
     using MftVideoDecoder = ::starboard::xb1::shared::VideoDecoderUwp;
+    using ExtendedResourcesManager = shared::uwp::ExtendedResourcesManager;
 
     const auto output_mode = creation_parameters.output_mode();
     const auto is_hdr_video =
@@ -208,6 +209,9 @@
          MftVideoDecoder::IsHardwareAv1DecoderSupported())) {
       video_render_algorithm->reset(
           new VideoRenderAlgorithmImpl(std::bind(GetRefreshRate)));
+      // The memory heap for gpu decoders isn't used by hw decoders.
+      // Release it.
+      ExtendedResourcesManager::GetInstance()->ReleaseBuffersHeap();
       video_decoder->reset(new MftVideoDecoder(
           video_codec, output_mode,
           creation_parameters.decode_target_graphics_context_provider(),
diff --git a/starboard/xb1/shared/video_decoder_uwp.cc b/starboard/xb1/shared/video_decoder_uwp.cc
index 279fe0a..7213039 100644
--- a/starboard/xb1/shared/video_decoder_uwp.cc
+++ b/starboard/xb1/shared/video_decoder_uwp.cc
@@ -15,9 +15,11 @@
 #include "starboard/xb1/shared/video_decoder_uwp.h"
 
 #include <windows.h>
+#include <set>
 
 #include "starboard/shared/uwp/async_utils.h"
 #include "starboard/shared/uwp/decoder_utils.h"
+#include "starboard/shared/uwp/xb1_get_type.h"
 #include "third_party/angle/include/angle_hdr.h"
 
 using ::starboard::shared::uwp::ApplicationUwp;
@@ -31,6 +33,13 @@
 
 const int kXboxeOneXMaxOutputSamples = 5;
 
+// Set of devices that shouldn't use hardware AV1 decoding.
+// TODO (b/259431190): Series devices had performance issues with hardware AV1
+// decoders during our initial rollout. Disable hardware AV1 decoding in favor
+// of the software decoder to test if performance improves.
+const std::set<starboard::shared::uwp::XboxType> hw_av1_disabled_devices = {
+    starboard::shared::uwp::kXboxSeriesS, starboard::shared::uwp::kXboxSeriesX};
+
 VideoDecoderUwp::~VideoDecoderUwp() {
   if (IsHdrSupported() && IsHdrAngleModeEnabled()) {
     SetHdrAngleModeEnabled(false);
@@ -52,7 +61,9 @@
   // So to avoid using av1 mft in these devices we test if
   // the current XBOX has vp9 hw decoder. If it doesn't it means
   // that there is XboxOneS/Base and we don't use mft av1 decoder.
-  return VideoDecoderUwp::IsHardwareVp9DecoderSupported() &&
+  return hw_av1_disabled_devices.find(starboard::shared::uwp::GetXboxType()) ==
+             hw_av1_disabled_devices.end() &&
+         VideoDecoderUwp::IsHardwareVp9DecoderSupported() &&
          ::starboard::shared::win32::IsHardwareAv1DecoderSupported();
 }
 
diff --git a/starboard/xb1/test_filters.py b/starboard/xb1/test_filters.py
index 6bb45b5..bb9d5e7 100644
--- a/starboard/xb1/test_filters.py
+++ b/starboard/xb1/test_filters.py
@@ -25,6 +25,8 @@
         # Enable multiplayer tests once it's supported.
         'MultiplePlayerTests/*',
         'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.SecondaryPlayerTest/*',
+        # TODO: b/333412348
+        'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
     ],
     'player_filter_tests': [
         'PlayerComponentsTests/PlayerComponentsTest.*',
@@ -37,6 +39,16 @@
         'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/*c3*',
         'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/*c3*',
         'AudioDecoderTests/AudioDecoderTest.SingleInputHEAAC/*c3*',
+        # TODO: b/333416764
+        'SbSocketAddressTypes/SbSocketBindTest.RainyDayBadInterface/type_ipv6_filter_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/type_ipv6',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/type_ipv6',
+        'SbSocketAddressTypes/SbSocketResolveTest.SunnyDayFiltered/filter_ipv6_type_ipv6',
+        # TODO: b/333412348
+        'VideoDecoderTests/VideoDecoderTest.SingleInput/beneath_the_canopy_248_vp9_dmp_DecodeToTexture',
+        'VideoDecoderTests/VideoDecoderTest.ResetBeforeInput/beneath_the_canopy_248_vp9_dmp_DecodeToTexture',
+        'VideoDecoderTests/VideoDecoderTest.MultipleResets/beneath_the_canopy_248_vp9_dmp_DecodeToTexture',
     ],
 }
 
diff --git a/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc b/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
index d8305f0..188878d 100644
--- a/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
+++ b/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "media/base/demuxer_memory_limit.h"
 
 #include "media/base/decoder_buffer.h"
diff --git a/third_party/chromium/media/base/demuxer_stream.h b/third_party/chromium/media/base/demuxer_stream.h
index 67769b3..f1a9754 100644
--- a/third_party/chromium/media/base/demuxer_stream.h
+++ b/third_party/chromium/media/base/demuxer_stream.h
@@ -70,7 +70,7 @@
 
 #if defined(STARBOARD)
   virtual std::string mime_type() const { return ""; }
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   // Request a buffer to returned via the provided callback.
   //
@@ -80,10 +80,10 @@
 #if defined(STARBOARD)
   typedef base::OnceCallback<void(Status, const std::vector<scoped_refptr<DecoderBuffer>>&)> ReadCB;
   virtual void Read(int max_number_of_buffers_to_read, ReadCB read_cb) = 0;
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
   typedef base::OnceCallback<void(Status, scoped_refptr<DecoderBuffer>)> ReadCB;
   virtual void Read(ReadCB read_cb) = 0;
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   // Returns the audio/video decoder configuration. It is an error to call the
   // audio method on a video stream and vice versa. After |kConfigChanged| is
diff --git a/third_party/chromium/media/base/starboard_utils.cc b/third_party/chromium/media/base/starboard_utils.cc
index 1518fb9..786b4b2 100644
--- a/third_party/chromium/media/base/starboard_utils.cc
+++ b/third_party/chromium/media/base/starboard_utils.cc
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "media/base/starboard_utils.h"
 
 #include <algorithm>
diff --git a/third_party/chromium/media/base/starboard_utils.h b/third_party/chromium/media/base/starboard_utils.h
index 50920bd..6f908e5 100644
--- a/third_party/chromium/media/base/starboard_utils.h
+++ b/third_party/chromium/media/base/starboard_utils.h
@@ -15,6 +15,10 @@
 #ifndef MEDIA_BASE_STARBOARD_UTILS_H_
 #define MEDIA_BASE_STARBOARD_UTILS_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "starboard/drm.h"
 #include "starboard/media.h"
 #include "media/base/audio_codecs.h"
diff --git a/third_party/chromium/media/base/starboard_utils_test.cc b/third_party/chromium/media/base/starboard_utils_test.cc
index ab5460c..a2feb6d 100644
--- a/third_party/chromium/media/base/starboard_utils_test.cc
+++ b/third_party/chromium/media/base/starboard_utils_test.cc
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "media/base/starboard_utils.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/third_party/chromium/media/base/status.h b/third_party/chromium/media/base/status.h
index 6101e3f..5ddbda4 100644
--- a/third_party/chromium/media/base/status.h
+++ b/third_party/chromium/media/base/status.h
@@ -17,7 +17,9 @@
 #include "media/base/media_export.h"
 #include "media/base/media_serializers_base.h"
 #include "media/base/status_codes.h"
+#if defined(STARBOARD)
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#endif  // defined(STARBOARD)
 
 // Mojo namespaces for serialization friend declarations.
 namespace mojo {
diff --git a/third_party/chromium/media/cobalt/base/bind_post_task.h b/third_party/chromium/media/cobalt/base/bind_post_task.h
index b914e8a..634a1d7 100644
--- a/third_party/chromium/media/cobalt/base/bind_post_task.h
+++ b/third_party/chromium/media/cobalt/base/bind_post_task.h
@@ -5,6 +5,10 @@
 #ifndef BASE_BIND_POST_TASK_H_
 #define BASE_BIND_POST_TASK_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/location.h"
 
 namespace base {
diff --git a/third_party/chromium/media/cobalt/base/check.h b/third_party/chromium/media/cobalt/base/check.h
index d26320b..17464da 100644
--- a/third_party/chromium/media/cobalt/base/check.h
+++ b/third_party/chromium/media/cobalt/base/check.h
@@ -15,6 +15,10 @@
 #ifndef BASE_CHECK_H_
 #define BASE_CHECK_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/logging.h"
 
 #endif  // BASE_CHECK_H_
diff --git a/third_party/chromium/media/cobalt/base/check_op.h b/third_party/chromium/media/cobalt/base/check_op.h
index 296c84f..74e046f 100644
--- a/third_party/chromium/media/cobalt/base/check_op.h
+++ b/third_party/chromium/media/cobalt/base/check_op.h
@@ -15,6 +15,10 @@
 #ifndef BASE_CHECK_OP_H_
 #define BASE_CHECK_OP_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/logging.h"
 
 #endif  // BASE_CHECK_OP_H_
diff --git a/third_party/chromium/media/cobalt/base/cxx17_backports.h b/third_party/chromium/media/cobalt/base/cxx17_backports.h
index a7c1c13..2f6e2ac 100644
--- a/third_party/chromium/media/cobalt/base/cxx17_backports.h
+++ b/third_party/chromium/media/cobalt/base/cxx17_backports.h
@@ -15,6 +15,10 @@
 #ifndef BASE_CXX17_BACKPORTS_H_
 #define BASE_CXX17_BACKPORTS_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/stl_util.h"
 
 #endif  // BASE_CXX17_BACKPORTS_H_
diff --git a/third_party/chromium/media/cobalt/base/hash/md5.h b/third_party/chromium/media/cobalt/base/hash/md5.h
index 2993131..8f9e6c0 100644
--- a/third_party/chromium/media/cobalt/base/hash/md5.h
+++ b/third_party/chromium/media/cobalt/base/hash/md5.h
@@ -15,6 +15,10 @@
 #ifndef BASE_HASH_MD5_H_
 #define BASE_HASH_MD5_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/md5.h"
 
 #endif  // BASE_HASH_MD5_H_
diff --git a/third_party/chromium/media/cobalt/base/notreached.h b/third_party/chromium/media/cobalt/base/notreached.h
index 0afca93..4eb09e6 100644
--- a/third_party/chromium/media/cobalt/base/notreached.h
+++ b/third_party/chromium/media/cobalt/base/notreached.h
@@ -15,6 +15,10 @@
 #ifndef BASE_NOTREACHED_H_
 #define BASE_NOTREACHED_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include "base/logging.h"
 
 #endif  // BASE_NOTREACHED_H_
diff --git a/third_party/chromium/media/cobalt/base/types/strong_alias.h b/third_party/chromium/media/cobalt/base/types/strong_alias.h
index 8c148d4..3fa4447 100644
--- a/third_party/chromium/media/cobalt/base/types/strong_alias.h
+++ b/third_party/chromium/media/cobalt/base/types/strong_alias.h
@@ -5,6 +5,10 @@
 #ifndef BASE_TYPES_STRONG_ALIAS_H_
 #define BASE_TYPES_STRONG_ALIAS_H_
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 #include <ostream>
 #include <type_traits>
 #include <utility>
diff --git a/third_party/chromium/media/cobalt/net/cookies/site_for_cookies.h b/third_party/chromium/media/cobalt/net/cookies/site_for_cookies.h
index 296e3e4..cf6b040 100644
--- a/third_party/chromium/media/cobalt/net/cookies/site_for_cookies.h
+++ b/third_party/chromium/media/cobalt/net/cookies/site_for_cookies.h
@@ -5,6 +5,10 @@
 #ifndef NET_SITE_FOR_COOKIES_H
 #define NET_SITE_FOR_COOKIES_H
 
+#if !defined(STARBOARD)
+#error "This file only works with Cobalt/Starboard."
+#endif  // !defined(STARBOARD)
+
 namespace net {
 
 // Reduced version enough to make media code depending on it to be built.
diff --git a/third_party/chromium/media/filters/chunk_demuxer.cc b/third_party/chromium/media/filters/chunk_demuxer.cc
index e24189d..da5b8b1 100644
--- a/third_party/chromium/media/filters/chunk_demuxer.cc
+++ b/third_party/chromium/media/filters/chunk_demuxer.cc
@@ -17,7 +17,7 @@
 #include "base/strings/string_number_conversions.h"
 #if defined(STARBOARD)
 #include "base/strings/string_split.h"
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "media/base/audio_decoder_config.h"
@@ -63,7 +63,6 @@
 }
 
 #if defined(STARBOARD)
-
 // Parse type and codecs from mime type. It will return "video/mp4" and
 // "avc1.42E01E, mp4a.40.2" for "video/mp4; codecs="avc1.42E01E, mp4a.40.2".
 // Note that this function does minimum validation as the media stack will check
@@ -95,8 +94,7 @@
   // underlying parsers.
   return true;
 }
-
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
 }  // namespace
 
@@ -107,10 +105,10 @@
                                        MediaTrack::Id media_track_id)
     : mime_type_(mime_type),
       type_(type),
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
 ChunkDemuxerStream::ChunkDemuxerStream(Type type, MediaTrack::Id media_track_id)
     : type_(type),
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
       liveness_(DemuxerStream::LIVENESS_UNKNOWN),
       media_track_id_(media_track_id),
       state_(UNINITIALIZED),
@@ -132,9 +130,9 @@
   if (read_cb_) {
 #if defined(STARBOARD)
     std::move(read_cb_).Run(kAborted, {});
-#else // defined (STARBOARD)
+#else // defined(STARBOARD)
     std::move(read_cb_).Run(kAborted, nullptr);
-#endif // defined (STARBOARD)
+#endif // defined(STARBOARD)
   }
 }
 
@@ -156,10 +154,10 @@
   if (read_cb_) {
 #if defined(STARBOARD)
     std::move(read_cb_).Run(DemuxerStream::kOk, {StreamParserBuffer::CreateEOSBuffer()});
-#else // defined (STARBOARD)
+#else // defined(STARBOARD)
     std::move(read_cb_).Run(DemuxerStream::kOk,
                             StreamParserBuffer::CreateEOSBuffer());
-#endif // defined (STARBOARD)
+#endif // defined(STARBOARD)
   }
 }
 
@@ -182,7 +180,7 @@
 
 #if defined(STARBOARD)
   write_head_ = time;
-#endif // defined (STARBOARD)
+#endif // defined(STARBOARD)
   stream_->Seek(time);
 }
 
@@ -229,7 +227,6 @@
 }
 
 #if defined(STARBOARD)
-
 base::TimeDelta ChunkDemuxerStream::GetWriteHead() const {
   base::AutoLock auto_lock(lock_);
   return write_head_;
@@ -246,8 +243,6 @@
   base::AutoLock auto_lock(lock_);
   stream_->set_memory_limit_override(memory_limit);
 }
-
-
 #endif  // defined(STARBOARD)
 
 void ChunkDemuxerStream::OnMemoryPressure(
@@ -335,9 +330,9 @@
     DCHECK_EQ(state_, UNINITIALIZED);
 #if defined(STARBOARD)
     stream_ = std::make_unique<SourceBufferStream>(mime_type_, config, media_log);
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
     stream_ = std::make_unique<SourceBufferStream>(config, media_log);
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
     return true;
   }
 
@@ -355,9 +350,9 @@
     DCHECK_EQ(state_, UNINITIALIZED);
 #if defined(STARBOARD)
     stream_ = std::make_unique<SourceBufferStream>(mime_type_, config, media_log);
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
     stream_ = std::make_unique<SourceBufferStream>(config, media_log);
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
     return true;
   }
 
@@ -889,7 +884,6 @@
 }
 
 #if defined(STARBOARD)
-
 ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
                                          const std::string& mime_type) {
   std::string type, codecs;
@@ -901,8 +895,7 @@
   id_to_mime_map_[id] = mime_type;
   return AddId(id, type, codecs);
 }
-
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
 ChunkDemuxer::Status ChunkDemuxer::AddIdInternal(
     const std::string& id,
@@ -1107,7 +1100,6 @@
 }
 
 #if defined(STARBOARD)
-
 base::TimeDelta ChunkDemuxer::GetWriteHead(const std::string& id) const {
   base::AutoLock auto_lock(lock_);
   DCHECK(IsValidId(id));
@@ -1138,7 +1130,6 @@
   }
   return source_state_map_[id]->GetSourceBufferStreamMemoryLimit();
 }
-
 #endif  // defined(STARBOARD)
 
 bool ChunkDemuxer::AppendData(const std::string& id,
@@ -1689,7 +1680,7 @@
 #else  // defined(STARBOARD)
   std::unique_ptr<ChunkDemuxerStream> stream =
       std::make_unique<ChunkDemuxerStream>(type, media_track_id);
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   DCHECK(track_id_to_demux_stream_map_.find(media_track_id) ==
          track_id_to_demux_stream_map_.end());
diff --git a/third_party/chromium/media/filters/chunk_demuxer.h b/third_party/chromium/media/filters/chunk_demuxer.h
index 76a01f4..52ebae4 100644
--- a/third_party/chromium/media/filters/chunk_demuxer.h
+++ b/third_party/chromium/media/filters/chunk_demuxer.h
@@ -43,9 +43,9 @@
 
 #if defined(STARBOARD)
   ChunkDemuxerStream(const std::string& mime_type, Type type, MediaTrack::Id media_track_id);
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
   ChunkDemuxerStream(Type type, MediaTrack::Id media_track_id);
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
   ~ChunkDemuxerStream() override;
 
   // ChunkDemuxerStream control methods.
@@ -134,14 +134,10 @@
   std::string mime_type() const override { return mime_type_; }
   size_t GetStreamMemoryLimit();
   void SetStreamMemoryLimitOverride(size_t memory_limit);
-#endif  // defined (STARBOARD)
-
-#if defined(STARBOARD)
   void Read(int max_number_of_buffers_to_read, ReadCB read_cb) override;
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
   void Read(ReadCB read_cb) override;
-#endif  // defined (STARBOARD)
-
+#endif  // defined(STARBOARD)
   Type type() const override;
   Liveness liveness() const override;
   AudioDecoderConfig audio_decoder_config() override;
@@ -193,7 +189,7 @@
   const std::string mime_type_;
   int max_number_of_buffers_to_read_{1};
   bool pending_config_change_ {false};
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   // Specifies the type of the stream.
   const Type type_;
@@ -295,7 +291,7 @@
   // Special version of AddId() that retains the |mime_type| from the web app.
   Status AddId(const std::string& id, const std::string& mime_type)
       WARN_UNUSED_RESULT;
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   // Notifies a caller via |tracks_updated_cb| that the set of media tracks
   // for a given |id| has changed. This callback must be set before any calls to
@@ -394,7 +390,6 @@
 
 #if defined(STARBOARD)
   base::TimeDelta GetWriteHead(const std::string& id) const;
-
   void SetSourceBufferStreamMemoryLimit(const std::string& guid, size_t limit);
   size_t GetSourceBufferStreamMemoryLimit(const std::string& guid);
 #endif  // defined(STARBOARD)
@@ -600,7 +595,7 @@
 
 #if defined(STARBOARD)
   std::map<std::string, std::string> id_to_mime_map_;
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 };
 
 }  // namespace media
diff --git a/third_party/chromium/media/filters/source_buffer_stream.cc b/third_party/chromium/media/filters/source_buffer_stream.cc
index 6d6789f..f16184b 100644
--- a/third_party/chromium/media/filters/source_buffer_stream.cc
+++ b/third_party/chromium/media/filters/source_buffer_stream.cc
@@ -165,11 +165,11 @@
                                        MediaLog* media_log)
     : mime_type_(mime_type),
       media_log_(media_log),
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
 SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config,
                                        MediaLog* media_log)
     : media_log_(media_log),
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
       seek_buffer_timestamp_(kNoTimestamp),
       coded_frame_group_start_pts_(kNoTimestamp),
       range_for_next_append_(ranges_.end()),
@@ -188,11 +188,11 @@
                                        MediaLog* media_log)
     : mime_type_(mime_type),
       media_log_(media_log),
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
 SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config,
                                        MediaLog* media_log)
     : media_log_(media_log),
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
       seek_buffer_timestamp_(kNoTimestamp),
       coded_frame_group_start_pts_(kNoTimestamp),
       range_for_next_append_(ranges_.end()),
@@ -201,8 +201,12 @@
           base::Milliseconds(kMinimumInterbufferDistanceInMs)),
       memory_limit_(
           GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer,
+#if !defined(STARBOARD)
+                                           &video_config)) {
+#else // !defined(STARBOARD)
                                            &video_config,
                                            mime_type_)) {
+#endif  // !defined(STARBOARD)
   DCHECK(video_config.IsValidConfig());
   video_configs_.push_back(video_config);
   DVLOG(2) << __func__ << ": video_buffer_size= " << memory_limit_;
@@ -2086,6 +2090,6 @@
   return duration;
 }
 
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
 }  // namespace media
diff --git a/third_party/chromium/media/filters/source_buffer_stream.h b/third_party/chromium/media/filters/source_buffer_stream.h
index 248b38e..c073ad9 100644
--- a/third_party/chromium/media/filters/source_buffer_stream.h
+++ b/third_party/chromium/media/filters/source_buffer_stream.h
@@ -68,12 +68,12 @@
   SourceBufferStream(const std::string& mime_type,
                      const VideoDecoderConfig& video_config,
                      MediaLog* media_log);
-#else  // defined (STARBOARD)
+#else  // defined(STARBOARD)
   SourceBufferStream(const AudioDecoderConfig& audio_config,
                      MediaLog* media_log);
   SourceBufferStream(const VideoDecoderConfig& video_config,
                      MediaLog* media_log);
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   SourceBufferStream(const TextTrackConfig& text_config, MediaLog* media_log);
 
@@ -196,7 +196,7 @@
     memory_limit_ = memory_limit;
     memory_override_ = true;
   }
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
  private:
   friend class SourceBufferStreamTest;
@@ -416,7 +416,7 @@
 
   const std::string mime_type_;
   bool memory_override_ = false;
-#endif  // defined (STARBOARD)
+#endif  // defined(STARBOARD)
 
   // Used to report log messages that can help the web developer figure out what
   // is wrong with the content.
diff --git a/third_party/crashpad/wrapper/wrapper.cc b/third_party/crashpad/wrapper/wrapper.cc
index 4d16aef..93eb5ae 100644
--- a/third_party/crashpad/wrapper/wrapper.cc
+++ b/third_party/crashpad/wrapper/wrapper.cc
@@ -94,11 +94,16 @@
   return base::FilePath(crashpad_directory_path.c_str());
 }
 
-void InitializeCrashpadDatabase(const base::FilePath database_directory_path) {
+bool InitializeCrashpadDatabase(const base::FilePath database_directory_path) {
   std::unique_ptr<::crashpad::CrashReportDatabase> database =
       ::crashpad::CrashReportDatabase::Initialize(database_directory_path);
+  if (!database) {
+    return false;
+  }
+
   ::crashpad::Settings* settings = database->GetSettings();
   settings->SetUploadsEnabled(true);
+  return true;
 }
 
 std::string GetProductName() {
@@ -190,8 +195,8 @@
 
   const base::FilePath handler_path = GetPathToCrashpadHandlerBinary();
   if (!SbFileExists(handler_path.value().c_str())) {
-    LOG(WARNING) << "crashpad_handler not at expected location of "
-                 << handler_path.value();
+    LOG(ERROR) << "crashpad_handler not at expected location of "
+               << handler_path.value();
     return;
   }
 
@@ -207,7 +212,17 @@
   const std::map<std::string, std::string> platform_info = GetPlatformInfo();
   default_annotations.insert(platform_info.begin(), platform_info.end());
 
-  InitializeCrashpadDatabase(database_directory_path);
+  if (!InitializeCrashpadDatabase(database_directory_path)) {
+    LOG(ERROR) << "Failed to initialize Crashpad database";
+
+    // As we investigate b/329458881 we may find that it's safe to continue with
+    // installation of the Crashpad handler here with the hope that the handler,
+    // when it runs, doesn't experience the same failure when initializing the
+    // Crashpad database. For now it seems safer to just give up on crash
+    // reporting for this particular Cobalt session.
+    return;
+  }
+
   client->SetUnhandledSignals({});
 
   if (start_at_crash)
diff --git a/tools/metrics/histograms/metadata/cobalt/enums.xml b/tools/metrics/histograms/metadata/cobalt/enums.xml
index feb372a..e3f4f19 100644
--- a/tools/metrics/histograms/metadata/cobalt/enums.xml
+++ b/tools/metrics/histograms/metadata/cobalt/enums.xml
@@ -65,7 +65,9 @@
   <int value="21" label="PIPELINE_ERROR_EXTERNAL_RENDERER_FAILED"/>
   <int value="22" label="DEMUXER_ERROR_DETECTED_HLS"/>
   <int value="23" label="PIPELINE_ERROR_HARDWARE_CONTEXT_RESET"/>
-  <int value="24" label="PLAYBACK_CAPABILITY_CHANGED"/>
+  <!-- Deprecated. Capability change errors are reported with
+       PIPELINE_ERROR_DECODE  -->
+  <int value="24" label="PLAYBACK_CAPABILITY_CHANGED (deprecated)"/>
 </enum>
 
 
diff --git a/tools/metrics/histograms/metadata/cobalt/histograms.xml b/tools/metrics/histograms/metadata/cobalt/histograms.xml
index 7a5547a..3393173 100644
--- a/tools/metrics/histograms/metadata/cobalt/histograms.xml
+++ b/tools/metrics/histograms/metadata/cobalt/histograms.xml
@@ -67,6 +67,28 @@
   </summary>
 </histogram>
 
+<histogram name="Cobalt.Media.HTMLMediaElement.CanPlayType.Timing"
+    units="microseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of format query latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Timing data for calls to `HTMLMediaElement.canPlayType`.
+  </summary>
+</histogram>
+
+<histogram name="Cobalt.Media.MediaSource.IsTypeSupported.Timing"
+    units="microseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of format query latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Timing data for calls to `MediaSource.isTypeSupported`.
+  </summary>
+</histogram>
+
 <histogram name="Cobalt.Media.PipelineStatus.AudioOnly" enum="PipelineStatus"
     expires_after="never">
 <!-- expires-never: Needed for baseline Media pipeline health metric. -->
@@ -151,6 +173,51 @@
   </summary>
 </histogram>
 
+<histogram name="Cobalt.Media.SourceBuffer.AppendBuffer.Timing"
+    units="microseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of SourceBuffer latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Timing data for the main action of the `SourceBuffer.appendBuffer` call.
+  </summary>
+</histogram>
+
+<histogram name="Cobalt.Media.SourceBuffer.PrepareAppend.Timing"
+    units="microseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of SourceBuffer latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Timing data for preparation of `SourceBuffer.appendBuffer` calls.
+  </summary>
+</histogram>
+
+<histogram name="Cobalt.Media.SourceBuffer.Other.Timing"
+    units="microseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of SourceBuffer latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Catch-all for timing data for `SourceBuffer.appendBuffer` calls. This
+    histogram is not expected to receive data normally.
+  </summary>
+</histogram>
+
+<histogram name="Cobalt.Media.WebMediaPlayer.Seek.Timing"
+    units="milliseconds" expires_after="never">
+<!-- expires-never: Needed for long-term tracking of Seek latency. -->
+
+  <owner>async@google.com</owner>
+  <owner>cobalt-team@google.com</owner>
+  <summary>
+    Timing data for the seek latency of WebMediaPlayer.
+  </summary>
+</histogram>
+
 <histogram name="Cobalt.MediaDevices.MicCreationSucceeded" enum="Boolean"
     expires_after="never">
 <!-- expires-never: Needed for long-term tracking of device mic support. -->