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_, ×tamp_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. -->