Import Cobalt 23.lts.6.1031897
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 860c540..1155938 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -149,14 +149,6 @@
stages: [push]
always_run: true
pass_filenames: false
- - id: run-py2-tests
- name: Run Python 2 Tests
- description: Run Python 2 unittests
- entry: python precommit_hooks/run_python2_unittests.py
- language: python
- language_version: python2.7
- additional_dependencies: ['mock']
- types: [python]
- id: osslint
name: osslint
entry: python precommit_hooks/osslint_wrapper.py
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 07a6de0..7dccc07 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -158,6 +158,7 @@
"//cobalt/browser/memory_settings:browser_memory_settings",
"//cobalt/browser/memory_tracker:memory_tracker_tool",
"//cobalt/build:cobalt_build_id",
+ "//cobalt/build:cobalt_build_info",
"//cobalt/cache",
"//cobalt/configuration",
"//cobalt/css_parser",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index fe4c098..7e541f0 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -65,6 +65,7 @@
#include "cobalt/configuration/configuration.h"
#include "cobalt/extension/crash_handler.h"
#include "cobalt/extension/installation_manager.h"
+#include "cobalt/h5vcc/h5vcc_crash_log.h"
#include "cobalt/loader/image/image_decoder.h"
#include "cobalt/math/size.h"
#include "cobalt/script/javascript_engine.h"
@@ -589,6 +590,26 @@
}
}
+void AddCrashLogApplicationState(base::ApplicationState state) {
+ std::string application_state = std::string(GetApplicationStateString(state));
+ application_state.push_back('\0');
+
+ auto crash_handler_extension =
+ static_cast<const CobaltExtensionCrashHandlerApi*>(
+ SbSystemGetExtension(kCobaltExtensionCrashHandlerName));
+ if (crash_handler_extension && crash_handler_extension->version >= 2) {
+ if (!crash_handler_extension->SetString("application_state",
+ application_state.c_str())) {
+ LOG(ERROR) << "Could not send application state to crash handler.";
+ }
+ return;
+ }
+
+ // Crash handler is not supported, fallback to crash log dictionary.
+ h5vcc::CrashLogDictionary::GetInstance()->SetString("application_state",
+ application_state);
+}
+
} // namespace
// Helper stub to disable histogram tracking in StatisticsRecorder
@@ -1198,6 +1219,7 @@
case kSbEventTypeStop:
LOG(INFO) << "Got quit event.";
if (watchdog) watchdog->UpdateState(base::kApplicationStateStopped);
+ AddCrashLogApplicationState(base::kApplicationStateStopped);
Quit();
LOG(INFO) << "Finished quitting.";
break;
@@ -1316,6 +1338,7 @@
return;
}
if (watchdog) watchdog->UpdateState(browser_module_->GetApplicationState());
+ AddCrashLogApplicationState(browser_module_->GetApplicationState());
}
void Application::OnWindowSizeChangedEvent(const base::Event* event) {
diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc
index 3531cbf..f1158dd 100644
--- a/cobalt/browser/web_module.cc
+++ b/cobalt/browser/web_module.cc
@@ -141,7 +141,21 @@
~Impl();
#if defined(ENABLE_DEBUGGER)
+ void EnsureDebugModule(
+ debug::backend::DebuggerState* debugger_state = nullptr) {
+ if (!debug_overlay_) {
+ debug_overlay_.reset(
+ new debug::backend::RenderOverlay(render_tree_produced_callback_));
+ }
+ if (!debug_module_) {
+ debug_module_.reset(new debug::backend::DebugModule(
+ &debugger_hooks_, web_context_->global_environment(),
+ debug_overlay_.get(), resource_provider_, window_, debugger_state));
+ }
+ }
+
debug::backend::DebugDispatcher* debug_dispatcher() {
+ EnsureDebugModule();
DCHECK(debug_module_);
return debug_module_->debug_dispatcher();
}
@@ -229,7 +243,8 @@
void FreezeDebugger(
std::unique_ptr<debug::backend::DebuggerState>* debugger_state) {
- if (debugger_state) *debugger_state = debug_module_->Freeze();
+ if (debugger_state && debug_module_)
+ *debugger_state = debug_module_->Freeze();
}
#endif // defined(ENABLE_DEBUGGER)
@@ -549,6 +564,7 @@
data.options.loader_thread_priority));
animated_image_tracker_.reset(new loader::image::AnimatedImageTracker(
+ web_context_->name().c_str(),
data.options.animated_image_decode_thread_priority));
DCHECK_LE(0, data.options.image_cache_capacity);
@@ -744,14 +760,9 @@
}
#if defined(ENABLE_DEBUGGER)
- debug_overlay_.reset(
- new debug::backend::RenderOverlay(render_tree_produced_callback_));
-
- debug_module_.reset(new debug::backend::DebugModule(
- &debugger_hooks_, web_context_->global_environment(),
- debug_overlay_.get(), resource_provider_, window_,
- data.options.debugger_state));
-#endif // ENABLE_DEBUGGER
+ if (data.options.debugger_state)
+ EnsureDebugModule(data.options.debugger_state);
+#endif
report_unload_timing_info_callback_ =
data.options.collect_unload_event_time_callback;
@@ -982,7 +993,11 @@
last_render_tree_produced_time_));
#if defined(ENABLE_DEBUGGER)
- debug_overlay_->OnRenderTreeProduced(layout_results_with_callback);
+ if (debug_overlay_) {
+ debug_overlay_->OnRenderTreeProduced(layout_results_with_callback);
+ } else {
+ render_tree_produced_callback_.Run(layout_results_with_callback);
+ }
#else // ENABLE_DEBUGGER
render_tree_produced_callback_.Run(layout_results_with_callback);
#endif // ENABLE_DEBUGGER
@@ -1003,6 +1018,7 @@
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
web_module_stat_tracker_->OnRenderTreeRasterized(produced_time,
rasterized_time);
+ animated_image_tracker_->OnRenderTreeRasterized();
if (produced_time >= last_render_tree_produced_time_) {
is_render_tree_rasterization_pending_ = false;
}
@@ -1075,7 +1091,7 @@
"\n Waiting for web debugger to connect "
"\n-------------------------------------";
// This blocks until the web debugger connects.
- debug_module_->debug_dispatcher()->SetPaused(true);
+ debug_dispatcher()->SetPaused(true);
waiting_for_web_debugger_->store(false);
}
#endif // defined(ENABLE_DEBUGGER)
@@ -1166,7 +1182,9 @@
#if defined(ENABLE_DEBUGGER)
// The debug overlay may be holding onto a render tree, clear that out.
- debug_overlay_->ClearInput();
+ if (debug_overlay_) {
+ debug_overlay_->ClearInput();
+ }
#endif
// Force garbage collection in |javascript_engine|.
diff --git a/cobalt/build/BUILD.gn b/cobalt/build/BUILD.gn
index 6c6e1e8..7b6a002 100644
--- a/cobalt/build/BUILD.gn
+++ b/cobalt/build/BUILD.gn
@@ -23,3 +23,9 @@
cobalt_version,
]
}
+
+action("cobalt_build_info") {
+ script = "build_info.py"
+ outputs = [ "$root_gen_dir/build_info.json" ]
+ args = [ rebase_path(outputs[0], root_build_dir) ]
+}
diff --git a/cobalt/build/build.id b/cobalt/build/build.id
index 3d15dd7..20a1fde 100644
--- a/cobalt/build/build.id
+++ b/cobalt/build/build.id
@@ -1 +1 @@
-1031871
\ No newline at end of file
+1031897
\ No newline at end of file
diff --git a/cobalt/build/build_info.py b/cobalt/build/build_info.py
new file mode 100755
index 0000000..52f4494
--- /dev/null
+++ b/cobalt/build/build_info.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Generates a Cobalt Build Info json."""
+
+import datetime
+import json
+import os
+import re
+import subprocess
+import sys
+
+FILE_DIR = os.path.dirname(__file__)
+COMMIT_COUNT_BUILD_ID_OFFSET = 1000000
+
+_BUILD_ID_PATTERN = '^BUILD_NUMBER=([1-9][0-9]{6,})$'
+_GIT_REV_PATTERN = '^GitOrigin-RevId: ([0-9a-f]{40})$'
+_COBALT_VERSION_PATTERN = '^#define COBALT_VERSION "(.*)"$'
+
+
+def get_build_id_and_git_rev_from_commits(cwd):
+ # Build id and git rev must come from the same commit.
+ output = subprocess.check_output(
+ ['git', 'log', '--grep', _BUILD_ID_PATTERN, '-1', '-E', '--pretty=%b'],
+ cwd=cwd).decode()
+
+ # Gets build id.
+ compiled_build_id_pattern = re.compile(_BUILD_ID_PATTERN, flags=re.MULTILINE)
+ match_build_id = compiled_build_id_pattern.search(output)
+ if not match_build_id:
+ return None, None
+ build_id = match_build_id.group(1)
+
+ # Gets git rev.
+ compiled_git_rev_pattern = re.compile(_GIT_REV_PATTERN, flags=re.MULTILINE)
+ match_git_rev = compiled_git_rev_pattern.search(output)
+ if not match_git_rev:
+ git_rev = ''
+ else:
+ git_rev = match_git_rev.group(1)
+
+ return build_id, git_rev
+
+
+def get_build_id_from_commit_count(cwd):
+ output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
+ cwd=cwd)
+ build_id = int(output.strip().decode()) + COMMIT_COUNT_BUILD_ID_OFFSET
+ return str(build_id)
+
+
+def _get_last_commit_with_format(placeholder, cwd):
+ output = subprocess.check_output(
+ ['git', 'log', '-1', f'--pretty=format:{placeholder}'], cwd=cwd)
+ return output.strip().decode()
+
+
+def _get_hash_from_last_commit(cwd):
+ return _get_last_commit_with_format(r'%H', cwd=cwd)
+
+
+def _get_author_from_last_commit(cwd):
+ return _get_last_commit_with_format(r'%an', cwd=cwd)
+
+
+def _get_subject_from_last_commit(cwd):
+ return _get_last_commit_with_format(r'%s', cwd=cwd)
+
+
+def _get_cobalt_version():
+ version_header_path = os.path.join(os.path.dirname(FILE_DIR), 'version.h')
+ contents = ''
+ with open(version_header_path, 'r', encoding='utf-8') as f:
+ contents = f.read()
+ compiled_cobalt_version_pattern = re.compile(
+ _COBALT_VERSION_PATTERN, flags=re.MULTILINE)
+ return compiled_cobalt_version_pattern.search(contents).group(1)
+
+
+def main(output_path, cwd=FILE_DIR):
+ """Writes a Cobalt build_info json file."""
+ build_rev = _get_hash_from_last_commit(cwd=cwd)
+ build_id, git_rev = get_build_id_and_git_rev_from_commits(cwd=cwd)
+ if build_id is None:
+ build_id = get_build_id_from_commit_count(cwd=cwd)
+ git_rev = build_rev
+ cobalt_version = _get_cobalt_version()
+ build_time = datetime.datetime.now().ctime()
+ author = _get_author_from_last_commit(cwd=cwd)
+ commit = _get_subject_from_last_commit(cwd=cwd)
+
+ info_json = {
+ 'build_id': build_id,
+ 'build_rev': build_rev,
+ 'git_rev': git_rev,
+ 'cobalt_version': cobalt_version,
+ 'build_time': build_time,
+ 'author': author,
+ 'commit': commit
+ }
+
+ with open(output_path, 'w', encoding='utf-8') as f:
+ f.write(json.dumps(info_json, indent=4))
+
+ # Supports legacy build_info.txt.
+ output_text_path = os.path.join(
+ os.path.dirname(output_path), 'build_info.txt')
+ with open(output_text_path, 'w', encoding='utf-8') as f:
+ f.write(f'''\
+Build ID: {build_id}
+Build Rev: {build_rev}
+Git Rev: {git_rev}
+Cobalt Version: {cobalt_version}
+Build Time: {build_time}
+Author: {author}
+Commit: {commit}
+''')
+
+
+if __name__ == '__main__':
+ main(sys.argv[1])
diff --git a/cobalt/build/get_build_id.py b/cobalt/build/get_build_id.py
index 9c19e4c..ba86bb3 100755
--- a/cobalt/build/get_build_id.py
+++ b/cobalt/build/get_build_id.py
@@ -15,35 +15,10 @@
"""Prints out the Cobalt Build ID."""
import os
-import re
-import subprocess
+from cobalt.build import build_info
from cobalt.build.build_number import GetOrGenerateNewBuildNumber
-_FILE_DIR = os.path.dirname(__file__)
-COMMIT_COUNT_BUILD_NUMBER_OFFSET = 1000000
-
-# Matches numbers > 1000000. The pattern is basic so git log --grep is able to
-# interpret it.
-GIT_BUILD_NUMBER_PATTERN = r'[1-9]' + r'[0-9]' * 6 + r'[0-9]*'
-BUILD_NUMBER_TAG_PATTERN = r'^BUILD_NUMBER={}$'
-
-# git log --grep can't handle capture groups.
-BUILD_NUBER_PATTERN_WITH_CAPTURE = f'({GIT_BUILD_NUMBER_PATTERN})'
-
-
-def get_build_number_from_commits():
- full_pattern = BUILD_NUMBER_TAG_PATTERN.format(GIT_BUILD_NUMBER_PATTERN)
- output = subprocess.check_output(
- ['git', 'log', '--grep', full_pattern, '-1', '--pretty=%b'],
- cwd=_FILE_DIR).decode()
-
- full_pattern_with_capture = re.compile(
- BUILD_NUMBER_TAG_PATTERN.format(BUILD_NUBER_PATTERN_WITH_CAPTURE),
- flags=re.MULTILINE)
- match = full_pattern_with_capture.search(output)
- return match.group(1) if match else None
-
def get_build_number_from_server():
# Note $BUILD_ID_SERVER_URL will always be set in CI.
@@ -57,24 +32,14 @@
return build_num
-def get_build_number_from_commit_count():
- output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
- cwd=_FILE_DIR)
- build_number = int(output.strip().decode('utf-8'))
- return build_number + COMMIT_COUNT_BUILD_NUMBER_OFFSET
-
-
-def main():
- build_number = get_build_number_from_commits()
-
+def main(cwd=build_info.FILE_DIR):
+ build_number, _ = build_info.get_build_id_and_git_rev_from_commits(cwd=cwd)
if not build_number:
build_number = get_build_number_from_server()
-
if not build_number:
- build_number = get_build_number_from_commit_count()
-
- print(build_number)
+ build_number = build_info.get_build_id_from_commit_count(cwd=cwd)
+ return build_number
if __name__ == '__main__':
- main()
+ print(main())
diff --git a/cobalt/build/get_build_id_test.py b/cobalt/build/get_build_id_test.py
new file mode 100644
index 0000000..26e7b2b
--- /dev/null
+++ b/cobalt/build/get_build_id_test.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+#
+# 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.
+#
+"""Tests the get_build_id module."""
+
+import os
+import subprocess
+import tempfile
+import unittest
+
+from cobalt.build import build_info
+from cobalt.build import get_build_id
+
+_TEST_BUILD_NUMBER = 1234 + build_info.COMMIT_COUNT_BUILD_ID_OFFSET
+
+
+# TODO(b/282040638): fix and re-enabled this
+@unittest.skipIf(os.name == 'nt', 'Broken on Windows')
+class GetBuildIdTest(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
+ self.original_cwd = os.getcwd()
+ os.chdir(self.test_dir.name)
+ subprocess.check_call(['git', 'init'])
+ subprocess.check_call(['git', 'config', 'user.name', 'pytest'])
+ subprocess.check_call(['git', 'config', 'user.email', 'pytest@pytest.com'])
+
+ def tearDown(self):
+ os.chdir(self.original_cwd)
+ self.test_dir.cleanup()
+
+ def make_commit(self, message='Temporary commit'):
+ with tempfile.NamedTemporaryFile('w', dir=self.test_dir.name) as temp_file:
+ subprocess.check_call(['git', 'add', temp_file.name])
+ subprocess.check_call(['git', 'commit', '-m', message])
+
+ def make_commit_with_build_number(self, build_number=_TEST_BUILD_NUMBER):
+ message = f'Subject line\n\nBUILD_NUMBER={build_number}'
+ self.make_commit(message)
+
+ def testSanity(self):
+ self.make_commit()
+ head_rev = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
+ self.assertNotEqual(head_rev.strip().decode('utf-8'), '')
+
+ def testGetBuildNumberFromCommitsSunnyDay(self):
+ self.make_commit_with_build_number()
+ build_number, _ = build_info.get_build_id_and_git_rev_from_commits(
+ cwd=self.test_dir.name)
+ self.assertEqual(int(build_number), _TEST_BUILD_NUMBER)
+
+ def testGetBuildNumberFromCommitsSunnyDayGetMostRecent(self):
+ num_commits = 5
+ for i in range(num_commits):
+ self.make_commit_with_build_number(
+ build_info.COMMIT_COUNT_BUILD_ID_OFFSET + i)
+ build_number, _ = build_info.get_build_id_and_git_rev_from_commits(
+ cwd=self.test_dir.name)
+ self.assertEqual(
+ int(build_number),
+ num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET - 1)
+
+ def testGetBuildNumberFromCommitsRainyDayInvalidBuildNumber(self):
+ self.make_commit()
+ self.make_commit(f'BUILD_NUMBER={_TEST_BUILD_NUMBER}')
+ build_number, _ = build_info.get_build_id_and_git_rev_from_commits(
+ cwd=self.test_dir.name)
+ self.assertIsNone(build_number)
+
+ def testGetBuildNumberFromCommitCountSunnyDay(self):
+ num_commits = 5
+ for _ in range(num_commits):
+ self.make_commit()
+ build_number = build_info.get_build_id_from_commit_count(
+ cwd=self.test_dir.name)
+ self.assertEqual(
+ int(build_number),
+ num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+
+ def testCommitsOutrankCommitCount(self):
+ self.make_commit()
+ self.make_commit_with_build_number()
+ self.make_commit()
+ build_number = get_build_id.main(cwd=self.test_dir.name)
+ self.assertEqual(int(build_number), _TEST_BUILD_NUMBER)
+
+ def testFallbackToCommitCount(self):
+ num_commits = 5
+ for _ in range(num_commits):
+ self.make_commit()
+ build_number = get_build_id.main(cwd=self.test_dir.name)
+ self.assertEqual(
+ int(build_number),
+ num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cobalt/debug/BUILD.gn b/cobalt/debug/BUILD.gn
index 91c5586..0bdf9b5 100644
--- a/cobalt/debug/BUILD.gn
+++ b/cobalt/debug/BUILD.gn
@@ -17,6 +17,8 @@
sources = [
"backend/agent_base.cc",
"backend/agent_base.h",
+ "backend/cobalt_agent.cc",
+ "backend/cobalt_agent.h",
"backend/command_map.h",
"backend/css_agent.cc",
"backend/css_agent.h",
@@ -90,6 +92,9 @@
"console/command_manager.cc",
"console/command_manager.h",
]
- deps = [ "//cobalt/base" ]
+ deps = [
+ "//cobalt/base",
+ "//starboard:starboard_headers_only",
+ ]
}
}
diff --git a/cobalt/debug/backend/cobalt_agent.cc b/cobalt/debug/backend/cobalt_agent.cc
new file mode 100644
index 0000000..63901c2
--- /dev/null
+++ b/cobalt/debug/backend/cobalt_agent.cc
@@ -0,0 +1,85 @@
+// 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/debug/backend/cobalt_agent.h"
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "cobalt/debug/console/command_manager.h"
+#include "cobalt/debug/json_object.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+CobaltAgent::CobaltAgent(DebugDispatcher* dispatcher)
+ : AgentBase("Cobalt", dispatcher) {
+ commands_["getConsoleCommands"] =
+ base::Bind(&CobaltAgent::GetConsoleCommands, base::Unretained(this));
+ commands_["sendConsoleCommand"] =
+ base::Bind(&CobaltAgent::SendConsoleCommand, base::Unretained(this));
+ // dispatcher_->AddDomain(domain_, commands_.Bind());
+}
+
+void CobaltAgent::GetConsoleCommands(Command command) {
+ JSONObject response(new base::DictionaryValue());
+ JSONList list(new base::ListValue());
+
+ console::ConsoleCommandManager* command_manager =
+ console::ConsoleCommandManager::GetInstance();
+ DCHECK(command_manager);
+ if (command_manager) {
+ std::set<std::string> commands = command_manager->GetRegisteredCommands();
+ for (auto& command_name : commands) {
+ JSONObject console_command(new base::DictionaryValue());
+ console_command->SetString("command", command_name);
+ console_command->SetString("shortHelp",
+ command_manager->GetShortHelp(command_name));
+ console_command->SetString("longHelp",
+ command_manager->GetLongHelp(command_name));
+ list->Append(std::move(console_command));
+ }
+ }
+
+ JSONObject commands(new base::DictionaryValue());
+ commands->Set("commands", std::move(list));
+ response->Set("result", std::move(commands));
+ command.SendResponse(response);
+}
+
+void CobaltAgent::SendConsoleCommand(Command command) {
+ JSONObject params = JSONParse(command.GetParams());
+ if (params) {
+ std::string console_command;
+ if (params->GetString("command", &console_command)) {
+ std::string message;
+ params->GetString("message", &message);
+ console::ConsoleCommandManager* console_command_manager =
+ console::ConsoleCommandManager::GetInstance();
+ DCHECK(console_command_manager);
+ console_command_manager->HandleCommand(console_command, message);
+ command.SendResponse();
+ return;
+ }
+ }
+ command.SendErrorResponse(Command::kInvalidParams, "Missing command.");
+}
+
+} // namespace backend
+} // namespace debug
+} // namespace cobalt
diff --git a/cobalt/debug/backend/cobalt_agent.h b/cobalt/debug/backend/cobalt_agent.h
new file mode 100644
index 0000000..27f8613
--- /dev/null
+++ b/cobalt/debug/backend/cobalt_agent.h
@@ -0,0 +1,47 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_DEBUG_BACKEND_COBALT_AGENT_H_
+#define COBALT_DEBUG_BACKEND_COBALT_AGENT_H_
+
+#include "cobalt/debug/backend/agent_base.h"
+#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/command.h"
+#include "cobalt/debug/json_object.h"
+#include "cobalt/dom/window.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+// Implements a small part of the the "Runtime" inspector protocol domain with
+// just enough to support console input. When using the V8 JavaScript engine,
+// this class is not needed since the V8 inspector implements the Runtime domain
+// for us.
+//
+// https://chromedevtools.github.io/devtools-protocol/tot/Runtime
+class CobaltAgent : public AgentBase {
+ public:
+ explicit CobaltAgent(DebugDispatcher* dispatcher);
+
+ private:
+ void GetConsoleCommands(Command command);
+ void SendConsoleCommand(Command command);
+};
+
+} // namespace backend
+} // namespace debug
+} // namespace cobalt
+
+#endif // COBALT_DEBUG_BACKEND_COBALT_AGENT_H_
diff --git a/cobalt/debug/backend/debug_module.cc b/cobalt/debug/backend/debug_module.cc
index 56e8b26..ea767af 100644
--- a/cobalt/debug/backend/debug_module.cc
+++ b/cobalt/debug/backend/debug_module.cc
@@ -23,6 +23,7 @@
namespace backend {
namespace {
+constexpr char kCobaltAgent[] = "CobaltAgent";
constexpr char kScriptDebuggerAgent[] = "ScriptDebuggerAgent";
constexpr char kLogAgent[] = "LogAgent";
constexpr char kDomAgent[] = "DomAgent";
@@ -140,6 +141,7 @@
// directly handle one or more protocol domains.
script_debugger_agent_.reset(
new ScriptDebuggerAgent(debug_dispatcher_.get(), script_debugger_.get()));
+ cobalt_agent_.reset(new CobaltAgent(debug_dispatcher_.get()));
log_agent_.reset(new LogAgent(debug_dispatcher_.get()));
dom_agent_.reset(new DOMAgent(debug_dispatcher_.get()));
css_agent_ = WrapRefCounted(new CSSAgent(debug_dispatcher_.get()));
@@ -178,6 +180,7 @@
base::DictionaryValue* agents_state =
data.debugger_state == nullptr ? nullptr
: data.debugger_state->agents_state.get();
+ cobalt_agent_->Thaw(RemoveAgentState(kCobaltAgent, agents_state));
script_debugger_agent_->Thaw(
RemoveAgentState(kScriptDebuggerAgent, agents_state));
log_agent_->Thaw(RemoveAgentState(kLogAgent, agents_state));
@@ -200,6 +203,7 @@
debugger_state->agents_state.reset(new base::DictionaryValue());
base::DictionaryValue* agents_state = debugger_state->agents_state.get();
+ StoreAgentState(agents_state, kCobaltAgent, cobalt_agent_->Freeze());
StoreAgentState(agents_state, kScriptDebuggerAgent,
script_debugger_agent_->Freeze());
StoreAgentState(agents_state, kLogAgent, log_agent_->Freeze());
diff --git a/cobalt/debug/backend/debug_module.h b/cobalt/debug/backend/debug_module.h
index da1778a..4736968 100644
--- a/cobalt/debug/backend/debug_module.h
+++ b/cobalt/debug/backend/debug_module.h
@@ -20,6 +20,7 @@
#include "base/message_loop/message_loop.h"
#include "cobalt/base/debugger_hooks.h"
+#include "cobalt/debug/backend/cobalt_agent.h"
#include "cobalt/debug/backend/css_agent.h"
#include "cobalt/debug/backend/debug_backend.h"
#include "cobalt/debug/backend/debug_dispatcher.h"
@@ -134,6 +135,7 @@
// Wrappable object providing native helpers for backend JavaScript.
scoped_refptr<DebugBackend> debug_backend_;
+ std::unique_ptr<CobaltAgent> cobalt_agent_;
std::unique_ptr<LogAgent> log_agent_;
std::unique_ptr<DOMAgent> dom_agent_;
scoped_refptr<CSSAgent> css_agent_;
diff --git a/cobalt/debug/backend/log_agent.cc b/cobalt/debug/backend/log_agent.cc
index 9fc974a..593fb0a 100644
--- a/cobalt/debug/backend/log_agent.cc
+++ b/cobalt/debug/backend/log_agent.cc
@@ -14,13 +14,22 @@
#include "cobalt/debug/backend/log_agent.h"
+#include "base/bind.h"
#include "base/logging.h"
+#include "cobalt/debug/console/command_manager.h"
namespace cobalt {
namespace debug {
namespace backend {
namespace {
+const char kDebugLogCommand[] = "debug_log";
+const char kDebugLogCommandShortHelp[] =
+ "Turns browser debug logging on or off.";
+const char kDebugLogCommandLongHelp[] =
+ "When turned on, browser logs are sent in such a way that they are visible "
+ "in devtools.";
+
// Error levels:
constexpr char kInfoLevel[] = "info";
constexpr char kWarningLevel[] = "warning";
@@ -42,7 +51,21 @@
}
} // namespace
-LogAgent::LogAgent(DebugDispatcher* dispatcher) : AgentBase("Log", dispatcher) {
+void LogAgent::OnDebugLog(const std::string& message) {
+ SetDebugLog(console::ConsoleCommandManager::CommandHandler::IsOnEnableOrTrue(
+ message));
+}
+void LogAgent::SetDebugLog(bool enable) {
+ event_method_ = domain_ + (enable ? ".entryAdded" : ".browserEntryAdded");
+}
+
+LogAgent::LogAgent(DebugDispatcher* dispatcher)
+ : AgentBase("Log", dispatcher),
+ debug_log_command_handler_(
+ kDebugLogCommand,
+ base::Bind(&LogAgent::OnDebugLog, base::Unretained(this)),
+ kDebugLogCommandShortHelp, kDebugLogCommandLongHelp) {
+ SetDebugLog(false);
// Get log output while still making it available elsewhere.
log_message_handler_callback_id_ =
base::LogMessageHandler::GetInstance()->AddCallback(
@@ -63,9 +86,10 @@
// except it only shows up in the debug console and not in remote devtools.
// TODO: Flesh out the rest of LogEntry properties (source, timestamp)
JSONObject params(new base::DictionaryValue());
+ params->SetString("entry.source", "other");
params->SetString("entry.text", str);
params->SetString("entry.level", GetLogLevelFromSeverity(severity));
- dispatcher_->SendEvent(domain_ + ".browserEntryAdded", params);
+ dispatcher_->SendEvent(event_method_, params);
}
// Don't suppress the log message.
diff --git a/cobalt/debug/backend/log_agent.h b/cobalt/debug/backend/log_agent.h
index 97e6fe0..5a6fb50 100644
--- a/cobalt/debug/backend/log_agent.h
+++ b/cobalt/debug/backend/log_agent.h
@@ -19,6 +19,7 @@
#include "cobalt/base/log_message_handler.h"
#include "cobalt/debug/backend/agent_base.h"
#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/console/command_manager.h"
namespace cobalt {
namespace debug {
@@ -33,6 +34,9 @@
explicit LogAgent(DebugDispatcher* dispatcher);
~LogAgent();
+ void OnDebugLog(const std::string& message);
+ void SetDebugLog(bool enable);
+
private:
// Called by LogMessageHandler for each log message.
// May be called from any thread.
@@ -46,6 +50,11 @@
// The callback id of our recipient of log messages so we can unregister it.
base::LogMessageHandler::CallbackId log_message_handler_callback_id_;
+
+ std::string event_method_;
+
+ debug::console::ConsoleCommandManager::CommandHandler
+ debug_log_command_handler_;
};
} // namespace backend
diff --git a/cobalt/debug/console/command_manager.cc b/cobalt/debug/console/command_manager.cc
index a25879a..3c30ae3 100644
--- a/cobalt/debug/console/command_manager.cc
+++ b/cobalt/debug/console/command_manager.cc
@@ -15,6 +15,7 @@
#include "cobalt/debug/console/command_manager.h"
#include "base/logging.h"
+#include "starboard/string.h"
namespace cobalt {
namespace debug {
@@ -45,6 +46,16 @@
manager->UnregisterCommandHandler(this);
}
+// Returns true if the message is 'on', 'enable', or 'true'.
+// static
+bool ConsoleCommandManager::CommandHandler::IsOnEnableOrTrue(
+ const std::string& message) {
+ return (SbStringCompareNoCase("on", message.c_str()) == 0) ||
+ (SbStringCompareNoCase("enable", message.c_str()) == 0) ||
+ (SbStringCompareNoCase("true", message.c_str()) == 0);
+}
+
+
void ConsoleCommandManager::HandleCommand(const std::string& command,
const std::string& message) const {
DCHECK_GT(command.length(), size_t(0));
diff --git a/cobalt/debug/console/command_manager.h b/cobalt/debug/console/command_manager.h
index 66a5601..1ace46c 100644
--- a/cobalt/debug/console/command_manager.h
+++ b/cobalt/debug/console/command_manager.h
@@ -61,6 +61,9 @@
const std::string& short_help() const { return short_help_; }
const std::string& long_help() const { return long_help_; }
+ // Returns true if the message is 'on', 'enable', or 'true'.
+ static bool IsOnEnableOrTrue(const std::string& message);
+
private:
std::string command_;
CommandCallback callback_;
diff --git a/cobalt/demos/content/media-element-demo/src/components/player.ts b/cobalt/demos/content/media-element-demo/src/components/player.ts
index 7bd9c17..60e3a5c 100644
--- a/cobalt/demos/content/media-element-demo/src/components/player.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/player.ts
@@ -25,6 +25,18 @@
/** The <video> element. */
private videoEl!: HTMLVideoElement;
+ /** The video SourceBuffer */
+ private videoSourceBuffer!: SourceBuffer;
+
+ /** The audio SourceBuffer */
+ private audioSourceBuffer!: SourceBuffer;
+
+ /** max(videoSourceBuffer.writeHead - videoEl.currentTime) */
+ private maxVideoWriteHeadDistance!: number;
+
+ /** max(audioSourceBuffer.writeHead - videoEl.currentTime) */
+ private maxAudioWriteHeadDistance!: number;
+
/** The element displaying video download buffer info. */
private videoDownloadBufferInfo!: Element;
@@ -53,6 +65,8 @@
super(props);
this.videos = convertToMediaArray(props.video);
this.audios = convertToMediaArray(props.audio);
+ this.maxVideoWriteHeadDistance = 0;
+ this.maxAudioWriteHeadDistance = 0;
}
/** @override */
@@ -93,12 +107,37 @@
}
private renderVideoInfo() {
+ var h5vccAudioConnectors = '';
+ try {
+ h5vccAudioConnectors = this.videoEl.h5vccAudioConnectors;
+ } catch (error) {}
renderComponent(
VideoInfo, {
duration: this.videoEl.duration,
currentTime: this.videoEl.currentTime,
+ audioConnectors: h5vccAudioConnectors,
},
this.videoInfo);
+ if (this.videoSourceBuffer) {
+ this.maxVideoWriteHeadDistance =
+ Math.max(this.maxVideoWriteHeadDistance,
+ this.videoSourceBuffer.writeHead - this.videoEl.currentTime);
+ renderComponent(
+ SourceBufferInfo,
+ {name: 'Video', sourceBuffer: this.videoSourceBuffer,
+ maxWriteHeadDistance: this.maxVideoWriteHeadDistance},
+ this.videoSourceBufferInfo);
+ }
+ if (this.audioSourceBuffer) {
+ this.maxAudioWriteHeadDistance =
+ Math.max(this.maxAudioWriteHeadDistance,
+ this.audioSourceBuffer.writeHead - this.videoEl.currentTime);
+ renderComponent(
+ SourceBufferInfo,
+ {name: 'Audio', sourceBuffer: this.audioSourceBuffer,
+ maxWriteHeadDistance: this.maxAudioWriteHeadDistance},
+ this.audioSourceBufferInfo);
+ }
}
private async play() {
@@ -149,7 +188,7 @@
/**
* Plays all videos as adaptive videos.
- * TODO: dynmaically calculate the source buffer MIME.
+ * TODO: dynamically calculate the source buffer MIME.
*/
private playAdaptiveVideo() {
const ms = new MediaSource();
@@ -158,12 +197,7 @@
if (this.videos.length > 0) {
const videoSourceBuffer =
ms.addSourceBuffer('video/mp4; codecs="avc1.640028"');
- videoSourceBuffer.addEventListener('updateend', () => {
- renderComponent(
- SourceBufferInfo,
- {name: 'Video', sourceBuffer: videoSourceBuffer},
- this.videoSourceBufferInfo);
- });
+ this.videoSourceBuffer = videoSourceBuffer;
const downloadBuffer = new DownloadBuffer(this.videos);
downloadBuffer.register((reportMap) => {
renderComponent(
@@ -176,12 +210,7 @@
if (this.audios.length > 0) {
const audioSourceBuffer =
ms.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
- audioSourceBuffer.addEventListener('updateend', () => {
- renderComponent(
- SourceBufferInfo,
- {name: 'Audio', sourceBuffer: audioSourceBuffer},
- this.audioSourceBufferInfo);
- });
+ this.audioSourceBuffer = audioSourceBuffer;
const downloadBuffer = new DownloadBuffer(this.audios);
downloadBuffer.register(
(reportMap) => {renderComponent(
diff --git a/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts b/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
index 92343f3..6747f66 100644
--- a/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
@@ -1,9 +1,12 @@
interface Props {
sourceBuffer: SourceBuffer;
name: string;
+ maxWriteHeadDistance: number;
}
/** A component that displays the source buffer info. */
-export function SourceBufferInfo({sourceBuffer, name}: Props) {
- return `<div>${name} buffered: ${sourceBuffer.buffered.end(0)} sec</div>`;
+export function SourceBufferInfo({sourceBuffer, name, maxWriteHeadDistance}: Props) {
+ return `<div>${name} buffered: ${sourceBuffer.buffered.end(0)} sec` +
+ `, writeHead: ${sourceBuffer.writeHead} sec` +
+ `, maxWriteHeadDistance: ${maxWriteHeadDistance} sec</div>`;
}
diff --git a/cobalt/demos/content/media-element-demo/src/components/video_info.ts b/cobalt/demos/content/media-element-demo/src/components/video_info.ts
index 3f8306f..68012c8 100644
--- a/cobalt/demos/content/media-element-demo/src/components/video_info.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/video_info.ts
@@ -1,9 +1,13 @@
interface Props {
duration: number;
currentTime: number;
+ audioConnectors: string;
}
/** A component that displays video info. */
-export function VideoInfo({duration, currentTime}: Props) {
+export function VideoInfo({duration, currentTime, audioConnectors}: Props) {
+ if (audioConnectors) {
+ return `<div>${currentTime} / ${duration} / audioConnectors: ${audioConnectors}</div>`;
+ }
return `<div>${currentTime} / ${duration}</div>`;
}
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index d516ac8..054209f 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -19,6 +19,7 @@
#include <limits>
#include <memory>
#include <utility>
+#include <vector>
#include "base/bind.h"
#include "base/compiler_specific.h"
@@ -26,6 +27,7 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/instance_counter.h"
#include "cobalt/base/tokens.h"
@@ -36,6 +38,7 @@
#include "cobalt/dom/media_settings.h"
#include "cobalt/dom/media_source.h"
#include "cobalt/dom/media_source_ready_state.h"
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/loader/fetcher_factory.h"
#include "cobalt/media/url_fetcher_data_source.h"
#include "cobalt/media/web_media_player_factory.h"
@@ -45,6 +48,7 @@
#include "cobalt/web/dom_exception.h"
#include "cobalt/web/event.h"
#include "cobalt/web/web_settings.h"
+#include "starboard/system.h"
#include "cobalt/dom/eme/media_encrypted_event.h"
#include "cobalt/dom/eme/media_encrypted_event_init.h"
@@ -641,6 +645,32 @@
event_queue_.Enqueue(event);
}
+std::string HTMLMediaElement::h5vcc_audio_connectors(
+ script::ExceptionState* exception_state) const {
+ static const CobaltExtensionConfigurableAudioWriteAheadApi* extension_api =
+ static_cast<const CobaltExtensionConfigurableAudioWriteAheadApi*>(
+ SbSystemGetExtension(
+ kCobaltExtensionConfigurableAudioWriteAheadName));
+ if (extension_api) {
+ if (!player_) {
+ web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+ exception_state);
+ return std::string();
+ }
+
+ DCHECK_EQ(extension_api->name,
+ std::string(kCobaltExtensionConfigurableAudioWriteAheadName));
+ DCHECK_EQ(extension_api->version, 1u);
+
+ std::vector<std::string> configs = player_->GetAudioConnectors();
+ return base::JoinString(configs, ";");
+ }
+
+ web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+ exception_state);
+ return std::string();
+}
+
void HTMLMediaElement::CreateMediaPlayer() {
TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::CreateMediaPlayer()");
LOG(INFO) << "Create media player.";
diff --git a/cobalt/dom/html_media_element.h b/cobalt/dom/html_media_element.h
index 4276fd8..612e21c 100644
--- a/cobalt/dom/html_media_element.h
+++ b/cobalt/dom/html_media_element.h
@@ -146,6 +146,12 @@
// function won't modify the target of the |event| passed in.
void ScheduleEvent(const scoped_refptr<web::Event>& event);
+ // 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;
+
// Set max video capabilities.
void SetMaxVideoCapabilities(const std::string& max_video_capabilities,
script::ExceptionState* exception_state);
diff --git a/cobalt/dom/html_media_element.idl b/cobalt/dom/html_media_element.idl
index 4a1a509..401060a 100644
--- a/cobalt/dom/html_media_element.idl
+++ b/cobalt/dom/html_media_element.idl
@@ -61,4 +61,10 @@
attribute boolean controls;
[RaisesException] attribute double volume;
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.
+ // TODO(b/267678497): The current interface is tentative, to be refined.
+ [RaisesException] readonly attribute DOMString h5vccAudioConnectors;
};
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 267677b..039303f 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -453,6 +453,17 @@
track_defaults_ = track_defaults;
}
+double SourceBuffer::write_head(script::ExceptionState* exception_state) const {
+ if (media_source_ == NULL) {
+ web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+ exception_state);
+ return 0.0;
+ }
+
+ DCHECK(chunk_demuxer_);
+ return chunk_demuxer_->GetWriteHead(id_).InSecondsF();
+}
+
void SourceBuffer::OnRemovedFromMediaSource() {
if (media_source_ == NULL) {
return;
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index c611177..76d0aa7 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -134,6 +134,9 @@
// Custom, not in any spec.
//
+ // Return the highest presentation timestamp written to SbPlayer.
+ double write_head(script::ExceptionState* exception_state) const;
+
void OnRemovedFromMediaSource();
double GetHighestPresentationTimestamp() const;
diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl
index 1fd8053..bb755e2 100644
--- a/cobalt/dom/source_buffer.idl
+++ b/cobalt/dom/source_buffer.idl
@@ -38,4 +38,10 @@
// [RaisesException] void abort();
[RaisesException] void remove(double start, unrestricted double end);
[RaisesException] attribute TrackDefaultList trackDefaults;
+
+ // Non standard interface (b/267678497).
+ // Returns the highest presentation timestamp written to SbPlayer, raises
+ // `InvalidStateError` if the SourceBuffer object has been removed from the
+ // MediaSource object.
+ [RaisesException] readonly attribute double writeHead;
};
diff --git a/cobalt/extension/audio_write_ahead.h b/cobalt/extension/audio_write_ahead.h
new file mode 100644
index 0000000..985b6d3
--- /dev/null
+++ b/cobalt/extension/audio_write_ahead.h
@@ -0,0 +1,75 @@
+// 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_EXTENSION_AUDIO_WRITE_AHEAD_H_
+#define COBALT_EXTENSION_AUDIO_WRITE_AHEAD_H_
+
+#include "starboard/media.h"
+#include "starboard/player.h"
+#include "starboard/time.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kCobaltExtensionConfigurableAudioWriteAheadName \
+ "dev.cobalt.extension.ConfigurableAudioWriteAhead"
+
+#define kCobaltExtensionPlayerWriteDurationLocal (kSbTimeSecond / 2)
+#define kCobaltExtensionPlayerWriteDurationRemote (kSbTimeSecond * 10)
+
+typedef enum CobaltExtensionMediaAudioConnector {
+ kCobaltExtensionMediaAudioConnectorUnknown,
+
+ kCobaltExtensionMediaAudioConnectorAnalog,
+ kCobaltExtensionMediaAudioConnectorBluetooth,
+ kCobaltExtensionMediaAudioConnectorBuiltIn,
+ kCobaltExtensionMediaAudioConnectorHdmi,
+ kCobaltExtensionMediaAudioConnectorRemoteWired,
+ kCobaltExtensionMediaAudioConnectorRemoteWireless,
+ kCobaltExtensionMediaAudioConnectorRemoteOther,
+ kCobaltExtensionMediaAudioConnectorSpdif,
+ kCobaltExtensionMediaAudioConnectorUsb,
+} CobaltExtensionMediaAudioConnector;
+
+typedef struct CobaltExtensionMediaAudioConfiguration {
+ int index;
+ CobaltExtensionMediaAudioConnector connector;
+ SbTime latency;
+ SbMediaAudioCodingType coding_type;
+ int number_of_channels;
+} CobaltExtensionMediaAudioConfiguration;
+
+typedef struct CobaltExtensionConfigurableAudioWriteAheadApi {
+ // Name should be the string
+ // |kCobaltExtensionConfigurableAudioWriteAheadName|. 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.
+
+ bool (*PlayerGetAudioConfiguration)(
+ SbPlayer player, int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration);
+
+} CobaltExtensionConfigurableAudioWriteAheadApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // COBALT_EXTENSION_AUDIO_WRITE_AHEAD_H_
diff --git a/cobalt/extension/extension_test.cc b/cobalt/extension/extension_test.cc
index b6cf3bc..2dfdae9 100644
--- a/cobalt/extension/extension_test.cc
+++ b/cobalt/extension/extension_test.cc
@@ -20,6 +20,7 @@
#include "cobalt/extension/font.h"
#include "cobalt/extension/free_space.h"
#include "cobalt/extension/graphics.h"
+#include "cobalt/extension/ifa.h"
#include "cobalt/extension/installation_manager.h"
#include "cobalt/extension/javascript_cache.h"
#include "cobalt/extension/media_session.h"
@@ -382,5 +383,27 @@
EXPECT_EQ(second_extension_api, extension_api)
<< "Extension struct should be a singleton";
}
+
+TEST(ExtensionTest, Ifa) {
+ typedef StarboardExtensionIfaApi ExtensionApi;
+ const char* kExtensionName = kStarboardExtensionIfaName;
+
+ const ExtensionApi* extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ if (!extension_api) {
+ return;
+ }
+
+ EXPECT_STREQ(extension_api->name, kExtensionName);
+ EXPECT_EQ(extension_api->version, 1u);
+ EXPECT_NE(extension_api->GetAdvertisingId, nullptr);
+ EXPECT_NE(extension_api->GetLimitAdTracking, nullptr);
+
+ const ExtensionApi* second_extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ EXPECT_EQ(second_extension_api, extension_api)
+ << "Extension struct should be a singleton";
+}
+
} // namespace extension
} // namespace cobalt
diff --git a/cobalt/extension/ifa.h b/cobalt/extension/ifa.h
new file mode 100644
index 0000000..a207049
--- /dev/null
+++ b/cobalt/extension/ifa.h
@@ -0,0 +1,58 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_EXTENSION_IFA_H_
+#define STARBOARD_EXTENSION_IFA_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kStarboardExtensionIfaName "dev.cobalt.extension.Ifa"
+
+typedef struct StarboardExtensionIfaApi {
+ // Name should be the string |kCobaltExtensionIfaName|.
+ // This helps to validate that the extension API is correct.
+ const char* name;
+
+ // This specifies the version of the API that is implemented.
+ uint32_t version;
+
+ // The fields below this point were added in version 1 or later.
+
+ // Advertising ID or IFA, typically a 128-bit UUID
+ // Please see https://iabtechlab.com/OTT-IFA for details.
+ // Corresponds to 'ifa' field. Note: `ifa_type` field is not provided.
+ // In Starboard 14 this the value is retrieved through the system
+ // property `kSbSystemPropertyAdvertisingId` defined in
+ // `starboard/system.h`.
+ bool (*GetAdvertisingId)(char* out_value, int value_length);
+
+ // Limit advertising tracking, treated as boolean. Set to nonzero to indicate
+ // a true value. Corresponds to 'lmt' field.
+ // In Starboard 14 this the value is retrieved through the system
+ // property `kSbSystemPropertyLimitAdTracking` defined in
+ // `starboard/system.h`.
+
+ bool (*GetLimitAdTracking)(char* out_value, int value_length);
+
+} CobaltExtensionIfaApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // STARBOARD_EXTENSION_IFA_H_
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index a240d92..4088a84 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -31,70 +31,49 @@
namespace cobalt {
namespace h5vcc {
-// We keep a global mapping of all registered logs. When a crash occurs, we
-// will iterate through the mapping in this dictionary to extract all
-// logged entries and write them to the system's crash logger via Starboard.
-class CrashLogDictionary {
- public:
- static CrashLogDictionary* GetInstance() {
- return base::Singleton<CrashLogDictionary, base::DefaultSingletonTraits<
- CrashLogDictionary> >::get();
- }
+CrashLogDictionary* CrashLogDictionary::GetInstance() {
+ return base::Singleton<CrashLogDictionary, base::DefaultSingletonTraits<
+ CrashLogDictionary>>::get();
+}
- void SetString(const std::string& key, const std::string& value) {
- base::AutoLock lock(mutex_);
- // While the lock prevents contention between other calls to SetString(),
- // the atomics guard against OnCrash(), which doesn't acquire |mutex_|, from
- // accessing the data at the same time. In the case that OnCrash() is
- // being called, we give up and skip adding the data.
- if (base::subtle::Acquire_CompareAndSwap(&accessing_log_data_, 0, 1) == 0) {
- string_log_map_[key] = value;
- base::subtle::Release_Store(&accessing_log_data_, 0);
- }
+void CrashLogDictionary::SetString(const std::string& key,
+ const std::string& value) {
+ base::AutoLock lock(mutex_);
+ // While the lock prevents contention between other calls to SetString(),
+ // the atomics guard against OnCrash(), which doesn't acquire |mutex_|, from
+ // accessing the data at the same time. In the case that OnCrash() is
+ // being called, we give up and skip adding the data.
+ if (base::subtle::Acquire_CompareAndSwap(&accessing_log_data_, 0, 1) == 0) {
+ string_log_map_[key] = value;
+ base::subtle::Release_Store(&accessing_log_data_, 0);
}
+}
- private:
- CrashLogDictionary() : accessing_log_data_(0) {
+CrashLogDictionary::CrashLogDictionary() : accessing_log_data_(0) {
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
- SbCoreDumpRegisterHandler(&CoreDumpHandler, this);
+ SbCoreDumpRegisterHandler(&CoreDumpHandler, this);
#endif
- }
+}
- static void CoreDumpHandler(void* context) {
- CrashLogDictionary* crash_log_dictionary =
- static_cast<CrashLogDictionary*>(context);
- crash_log_dictionary->OnCrash();
- }
+void CrashLogDictionary::CoreDumpHandler(void* context) {
+ CrashLogDictionary* crash_log_dictionary =
+ static_cast<CrashLogDictionary*>(context);
+ crash_log_dictionary->OnCrash();
+}
- void OnCrash() {
+void CrashLogDictionary::OnCrash() {
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
- // Check that we're not already updating log data. If we are, we just
- // give up and skip recording any crash data, but hopefully this is rare.
- if (base::subtle::Acquire_CompareAndSwap(&accessing_log_data_, 0, 1) == 0) {
- for (StringMap::const_iterator iter = string_log_map_.begin();
- iter != string_log_map_.end(); ++iter) {
- SbCoreDumpLogString(iter->first.c_str(), iter->second.c_str());
- }
- base::subtle::Release_Store(&accessing_log_data_, 0);
+ // Check that we're not already updating log data. If we are, we just
+ // give up and skip recording any crash data, but hopefully this is rare.
+ if (base::subtle::Acquire_CompareAndSwap(&accessing_log_data_, 0, 1) == 0) {
+ for (StringMap::const_iterator iter = string_log_map_.begin();
+ iter != string_log_map_.end(); ++iter) {
+ SbCoreDumpLogString(iter->first.c_str(), iter->second.c_str());
}
-#endif
+ base::subtle::Release_Store(&accessing_log_data_, 0);
}
-
- friend struct base::DefaultSingletonTraits<CrashLogDictionary>;
-
- base::subtle::Atomic32 accessing_log_data_;
-
- // It is possible for multiple threads to call the H5VCC interface at the
- // same time, and they will all forward to this global singleton, so we
- // use this mutex to protect against concurrent access.
- base::Lock mutex_;
-
- typedef std::map<std::string, std::string> StringMap;
- // Keeps track of all string values to be logged when a crash occurs.
- StringMap string_log_map_;
-
- DISALLOW_COPY_AND_ASSIGN(CrashLogDictionary);
-};
+#endif
+}
bool H5vccCrashLog::SetString(const std::string& key,
const std::string& value) {
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index b85c0c5..bb20a48 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -15,8 +15,10 @@
#ifndef COBALT_H5VCC_H5VCC_CRASH_LOG_H_
#define COBALT_H5VCC_H5VCC_CRASH_LOG_H_
+#include <map>
#include <string>
+#include "base/memory/singleton.h"
#include "cobalt/h5vcc/h5vcc_crash_type.h"
#include "cobalt/h5vcc/watchdog_replace.h"
#include "cobalt/h5vcc/watchdog_state.h"
@@ -26,6 +28,38 @@
namespace cobalt {
namespace h5vcc {
+// We keep a global mapping of all registered logs. When a crash occurs, we
+// will iterate through the mapping in this dictionary to extract all
+// logged entries and write them to the system's crash logger via Starboard.
+class CrashLogDictionary {
+ public:
+ static CrashLogDictionary* GetInstance();
+
+ void SetString(const std::string& key, const std::string& value);
+
+ private:
+ CrashLogDictionary();
+
+ static void CoreDumpHandler(void* context);
+
+ void OnCrash();
+
+ friend struct base::DefaultSingletonTraits<CrashLogDictionary>;
+
+ base::subtle::Atomic32 accessing_log_data_;
+
+ // It is possible for multiple threads to call the H5VCC interface at the
+ // same time, and they will all forward to this global singleton, so we
+ // use this mutex to protect against concurrent access.
+ base::Lock mutex_;
+
+ typedef std::map<std::string, std::string> StringMap;
+ // Keeps track of all string values to be logged when a crash occurs.
+ StringMap string_log_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrashLogDictionary);
+};
+
class H5vccCrashLog : public script::Wrappable {
public:
H5vccCrashLog() {}
diff --git a/cobalt/h5vcc/h5vcc_system.cc b/cobalt/h5vcc/h5vcc_system.cc
index b28e941..ad4de95 100644
--- a/cobalt/h5vcc/h5vcc_system.cc
+++ b/cobalt/h5vcc/h5vcc_system.cc
@@ -20,6 +20,10 @@
#include "cobalt_build_id.h" // NOLINT(build/include_subdir)
#include "starboard/system.h"
+#if SB_API_VERSION < 14
+#include "cobalt/extension/ifa.h"
+#endif // SB_API_VERSION < 14
+
namespace cobalt {
namespace h5vcc {
@@ -54,29 +58,58 @@
std::string H5vccSystem::advertising_id() const {
std::string result;
-#if SB_API_VERSION >= 14
const size_t kSystemPropertyMaxLength = 1024;
char property[kSystemPropertyMaxLength] = {0};
+#if SB_API_VERSION >= 14
if (!SbSystemGetProperty(kSbSystemPropertyAdvertisingId, property,
SB_ARRAY_SIZE_INT(property))) {
DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
} else {
result = property;
}
+#else
+ static auto const* ifa_extension =
+ static_cast<const StarboardExtensionIfaApi*>(
+ SbSystemGetExtension(kStarboardExtensionIfaName));
+ if (ifa_extension &&
+ strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 &&
+ ifa_extension->version >= 1) {
+ if (!ifa_extension->GetAdvertisingId(property,
+ SB_ARRAY_SIZE_INT(property))) {
+ DLOG(FATAL) << "Failed to get AdvertisingId from IFA extension.";
+ } else {
+ result = property;
+ }
+ }
#endif
return result;
}
bool H5vccSystem::limit_ad_tracking() const {
bool result = false;
-#if SB_API_VERSION >= 14
const size_t kSystemPropertyMaxLength = 1024;
char property[kSystemPropertyMaxLength] = {0};
+#if SB_API_VERSION >= 14
if (!SbSystemGetProperty(kSbSystemPropertyLimitAdTracking, property,
SB_ARRAY_SIZE_INT(property))) {
DLOG(FATAL) << "Failed to get kSbSystemPropertyAdvertisingId.";
} else {
result = std::atoi(property);
}
+#else
+ static auto const* ifa_extension =
+ static_cast<const StarboardExtensionIfaApi*>(
+ SbSystemGetExtension(kStarboardExtensionIfaName));
+
+ if (ifa_extension &&
+ strcmp(ifa_extension->name, kStarboardExtensionIfaName) == 0 &&
+ ifa_extension->version >= 1) {
+ if (!ifa_extension->GetLimitAdTracking(property,
+ SB_ARRAY_SIZE_INT(property))) {
+ DLOG(FATAL) << "Failed to get LimitAdTracking from IFA extension.";
+ } else {
+ result = std::atoi(property);
+ }
+ }
#endif
return result;
}
diff --git a/cobalt/loader/image/animated_image_tracker.cc b/cobalt/loader/image/animated_image_tracker.cc
index 98dadca..c7a3e73 100644
--- a/cobalt/loader/image/animated_image_tracker.cc
+++ b/cobalt/loader/image/animated_image_tracker.cc
@@ -15,7 +15,9 @@
#include "cobalt/loader/image/animated_image_tracker.h"
#include <algorithm>
+#include <utility>
+#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/polymorphic_downcast.h"
@@ -24,8 +26,22 @@
namespace image {
AnimatedImageTracker::AnimatedImageTracker(
+ const char* name,
base::ThreadPriority animated_image_decode_thread_priority)
- : animated_image_decode_thread_("AnimatedImage") {
+ : animated_image_decode_thread_("AnimatedImage"),
+ name_(name),
+ count_animated_images_active(
+ base::StringPrintf("Count.%s.AnimatedImage.Active", name), 0,
+ "Total number of active animated image decoders."),
+ count_animated_frames_decoded(
+ base::StringPrintf("Count.%s.AnimatedImage.DecodedFrames", name), 0,
+ "Total number of decoded animated image frames."),
+ count_animated_frames_decoding_underrun(
+ base::StringPrintf("Count.%s.AnimatedImage.DecodingUnderruns", name),
+ 0, "Number of underruns from decoding animated images"),
+ count_animated_frames_decoding_overrun(
+ base::StringPrintf("Count.%s.AnimatedImage.DecodingOverruns", name),
+ 0, "Number of overruns from decoding animated images") {
TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::RecordImage()");
base::Thread::Options options(base::MessageLoop::TYPE_DEFAULT,
0 /* default stack size */);
@@ -123,6 +139,17 @@
playing_urls_.clear();
}
+void AnimatedImageTracker::OnRenderTreeRasterized() {
+ count_animated_images_active = playing_urls_.size();
+ for (const auto& playing_url : playing_urls_) {
+ auto image = image_map_[playing_url.first].get();
+ auto stats = image->GetFrameDeltaStats();
+ count_animated_frames_decoded += stats.frames_decoded;
+ count_animated_frames_decoding_underrun += stats.frames_underrun;
+ count_animated_frames_decoding_overrun += stats.frames_overrun;
+ }
+}
+
} // namespace image
} // namespace loader
} // namespace cobalt
diff --git a/cobalt/loader/image/animated_image_tracker.h b/cobalt/loader/image/animated_image_tracker.h
index f6f38e4..6df920b 100644
--- a/cobalt/loader/image/animated_image_tracker.h
+++ b/cobalt/loader/image/animated_image_tracker.h
@@ -20,6 +20,7 @@
#include "base/containers/small_map.h"
#include "base/threading/thread.h"
+#include "cobalt/base/c_val.h"
#include "cobalt/base/unused.h"
#include "cobalt/loader/image/image.h"
#include "url/gurl.h"
@@ -33,7 +34,8 @@
// playing status is updated hence decoding is turned on / off for it.
class AnimatedImageTracker {
public:
- explicit AnimatedImageTracker(
+ AnimatedImageTracker(
+ const char* name,
base::ThreadPriority animated_image_decode_thread_priority);
~AnimatedImageTracker();
@@ -54,6 +56,9 @@
// animations.
void Reset();
+ // Called from WebModule to compute image animation related stats.
+ void OnRenderTreeRasterized();
+
private:
void OnDisplayStart(loader::image::AnimatedImage* image);
void OnDisplayEnd(loader::image::AnimatedImage* image);
@@ -72,6 +77,15 @@
URLToIntMap current_url_counts_;
URLSet playing_urls_;
+ // The name of the WebModule this AnimatedImage tracker belongs to, for CVals.
+ const std::string name_;
+
+ // Animated image counters
+ base::CVal<int, base::CValPublic> count_animated_images_active;
+ base::CVal<int, base::CValPublic> count_animated_frames_decoded;
+ base::CVal<int, base::CValPublic> count_animated_frames_decoding_underrun;
+ base::CVal<int, base::CValPublic> count_animated_frames_decoding_overrun;
+
// Used to ensure that all AnimatedImageTracker methods are called on the
// same thread (*not* |animated_image_decode_thread_|), the thread that we
// were constructed on.
diff --git a/cobalt/loader/image/animated_webp_image.cc b/cobalt/loader/image/animated_webp_image.cc
index ed0b959..8689168 100644
--- a/cobalt/loader/image/animated_webp_image.cc
+++ b/cobalt/loader/image/animated_webp_image.cc
@@ -148,6 +148,8 @@
return;
}
is_playing_ = true;
+ current_stats.frames_underrun = 0;
+ current_stats.frames_overrun = 0;
if (received_first_frame_) {
StartDecoding();
@@ -171,7 +173,8 @@
void AnimatedWebPImage::StartDecoding() {
TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::StartDecoding()");
lock_.AssertAcquired();
- current_frame_time_ = base::TimeTicks::Now();
+ decoding_start_time_ = current_frame_time_ = base::TimeTicks::Now();
+ current_stats.frames_decoded = 0;
if (task_runner_->BelongsToCurrentThread()) {
DecodeFrames();
} else {
@@ -269,6 +272,7 @@
LOG(ERROR) << "Failed to decode WebP image frame.";
return false;
}
+ current_stats.frames_decoded++;
}
// Alpha blend the current frame on top of the buffer.
@@ -354,6 +358,7 @@
// Always wait for a consumer to consume the previous frame before moving
// forward with decoding the next frame.
if (!frame_provider_->FrameConsumed()) {
+ current_stats.frames_overrun++;
return false;
}
@@ -386,6 +391,7 @@
if (next_frame_time_ < current_time) {
// Don't let the animation fall back for more than a frame.
next_frame_time_ = current_time;
+ current_stats.frames_underrun++;
}
return true;
@@ -431,6 +437,25 @@
return target_canvas;
}
+AnimatedImage::AnimatedImageDecodingStats
+AnimatedWebPImage::GetFrameDeltaStats() {
+ AnimatedImageDecodingStats result;
+ if (current_stats.frames_decoded >= last_stats.frames_decoded) {
+ result.frames_decoded =
+ current_stats.frames_decoded - last_stats.frames_decoded;
+ result.frames_underrun =
+ current_stats.frames_underrun - last_stats.frames_underrun;
+ result.frames_overrun =
+ current_stats.frames_overrun - last_stats.frames_overrun;
+ } else {
+ // There was a reset somewhere
+ // Simply return total, this discards any overflow data we might have had.
+ result = current_stats;
+ }
+ last_stats = current_stats;
+ return result;
+}
+
} // namespace image
} // namespace loader
} // namespace cobalt
diff --git a/cobalt/loader/image/animated_webp_image.h b/cobalt/loader/image/animated_webp_image.h
index 135bf6a..4d1be04 100644
--- a/cobalt/loader/image/animated_webp_image.h
+++ b/cobalt/loader/image/animated_webp_image.h
@@ -66,6 +66,8 @@
// Returns the render image of the frame for debugging
scoped_refptr<render_tree::Image> GetFrameForDebugging(int target_frame);
+ AnimatedImageDecodingStats GetFrameDeltaStats() override;
+
private:
~AnimatedWebPImage() override;
@@ -119,12 +121,17 @@
base::CancelableClosure decode_closure_;
base::TimeTicks current_frame_time_;
base::Optional<base::TimeTicks> next_frame_time_;
+
// The original encoded data.
std::vector<uint8> data_buffer_;
scoped_refptr<render_tree::Image> current_canvas_;
scoped_refptr<FrameProvider> frame_provider_;
base::Lock lock_;
+ base::TimeTicks decoding_start_time_;
+ AnimatedImageDecodingStats current_stats;
+ AnimatedImageDecodingStats last_stats;
+
// Makes sure that the thread that sets the task_runner is always consistent.
// This is the thread sending Play()/Stop() calls, and is not necessarily
// the same thread that the task_runner itself is running on.
diff --git a/cobalt/loader/image/image.h b/cobalt/loader/image/image.h
index af99363..bac9849 100644
--- a/cobalt/loader/image/image.h
+++ b/cobalt/loader/image/image.h
@@ -145,6 +145,16 @@
image_node_builder->destination_rect = destination_rect;
image_node_builder->local_transform = local_transform;
}
+
+ // Frame counters for decoding.
+ struct AnimatedImageDecodingStats {
+ unsigned int frames_decoded = 0;
+ unsigned int frames_underrun = 0;
+ unsigned int frames_overrun = 0;
+ };
+
+ // Returns decoded frame stats since the last call, as a delta.
+ virtual AnimatedImageDecodingStats GetFrameDeltaStats() = 0;
};
} // namespace image
diff --git a/cobalt/media/base/pipeline.h b/cobalt/media/base/pipeline.h
index ccb3a1b..d2d8296 100644
--- a/cobalt/media/base/pipeline.h
+++ b/cobalt/media/base/pipeline.h
@@ -98,6 +98,7 @@
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, bool allow_batched_sample_write,
+ SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
MediaLog* media_log, DecodeTargetProvider* decode_target_provider);
virtual ~Pipeline() {}
@@ -217,6 +218,9 @@
// be 0.
virtual void GetNaturalVideoSize(gfx::Size* out_size) const = 0;
+ // Gets the names of audio connectors used by the audio output.
+ virtual std::vector<std::string> GetAudioConnectors() const = 0;
+
// Return true if loading progress has been made since the last time this
// method was called.
virtual bool DidLoadingProgress() const = 0;
diff --git a/cobalt/media/base/sbplayer_bridge.cc b/cobalt/media/base/sbplayer_bridge.cc
index 10674e3..7d45642 100644
--- a/cobalt/media/base/sbplayer_bridge.cc
+++ b/cobalt/media/base/sbplayer_bridge.cc
@@ -370,6 +370,36 @@
GetInfo_Locked(video_frames_decoded, video_frames_dropped, media_time);
}
+std::vector<CobaltExtensionMediaAudioConfiguration>
+SbPlayerBridge::GetAudioConfigurations() {
+ DCHECK(sbplayer_interface_->IsAudioWriteAheadExtensionEnabled());
+ base::AutoLock auto_lock(lock_);
+
+ if (!SbPlayerIsValid(player_)) {
+ return std::vector<CobaltExtensionMediaAudioConfiguration>();
+ }
+
+ std::vector<CobaltExtensionMediaAudioConfiguration> configurations;
+
+ // Set a limit to avoid infinite loop.
+ constexpr int kMaxAudioConfigurations = 32;
+
+ for (int i = 0; i < kMaxAudioConfigurations; ++i) {
+ CobaltExtensionMediaAudioConfiguration configuration;
+ if (!sbplayer_interface_->GetAudioConfiguration(player_, i,
+ &configuration)) {
+ break;
+ }
+
+ configurations.push_back(configuration);
+ }
+
+ LOG_IF(WARNING, configurations.empty())
+ << "Failed to find any audio configurations.";
+
+ return configurations;
+}
+
#if SB_HAS(PLAYER_WITH_URL)
void SbPlayerBridge::GetUrlPlayerBufferedTimeRanges(
base::TimeDelta* buffer_start_time, base::TimeDelta* buffer_length_time) {
diff --git a/cobalt/media/base/sbplayer_bridge.h b/cobalt/media/base/sbplayer_bridge.h
index efb6ea8..80f1215 100644
--- a/cobalt/media/base/sbplayer_bridge.h
+++ b/cobalt/media/base/sbplayer_bridge.h
@@ -24,6 +24,7 @@
#include "base/message_loop/message_loop.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/media/base/cval_stats.h"
#include "cobalt/media/base/decode_target_provider.h"
#include "cobalt/media/base/decoder_buffer_cache.h"
@@ -115,6 +116,7 @@
void SetPlaybackRate(double playback_rate);
void GetInfo(uint32* video_frames_decoded, uint32* video_frames_dropped,
base::TimeDelta* media_time);
+ std::vector<CobaltExtensionMediaAudioConfiguration> GetAudioConfigurations();
#if SB_HAS(PLAYER_WITH_URL)
void GetUrlPlayerBufferedTimeRanges(base::TimeDelta* buffer_start_time,
diff --git a/cobalt/media/base/sbplayer_interface.cc b/cobalt/media/base/sbplayer_interface.cc
index 7c44dae..968d526 100644
--- a/cobalt/media/base/sbplayer_interface.cc
+++ b/cobalt/media/base/sbplayer_interface.cc
@@ -14,9 +14,29 @@
#include "cobalt/media/base/sbplayer_interface.h"
+#include <string>
+
namespace cobalt {
namespace media {
+DefaultSbPlayerInterface::DefaultSbPlayerInterface() {
+ const CobaltExtensionConfigurableAudioWriteAheadApi* extension_api =
+ static_cast<const CobaltExtensionConfigurableAudioWriteAheadApi*>(
+ SbSystemGetExtension(
+ kCobaltExtensionConfigurableAudioWriteAheadName));
+ if (!extension_api) {
+ return;
+ }
+
+ DCHECK_EQ(extension_api->name,
+ std::string(kCobaltExtensionConfigurableAudioWriteAheadName));
+ DCHECK_EQ(extension_api->version, 1u);
+ DCHECK_NE(extension_api->PlayerGetAudioConfiguration, nullptr);
+
+ audio_write_duration_player_get_audio_configuration_ =
+ extension_api->PlayerGetAudioConfiguration;
+}
+
SbPlayer DefaultSbPlayerInterface::Create(
SbWindow window, const SbPlayerCreationParam* creation_param,
SbPlayerDeallocateSampleFunc sample_deallocate_func,
@@ -109,5 +129,17 @@
}
#endif // SB_HAS(PLAYER_WITH_URL)
+bool DefaultSbPlayerInterface::IsAudioWriteAheadExtensionEnabled() const {
+ return audio_write_duration_player_get_audio_configuration_ != nullptr;
+}
+
+bool DefaultSbPlayerInterface::GetAudioConfiguration(
+ SbPlayer player, int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration) {
+ DCHECK(IsAudioWriteAheadExtensionEnabled());
+ return audio_write_duration_player_get_audio_configuration_(
+ player, index, out_audio_configuration);
+}
+
} // namespace media
} // namespace cobalt
diff --git a/cobalt/media/base/sbplayer_interface.h b/cobalt/media/base/sbplayer_interface.h
index e79c1e6..751ff11 100644
--- a/cobalt/media/base/sbplayer_interface.h
+++ b/cobalt/media/base/sbplayer_interface.h
@@ -15,6 +15,7 @@
#ifndef COBALT_MEDIA_BASE_SBPLAYER_INTERFACE_H_
#define COBALT_MEDIA_BASE_SBPLAYER_INTERFACE_H_
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/media/base/cval_stats.h"
#include "starboard/player.h"
@@ -68,6 +69,11 @@
SbPlayer player, SbUrlPlayerExtraInfo* out_url_player_info) = 0;
#endif // SB_HAS(PLAYER_WITH_URL)
+ virtual bool IsAudioWriteAheadExtensionEnabled() const = 0;
+ virtual bool GetAudioConfiguration(
+ SbPlayer player, int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration) = 0;
+
// disabled by default, but can be enabled via h5vcc setting.
void EnableCValStats(bool should_enable) {
cval_stats_.Enable(should_enable);
@@ -77,6 +83,8 @@
class DefaultSbPlayerInterface final : public SbPlayerInterface {
public:
+ DefaultSbPlayerInterface();
+
SbPlayer Create(
SbWindow window, const SbPlayerCreationParam* creation_param,
SbPlayerDeallocateSampleFunc sample_deallocate_func,
@@ -113,6 +121,17 @@
void GetUrlPlayerExtraInfo(
SbPlayer player, SbUrlPlayerExtraInfo* out_url_player_info) override;
#endif // SB_HAS(PLAYER_WITH_URL)
+
+ bool IsAudioWriteAheadExtensionEnabled() const override;
+ bool GetAudioConfiguration(
+ SbPlayer player, int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration) override;
+
+ private:
+ bool (*audio_write_duration_player_get_audio_configuration_)(
+ SbPlayer player, int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration) =
+ nullptr;
};
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index 261af02..e14d0d0 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -30,12 +30,14 @@
#include "base/trace_event/trace_event.h"
#include "cobalt/base/c_val.h"
#include "cobalt/base/startup_timer.h"
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/math/size.h"
#include "cobalt/media/base/media_export.h"
#include "cobalt/media/base/pipeline.h"
#include "cobalt/media/base/playback_statistics.h"
#include "cobalt/media/base/sbplayer_bridge.h"
#include "cobalt/media/base/sbplayer_set_bounds_helper.h"
+#include "starboard/common/media.h"
#include "starboard/common/string.h"
#include "starboard/configuration_constants.h"
#include "starboard/time.h"
@@ -66,6 +68,7 @@
using ::media::PipelineStatistics;
using ::media::PipelineStatusCallback;
using ::media::VideoDecoderConfig;
+using ::starboard::GetCobaltExtensionMediaAudioConnectorName;
static const int kRetryDelayAtSuspendInMilliseconds = 100;
@@ -90,6 +93,35 @@
#endif // SB_HAS(PLAYER_WITH_URL)
};
+bool HasRemoteAudioOutputs(
+ const std::vector<CobaltExtensionMediaAudioConfiguration>& configurations) {
+ for (auto&& configuration : configurations) {
+ const auto connector = configuration.connector;
+ switch (connector) {
+ case kCobaltExtensionMediaAudioConnectorUnknown:
+ case kCobaltExtensionMediaAudioConnectorAnalog:
+ case kCobaltExtensionMediaAudioConnectorBuiltIn:
+ case kCobaltExtensionMediaAudioConnectorHdmi:
+ case kCobaltExtensionMediaAudioConnectorSpdif:
+ case kCobaltExtensionMediaAudioConnectorUsb:
+ LOG(INFO) << "Encountered local audio connector: "
+ << GetCobaltExtensionMediaAudioConnectorName(connector);
+ break;
+ case kCobaltExtensionMediaAudioConnectorBluetooth:
+ case kCobaltExtensionMediaAudioConnectorRemoteWired:
+ case kCobaltExtensionMediaAudioConnectorRemoteWireless:
+ case kCobaltExtensionMediaAudioConnectorRemoteOther:
+ LOG(INFO) << "Encountered remote audio connector: "
+ << GetCobaltExtensionMediaAudioConnectorName(connector);
+ return true;
+ }
+ }
+
+ LOG(INFO) << "No remote audio outputs found.";
+
+ return false;
+}
+
// SbPlayerPipeline is a PipelineBase implementation that uses the SbPlayer
// interface internally.
class MEDIA_EXPORT SbPlayerPipeline : public Pipeline,
@@ -103,6 +135,7 @@
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, bool allow_batched_sample_write,
+ SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
MediaLog* media_log, DecodeTargetProvider* decode_target_provider);
~SbPlayerPipeline() override;
@@ -148,6 +181,7 @@
TimeDelta GetMediaStartDate() const override;
#endif // SB_HAS(PLAYER_WITH_URL)
void GetNaturalVideoSize(gfx::Size* out_size) const override;
+ std::vector<std::string> GetAudioConnectors() const override;
bool DidLoadingProgress() const override;
PipelineStatistics GetStatistics() const override;
@@ -322,19 +356,30 @@
DecodeTargetProvider* decode_target_provider_;
+ // The following two variables are used only when the Configurable Audio Write
+ // Ahead extension is enabled.
+ SbTime audio_write_duration_local_;
+ SbTime audio_write_duration_remote_;
+
+ // When the Configurable Audio Write Ahead extension is enabled,
+ // the two variables below should always contain the same value.
+ //
// Read audio from the stream if |timestamp_of_last_written_audio_| is less
- // than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10
- // seconds of audio to be written to the SbPlayer after playback startup or
- // seek.
- static const SbTime kAudioPrerollLimit = 10 * kSbTimeSecond;
- // Don't read audio from the stream more than |kAudioLimit| ahead of the
- // current media time during playing.
- static const SbTime kAudioLimit = kSbTimeSecond;
+ // than |seek_time_| + |audio_write_duration_for_preroll_|. When the
+ // Configurable Audio Write Ahead extension is disabled, this effectively
+ // allows 10 seconds of audio to be written to the SbPlayer after playback
+ // startup or seek.
+ SbTime audio_write_duration_ = kSbTimeSecond;
+ // Don't read audio from the stream more than |audio_write_duration_| ahead of
+ // the current media time during playing.
+ SbTime audio_write_duration_for_preroll_ = 10 * kSbTimeSecond;
+
// Only call GetMediaTime() from OnNeedData if it has been
// |kMediaTimeCheckInterval| since the last call to GetMediaTime().
static const SbTime kMediaTimeCheckInterval = 0.1 * kSbTimeSecond;
// Timestamp for the last written audio.
SbTime timestamp_of_last_written_audio_ = 0;
+
// Last media time reported by GetMediaTime().
base::CVal<SbTime> last_media_time_;
// Time when we last checked the media time.
@@ -357,6 +402,7 @@
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, bool allow_batched_sample_write,
+ SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
MediaLog* media_log, DecodeTargetProvider* decode_target_provider)
: pipeline_identifier_(
base::StringPrintf("%X", g_pipeline_identifier_counter++)),
@@ -406,7 +452,16 @@
pipeline_identifier_.c_str()),
"", "The max video capabilities required for the media pipeline."),
playback_statistics_(pipeline_identifier_) {
- SbMediaSetAudioWriteDuration(kAudioLimit);
+ SbMediaSetAudioWriteDuration(audio_write_duration_);
+ DCHECK(sbplayer_interface_);
+ if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ audio_write_duration_local_ = audio_write_duration_local;
+ audio_write_duration_remote_ = audio_write_duration_remote;
+ } else {
+ LOG(INFO) << "Setting audio write duration to " << audio_write_duration_
+ << ", the duration during preroll is "
+ << audio_write_duration_for_preroll_;
+ }
}
SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_bridge_); }
@@ -571,6 +626,8 @@
if (demuxer_) {
stop_cb_ = stop_cb;
demuxer_->Stop();
+ video_stream_ = nullptr;
+ audio_stream_ = nullptr;
OnDemuxerStopped();
} else {
stop_cb.Run();
@@ -780,6 +837,27 @@
*out_size = natural_size_;
}
+std::vector<std::string> SbPlayerPipeline::GetAudioConnectors() const {
+ if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ base::AutoLock auto_lock(lock_);
+ if (!player_bridge_) {
+ return std::vector<std::string>();
+ }
+
+ std::vector<std::string> connectors;
+
+ auto configurations = player_bridge_->GetAudioConfigurations();
+ for (auto&& configuration : configurations) {
+ connectors.push_back(
+ GetCobaltExtensionMediaAudioConnectorName(configuration.connector));
+ }
+
+ return connectors;
+ }
+
+ return std::vector<std::string>();
+}
+
bool SbPlayerPipeline::DidLoadingProgress() const {
base::AutoLock auto_lock(lock_);
bool ret = did_loading_progress_;
@@ -1035,6 +1113,18 @@
*decode_to_texture_output_mode_, decode_target_provider_,
max_video_capabilities_, pipeline_identifier_));
if (player_bridge_->IsValid()) {
+ if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ // TODO(b/267678497): When `player_bridge_->GetAudioConfigurations()`
+ // returns no audio configurations, update the write durations again
+ // before the SbPlayer reaches `kSbPlayerStatePresenting`.
+ audio_write_duration_for_preroll_ = audio_write_duration_ =
+ HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations())
+ ? audio_write_duration_remote_
+ : audio_write_duration_local_;
+ LOG(INFO) << "SbPlayerBridge created, with audio write duration at "
+ << audio_write_duration_for_preroll_;
+ }
+
SetPlaybackRateTask(playback_rate_);
SetVolumeTask(volume_);
} else {
@@ -1268,17 +1358,18 @@
GetMediaTime();
}
- // Delay reading audio more than |kAudioLimit| ahead of playback after the
- // player has received enough audio for preroll, taking into account that
- // our estimate of playback time might be behind by
+ // Delay reading audio more than |audio_write_duration_| ahead of playback
+ // 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() >
- kAudioPrerollLimit) {
+ audio_write_duration_for_preroll_) {
// The estimated time ahead of playback may be negative if no audio has
// been written.
SbTime time_ahead_of_playback =
timestamp_of_last_written_audio_ - last_media_time_;
- if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
+ if (time_ahead_of_playback >
+ (audio_write_duration_ + kMediaTimeCheckInterval)) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&SbPlayerPipeline::DelayedNeedData, this, max_buffers),
@@ -1348,6 +1439,15 @@
playback_statistics_.OnPresenting(
video_stream_->video_decoder_config());
}
+ if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ audio_write_duration_for_preroll_ = audio_write_duration_ =
+ HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations())
+ ? audio_write_duration_remote_
+ : audio_write_duration_local_;
+ LOG(INFO)
+ << "SbPlayerBridge reaches kSbPlayerStatePresenting, with audio"
+ << " write duration at " << audio_write_duration_;
+ }
break;
}
case kSbPlayerStateEndOfStream:
@@ -1535,6 +1635,8 @@
std::string SbPlayerPipeline::AppendStatisticsString(
const std::string& message) const {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
if (nullptr == video_stream_) {
return message + ", playback statistics: n/a.";
} else {
@@ -1602,12 +1704,14 @@
const GetDecodeTargetGraphicsContextProviderFunc&
get_decode_target_graphics_context_provider_func,
bool allow_resume_after_suspend, bool allow_batched_sample_write,
+ SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
MediaLog* media_log, DecodeTargetProvider* decode_target_provider) {
- return new SbPlayerPipeline(interface, window, task_runner,
- get_decode_target_graphics_context_provider_func,
- allow_resume_after_suspend,
- allow_batched_sample_write, media_log,
- decode_target_provider);
+ return new SbPlayerPipeline(
+ interface, window, task_runner,
+ get_decode_target_graphics_context_provider_func,
+ allow_resume_after_suspend, allow_batched_sample_write,
+ audio_write_duration_local, audio_write_duration_remote, media_log,
+ decode_target_provider);
}
} // namespace media
diff --git a/cobalt/media/media_module.cc b/cobalt/media/media_module.cc
index 95965a5..aedc5c6 100644
--- a/cobalt/media/media_module.cc
+++ b/cobalt/media/media_module.cc
@@ -194,6 +194,18 @@
LOG(INFO) << (value ? "Enabling" : "Disabling")
<< " media metrics collection.";
return true;
+ } else if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ if (name == "AudioWriteDurationLocal" && value > 0) {
+ audio_write_duration_local_ = value;
+ LOG(INFO) << "Set AudioWriteDurationLocal to "
+ << audio_write_duration_local_;
+ return true;
+ } else if (name == "AudioWriteDurationRemote" && value > 0) {
+ audio_write_duration_remote_ = value;
+ LOG(INFO) << "Set AudioWriteDurationRemote to "
+ << audio_write_duration_remote_;
+ return true;
+ }
}
return false;
}
@@ -206,12 +218,21 @@
window = system_window_->GetSbWindow();
}
+ SbTime audio_write_duration_local = -1;
+ SbTime audio_write_duration_remote = -1;
+
+ if (sbplayer_interface_->IsAudioWriteAheadExtensionEnabled()) {
+ audio_write_duration_local = audio_write_duration_local_;
+ audio_write_duration_remote = audio_write_duration_remote_;
+ }
+
return std::unique_ptr<WebMediaPlayer>(new media::WebMediaPlayerImpl(
sbplayer_interface_.get(), window,
base::Bind(&MediaModule::GetSbDecodeTargetGraphicsContextProvider,
base::Unretained(this)),
client, this, options_.allow_resume_after_suspend,
- allow_batched_sample_write_, &media_log_));
+ allow_batched_sample_write_, audio_write_duration_local,
+ audio_write_duration_remote, &media_log_));
}
void MediaModule::Suspend() {
diff --git a/cobalt/media/media_module.h b/cobalt/media/media_module.h
index 6ca47b8..f4c2594 100644
--- a/cobalt/media/media_module.h
+++ b/cobalt/media/media_module.h
@@ -24,6 +24,7 @@
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/math/size.h"
#include "cobalt/media/base/sbplayer_interface.h"
#include "cobalt/media/can_play_type_handler.h"
@@ -123,6 +124,12 @@
bool allow_batched_sample_write_ = false;
+ // The following two variables are used only when the Configurable Audio Write
+ // Ahead extension is enabled.
+ SbTime audio_write_duration_local_ = kCobaltExtensionPlayerWriteDurationLocal;
+ SbTime audio_write_duration_remote_ =
+ kCobaltExtensionPlayerWriteDurationRemote;
+
DecoderBufferAllocator decoder_buffer_allocator_;
};
diff --git a/cobalt/media/player/web_media_player.h b/cobalt/media/player/web_media_player.h
index 1322473..459ec6e 100644
--- a/cobalt/media/player/web_media_player.h
+++ b/cobalt/media/player/web_media_player.h
@@ -130,6 +130,9 @@
virtual int GetNaturalWidth() const = 0;
virtual int GetNaturalHeight() const = 0;
+ // Names of audio connectors used by the playback.
+ virtual std::vector<std::string> GetAudioConnectors() const = 0;
+
// Getters of playback state.
virtual bool IsPaused() const = 0;
virtual bool IsSeeking() const = 0;
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index 8073366..8f9ba9a 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -118,6 +118,7 @@
get_decode_target_graphics_context_provider_func,
WebMediaPlayerClient* client, WebMediaPlayerDelegate* delegate,
bool allow_resume_after_suspend, bool allow_batched_sample_write,
+ SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
::media::MediaLog* const media_log)
: pipeline_thread_("media_pipeline"),
network_state_(WebMediaPlayer::kNetworkStateEmpty),
@@ -146,6 +147,7 @@
Pipeline::Create(interface, window, pipeline_thread_.task_runner(),
get_decode_target_graphics_context_provider_func,
allow_resume_after_suspend_, allow_batched_sample_write_,
+ audio_write_duration_local, audio_write_duration_remote,
media_log_, decode_target_provider_.get());
// Also we want to be notified of |main_loop_| destruction.
@@ -428,6 +430,11 @@
return size.height();
}
+std::vector<std::string> WebMediaPlayerImpl::GetAudioConnectors() const {
+ DCHECK_EQ(main_loop_, base::MessageLoop::current());
+ return pipeline_->GetAudioConnectors();
+}
+
bool WebMediaPlayerImpl::IsPaused() const {
DCHECK_EQ(main_loop_, base::MessageLoop::current());
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h
index b64f318..9139ce8 100644
--- a/cobalt/media/player/web_media_player_impl.h
+++ b/cobalt/media/player/web_media_player_impl.h
@@ -110,6 +110,8 @@
WebMediaPlayerDelegate* delegate,
bool allow_resume_after_suspend,
bool allow_batched_sample_write,
+ SbTime audio_write_duration_local,
+ SbTime audio_write_duration_remote,
::media::MediaLog* const media_log);
~WebMediaPlayerImpl() override;
@@ -146,6 +148,9 @@
int GetNaturalWidth() const override;
int GetNaturalHeight() const override;
+ // Names of audio connectors used by the playback.
+ std::vector<std::string> GetAudioConnectors() const override;
+
// Getters of playback state.
bool IsPaused() const override;
bool IsSeeking() const override;
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc
index c4db368..c93ff54 100644
--- a/cobalt/persistent_storage/persistent_settings.cc
+++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -201,7 +201,7 @@
void PersistentSettings::SetPersistentSetting(
const std::string& key, std::unique_ptr<base::Value> value,
- base::OnceClosure closure) {
+ base::OnceClosure closure, bool blocking) {
if (key == kValidated) {
LOG(ERROR) << "Cannot set protected persistent setting: " << key;
return;
@@ -209,12 +209,12 @@
message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&PersistentSettings::SetPersistentSettingHelper,
base::Unretained(this), key, std::move(value),
- std::move(closure)));
+ std::move(closure), blocking));
}
void PersistentSettings::SetPersistentSettingHelper(
const std::string& key, std::unique_ptr<base::Value> value,
- base::OnceClosure closure) {
+ base::OnceClosure closure, bool blocking) {
DCHECK_EQ(base::MessageLoop::current(), message_loop());
if (validated_initial_settings_) {
base::AutoLock auto_lock(pref_store_lock_);
@@ -223,7 +223,7 @@
WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
writeable_pref_store()->SetValue(
key, std::move(value), WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
- writeable_pref_store()->CommitPendingWrite();
+ commit_pending_write(blocking);
} else {
LOG(ERROR) << "Cannot set persistent setting while unvalidated: " << key;
}
@@ -231,15 +231,17 @@
}
void PersistentSettings::RemovePersistentSetting(const std::string& key,
- base::OnceClosure closure) {
+ base::OnceClosure closure,
+ bool blocking) {
message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&PersistentSettings::RemovePersistentSettingHelper,
- base::Unretained(this), key, std::move(closure)));
+ base::Unretained(this), key, std::move(closure),
+ blocking));
}
void PersistentSettings::RemovePersistentSettingHelper(
- const std::string& key, base::OnceClosure closure) {
+ const std::string& key, base::OnceClosure closure, bool blocking) {
DCHECK_EQ(base::MessageLoop::current(), message_loop());
if (validated_initial_settings_) {
base::AutoLock auto_lock(pref_store_lock_);
@@ -248,7 +250,7 @@
WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
writeable_pref_store()->RemoveValue(
key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
- writeable_pref_store()->CommitPendingWrite();
+ commit_pending_write(blocking);
} else {
LOG(ERROR) << "Cannot remove persistent setting while unvalidated: " << key;
}
diff --git a/cobalt/persistent_storage/persistent_settings.h b/cobalt/persistent_storage/persistent_settings.h
index 0cf789f..ff3ea58 100644
--- a/cobalt/persistent_storage/persistent_settings.h
+++ b/cobalt/persistent_storage/persistent_settings.h
@@ -71,11 +71,13 @@
void SetPersistentSetting(
const std::string& key, std::unique_ptr<base::Value> value,
- base::OnceClosure closure = std::move(base::DoNothing()));
+ base::OnceClosure closure = std::move(base::DoNothing()),
+ bool blocking = false);
void RemovePersistentSetting(
const std::string& key,
- base::OnceClosure closure = std::move(base::DoNothing()));
+ base::OnceClosure closure = std::move(base::DoNothing()),
+ bool blocking = false);
void DeletePersistentSettings(
base::OnceClosure closure = std::move(base::DoNothing()));
@@ -89,10 +91,10 @@
void SetPersistentSettingHelper(const std::string& key,
std::unique_ptr<base::Value> value,
- base::OnceClosure closure);
+ base::OnceClosure closure, bool blocking);
void RemovePersistentSettingHelper(const std::string& key,
- base::OnceClosure closure);
+ base::OnceClosure closure, bool blocking);
void DeletePersistentSettingsHelper(base::OnceClosure closure);
@@ -101,6 +103,18 @@
return pref_store_;
}
+ void commit_pending_write(bool blocking) {
+ if (blocking) {
+ base::WaitableEvent written;
+ writeable_pref_store()->CommitPendingWrite(
+ base::OnceClosure(),
+ base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
+ written.Wait();
+ } else {
+ writeable_pref_store()->CommitPendingWrite();
+ }
+ }
+
// Persistent settings file path.
std::string persistent_settings_file_;
diff --git a/cobalt/persistent_storage/persistent_settings_test.cc b/cobalt/persistent_storage/persistent_settings_test.cc
index 3f88397..91b2bce 100644
--- a/cobalt/persistent_storage/persistent_settings_test.cc
+++ b/cobalt/persistent_storage/persistent_settings_test.cc
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "cobalt/persistent_storage/persistent_settings.h"
+
#include <utility>
#include <vector>
@@ -20,9 +22,7 @@
#include "base/callback_forward.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/scoped_task_environment.h"
-#include "base/threading/platform_thread.h"
#include "base/values.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
#include "starboard/common/file.h"
#include "starboard/common/log.h"
#include "starboard/configuration_constants.h"
@@ -81,13 +81,13 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsInt("key", true));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(4.2), std::move(closure));
+ "key", std::make_unique<base::Value>(4.2), std::move(closure), true);
test_done_.Wait();
}
@@ -99,32 +99,32 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(true), std::move(closure));
+ "key", std::make_unique<base::Value>(true), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_FALSE(
+ EXPECT_FALSE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_FALSE(
+ EXPECT_FALSE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(false), std::move(closure));
+ "key", std::make_unique<base::Value>(false), std::move(closure), true);
test_done_.Wait();
}
@@ -143,13 +143,13 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ(8, persistent_settings->GetPersistentSettingAsInt("key", 8));
+ EXPECT_EQ(8, persistent_settings->GetPersistentSettingAsInt("key", 8));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(4.2), std::move(closure));
+ "key", std::make_unique<base::Value>(4.2), std::move(closure), true);
test_done_.Wait();
}
@@ -161,36 +161,36 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ(-1, persistent_settings->GetPersistentSettingAsInt("key", 8));
+ EXPECT_EQ(-1, persistent_settings->GetPersistentSettingAsInt("key", 8));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(-1), std::move(closure));
+ "key", std::make_unique<base::Value>(-1), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ(0, persistent_settings->GetPersistentSettingAsInt("key", 8));
+ EXPECT_EQ(0, persistent_settings->GetPersistentSettingAsInt("key", 8));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(0), std::move(closure));
+ "key", std::make_unique<base::Value>(0), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ(42, persistent_settings->GetPersistentSettingAsInt("key", 8));
+ EXPECT_EQ(42, persistent_settings->GetPersistentSettingAsInt("key", 8));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(42), std::move(closure));
+ "key", std::make_unique<base::Value>(42), std::move(closure), true);
test_done_.Wait();
}
@@ -214,13 +214,13 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ("hello", persistent_settings->GetPersistentSettingAsString(
+ EXPECT_EQ("hello", persistent_settings->GetPersistentSettingAsString(
"key", "hello"));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(4.2), std::move(closure));
+ "key", std::make_unique<base::Value>(4.2), std::move(closure), true);
test_done_.Wait();
}
@@ -232,21 +232,21 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ("", persistent_settings->GetPersistentSettingAsString(
+ EXPECT_EQ("", persistent_settings->GetPersistentSettingAsString(
"key", "hello"));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(""), std::move(closure));
+ "key", std::make_unique<base::Value>(""), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ(
+ EXPECT_EQ(
"hello there",
persistent_settings->GetPersistentSettingAsString("key", "hello"));
test_done->Signal();
@@ -254,46 +254,47 @@
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>("hello there"), std::move(closure));
+ "key", std::make_unique<base::Value>("hello there"), std::move(closure),
+ true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ("42", persistent_settings->GetPersistentSettingAsString(
+ EXPECT_EQ("42", persistent_settings->GetPersistentSettingAsString(
"key", "hello"));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>("42"), std::move(closure));
+ "key", std::make_unique<base::Value>("42"), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ("\n", persistent_settings->GetPersistentSettingAsString(
+ EXPECT_EQ("\n", persistent_settings->GetPersistentSettingAsString(
"key", "hello"));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>("\n"), std::move(closure));
+ "key", std::make_unique<base::Value>("\n"), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_EQ("\\n", persistent_settings->GetPersistentSettingAsString(
+ EXPECT_EQ("\\n", persistent_settings->GetPersistentSettingAsString(
"key", "hello"));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>("\\n"), std::move(closure));
+ "key", std::make_unique<base::Value>("\\n"), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
}
@@ -307,10 +308,10 @@
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
auto test_list = persistent_settings->GetPersistentSettingAsList("key");
- ASSERT_FALSE(test_list.empty());
- ASSERT_EQ(1, test_list.size());
- ASSERT_TRUE(test_list[0].is_string());
- ASSERT_EQ("hello", test_list[0].GetString());
+ EXPECT_FALSE(test_list.empty());
+ EXPECT_EQ(1, test_list.size());
+ EXPECT_TRUE(test_list[0].is_string());
+ EXPECT_EQ("hello", test_list[0].GetString());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
@@ -318,7 +319,7 @@
std::vector<base::Value> list;
list.emplace_back("hello");
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(list), std::move(closure));
+ "key", std::make_unique<base::Value>(list), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
@@ -326,19 +327,19 @@
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
auto test_list = persistent_settings->GetPersistentSettingAsList("key");
- ASSERT_FALSE(test_list.empty());
- ASSERT_EQ(2, test_list.size());
- ASSERT_TRUE(test_list[0].is_string());
- ASSERT_EQ("hello", test_list[0].GetString());
- ASSERT_TRUE(test_list[1].is_string());
- ASSERT_EQ("there", test_list[1].GetString());
+ EXPECT_FALSE(test_list.empty());
+ EXPECT_EQ(2, test_list.size());
+ EXPECT_TRUE(test_list[0].is_string());
+ EXPECT_EQ("hello", test_list[0].GetString());
+ EXPECT_TRUE(test_list[1].is_string());
+ EXPECT_EQ("there", test_list[1].GetString());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
list.emplace_back("there");
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(list), std::move(closure));
+ "key", std::make_unique<base::Value>(list), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
@@ -346,21 +347,21 @@
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
auto test_list = persistent_settings->GetPersistentSettingAsList("key");
- ASSERT_FALSE(test_list.empty());
- ASSERT_EQ(3, test_list.size());
- ASSERT_TRUE(test_list[0].is_string());
- ASSERT_EQ("hello", test_list[0].GetString());
- ASSERT_TRUE(test_list[1].is_string());
- ASSERT_EQ("there", test_list[1].GetString());
- ASSERT_TRUE(test_list[2].is_int());
- ASSERT_EQ(42, test_list[2].GetInt());
+ EXPECT_FALSE(test_list.empty());
+ EXPECT_EQ(3, test_list.size());
+ EXPECT_TRUE(test_list[0].is_string());
+ EXPECT_EQ("hello", test_list[0].GetString());
+ EXPECT_TRUE(test_list[1].is_string());
+ EXPECT_EQ("there", test_list[1].GetString());
+ EXPECT_TRUE(test_list[2].is_int());
+ EXPECT_EQ(42, test_list[2].GetInt());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
list.emplace_back(42);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(list), std::move(closure));
+ "key", std::make_unique<base::Value>(list), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
}
@@ -375,10 +376,10 @@
base::WaitableEvent* test_done) {
auto test_dict =
persistent_settings->GetPersistentSettingAsDictionary("key");
- ASSERT_FALSE(test_dict.empty());
- ASSERT_EQ(1, test_dict.size());
- ASSERT_TRUE(test_dict["key_string"]->is_string());
- ASSERT_EQ("hello", test_dict["key_string"]->GetString());
+ EXPECT_FALSE(test_dict.empty());
+ EXPECT_EQ(1, test_dict.size());
+ EXPECT_TRUE(test_dict["key_string"]->is_string());
+ EXPECT_EQ("hello", test_dict["key_string"]->GetString());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
@@ -386,7 +387,7 @@
base::flat_map<std::string, std::unique_ptr<base::Value>> dict;
dict.try_emplace("key_string", std::make_unique<base::Value>("hello"));
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(dict), std::move(closure));
+ "key", std::make_unique<base::Value>(dict), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
@@ -395,19 +396,19 @@
base::WaitableEvent* test_done) {
auto test_dict =
persistent_settings->GetPersistentSettingAsDictionary("key");
- ASSERT_FALSE(test_dict.empty());
- ASSERT_EQ(2, test_dict.size());
- ASSERT_TRUE(test_dict["key_string"]->is_string());
- ASSERT_EQ("hello", test_dict["key_string"]->GetString());
- ASSERT_TRUE(test_dict["key_int"]->is_int());
- ASSERT_EQ(42, test_dict["key_int"]->GetInt());
+ EXPECT_FALSE(test_dict.empty());
+ EXPECT_EQ(2, test_dict.size());
+ EXPECT_TRUE(test_dict["key_string"]->is_string());
+ EXPECT_EQ("hello", test_dict["key_string"]->GetString());
+ EXPECT_TRUE(test_dict["key_int"]->is_int());
+ EXPECT_EQ(42, test_dict["key_int"]->GetInt());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
dict.try_emplace("key_int", std::make_unique<base::Value>(42));
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(dict), std::move(closure));
+ "key", std::make_unique<base::Value>(dict), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
}
@@ -425,10 +426,10 @@
base::WaitableEvent* test_done) {
auto test_dict = persistent_settings->GetPersistentSettingAsDictionary(
"http://127.0.0.1:45019/");
- ASSERT_FALSE(test_dict.empty());
- ASSERT_EQ(1, test_dict.size());
- ASSERT_TRUE(test_dict["http://127.0.0.1:45019/"]->is_string());
- ASSERT_EQ("Dictionary URL Key Works!",
+ EXPECT_FALSE(test_dict.empty());
+ EXPECT_EQ(1, test_dict.size());
+ EXPECT_TRUE(test_dict["http://127.0.0.1:45019/"]->is_string());
+ EXPECT_EQ("Dictionary URL Key Works!",
test_dict["http://127.0.0.1:45019/"]->GetString());
test_done->Signal();
},
@@ -441,7 +442,7 @@
std::make_unique<base::Value>("Dictionary URL Key Works!"));
persistent_settings->SetPersistentSetting("http://127.0.0.1:45019/",
std::make_unique<base::Value>(dict),
- std::move(closure));
+ std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
@@ -450,12 +451,12 @@
base::WaitableEvent* test_done) {
auto test_dict = persistent_settings->GetPersistentSettingAsDictionary(
"http://127.0.0.1:45019/");
- ASSERT_TRUE(test_dict.empty());
+ EXPECT_TRUE(test_dict.empty());
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->RemovePersistentSetting("http://127.0.0.1:45019/",
- std::move(closure));
+ std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
}
@@ -471,29 +472,29 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(true), std::move(closure));
+ "key", std::make_unique<base::Value>(true), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_FALSE(
+ EXPECT_FALSE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
- persistent_settings->RemovePersistentSetting("key", std::move(closure));
+ persistent_settings->RemovePersistentSetting("key", std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
}
@@ -509,24 +510,24 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(true), std::move(closure));
+ "key", std::make_unique<base::Value>(true), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_FALSE(
+ EXPECT_FALSE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
@@ -547,22 +548,18 @@
base::OnceClosure closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(true), std::move(closure));
+ "key", std::make_unique<base::Value>(true), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
- // Sleep for one second to allow for the previous persistent_setting's
- // JsonPrefStore instance time to write to disk before creating a new
- // persistent_settings and JsonPrefStore instance.
- base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
persistent_settings =
std::make_unique<PersistentSettings>(kPersistentSettingsJson);
ASSERT_TRUE(persistent_settings->GetPersistentSettingAsBool("key", true));
@@ -571,15 +568,15 @@
closure = base::BindOnce(
[](PersistentSettings* persistent_settings,
base::WaitableEvent* test_done) {
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", true));
- ASSERT_TRUE(
+ EXPECT_TRUE(
persistent_settings->GetPersistentSettingAsBool("key", false));
test_done->Signal();
},
persistent_settings.get(), &test_done_);
persistent_settings->SetPersistentSetting(
- "key", std::make_unique<base::Value>(false), std::move(closure));
+ "key", std::make_unique<base::Value>(false), std::move(closure), true);
test_done_.Wait();
test_done_.Reset();
diff --git a/cobalt/version.h b/cobalt/version.h
index 3182bc9..74f79af 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
// release is cut.
//.
-#define COBALT_VERSION "23.lts.5"
+#define COBALT_VERSION "23.lts.6"
#endif // COBALT_VERSION_H_
diff --git a/docker-compose-windows.yml b/docker-compose-windows.yml
index e577ee4..c60c372 100644
--- a/docker-compose-windows.yml
+++ b/docker-compose-windows.yml
@@ -98,3 +98,14 @@
depends_on:
- cobalt-build-win32-base
image: cobalt-build-win-win32
+
+ runner-win-win32:
+ <<: *common-definitions
+ build:
+ context: ./docker/windows/runner
+ dockerfile: ./Dockerfile
+ args:
+ - RUNNER_VERSION=2.294.0
+ depends_on:
+ - cobalt-build-win32-base
+ image: cobalt-runner-win-win32
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index 60533d6..26590dd 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -26,7 +26,6 @@
ninja-build \
pkgconf \
unzip \
- yasm \
&& /opt/clean-after-apt.sh
# === Get Nodejs pinned LTS version via NVM
diff --git a/docker/windows/base/build/Dockerfile b/docker/windows/base/build/Dockerfile
index 19aa9b6..6e76594 100644
--- a/docker/windows/base/build/Dockerfile
+++ b/docker/windows/base/build/Dockerfile
@@ -29,10 +29,14 @@
# of the execution, i.e. the full invocation string.
COPY ./list_python_processes.py /list_python_processes.py
+# Pin Choco to 1.4.0 to avoid required reboot in 2.0.0
+ENV chocolateyVersion '1.4.0'
# Install deps via chocolatey.
RUN iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));`
mkdir C:\choco-cache;`
- choco install -y -c C:\choco-cache python3 -ia '/quiet InstallAllUsers=1 PrependPath=1 TargetDir="C:\Python3"';`
+ # Note: We pinned python 3.11.0 version here because 3.11.2 has a regression
+ # where the install arguments like TargetDir were being ignored.
+ choco install -y -c C:\choco-cache python3 --version 3.11.0 -ia '/quiet InstallAllUsers=1 PrependPath=1 TargetDir="C:\Python3"';`
choco install -y -c C:\choco-cache winflexbison3 --params '/InstallDir:C:\bison';`
choco install -y -c C:\choco-cache ninja;`
choco install -y -c C:\choco-cache nodejs-lts;`
diff --git a/docker/windows/runner/Dockerfile b/docker/windows/runner/Dockerfile
new file mode 100644
index 0000000..a494811
--- /dev/null
+++ b/docker/windows/runner/Dockerfile
@@ -0,0 +1,34 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+FROM cobalt-build-win32-base
+
+SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
+
+ARG RUNNER_VERSION
+
+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; \
+ 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\")
+
+# Required for packaging artifacts.
+RUN choco install -f -y 7zip --version=19.0
+
+ENV TMP "C:\Users\ContainerAdministrator\AppData\Local\Temp2"
+
+ADD runner.ps1 C:/runner.ps1
+CMD ["pwsh", "-ExecutionPolicy", "Unrestricted", "-File", ".\\runner.ps1"]
diff --git a/docker/windows/runner/runner.ps1 b/docker/windows/runner/runner.ps1
new file mode 100644
index 0000000..9e96a5e
--- /dev/null
+++ b/docker/windows/runner/runner.ps1
@@ -0,0 +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 --labels $env:RUNNER_LABELS;
+.\actions-runner\run.cmd;
diff --git a/net/url_request/url_fetcher_core.cc b/net/url_request/url_fetcher_core.cc
index 0d77b78..6835662 100644
--- a/net/url_request/url_fetcher_core.cc
+++ b/net/url_request/url_fetcher_core.cc
@@ -175,8 +175,9 @@
}
void URLFetcherCore::Stop() {
- if (delegate_task_runner_) // May be NULL in tests.
+ if (delegate_task_runner_) { // May be NULL in tests.
DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
+ }
delegate_ = NULL;
fetcher_ = NULL;
@@ -783,8 +784,11 @@
if (!extra_request_headers_.IsEmpty())
request_->SetExtraRequestHeaders(extra_request_headers_);
+#if defined(STARBOARD)
request_->SetLoadTimingInfoCallback(base::Bind(&URLFetcherCore::GetLoadTimingInfo,
base::Unretained(this)));
+#endif
+
request_->Start();
}
@@ -1133,6 +1137,14 @@
#if defined(STARBOARD)
void URLFetcherCore::GetLoadTimingInfo(
const net::LoadTimingInfo& timing_info) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::GetLoadTimingInfoInDelegateThread,
+ this, timing_info));
+}
+
+void URLFetcherCore::GetLoadTimingInfoInDelegateThread(
+ const net::LoadTimingInfo& timing_info) {
// Check if the URLFetcherCore has been stopped before.
if (delegate_) {
delegate_->ReportLoadTimingInfo(timing_info);
diff --git a/net/url_request/url_fetcher_core.h b/net/url_request/url_fetcher_core.h
index 02048d1..cfbd717 100644
--- a/net/url_request/url_fetcher_core.h
+++ b/net/url_request/url_fetcher_core.h
@@ -165,6 +165,7 @@
static void SetIgnoreCertificateRequests(bool ignored);
#if defined (STARBOARD)
void GetLoadTimingInfo(const net::LoadTimingInfo& timing_info);
+ void GetLoadTimingInfoInDelegateThread(const net::LoadTimingInfo& timing_info);
#endif // defined(STARBOARD)
private:
friend class base::RefCountedThreadSafe<URLFetcherCore>;
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 1478b37..7efa465 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -498,6 +498,9 @@
return;
}
nativeOnMediaCodecOutputFormatChanged(mNativeMediaCodecBridge);
+ if (mFrameRateEstimator != null) {
+ mFrameRateEstimator.reset();
+ }
}
}
};
diff --git a/starboard/common/media.cc b/starboard/common/media.cc
index eb74ac8..f9cdac0 100644
--- a/starboard/common/media.cc
+++ b/starboard/common/media.cc
@@ -639,6 +639,55 @@
return "invalid";
}
+const char* GetMediaAudioConnectorName(SbMediaAudioConnector connector) {
+ switch (connector) {
+ case kSbMediaAudioConnectorNone:
+ return "none";
+ case kSbMediaAudioConnectorAnalog:
+ return "analog";
+ case kSbMediaAudioConnectorBluetooth:
+ return "bluetooth";
+ case kSbMediaAudioConnectorHdmi:
+ return "hdmi";
+ case kSbMediaAudioConnectorNetwork:
+ return "network";
+ case kSbMediaAudioConnectorSpdif:
+ return "spdif";
+ case kSbMediaAudioConnectorUsb:
+ return "usb";
+ }
+ SB_NOTREACHED();
+ return "invalid";
+}
+
+const char* GetCobaltExtensionMediaAudioConnectorName(
+ CobaltExtensionMediaAudioConnector connector) {
+ switch (connector) {
+ case kCobaltExtensionMediaAudioConnectorUnknown:
+ return "unknown";
+ case kCobaltExtensionMediaAudioConnectorAnalog:
+ return "analog";
+ case kCobaltExtensionMediaAudioConnectorBluetooth:
+ return "bluetooth";
+ case kCobaltExtensionMediaAudioConnectorBuiltIn:
+ return "builtin";
+ case kCobaltExtensionMediaAudioConnectorHdmi:
+ return "hdmi";
+ case kCobaltExtensionMediaAudioConnectorRemoteWired:
+ return "remote-wired";
+ case kCobaltExtensionMediaAudioConnectorRemoteWireless:
+ return "remote-wireless";
+ case kCobaltExtensionMediaAudioConnectorRemoteOther:
+ return "remote-other";
+ case kCobaltExtensionMediaAudioConnectorSpdif:
+ return "spdif";
+ case kCobaltExtensionMediaAudioConnectorUsb:
+ return "usb";
+ }
+ SB_NOTREACHED();
+ return "invalid";
+}
+
const char* GetMediaPrimaryIdName(SbMediaPrimaryId primary_id) {
switch (primary_id) {
case kSbMediaPrimaryIdReserved0:
diff --git a/starboard/common/media.h b/starboard/common/media.h
index a51641e..401551d 100644
--- a/starboard/common/media.h
+++ b/starboard/common/media.h
@@ -17,12 +17,16 @@
#include <ostream>
+#include "cobalt/extension/audio_write_ahead.h"
#include "starboard/media.h"
namespace starboard {
const char* GetMediaAudioCodecName(SbMediaAudioCodec codec);
const char* GetMediaVideoCodecName(SbMediaVideoCodec codec);
+const char* GetMediaAudioConnectorName(SbMediaAudioConnector connector);
+const char* GetCobaltExtensionMediaAudioConnectorName(
+ CobaltExtensionMediaAudioConnector connector);
const char* GetMediaPrimaryIdName(SbMediaPrimaryId primary_id);
const char* GetMediaTransferIdName(SbMediaTransferId transfer_id);
const char* GetMediaMatrixIdName(SbMediaMatrixId matrix_id);
diff --git a/starboard/doc/evergreen/cobalt_evergreen_overview.md b/starboard/doc/evergreen/cobalt_evergreen_overview.md
index cc21fa2..e3ce087 100644
--- a/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -90,38 +90,43 @@
### Building Cobalt Evergreen Components
-Cobalt Evergreen requires that there are two separate build(`gyp`)
+Cobalt Evergreen requires that there are two separate build
configurations used due to the separation of the Cobalt core(`libcobalt.so`) and
-the platform-specific Starboard layer(`loader_app`). As a result, you will have
-to initiate a separate gyp process for each. This is required since the Cobalt
-core binary is built with the Google toolchain settings and the
-platform-specific Starboard layer is built with partner toolchain
-configurations.
+the platform-specific layers. As a result, you will have to run `gen gn`
+separately for each. This is required since the Cobalt core binary is built
+with the Google toolchain settings and the components in the platform-specific
+Starboard layer are built with partner toolchain configurations.
-Cobalt Evergreen is built by a separate gyp platform using the Google toolchain:
+Cobalt Evergreen is built by a separate GN platform using the Google toolchain.
+For example:
```
-$ cobalt/build/gn.py -p evergreen-arm-softfp -c qa
-$ ninja -C out/evergreen-arm-softfp_qa cobalt
+$ gn gen out/evergreen-arm-softfp_qa --args="target_platform=\"evergreen-arm-softfp\" build_type=\"qa\" target_cpu=\"arm\" target_os=\"linux\" sb_api_version=14"
+$ ninja -C out/evergreen-arm-softfp_qa cobalt_install
```
Which produces a shared library `libcobalt.so` targeted for specific
architecture, ABI and Starboard version.
-The gyp variable `sb_evergreen` is set to 1 when building `libcobalt.so`.
+The GN variable `sb_is_evergreen` is set to `true` when building `libcobalt.so`.
+
+Finally, note that these instructions are for producing a local build of
+`libcobalt.so`. For submissions to YTS and deployments to users' devices, an
+official release build of `libcobalt.so` should be downloaded from GitHub.
The partner port of Starboard is built with the partner’s toolchain and is
linked into the `loader_app` which knows how to dynamically load
`libcobalt.so`, and the `crashpad_handler` which handles crashes.
```
-$ cobalt/build/gn.py -p <partner_port_name> -c qa
+$ gn gen out/<partner_port_name>_qa --args='target_platform="<partner_port_name>" build_type="qa" sb_api_version=14'
$ ninja -C out/<partner_port_name>_qa loader_app crashpad_handler
```
-Partners should set `sb_evergreen_compatible` to 1 in their gyp platform config.
-DO NOT set the `sb_evergreen` to 1 in your platform-specific configuration as it
-is used only by Cobalt when building with the Google toolchain.
+Partners should set `sb_is_evergreen_compatible` to `true` in their GN platform
+config. DO NOT set the `sb_is_evergreen` to `true` in your platform-specific
+configuration as it is used only by Cobalt when building with the Google
+toolchain.
Additionally, partners should install crash handlers as instructed in the
[Installing Crash Handlers for Cobalt guide](../crash_handlers.md).
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index f097404..f313fde 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -56,6 +56,10 @@
sources = [
"//starboard/linux/shared/atomic_public.h",
"//starboard/linux/shared/audio_sink_type_dispatcher.cc",
+ "//starboard/linux/shared/audio_write_ahead.cc",
+ "//starboard/linux/shared/audio_write_ahead.h",
+ "//starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.cc",
+ "//starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h",
"//starboard/linux/shared/configuration.cc",
"//starboard/linux/shared/configuration.h",
"//starboard/linux/shared/configuration_constants.cc",
@@ -64,6 +68,8 @@
"//starboard/linux/shared/decode_target_internal.cc",
"//starboard/linux/shared/decode_target_internal.h",
"//starboard/linux/shared/decode_target_release.cc",
+ "//starboard/linux/shared/ifa.cc",
+ "//starboard/linux/shared/ifa.h",
"//starboard/linux/shared/media_is_audio_supported.cc",
"//starboard/linux/shared/media_is_video_supported.cc",
"//starboard/linux/shared/netlink.cc",
diff --git a/starboard/linux/shared/audio_write_ahead.cc b/starboard/linux/shared/audio_write_ahead.cc
new file mode 100644
index 0000000..ffcf1f1
--- /dev/null
+++ b/starboard/linux/shared/audio_write_ahead.cc
@@ -0,0 +1,43 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/audio_write_ahead.h"
+
+#include "cobalt/extension/audio_write_ahead.h"
+#include "starboard/common/log.h"
+#include "starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h"
+
+// Omit namespace linux due to symbol name conflict.
+
+namespace starboard {
+namespace shared {
+
+namespace {
+
+const CobaltExtensionConfigurableAudioWriteAheadApi
+ kConfigurableAudioWriteAheadApi = {
+ kCobaltExtensionConfigurableAudioWriteAheadName,
+ 1,
+ &ConfigurableAudioWriteAheadPlayerGetAudioConfiguration,
+};
+
+} // namespace
+
+const void* GetConfigurableAudioWriteAheadApi() {
+ SB_LOG(INFO) << "Audio write ahead extension enabled.";
+ return &kConfigurableAudioWriteAheadApi;
+}
+
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/linux/shared/audio_write_ahead.h b/starboard/linux/shared/audio_write_ahead.h
new file mode 100644
index 0000000..15d19e8
--- /dev/null
+++ b/starboard/linux/shared/audio_write_ahead.h
@@ -0,0 +1,28 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_AUDIO_WRITE_AHEAD_H_
+#define STARBOARD_SHARED_AUDIO_WRITE_AHEAD_H_
+
+// Omit namespace linux due to symbol name conflict.
+
+namespace starboard {
+namespace shared {
+
+const void* GetConfigurableAudioWriteAheadApi();
+
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_SHARED_AUDIO_WRITE_AHEAD_H_
diff --git a/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.cc b/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.cc
new file mode 100644
index 0000000..c2e5f00
--- /dev/null
+++ b/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.cc
@@ -0,0 +1,56 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h"
+
+#include <string>
+
+#include "starboard/audio_sink.h"
+#include "starboard/common/log.h"
+#include "starboard/system.h"
+
+// Omit namespace linux due to symbol name conflict.
+
+namespace starboard {
+namespace shared {
+
+bool ConfigurableAudioWriteAheadPlayerGetAudioConfiguration(
+ SbPlayer player,
+ int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration) {
+ SB_DCHECK(SbPlayerIsValid(player));
+ SB_DCHECK(index >= 0);
+ SB_DCHECK(out_audio_configuration);
+
+ if (index > 0) {
+ // We assume that |player| only uses the primary (index 0) audio output.
+ // For playbacks using more than one audio outputs, or using audio outputs
+ // other than the primary one, the platform should have its own
+ // `SbPlayerGetAudioConfiguration()` implementation.
+ return false;
+ }
+
+ *out_audio_configuration = {};
+
+ out_audio_configuration->connector =
+ kCobaltExtensionMediaAudioConnectorUnknown;
+ out_audio_configuration->latency = 0;
+ out_audio_configuration->coding_type = kSbMediaAudioCodingTypePcm;
+ out_audio_configuration->number_of_channels = SbAudioSinkGetMaxChannels();
+
+ return true;
+}
+
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h b/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h
new file mode 100644
index 0000000..08126df
--- /dev/null
+++ b/starboard/linux/shared/audio_write_ahead_player_get_audio_configuration.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_AUDIO_WRITE_AHEAD_PLAYER_GET_AUDIO_CONFIGURATION_H_
+#define STARBOARD_SHARED_AUDIO_WRITE_AHEAD_PLAYER_GET_AUDIO_CONFIGURATION_H_
+
+#include "cobalt/extension/audio_write_ahead.h"
+
+#include "starboard/player.h"
+
+// Omit namespace linux due to symbol name conflict.
+
+namespace starboard {
+namespace shared {
+
+bool ConfigurableAudioWriteAheadPlayerGetAudioConfiguration(
+ SbPlayer player,
+ int index,
+ CobaltExtensionMediaAudioConfiguration* out_audio_configuration);
+
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_SHARED_AUDIO_WRITE_AHEAD_PLAYER_GET_AUDIO_CONFIGURATION_H_
diff --git a/starboard/linux/shared/ifa.cc b/starboard/linux/shared/ifa.cc
new file mode 100644
index 0000000..548f9bb
--- /dev/null
+++ b/starboard/linux/shared/ifa.cc
@@ -0,0 +1,65 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/ifa.h"
+
+#include "cobalt/extension/ifa.h"
+
+#include "starboard/common/string.h"
+#include "starboard/shared/environment.h"
+
+namespace starboard {
+namespace shared {
+
+namespace {
+
+bool CopyStringAndTestIfSuccess(char* out_value,
+ int value_length,
+ const char* from_value) {
+ if (strlen(from_value) + 1 > value_length)
+ return false;
+ starboard::strlcpy(out_value, from_value, value_length);
+ return true;
+}
+
+// Definitions of any functions included as components in the extension
+// are added here.
+
+bool GetAdvertisingId(char* out_value, int value_length) {
+ return CopyStringAndTestIfSuccess(
+ out_value, value_length,
+ starboard::GetEnvironment("COBALT_ADVERTISING_ID").c_str());
+}
+
+bool GetLimitAdTracking(char* out_value, int value_length) {
+ return CopyStringAndTestIfSuccess(
+ out_value, value_length,
+ GetEnvironment("COBALT_LIMIT_AD_TRACKING").c_str());
+}
+
+const StarboardExtensionIfaApi kIfaApi = {
+ kStarboardExtensionIfaName,
+ 1, // API version that's implemented.
+ &GetAdvertisingId,
+ &GetLimitAdTracking,
+};
+
+} // namespace
+
+const void* GetIfaApi() {
+ return &kIfaApi;
+}
+
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/linux/shared/ifa.h b/starboard/linux/shared/ifa.h
new file mode 100644
index 0000000..fbe61ab
--- /dev/null
+++ b/starboard/linux/shared/ifa.h
@@ -0,0 +1,27 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_LINUX_SHARED_IFA_H_
+#define STARBOARD_LINUX_SHARED_IFA_H_
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+const void* GetIfaApi();
+
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_LINUX_SHARED_IFA_H_
diff --git a/starboard/linux/shared/system_get_extensions.cc b/starboard/linux/shared/system_get_extensions.cc
index fba44bd..f6a7e8d 100644
--- a/starboard/linux/shared/system_get_extensions.cc
+++ b/starboard/linux/shared/system_get_extensions.cc
@@ -14,13 +14,16 @@
#include "starboard/system.h"
+#include "cobalt/extension/audio_write_ahead.h"
#include "cobalt/extension/configuration.h"
#include "cobalt/extension/crash_handler.h"
#include "cobalt/extension/demuxer.h"
#include "cobalt/extension/free_space.h"
+#include "cobalt/extension/ifa.h"
#include "cobalt/extension/memory_mapped_file.h"
#include "cobalt/extension/platform_service.h"
#include "starboard/common/string.h"
+#include "starboard/linux/shared/ifa.h"
#include "starboard/linux/shared/soft_mic_platform_service.h"
#include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
#include "starboard/shared/posix/free_space.h"
@@ -30,6 +33,7 @@
#if SB_IS(EVERGREEN_COMPATIBLE)
#include "starboard/elf_loader/evergreen_config.h"
#endif
+#include "starboard/linux/shared/audio_write_ahead.h"
#include "starboard/linux/shared/configuration.h"
const void* SbSystemGetExtension(const char* name) {
@@ -59,6 +63,9 @@
if (strcmp(name, kCobaltExtensionFreeSpaceName) == 0) {
return starboard::shared::posix::GetFreeSpaceApi();
}
+ if (strcmp(name, kCobaltExtensionConfigurableAudioWriteAheadName) == 0) {
+ return starboard::shared::GetConfigurableAudioWriteAheadApi();
+ }
if (strcmp(name, kCobaltExtensionDemuxerApi) == 0) {
auto command_line =
starboard::shared::starboard::Application::Get()->GetCommandLine();
@@ -67,5 +74,10 @@
return use_ffmpeg_demuxer ? starboard::shared::ffmpeg::GetFFmpegDemuxerApi()
: NULL;
}
+#if SB_API_VERSION < 14
+ if (strcmp(name, kStarboardExtensionIfaName) == 0) {
+ return starboard::shared::GetIfaApi();
+ }
+#endif // SB_API_VERSION < 14
return NULL;
}
diff --git a/starboard/shared/win32/video_decoder.cc b/starboard/shared/win32/video_decoder.cc
index fe58e7b..dd588a3 100644
--- a/starboard/shared/win32/video_decoder.cc
+++ b/starboard/shared/win32/video_decoder.cc
@@ -468,8 +468,9 @@
ComPtr<IMFAttributes> attributes = transform->GetAttributes();
SB_DCHECK(attributes);
- CheckResult(attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT,
- kMaxOutputSamples));
+ CheckResult(
+ attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT,
+ static_cast<UINT32>(GetMaxNumberOfCachedFrames())));
UpdateVideoArea(transform->GetCurrentOutputType());
@@ -704,7 +705,7 @@
// MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT.
thread_lock_.Acquire();
bool input_full = thread_events_.size() >= kMaxInputSamples;
- bool output_full = thread_outputs_.size() >= kMaxOutputSamples;
+ bool output_full = thread_outputs_.size() >= GetMaxNumberOfCachedFrames();
thread_lock_.Release();
Status status = input_full ? kBufferFull : kNeedMoreInput;
diff --git a/third_party/angle/include/GLSLANG/ShaderVars.h b/third_party/angle/include/GLSLANG/ShaderVars.h
index 52f6ad0..5d2d146 100644
--- a/third_party/angle/include/GLSLANG/ShaderVars.h
+++ b/third_party/angle/include/GLSLANG/ShaderVars.h
@@ -12,6 +12,7 @@
#include <algorithm>
#include <array>
+#include <cstdint>
#include <string>
#include <vector>
diff --git a/third_party/angle/src/common/angleutils.h b/third_party/angle/src/common/angleutils.h
index 3a1391e..c8d36f5 100644
--- a/third_party/angle/src/common/angleutils.h
+++ b/third_party/angle/src/common/angleutils.h
@@ -14,6 +14,7 @@
#include <climits>
#include <cstdarg>
#include <cstddef>
+#include <cstdint>
#include <set>
#include <sstream>
#include <string>
diff --git a/third_party/chromium/media/filters/chunk_demuxer.cc b/third_party/chromium/media/filters/chunk_demuxer.cc
index 1d6e475..2b729a4 100644
--- a/third_party/chromium/media/filters/chunk_demuxer.cc
+++ b/third_party/chromium/media/filters/chunk_demuxer.cc
@@ -180,6 +180,9 @@
DCHECK(state_ == UNINITIALIZED || state_ == RETURNING_ABORT_FOR_READS)
<< state_;
+#if defined(STARBOARD)
+ write_head_ = time;
+#endif // defined (STARBOARD)
stream_->Seek(time);
}
@@ -225,6 +228,15 @@
return stream_->GarbageCollectIfNeeded(media_time, newDataSize);
}
+#if defined(STARBOARD)
+
+base::TimeDelta ChunkDemuxerStream::GetWriteHead() const {
+ base::AutoLock auto_lock(lock_);
+ return write_head_;
+}
+
+#endif // defined(STARBOARD)
+
void ChunkDemuxerStream::OnMemoryPressure(
base::TimeDelta media_time,
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level,
@@ -473,6 +485,7 @@
DemuxerStream::Status status = DemuxerStream::kAborted;
std::vector<scoped_refptr<DecoderBuffer>> buffers;
+ base::TimeDelta write_head = write_head_;
if (pending_config_change_) {
status = kConfigChanged;
@@ -491,6 +504,7 @@
<< buffer->timestamp().InSecondsF() << ", dur "
<< buffer->duration().InSecondsF() << ", key "
<< buffer->is_key_frame();
+ write_head = std::max(write_head, buffer->timestamp());
buffers.push_back(std::move(buffer));
status = DemuxerStream::kOk;
} else if (stream_status == SourceBufferStreamStatus::kEndOfStream) {
@@ -529,6 +543,8 @@
return;
}
+ DCHECK_LE(write_head_, write_head);
+ write_head_ = write_head;
std::move(read_cb_).Run(status, buffers);
}
#else // defined(STARBOARD)
@@ -1077,6 +1093,25 @@
return itr->second->EvictCodedFrames(currentMediaTime, newDataSize);
}
+#if defined(STARBOARD)
+
+base::TimeDelta ChunkDemuxer::GetWriteHead(const std::string& id) const {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(IsValidId(id));
+
+ auto iter = id_to_streams_map_.find(id);
+ if (iter == id_to_streams_map_.end() ||
+ iter->second.empty()) {
+ // Handled just in case.
+ SB_NOTREACHED();
+ return base::TimeDelta();
+ }
+
+ return iter->second[0]->GetWriteHead();
+}
+
+#endif // defined(STARBOARD)
+
bool ChunkDemuxer::AppendData(const std::string& id,
const uint8_t* data,
size_t length,
diff --git a/third_party/chromium/media/filters/chunk_demuxer.h b/third_party/chromium/media/filters/chunk_demuxer.h
index fd67e6f..2e71291 100644
--- a/third_party/chromium/media/filters/chunk_demuxer.h
+++ b/third_party/chromium/media/filters/chunk_demuxer.h
@@ -79,6 +79,10 @@
// https://w3c.github.io/media-source/#sourcebuffer-coded-frame-eviction
bool EvictCodedFrames(base::TimeDelta media_time, size_t newDataSize);
+#if defined(STARBOARD)
+ base::TimeDelta GetWriteHead() const;
+#endif // defined(STARBOARD)
+
void OnMemoryPressure(
base::TimeDelta media_time,
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level,
@@ -207,6 +211,9 @@
State state_ GUARDED_BY(lock_);
ReadCB read_cb_ GUARDED_BY(lock_);
bool is_enabled_ GUARDED_BY(lock_);
+#if defined(STARBOARD)
+ base::TimeDelta write_head_ GUARDED_BY(lock_);
+#endif // defined(STARBOARD)
DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
};
@@ -383,6 +390,10 @@
base::TimeDelta currentMediaTime,
size_t newDataSize) WARN_UNUSED_RESULT;
+#if defined(STARBOARD)
+ base::TimeDelta GetWriteHead(const std::string& id) const;
+#endif // defined(STARBOARD)
+
void OnMemoryPressure(
base::TimeDelta currentMediaTime,
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level,
diff --git a/third_party/crashpad/util/misc/reinterpret_bytes.cc b/third_party/crashpad/util/misc/reinterpret_bytes.cc
index 65ec33f..3fee722 100644
--- a/third_party/crashpad/util/misc/reinterpret_bytes.cc
+++ b/third_party/crashpad/util/misc/reinterpret_bytes.cc
@@ -17,6 +17,7 @@
#include <string.h>
#include <algorithm>
+#include <cstdint>
#include "base/logging.h"
diff --git a/third_party/devtools/front_end/cobalt/cobalt.js b/third_party/devtools/front_end/cobalt/cobalt.js
index a085c62..c65ff59 100644
--- a/third_party/devtools/front_end/cobalt/cobalt.js
+++ b/third_party/devtools/front_end/cobalt/cobalt.js
@@ -14,6 +14,7 @@
this._target = UI.context.flavor(SDK.Target);
this._runtimeAgent = this._target.runtimeAgent();
+ this._cobaltAgent = this._target.cobaltAgent();
this.element = this._shadowRoot.createChild('div');
this.element.textContent = 'Cobalt Console';
@@ -32,7 +33,7 @@
this.run(`(function() { window.h5vcc.traceEvent.stop();})()`);
console.log("Stopped Trace");
}));
- trace_files.forEach( (file) => {
+ trace_files.forEach((file) => {
traceContainer.appendChild(UI.createTextButton(Common.UIString('Download ' + file[0]), event => {
console.log("Download Trace");
const filename = file[1];
@@ -46,6 +47,39 @@
});
}));
});
+ const debugLogContainer = this.element.createChild('div', 'debug-log-container');
+ debugLogContainer.appendChild(UI.createTextButton(Common.UIString('DebugLog On'), event => {
+ this._cobaltAgent.invoke_sendConsoleCommand({
+ command: 'debug_log', message: 'on'
+ });
+ }));
+ debugLogContainer.appendChild(UI.createTextButton(Common.UIString('DebugLog Off'), event => {
+ this._cobaltAgent.invoke_sendConsoleCommand({
+ command: 'debug_log', message: 'off'
+ });
+ }));
+ const consoleContainer = this.element.createChild('div', 'console-container');
+ consoleContainer.appendChild(UI.createTextButton(Common.UIString('DebugCommand'), event => {
+ const outputElement = document.getElementsByClassName('console-output')[0];
+ const command = document.getElementsByClassName('debug-command')[0].value;
+ if (command.length == 0) {
+ const result = this._cobaltAgent.invoke_getConsoleCommands().then(result => {
+ outputElement.innerHTML = JSON.stringify(result.commands, undefined, 2);
+ });
+ } else {
+ const result = this._cobaltAgent.invoke_sendConsoleCommand({
+ command: command,
+ message: document.getElementsByClassName('debug-message')[0].value
+ }).then(result => {
+ outputElement.innerHTML = JSON.stringify(result, undefined, 2);
+ });
+ }
+ }));
+ consoleContainer.appendChild(UI.createLabel('Command:'));
+ consoleContainer.appendChild(UI.createInput('debug-command', 'text'));
+ consoleContainer.appendChild(UI.createLabel('Message:'));
+ consoleContainer.appendChild(UI.createInput('debug-message', 'text'));
+ consoleContainer.createChild('pre', 'console-output');
}
async run(expression) {
diff --git a/third_party/devtools/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/devtools/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 0091c32..e98162f 100644
--- a/third_party/devtools/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/devtools/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -8,6 +8,27 @@
major 1
minor 3
+# The Cobalt domain defines custom methods and events for Cobalt
+experimental domain Cobalt
+ command disable
+
+ command enable
+
+ experimental type ConsoleCommand extends object
+ properties
+ string command
+ string shortHelp
+ string longHelp
+
+ experimental command getConsoleCommands
+ returns
+ array of ConsoleCommand commands
+
+ experimental command sendConsoleCommand
+ parameters
+ string command
+ optional string message
+
experimental domain Accessibility
depends on DOM
diff --git a/third_party/v8/src/base/logging.h b/third_party/v8/src/base/logging.h
index fe39f98..dbe1305 100644
--- a/third_party/v8/src/base/logging.h
+++ b/third_party/v8/src/base/logging.h
@@ -5,6 +5,7 @@
#ifndef V8_BASE_LOGGING_H_
#define V8_BASE_LOGGING_H_
+#include <cstdint>
#include <cstring>
#include <sstream>
#include <string>
diff --git a/third_party/v8/src/base/macros.h b/third_party/v8/src/base/macros.h
index 515a9e3..a7cd7c0 100644
--- a/third_party/v8/src/base/macros.h
+++ b/third_party/v8/src/base/macros.h
@@ -5,6 +5,7 @@
#ifndef V8_BASE_MACROS_H_
#define V8_BASE_MACROS_H_
+#include <cstdint>
#include <limits>
#include <type_traits>
diff --git a/third_party/v8/src/inspector/v8-string-conversions.h b/third_party/v8/src/inspector/v8-string-conversions.h
index c1d69c1..eb33c68 100644
--- a/third_party/v8/src/inspector/v8-string-conversions.h
+++ b/third_party/v8/src/inspector/v8-string-conversions.h
@@ -5,6 +5,7 @@
#ifndef V8_INSPECTOR_V8_STRING_CONVERSIONS_H_
#define V8_INSPECTOR_V8_STRING_CONVERSIONS_H_
+#include <cstdint>
#include <string>
// Conversion routines between UT8 and UTF16, used by string-16.{h,cc}. You may
diff --git a/tools/copy_and_filter_out_dir.py b/tools/copy_and_filter_out_dir.py
index 8e01bf8..080a69f 100755
--- a/tools/copy_and_filter_out_dir.py
+++ b/tools/copy_and_filter_out_dir.py
@@ -102,6 +102,12 @@
return 0
_IterateAndFilter(source_out_dir, dest_out_dir, copies)
+
+ # Add build_info json to the out directory root.
+ source_build_info = os.path.join(source_out_dir, 'gen', 'build_info.json')
+ dest_build_info = os.path.join(dest_out_dir, 'build_info.json')
+ copies[source_build_info] = dest_build_info
+
for source, dest in copies.items():
dirname = os.path.dirname(dest)
if not os.path.exists(dirname):
diff --git a/tools/lz4_compress/lz4_compress.cc b/tools/lz4_compress/lz4_compress.cc
index 26421dc..08a79f5 100644
--- a/tools/lz4_compress/lz4_compress.cc
+++ b/tools/lz4_compress/lz4_compress.cc
@@ -22,6 +22,7 @@
#include <stdio.h>
+#include <cstdint>
#include <iostream>
#include <vector>