Import Cobalt 23.lts.1.308713
diff --git a/.codespellignorelines b/.codespellignorelines index 0079aa8..0c7fa5f 100644 --- a/.codespellignorelines +++ b/.codespellignorelines
@@ -7,3 +7,4 @@ texture_size.height(), GrMipMapped::kNo, texture_info)); Onces represent initializations that should only ever happen once per process, + <Resource Language="TE" />
diff --git a/cobalt/bindings/contexts.py b/cobalt/bindings/contexts.py index 9285877..fb03640 100644 --- a/cobalt/bindings/contexts.py +++ b/cobalt/bindings/contexts.py
@@ -244,7 +244,8 @@ not idl_type.is_callback_interface), 'Callback types not supported.' element_cobalt_type = self.idl_type_to_cobalt_type( self.resolve_typedef(result_idl_type)) - result = '::cobalt::script::Promise< %s >' % element_cobalt_type + result = 'std::unique_ptr<::cobalt::script::Promise< %s* > >' % ( + element_cobalt_type) return result def idl_union_type_to_cobalt(self, idl_type):
diff --git a/cobalt/bindings/v8c/templates/interface.cc.template b/cobalt/bindings/v8c/templates/interface.cc.template index a11e229..65f703c 100644 --- a/cobalt/bindings/v8c/templates/interface.cc.template +++ b/cobalt/bindings/v8c/templates/interface.cc.template
@@ -40,6 +40,7 @@ #include "cobalt/script/v8c/entry_scope.h" #include "cobalt/script/v8c/helpers.h" #include "cobalt/script/v8c/native_promise.h" +#include "cobalt/script/v8c/script_promise.h" #include "cobalt/script/v8c/type_traits.h" #include "cobalt/script/v8c/v8c_typed_arrays.h" #include "cobalt/script/v8c/v8c_data_view.h"
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.js b/cobalt/black_box_tests/testdata/service_worker_test.js index 44ffc55..5b9e117 100644 --- a/cobalt/black_box_tests/testdata/service_worker_test.js +++ b/cobalt/black_box_tests/testdata/service_worker_test.js
@@ -53,10 +53,10 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); @@ -106,10 +106,10 @@ console.log('(Expected) self.clients.matchAll():', clients.length, clients); // Note: This will return 0 clients if none are controlled so far. for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); @@ -123,18 +123,18 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); console.log('self.clients.get()'); self.clients.get(clients[i].id).then(function (client) { console.log('(Expected) self.clients.get():', client); - console.log('Client with url', client.url); - console.log('Client with frameType', client.frameType); - console.log('Client with id', client.id); - console.log('Client with type', client.type); + console.log('Client with url', client.url, + 'frameType', client.frameType, + 'id', client.id, + 'type', client.type); }, function (error) { console.log(`(Unexpected) self.clients.get(): ${error}`, error); });
diff --git a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js index cc0fcde..21cb0d7 100644 --- a/cobalt/black_box_tests/testdata/service_worker_test_claimable.js +++ b/cobalt/black_box_tests/testdata/service_worker_test_claimable.js
@@ -27,37 +27,47 @@ }); } +function delay_promise(delay) { + return new Promise(function (resolve) { + setTimeout(resolve.bind(null), delay) + }); +} + self.oninstall = function (e) { console.log('oninstall event received', e); + e.waitUntil(delay_promise(500).then(() => console.log('Promised delay.'), () => console.log('\nPromised rejected.\n'))); } + self.onactivate = function (e) { console.log('onactivate event received', e); // Claim should pass here, since the state is activating. console.log('self.clients.claim()'); - self.clients.claim().then(function (result) { + e.waitUntil(self.clients.claim().then(function (result) { console.log('(Expected) self.clients.claim():', result); var options = { includeUncontrolled: false, type: 'window' }; - console.log('self.clients.matchAll(options)'); - self.clients.matchAll(options).then(function (clients) { - console.log('(Expected) self.clients.matchAll():', clients.length, clients); - for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); - clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`); - } - }, function (error) { - console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); - }); + e.waitUntil(delay_promise(1000).then(function () { + console.log('self.clients.matchAll(options)'); + e.waitUntil(self.clients.matchAll(options).then(function (clients) { + console.log('(Expected) self.clients.matchAll():', clients.length, clients); + for (var i = 0; i < clients.length; i++) { + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); + clients[i].postMessage(`You have been claimed, client with id ${clients[i].id}`); + } + }, function (error) { + console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error); + })); + })); }, function (error) { console.log(`(Unexpected) self.clients.claim(): ${error}`, error); - }); + })); } console.log('self.registration', self.registration); @@ -73,10 +83,10 @@ self.clients.matchAll(options).then(function (clients) { console.log('(Expected) self.clients.matchAll():', clients.length, clients); for (var i = 0; i < clients.length; i++) { - console.log('Client with url', clients[i].url); - console.log('Client with frameType', clients[i].frameType); - console.log('Client with id', clients[i].id); - console.log('Client with type', clients[i].type); + console.log('Client with url', clients[i].url, + 'frameType', clients[i].frameType, + 'id', clients[i].id, + 'type', clients[i].type); } }, function (error) { console.log(`(Unexpected) self.clients.matchAll(): ${error}`, error);
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn index 3816d70..e3ca3a6 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/cache", "//cobalt/configuration", "//cobalt/css_parser", "//cobalt/cssom",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc index d710cae..5fa7318 100644 --- a/cobalt/browser/application.cc +++ b/cobalt/browser/application.cc
@@ -61,6 +61,7 @@ #include "cobalt/browser/switches.h" #include "cobalt/browser/user_agent_platform_info.h" #include "cobalt/browser/user_agent_string.h" +#include "cobalt/cache/cache.h" #include "cobalt/configuration/configuration.h" #include "cobalt/extension/crash_handler.h" #include "cobalt/extension/installation_manager.h" @@ -648,6 +649,9 @@ watchdog::Watchdog::CreateInstance(persistent_settings_.get()); DCHECK(watchdog); + cobalt::cache::Cache::GetInstance()->set_persistent_settings( + persistent_settings_.get()); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::Optional<cssom::ViewportSize> requested_viewport_size = GetRequestedViewportSize(command_line); @@ -661,6 +665,7 @@ // Create the main components of our browser. BrowserModule::Options options(web_options); network_module_options.preferred_language = language; + network_module_options.persistent_settings = persistent_settings_.get(); options.persistent_settings = persistent_settings_.get(); options.command_line_auto_mem_settings = memory_settings::GetSettings(*command_line); @@ -860,7 +865,7 @@ #if SB_IS(EVERGREEN) updater_module_.get(), #endif - options, persistent_settings_.get())); + options)); UpdateUserAgent();
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc index 8b7b1d4..79dd071 100644 --- a/cobalt/browser/browser_module.cc +++ b/cobalt/browser/browser_module.cc
@@ -219,16 +219,15 @@ } // namespace -BrowserModule::BrowserModule( - const GURL& url, base::ApplicationState initial_application_state, - base::EventDispatcher* event_dispatcher, - account::AccountManager* account_manager, - network::NetworkModule* network_module, +BrowserModule::BrowserModule(const GURL& url, + base::ApplicationState initial_application_state, + base::EventDispatcher* event_dispatcher, + account::AccountManager* account_manager, + network::NetworkModule* network_module, #if SB_IS(EVERGREEN) - updater::UpdaterModule* updater_module, + updater::UpdaterModule* updater_module, #endif - const Options& options, - persistent_storage::PersistentSettings* persistent_settings) + const Options& options) : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), ALLOW_THIS_IN_INITIALIZER_LIST( weak_this_(weak_ptr_factory_.GetWeakPtr())), @@ -298,8 +297,7 @@ next_timeline_id_(1), current_splash_screen_timeline_id_(-1), current_main_web_module_timeline_id_(-1), - service_worker_registry_(network_module), - persistent_settings_(persistent_settings) { + service_worker_registry_(network_module) { TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()"); // Apply platform memory setting adjustments and defaults. @@ -2075,7 +2073,7 @@ dom_settings->window()->navigator()->user_agent_data(); h5vcc_settings.global_environment = dom_settings->context()->global_environment(); - h5vcc_settings.persistent_settings = persistent_settings_; + h5vcc_settings.persistent_settings = options_.persistent_settings; auto* h5vcc_object = new h5vcc::H5vcc(h5vcc_settings); if (!web_module_created_callback_.is_null()) {
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h index 0706039..a12551e 100644 --- a/cobalt/browser/browser_module.h +++ b/cobalt/browser/browser_module.h
@@ -131,8 +131,7 @@ #if SB_IS(EVERGREEN) updater::UpdaterModule* updater_module, #endif - const Options& options, - persistent_storage::PersistentSettings* persistent_settings); + const Options& options); ~BrowserModule(); std::string GetUserAgent() { return network_module_->GetUserAgent(); } @@ -724,8 +723,6 @@ // Manages the Service Workers. ServiceWorkerRegistry service_worker_registry_; - - persistent_storage::PersistentSettings* persistent_settings_; }; } // namespace browser
diff --git a/cobalt/build/ninja/README b/cobalt/build/ninja/README deleted file mode 100644 index fe0f765..0000000 --- a/cobalt/build/ninja/README +++ /dev/null
@@ -1,7 +0,0 @@ -ninja-win.exe built with MSVC 2012 -from -https://github.com/REDACTED/ninja/commit/1287257fe343c4b6b0f760308a7bec52d86b0edf - -ninja-linux built with gcc -from -https://github.com/REDACTED/ninja/commit/613d89893e56acc8c3670a66a33fcd2caf7d9050
diff --git a/cobalt/build/ninja/ninja-linux b/cobalt/build/ninja/ninja-linux deleted file mode 100755 index 1f30022..0000000 --- a/cobalt/build/ninja/ninja-linux +++ /dev/null Binary files differ
diff --git a/cobalt/build/ninja/ninja-linux32.armv7l b/cobalt/build/ninja/ninja-linux32.armv7l deleted file mode 100644 index cb4c3ec..0000000 --- a/cobalt/build/ninja/ninja-linux32.armv7l +++ /dev/null Binary files differ
diff --git a/cobalt/build/ninja/ninja-win.exe b/cobalt/build/ninja/ninja-win.exe deleted file mode 100755 index 4a68328..0000000 --- a/cobalt/build/ninja/ninja-win.exe +++ /dev/null Binary files differ
diff --git a/cobalt/cache/BUILD.gn b/cobalt/cache/BUILD.gn index 7fe8ddd..edc1daf 100644 --- a/cobalt/cache/BUILD.gn +++ b/cobalt/cache/BUILD.gn
@@ -22,6 +22,7 @@ "//base", "//cobalt/base", "//cobalt/configuration", + "//cobalt/persistent_storage:persistent_settings", "//net", "//starboard:starboard_headers_only", ]
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc index 0600895..e2c568d 100644 --- a/cobalt/cache/cache.cc +++ b/cobalt/cache/cache.cc
@@ -24,6 +24,8 @@ #include "base/optional.h" #include "cobalt/configuration/configuration.h" #include "cobalt/extension/javascript_cache.h" +#include "cobalt/persistent_storage/persistent_settings.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "starboard/configuration_constants.h" #include "starboard/system.h" @@ -86,14 +88,6 @@ return nullptr; } -bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size) { - return cobalt::configuration::Configuration::GetInstance() - ->CobaltCanStoreCompiledJavascript() && - data_size > 0u && - data_size >= GetMinSizeToCacheInBytes(resource_type) && - data_size <= GetMaxCacheStorageInBytes(resource_type); -} - } // namespace namespace cobalt { @@ -158,6 +152,19 @@ return data; } +void Cache::set_enabled(bool enabled) { enabled_ = enabled; } + +void Cache::set_persistent_settings( + persistent_storage::PersistentSettings* persistent_settings) { + persistent_settings_ = persistent_settings; + + // Guaranteed to be called before any calls to Retrieve() + // since set_persistent_settings() is called from the Application() + // constructor before the NetworkModule is initialized. + set_enabled(persistent_settings_->GetPersistentSettingAsBool( + disk_cache::kCacheEnabledPersistentSettingsKey, true)); +} + MemoryCappedDirectory* Cache::GetMemoryCappedDirectory( disk_cache::ResourceType resource_type) { base::AutoLock auto_lock(lock_); @@ -221,5 +228,15 @@ } } +bool Cache::CanCache(disk_cache::ResourceType resource_type, + uint32_t data_size) { + return enabled_ && + cobalt::configuration::Configuration::GetInstance() + ->CobaltCanStoreCompiledJavascript() && + data_size > 0u && + data_size >= GetMinSizeToCacheInBytes(resource_type) && + data_size <= GetMaxCacheStorageInBytes(resource_type); +} + } // namespace cache } // namespace cobalt
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h index 790da73..4f9e4fe 100644 --- a/cobalt/cache/cache.h +++ b/cobalt/cache/cache.h
@@ -27,6 +27,7 @@ #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" #include "cobalt/cache/memory_capped_directory.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/disk_cache/cobalt/resource_type.h" namespace base { @@ -45,6 +46,11 @@ disk_cache::ResourceType resource_type, uint32_t key, std::function<std::unique_ptr<std::vector<uint8_t>>()> generate); + void set_enabled(bool enabled); + + void set_persistent_settings( + persistent_storage::PersistentSettings* persistent_settings); + private: friend struct base::DefaultSingletonTraits<Cache>; Cache() {} @@ -56,6 +62,7 @@ void Notify(disk_cache::ResourceType resource_type, uint32_t key); void TryStore(disk_cache::ResourceType resource_type, uint32_t key, const std::vector<uint8_t>& data); + bool CanCache(disk_cache::ResourceType resource_type, uint32_t data_size); mutable base::Lock lock_; // The following map is only used when the JavaScript cache extension is @@ -65,6 +72,9 @@ std::map<disk_cache::ResourceType, std::map<uint32_t, std::vector<base::WaitableEvent*>>> pending_; + bool enabled_; + + persistent_storage::PersistentSettings* persistent_settings_; DISALLOW_COPY_AND_ASSIGN(Cache); }; // class Cache
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html index 3354881..4114648 100644 --- a/cobalt/demos/content/watchdog-demo/index.html +++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -62,7 +62,7 @@ } else if (watchdogFunction == 'ping') { ret = h5vcc.crashLog.ping('test-name', `test-ping`); } else if (watchdogFunction == 'getWatchdogViolations') { - ret = h5vcc.crashLog.getWatchdogViolations(true); + ret = h5vcc.crashLog.getWatchdogViolations(); } else if (watchdogFunction == 'getPersistentSettingWatchdogCrash') { ret = h5vcc.crashLog.getPersistentSettingWatchdogCrash(); } else if (watchdogFunction == 'setPersistentSettingWatchdogCrashTrue') {
diff --git a/cobalt/evergreen_tests/evergreen_tests.py b/cobalt/evergreen_tests/evergreen_tests.py index 74eafb0..00d23ae 100644 --- a/cobalt/evergreen_tests/evergreen_tests.py +++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -31,6 +31,7 @@ def _Exec(cmd, env=None): + """Executes a command in a subprocess and returns the result.""" try: msg = 'Executing:\n ' + ' '.join(cmd) logging.info(msg) @@ -46,31 +47,8 @@ return 1 -def main(): - arg_parser = argparse.ArgumentParser() - arg_parser.add_argument( - '--no-can_mount_tmpfs', - dest='can_mount_tmpfs', - action='store_false', - help='A temporary filesystem cannot be mounted on the target device.') - arg_parser.add_argument( - '--platform_under_test', - default=_DEFAULT_PLATFORM_UNDER_TEST, - help='The platform to run the tests on (e.g., linux or raspi).') - authentication_method = arg_parser.add_mutually_exclusive_group() - authentication_method.add_argument( - '--public-key-auth', - help='Public key authentication should be used with the remote device.', - action='store_true') - authentication_method.add_argument( - '--password-auth', - help='Password authentication should be used with the remote device.', - action='store_true') - command_line.AddLauncherArguments(arg_parser) - args = arg_parser.parse_args() - - log_level.InitializeLogging(args) - +def _RunTests(arg_parser, args, use_compressed_system_image): + """Runs an instance of the Evergreen tests for the provided configuration.""" launcher_params = command_line.CreateLauncherParams(arg_parser) # Creating an instance of the Evergreen abstract launcher implementation @@ -86,7 +64,8 @@ loader_platform=launcher_params.loader_platform, loader_config=launcher_params.loader_config, loader_target='loader_app', - loader_out_directory=launcher_params.loader_out_directory) + loader_out_directory=launcher_params.loader_out_directory, + use_compressed_library=use_compressed_system_image) # The automated tests use the |OUT| environment variable as the path to a # known directory structure containing the desired binaries. This path is @@ -118,10 +97,52 @@ command.append('-a') command.append('password') + if use_compressed_system_image: + command.append('-c') + command.append(args.platform_under_test) return _Exec(command, env) +def main(): + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument( + '--no-can_mount_tmpfs', + dest='can_mount_tmpfs', + action='store_false', + help='A temporary filesystem cannot be mounted on the target device.') + arg_parser.add_argument( + '--platform_under_test', + default=_DEFAULT_PLATFORM_UNDER_TEST, + help='The platform to run the tests on (e.g., linux or raspi).') + authentication_method = arg_parser.add_mutually_exclusive_group() + authentication_method.add_argument( + '--public-key-auth', + help='Public key authentication should be used with the remote device.', + action='store_true') + authentication_method.add_argument( + '--password-auth', + help='Password authentication should be used with the remote device.', + action='store_true') + arg_parser.add_argument( + '--no-rerun_using_compressed_system_image', + dest='rerun_using_compressed_system_image', + action='store_false', + help='Do not run a second instance of the tests with a compressed system ' + 'image.') + command_line.AddLauncherArguments(arg_parser) + args = arg_parser.parse_args() + + log_level.InitializeLogging(args) + + uncompressed_system_image_result = _RunTests(arg_parser, args, False) + if args.rerun_using_compressed_system_image: + compressed_system_image_result = _RunTests(arg_parser, args, True) + return uncompressed_system_image_result or compressed_system_image_result + + return uncompressed_system_image_result + + if __name__ == '__main__': sys.exit(main())
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn index 246c396..9c5a145 100644 --- a/cobalt/h5vcc/BUILD.gn +++ b/cobalt/h5vcc/BUILD.gn
@@ -76,6 +76,7 @@ "//cobalt/base", "//cobalt/browser:browser_switches", "//cobalt/build:cobalt_build_id", + "//cobalt/cache", "//cobalt/configuration", "//cobalt/dom", "//cobalt/media",
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc index f9304e9..d61dca0 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.cc +++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -180,9 +180,9 @@ return false; } -std::string H5vccCrashLog::GetWatchdogViolations(bool current) { +std::string H5vccCrashLog::GetWatchdogViolations() { watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance(); - if (watchdog) return watchdog->GetWatchdogViolations(current); + if (watchdog) return watchdog->GetWatchdogViolations(); return ""; }
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h index c39f4bd..5b1557b 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.h +++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -42,7 +42,7 @@ bool Ping(const std::string& name, const std::string& ping_info); - std::string GetWatchdogViolations(bool current); + std::string GetWatchdogViolations(); bool GetPersistentSettingWatchdogCrash();
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl index d1e9e1a..5c91aa0 100644 --- a/cobalt/h5vcc/h5vcc_crash_log.idl +++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -33,10 +33,12 @@ // name, Watchdog client to register. // description, information on the Watchdog client. // monitor_state, application state up to which the client is monitored. + // Inclusive. // time_interval, maximum number of microseconds allowed between pings // before triggering a Watchdog violation. // time_wait, number of microseconds to initially wait before Watchdog - // violations can be triggered. + // violations can be triggered. Reapplies after client resumes from idle + // state due to application state changes. // replace, behavior with previously registered Watchdog clients of the // same name. boolean register(DOMString name, DOMString description, @@ -52,11 +54,9 @@ // metadata. boolean ping(DOMString name, DOMString ping_info); - // Returns a json string containing the Watchdog violations. Current boolean - // determines whether the current file representing ongoing violations or the - // previous file containing violations from previous app starts and since the - // last call (up to a limit) is returned. - DOMString getWatchdogViolations(boolean current); + // Returns a json string containing the Watchdog violations since the last + // call. Clears internal cache of Watchdog violations to prevent duplicates. + DOMString getWatchdogViolations(); // Gets a persistent Watchdog setting that determines whether or not a // Watchdog violation will trigger a crash.
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc index e9a9229..dbb6b3b 100644 --- a/cobalt/h5vcc/h5vcc_storage.cc +++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -19,10 +19,14 @@ #include "base/files/file_util.h" #include "base/values.h" +#include "cobalt/cache/cache.h" #include "cobalt/h5vcc/h5vcc_storage.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "cobalt/storage/storage_manager.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "net/disk_cache/cobalt/resource_type.h" +#include "net/http/http_cache.h" +#include "net/http/http_transaction_factory.h" #include "starboard/common/file.h" #include "starboard/common/string.h" @@ -31,6 +35,7 @@ namespace h5vcc { namespace { + const char kTestFileName[] = "cache_test_file.json"; const uint32 kWriteBufferSize = 1024 * 1024; @@ -69,7 +74,16 @@ network::NetworkModule* network_module, persistent_storage::PersistentSettings* persistent_settings) : network_module_(network_module), - persistent_settings_(persistent_settings) {} + persistent_settings_(persistent_settings) { + http_cache_ = nullptr; + if (network_module == nullptr) { + return; + } + auto url_request_context = network_module_->url_request_context(); + if (url_request_context->using_http_cache()) { + http_cache_ = url_request_context->http_transaction_factory()->GetCache(); + } +} void H5vccStorage::ClearCookies() { net::CookieStore* cookie_store = @@ -334,5 +348,29 @@ return quota; } +void H5vccStorage::EnableCache() { + persistent_settings_->SetPersistentSetting( + disk_cache::kCacheEnabledPersistentSettingsKey, + std::make_unique<base::Value>(true)); + + cobalt::cache::Cache::GetInstance()->set_enabled(true); + + if (http_cache_) { + http_cache_->set_mode(net::HttpCache::Mode::NORMAL); + } +} + +void H5vccStorage::DisableCache() { + persistent_settings_->SetPersistentSetting( + disk_cache::kCacheEnabledPersistentSettingsKey, + std::make_unique<base::Value>(false)); + + cobalt::cache::Cache::GetInstance()->set_enabled(false); + + if (http_cache_) { + http_cache_->set_mode(net::HttpCache::Mode::DISABLE); + } +} + } // namespace h5vcc } // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_storage.h b/cobalt/h5vcc/h5vcc_storage.h index 7f6368b..ff96351 100644 --- a/cobalt/h5vcc/h5vcc_storage.h +++ b/cobalt/h5vcc/h5vcc_storage.h
@@ -26,6 +26,7 @@ #include "cobalt/network/network_module.h" #include "cobalt/persistent_storage/persistent_settings.h" #include "cobalt/script/wrappable.h" +#include "net/http/http_cache.h" namespace cobalt { namespace h5vcc { @@ -56,6 +57,10 @@ H5vccStorageSetQuotaResponse SetQuota( H5vccStorageResourceTypeQuotaBytesDictionary quota); + void EnableCache(); + + void DisableCache(); + DEFINE_WRAPPABLE_TYPE(H5vccStorage); private: @@ -63,6 +68,8 @@ persistent_storage::PersistentSettings* persistent_settings_; + net::HttpCache* http_cache_; + DISALLOW_COPY_AND_ASSIGN(H5vccStorage); };
diff --git a/cobalt/h5vcc/h5vcc_storage.idl b/cobalt/h5vcc/h5vcc_storage.idl index 3a45910..93a6d37 100644 --- a/cobalt/h5vcc/h5vcc_storage.idl +++ b/cobalt/h5vcc/h5vcc_storage.idl
@@ -24,4 +24,7 @@ H5vccStorageResourceTypeQuotaBytesDictionary getQuota(); H5vccStorageSetQuotaResponse setQuota(H5vccStorageResourceTypeQuotaBytesDictionary quota); + + void enableCache(); + void disableCache(); };
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn index a68d213..53821e9 100644 --- a/cobalt/network/BUILD.gn +++ b/cobalt/network/BUILD.gn
@@ -51,6 +51,7 @@ "//cobalt/build:cobalt_build_id", "//cobalt/configuration", "//cobalt/network_bridge", + "//cobalt/persistent_storage:persistent_settings", "//cobalt/storage", "//starboard/common", "//third_party/protobuf:protobuf_lite",
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc index 4eae9a8..f192961 100644 --- a/cobalt/network/network_module.cc +++ b/cobalt/network/network_module.cc
@@ -178,7 +178,8 @@ #endif url_request_context_.reset( new URLRequestContext(storage_manager_, options_.custom_proxy, net_log, - options_.ignore_certificate_errors, task_runner())); + options_.ignore_certificate_errors, task_runner(), + options_.persistent_settings)); network_delegate_.reset( new NetworkDelegate(options_.cookie_policy, options_.https_requirement)); url_request_context_->set_http_user_agent_settings(
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h index be655f9..ee04620 100644 --- a/cobalt/network/network_module.h +++ b/cobalt/network/network_module.h
@@ -28,6 +28,7 @@ #include "cobalt/network/network_delegate.h" #include "cobalt/network/url_request_context.h" #include "cobalt/network/url_request_context_getter.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/base/static_cookie_policy.h" #include "url/gurl.h" #if defined(DIAL_SERVER) @@ -60,13 +61,15 @@ ignore_certificate_errors(false), https_requirement(network::kHTTPSRequired), preferred_language("en-US"), - max_network_delay(0) {} + max_network_delay(0), + persistent_settings(nullptr) {} net::StaticCookiePolicy::Type cookie_policy; bool ignore_certificate_errors; HTTPSRequirement https_requirement; std::string preferred_language; std::string custom_proxy; SbTime max_network_delay; + persistent_storage::PersistentSettings* persistent_settings; }; // Simple constructor intended to be used only by tests.
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc index 020410c..34cc482 100644 --- a/cobalt/network/url_request_context.cc +++ b/cobalt/network/url_request_context.cc
@@ -26,12 +26,14 @@ #include "cobalt/network/persistent_cookie_store.h" #include "cobalt/network/proxy_config_service.h" #include "cobalt/network/switches.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/cert/cert_net_fetcher.h" #include "net/cert/cert_verifier.h" #include "net/cert/cert_verify_proc.h" #include "net/cert/ct_policy_enforcer.h" #include "net/cert/do_nothing_ct_verifier.h" #include "net/cert_net/cert_net_fetcher_impl.h" +#include "net/disk_cache/cobalt/cobalt_backend_impl.h" #include "net/dns/host_cache.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_cache.h" @@ -68,7 +70,8 @@ URLRequestContext::URLRequestContext( storage::StorageManager* storage_manager, const std::string& custom_proxy, net::NetLog* net_log, bool ignore_certificate_errors, - scoped_refptr<base::SingleThreadTaskRunner> network_task_runner) + scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, + persistent_storage::PersistentSettings* persistent_settings) : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) #if defined(ENABLE_DEBUGGER) , @@ -176,6 +179,8 @@ std::unique_ptr<net::HttpNetworkLayer>( new net::HttpNetworkLayer(storage_.http_network_session()))); } else { + using_http_cache_ = true; + // TODO: Set max size of cache in Starboard. const int cache_size_mb = 24; auto http_cache = std::make_unique<net::HttpCache>( @@ -185,6 +190,15 @@ base::FilePath(std::string(path.data())), /* max_bytes */ 1024 * 1024 * cache_size_mb), true); + if (persistent_settings != nullptr) { + auto cache_enabled = persistent_settings->GetPersistentSettingAsBool( + disk_cache::kCacheEnabledPersistentSettingsKey, true); + + if (!cache_enabled) { + http_cache->set_mode(net::HttpCache::Mode::DISABLE); + } + } + storage_.set_http_transaction_factory(std::move(http_cache)); } @@ -214,6 +228,8 @@ storage_.http_network_session()->SetEnableQuic(enable_quic); } +bool URLRequestContext::using_http_cache() { return using_http_cache_; } + #if defined(ENABLE_DEBUGGER) void URLRequestContext::OnQuicToggle(const std::string& message) { DCHECK(storage_.http_network_session());
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h index 074fcdb..8f673c3 100644 --- a/cobalt/network/url_request_context.h +++ b/cobalt/network/url_request_context.h
@@ -20,6 +20,7 @@ #include "base/basictypes.h" #include "base/macros.h" #include "base/threading/thread_checker.h" +#include "cobalt/persistent_storage/persistent_settings.h" #include "net/cookies/cookie_monster.h" #include "net/log/net_log.h" #include "net/url_request/url_request_context.h" @@ -42,19 +43,24 @@ URLRequestContext( storage::StorageManager* storage_manager, const std::string& custom_proxy, net::NetLog* net_log, bool ignore_certificate_errors, - scoped_refptr<base::SingleThreadTaskRunner> network_task_runner); + scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, + persistent_storage::PersistentSettings* persistent_settings); ~URLRequestContext() override; void SetProxy(const std::string& custom_proxy_rules); void SetEnableQuic(bool enable_quic); + bool using_http_cache(); + private: THREAD_CHECKER(thread_checker_); net::URLRequestContextStorage storage_; scoped_refptr<net::CookieMonster::PersistentCookieStore> persistent_cookie_store_; + bool using_http_cache_; + #if defined(ENABLE_DEBUGGER) // Command handler object for toggling the input fuzzer on/off. debug::console::ConsoleCommandManager::CommandHandler
diff --git a/cobalt/persistent_storage/persistent_settings.cc b/cobalt/persistent_storage/persistent_settings.cc index d420c0e..ffebc2a 100644 --- a/cobalt/persistent_storage/persistent_settings.cc +++ b/cobalt/persistent_storage/persistent_settings.cc
@@ -12,11 +12,12 @@ // 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> #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"
diff --git a/cobalt/script/promise.h b/cobalt/script/promise.h index 6fa4b54..d9e1c17 100644 --- a/cobalt/script/promise.h +++ b/cobalt/script/promise.h
@@ -15,6 +15,9 @@ #ifndef COBALT_SCRIPT_PROMISE_H_ #define COBALT_SCRIPT_PROMISE_H_ +#include <memory> + +#include "base/callback.h" #include "base/memory/ref_counted.h" #include "cobalt/script/exception_message.h" #include "cobalt/script/script_exception.h" @@ -42,21 +45,19 @@ public: // Call the |resolve| function that was passed as an argument to the Promise's // executor function supplying |result| as its argument. - virtual void Resolve(const T& result) const { NOTREACHED(); } + virtual void Resolve(const T& result) const = 0; // Call the |reject| function passed as an argument to the Promise's executor // function. - virtual void Reject() const { NOTREACHED(); } - virtual void Reject(SimpleExceptionType exception) const { NOTREACHED(); } - virtual void Reject(const scoped_refptr<ScriptException>& result) const { - NOTREACHED(); - } + virtual void Reject() const = 0; + virtual void Reject(SimpleExceptionType exception) const = 0; + virtual void Reject(const scoped_refptr<ScriptException>& result) const = 0; // Returns the value of the [[PromiseState]] field. - virtual PromiseState State() const { - NOTREACHED(); - return PromiseState::kRejected; - } + virtual PromiseState State() const = 0; + + virtual void AddStateChangeCallback( + std::unique_ptr<base::OnceCallback<void()>> callback) = 0; virtual ~Promise() {} };
diff --git a/cobalt/script/v8c/conversion_helpers.h b/cobalt/script/v8c/conversion_helpers.h index 85aa597..81bf303 100644 --- a/cobalt/script/v8c/conversion_helpers.h +++ b/cobalt/script/v8c/conversion_helpers.h
@@ -16,7 +16,6 @@ #define COBALT_SCRIPT_V8C_CONVERSION_HELPERS_H_ #include <cmath> - #include <limits> #include <string> #include <utility> @@ -29,6 +28,7 @@ #include "cobalt/base/compiler.h" #include "cobalt/base/enable_if.h" #include "cobalt/base/token.h" +#include "cobalt/script/promise.h" #include "cobalt/script/sequence.h" #include "cobalt/script/v8c/algorithm_helpers.h" #include "cobalt/script/v8c/helpers.h" @@ -713,11 +713,7 @@ template <typename T> void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value, int conversion_flags, ExceptionState* exception_state, - script::Promise<T>* out_promise) { - // TODO(b/228976500): Implement conversion from JS to native for Promise<T>. - // https://webidl.spec.whatwg.org/#es-promise - NOTIMPLEMENTED(); -} + script::Promise<T>* out_promise); // script::Handle<T> -> JSValue template <typename T>
diff --git a/cobalt/script/v8c/native_promise.h b/cobalt/script/v8c/native_promise.h index 85069c7..ab8b323 100644 --- a/cobalt/script/v8c/native_promise.h +++ b/cobalt/script/v8c/native_promise.h
@@ -15,12 +15,15 @@ #ifndef COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_ #define COBALT_SCRIPT_V8C_NATIVE_PROMISE_H_ +#include <memory> + #include "base/logging.h" #include "base/threading/thread_checker.h" #include "cobalt/script/promise.h" #include "cobalt/script/v8c/conversion_helpers.h" #include "cobalt/script/v8c/entry_scope.h" #include "cobalt/script/v8c/scoped_persistent.h" +#include "cobalt/script/v8c/script_promise.h" #include "cobalt/script/v8c/type_traits.h" #include "cobalt/script/v8c/v8c_exception_state.h" #include "cobalt/script/v8c/v8c_user_object_holder.h" @@ -40,10 +43,9 @@ *out_value = v8::Undefined(isolate); } -// Shared functionality for NativePromise<T>. Does not implement the Resolve -// function, since that needs to be specialized for Promise<T>. +// Shared functionality for NativePromise<T>. template <typename T> -class NativePromise : public ScopedPersistent<v8::Value>, public Promise<T> { +class NativePromise : public ScriptPromise<T> { public: // ScriptValue boilerplate. typedef Promise<T> BaseType; @@ -63,20 +65,20 @@ PromiseResultUndefined, T>::type; NativePromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver) - : isolate_(isolate), ScopedPersistent(isolate, resolver) { + : ScriptPromise<T>(isolate, resolver) { DCHECK(resolver->IsPromise()); } void Resolve(const ResolveType& value) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Local<v8::Value> converted_value; - ToJSValue(isolate_, value, &converted_value); + ToJSValue(this->isolate(), value, &converted_value); v8::Maybe<bool> reject_result = promise_resolver->Resolve(context, converted_value); DCHECK(reject_result.FromJust()); @@ -85,25 +87,26 @@ void Reject() const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Maybe<bool> reject_result = - promise_resolver->Reject(context, v8::Undefined(isolate_)); + promise_resolver->Reject(context, v8::Undefined(this->isolate())); DCHECK(reject_result.FromJust()); } void Reject(SimpleExceptionType exception) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); - v8::Local<v8::Value> error_result = CreateErrorObject(isolate_, exception); + v8::Local<v8::Value> error_result = + CreateErrorObject(this->isolate(), exception); v8::Maybe<bool> reject_result = promise_resolver->Reject(context, error_result); DCHECK(reject_result.FromJust()); @@ -112,51 +115,22 @@ void Reject(const scoped_refptr<ScriptException>& result) const override { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK(!this->IsEmpty()); - DCHECK(State() == PromiseState::kPending); - EntryScope entry_scope(isolate_); - v8::Local<v8::Context> context = isolate_->GetCurrentContext(); + DCHECK(this->State() == PromiseState::kPending); + EntryScope entry_scope(this->isolate()); + v8::Local<v8::Context> context = this->isolate()->GetCurrentContext(); v8::Local<v8::Promise::Resolver> promise_resolver = this->resolver(); v8::Local<v8::Value> converted_result; - ToJSValue(isolate_, result, &converted_result); + ToJSValue(this->isolate(), result, &converted_result); v8::Maybe<bool> reject_result = promise_resolver->Reject(context, converted_result); DCHECK(reject_result.FromJust()); } - PromiseState State() const override { - DCHECK(!this->IsEmpty()); - EntryScope entry_scope(isolate_); - - v8::Promise::PromiseState v8_promise_state = this->promise()->State(); - switch (v8_promise_state) { - case v8::Promise::kPending: - return PromiseState::kPending; - case v8::Promise::kFulfilled: - return PromiseState::kFulfilled; - case v8::Promise::kRejected: - return PromiseState::kRejected; - } - NOTREACHED(); - return PromiseState::kRejected; - } - - v8::Local<v8::Promise> promise() const { - DCHECK(!this->IsEmpty()); - return resolver()->GetPromise(); - } - private: - v8::Isolate* isolate_; - // Thread checker ensures all calls to the Promise are made from the same // thread that it is created in. THREAD_CHECKER(thread_checker_); - - v8::Local<v8::Promise::Resolver> resolver() const { - DCHECK(!this->IsEmpty()); - return this->Get().Get(isolate_).template As<v8::Promise::Resolver>(); - } }; template <typename T>
diff --git a/cobalt/script/v8c/script_promise.h b/cobalt/script/v8c/script_promise.h new file mode 100644 index 0000000..93c4561 --- /dev/null +++ b/cobalt/script/v8c/script_promise.h
@@ -0,0 +1,148 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_ +#define COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_ + +#include <memory> +#include <utility> + +#include "base/logging.h" +#include "base/threading/thread_checker.h" +#include "cobalt/script/promise.h" +#include "cobalt/script/v8c/conversion_helpers.h" +#include "cobalt/script/v8c/entry_scope.h" +#include "cobalt/script/v8c/scoped_persistent.h" +#include "cobalt/script/v8c/type_traits.h" +#include "cobalt/script/v8c/v8c_exception_state.h" +#include "cobalt/script/v8c/v8c_user_object_holder.h" +#include "v8/include/v8.h" + +namespace cobalt { +namespace script { +namespace v8c { + +// Shared functionality for ScriptPromise<T>. Does not implement the Resolve +// function, since that needs to be specialized for Promise<T>. +template <typename T> +class ScriptPromise : public ScopedPersistent<v8::Value>, public Promise<T> { + public: + // ScriptValue boilerplate. + typedef Promise<T> BaseType; + + // Handle special case T=void, by swapping the input parameter |T| for + // |PromiseResultUndefined|. Combined with how |Promise| handles this + // special case, we're left with something like: + // + // NativePromise<T> -> Promise<T> + // ^ + // | (T=PromiseResultUndefined) + // / + // NativePromise<void> -> Promise<void> + // + using ResolveType = + typename std::conditional<std::is_same<T, void>::value, + PromiseResultUndefined, T>::type; + + ScriptPromise(v8::Isolate* isolate, v8::Local<v8::Value> resolver) + : isolate_(isolate), ScopedPersistent(isolate, resolver) { + DCHECK(resolver->IsPromise()); + } + + void Resolve(const ResolveType& value) const override { NOTREACHED(); } + + void Reject() const override { NOTREACHED(); } + + void Reject(SimpleExceptionType exception) const override { NOTREACHED(); } + + void Reject(const scoped_refptr<ScriptException>& result) const override { + NOTREACHED(); + } + + PromiseState State() const override { + DCHECK(!this->IsEmpty()); + EntryScope entry_scope(isolate_); + + v8::Promise::PromiseState v8_promise_state = this->promise()->State(); + switch (v8_promise_state) { + case v8::Promise::kPending: + return PromiseState::kPending; + case v8::Promise::kFulfilled: + return PromiseState::kFulfilled; + case v8::Promise::kRejected: + return PromiseState::kRejected; + } + NOTREACHED(); + return PromiseState::kRejected; + } + + void AddStateChangeCallback( + std::unique_ptr<base::OnceCallback<void()>> callback) override { + v8::Local<v8::Context> context = context_.NewLocal(isolate_); + + auto callback_lambda = [](const v8::FunctionCallbackInfo<v8::Value>& info) { + auto* callback = static_cast<base::OnceCallback<void()>*>( + info.Data().As<v8::External>()->Value()); + DCHECK(callback); + std::move(*callback).Run(); + delete callback; + }; + v8::Local<v8::Function> function = + v8::Function::New(isolate_->GetCurrentContext(), callback_lambda, + v8::External::New(isolate_, callback.release())) + .ToLocalChecked(); + v8::Local<v8::Promise> result_promise; + if (!promise() + ->Then(context, function, function) + .ToLocal(&result_promise)) { + DLOG(ERROR) << "Unable to add promise state change callback."; + NOTREACHED(); + } + } + + v8::Local<v8::Promise> promise() const { + DCHECK(!this->IsEmpty()); + return resolver()->GetPromise(); + } + + protected: + v8::Local<v8::Promise::Resolver> resolver() const { + DCHECK(!this->IsEmpty()); + return this->Get().Get(isolate_).template As<v8::Promise::Resolver>(); + } + + v8::Isolate* isolate() const { return isolate_; } + + private: + v8::Isolate* isolate_; + ScopedPersistent<v8::Context> context_; +}; + +// JSValue -> Promise +template <typename T> +void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value, + int conversion_flags, ExceptionState* exception_state, + std::unique_ptr<script::Promise<T*>>* out_promise) { + if (!value->IsPromise()) { + exception_state->SetSimpleException(kNotSupportedType); + return; + } + out_promise->reset(new ScriptPromise<T*>(isolate, value)); +} + +} // namespace v8c +} // namespace script +} // namespace cobalt + +#endif // COBALT_SCRIPT_V8C_SCRIPT_PROMISE_H_
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc index 49349d1..3b6a888 100644 --- a/cobalt/watchdog/watchdog.cc +++ b/cobalt/watchdog/watchdog.cc
@@ -34,7 +34,7 @@ namespace { // The Watchdog violations json file names. -const char kWatchdogCurrentViolationsJson[] = "watchdog.json"; +const char kWatchdogViolationsJson[] = "watchdog.json"; const char kWatchdogPreviousViolationsJson[] = "watchdog_old.json"; // The default number of microseconds between each monitor loop. const int64_t kWatchdogSmallestTimeInterval = 1000000; @@ -104,21 +104,19 @@ SbThreadJoin(watchdog_thread_, nullptr); } -std::string Watchdog::GetWatchdogFilePaths(bool current) { - // Gets the current Watchdog violations file path or the previous Watchdog - // violations file path. +std::string Watchdog::GetWatchdogFilePath(bool current) { + // Gets the Watchdog violations file path or the previous Watchdog violations + // file path with lazy initialization. if (watchdog_file_ == "") { // Sets Watchdog violations file paths. std::vector<char> cache_dir(kSbFileMaxPath + 1, 0); SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(), kSbFileMaxPath); watchdog_file_ = std::string(cache_dir.data()) + kSbFileSepString + - std::string(kWatchdogCurrentViolationsJson); - SB_LOG(INFO) << "Current Watchdog violations file path: " << watchdog_file_; + std::string(kWatchdogViolationsJson); + SB_LOG(INFO) << "Watchdog violations file path: " << watchdog_file_; watchdog_old_file_ = std::string(cache_dir.data()) + kSbFileSepString + std::string(kWatchdogPreviousViolationsJson); - SB_LOG(INFO) << "Previous Watchdog violations file path: " - << watchdog_old_file_; PreservePreviousWatchdogViolations(); } if (current) return watchdog_file_; @@ -126,8 +124,8 @@ } void Watchdog::PreservePreviousWatchdogViolations() { - // Copies the previous Watchdog violations file containing violations since - // last app start, if it exists, to preserve it. + // Copies the previous Watchdog violations file containing violations before + // app start, if it exists, to preserve it. starboard::ScopedFile read_file(watchdog_file_.c_str(), kSbFileOpenOnly | kSbFileRead); if (read_file.IsValid()) { @@ -137,7 +135,6 @@ starboard::ScopedFile write_file(watchdog_old_file_.c_str(), kSbFileCreateAlways | kSbFileWrite); write_file.WriteAll(&watchdog_content[0], kFileSize); - starboard::SbFileDeleteRecursive(watchdog_file_.c_str(), true); } } @@ -159,66 +156,72 @@ int64_t current_time = SbTimeToPosix(SbTimeGetNow()); SbTimeMonotonic current_monotonic_time = SbTimeGetMonotonicNow(); - std::string serialized_watchdog_index = ""; + std::string serialized_client_map = ""; - // Iterates through Watchdog index to monitor all registered clients. + // Iterates through client map to monitor all registered clients. bool new_watchdog_violation = false; - for (auto& it : static_cast<Watchdog*>(context)->watchdog_index_) { - // Ignores and resets clients in idle states. - if (static_cast<Watchdog*>(context)->state_ > it.second->monitor_state) { - it.second->time_registered_monotonic_microseconds = - current_monotonic_time; - it.second->time_last_pinged_microseconds = current_monotonic_time; + for (auto& it : static_cast<Watchdog*>(context)->client_map_) { + Client* client = it.second.get(); + // Ignores and resets clients in idle states, clients whose monitor_state + // is below the current application state. Resets time_wait_microseconds + // and time_interval_microseconds deltas. + if (static_cast<Watchdog*>(context)->state_ > client->monitor_state) { + client->time_registered_monotonic_microseconds = current_monotonic_time; + client->time_last_pinged_microseconds = current_monotonic_time; continue; } SbTimeMonotonic time_delta = - current_monotonic_time - it.second->time_last_pinged_microseconds; + current_monotonic_time - client->time_last_pinged_microseconds; SbTimeMonotonic time_wait = current_monotonic_time - - it.second->time_registered_monotonic_microseconds; + client->time_registered_monotonic_microseconds; // Watchdog violation - if (time_delta > it.second->time_interval_microseconds && - time_wait > it.second->time_wait_microseconds) { + if (time_delta > client->time_interval_microseconds && + time_wait > client->time_wait_microseconds) { // Reset time last pinged. - it.second->time_last_pinged_microseconds = current_monotonic_time; - // Get serialized Watchdog index. - if (serialized_watchdog_index == "") { - serialized_watchdog_index = - static_cast<Watchdog*>(context)->GetSerializedWatchdogIndex(); + client->time_last_pinged_microseconds = current_monotonic_time; + // Get serialized client map. + if (serialized_client_map == "") { + serialized_client_map = + static_cast<Watchdog*>(context)->GetSerializedClientMap(); } // Updates Watchdog violations. auto iter = (static_cast<Watchdog*>(context)->watchdog_violations_) - .find(it.second->name); + .find(client->name); bool already_violated = iter != (static_cast<Watchdog*>(context)->watchdog_violations_).end(); if (already_violated) { // Prevents excessive Watchdog violation updates. - if (iter->second->violation_count <= kWatchdogMaxViolations) + Violation* violation = iter->second.get(); + if (violation->violation_count <= kWatchdogMaxViolations) new_watchdog_violation = true; - iter->second->ping_infos = it.second->ping_infos; - iter->second->violation_time_microseconds = current_time; - iter->second->violation_delta_microseconds = time_delta; - iter->second->violation_count++; - iter->second->serialized_watchdog_index = serialized_watchdog_index; + violation->ping_infos = client->ping_infos; + violation->violation_time_microseconds = current_time; + violation->violation_delta_microseconds = time_delta; + violation->violation_count++; + violation->serialized_client_map = serialized_client_map; } else { new_watchdog_violation = true; std::unique_ptr<Violation> violation(new Violation); - *violation = *(it.second); + *violation = *client; violation->violation_time_microseconds = current_time; violation->violation_delta_microseconds = time_delta; violation->violation_count = 1; - violation->serialized_watchdog_index = serialized_watchdog_index; + violation->serialized_client_map = serialized_client_map; (static_cast<Watchdog*>(context)->watchdog_violations_) .emplace(violation->name, std::move(violation)); } } } - if (new_watchdog_violation) SerializeWatchdogViolations(context); + if (new_watchdog_violation) { + SerializeWatchdogViolations(context); + MaybeTriggerCrash(context); + } SB_CHECK(SbMutexRelease(&(static_cast<Watchdog*>(context))->mutex_)); SbThreadSleep(static_cast<Watchdog*>(context)->smallest_time_interval_); @@ -226,71 +229,79 @@ return nullptr; } -std::string Watchdog::GetSerializedWatchdogIndex() { - // Gets the current list of registered clients from the Watchdog index and +std::string Watchdog::GetSerializedClientMap() { + // Gets the current list of registered clients from the client map and // returns it as a serialized json string. - std::string serialized_watchdog_index = "["; + std::string serialized_client_map = "["; std::string comma = ""; - for (auto& it : watchdog_index_) { - serialized_watchdog_index += (comma + "\"" + it.first + "\""); + for (auto& it : client_map_) { + serialized_client_map += (comma + "\"" + it.first + "\""); comma = ", "; } - serialized_watchdog_index += "]"; - return serialized_watchdog_index; + serialized_client_map += "]"; + return serialized_client_map; } void Watchdog::SerializeWatchdogViolations(void* context) { - // Writes current Watchdog violations to persistent storage as a json file. - std::string watchdog_json = "{\n \"watchdog_violations\": [\n"; + // Writes Watchdog violations to persistent storage as a partial json file. + std::string watchdog_json = ""; std::string comma = ""; for (auto& it : static_cast<Watchdog*>(context)->watchdog_violations_) { + Violation* violation = it.second.get(); std::string ping_infos = "["; std::string inner_comma = ""; - while (it.second->ping_infos.size() > 0) { - ping_infos += (inner_comma + "\"" + it.second->ping_infos.front() + "\""); - it.second->ping_infos.pop(); + while (violation->ping_infos.size() > 0) { + ping_infos += (inner_comma + "\"" + violation->ping_infos.front() + "\""); + violation->ping_infos.pop(); inner_comma = ", "; } ping_infos += "]"; std::ostringstream ss; ss << comma << " {\n" - << " \"name\": \"" << it.second->name << "\",\n" - << " \"description\": \"" << it.second->description << "\",\n" + << " \"name\": \"" << violation->name << "\",\n" + << " \"description\": \"" << violation->description << "\",\n" << " \"ping_infos\": " << ping_infos << ",\n" << " \"monitor_state\": \"" - << std::string(GetApplicationStateString(it.second->monitor_state)) + << std::string(GetApplicationStateString(violation->monitor_state)) << "\",\n" << " \"time_interval_microseconds\": " - << it.second->time_interval_microseconds << ",\n" + << violation->time_interval_microseconds << ",\n" << " \"time_wait_microseconds\": " - << it.second->time_wait_microseconds << ",\n" + << violation->time_wait_microseconds << ",\n" << " \"time_registered_microseconds\": " - << it.second->time_registered_microseconds << ",\n" + << violation->time_registered_microseconds << ",\n" << " \"violation_time_microseconds\": " - << it.second->violation_time_microseconds << ",\n" + << violation->violation_time_microseconds << ",\n" << " \"violation_delta_microseconds\": " - << it.second->violation_delta_microseconds << ",\n" - << " \"violation_count\": " << it.second->violation_count << ",\n" - << " \"watchdog_index\": " << it.second->serialized_watchdog_index - << "\n" + << violation->violation_delta_microseconds << ",\n" + << " \"violation_count\": " << violation->violation_count << ",\n" + << " \"client_map\": " << violation->serialized_client_map << "\n" << " }"; watchdog_json += ss.str(); comma = ",\n"; } - watchdog_json += "\n ]\n}"; - SB_LOG(INFO) << "Writing Watchdog violations to: " - << static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true); - SB_LOG(INFO) << watchdog_json; + // Appends previous Watchdog violations. + starboard::ScopedFile read_file( + (static_cast<Watchdog*>(context)->GetWatchdogFilePath(false)).c_str(), + kSbFileOpenOnly | kSbFileRead); + if (read_file.IsValid()) { + int64_t kFileSize = read_file.GetSize(); + std::string prev_watchdog_json(kFileSize + 1, '\0'); + read_file.ReadAll(&prev_watchdog_json[0], kFileSize); + prev_watchdog_json.erase(prev_watchdog_json.find('\0')); + watchdog_json += ",\n"; + watchdog_json += prev_watchdog_json; + } + + SB_LOG(INFO) << "Writing Watchdog violations:\n" << watchdog_json; starboard::ScopedFile watchdog_file( - (static_cast<Watchdog*>(context)->GetWatchdogFilePaths(true)).c_str(), + (static_cast<Watchdog*>(context)->GetWatchdogFilePath()).c_str(), kSbFileCreateAlways | kSbFileWrite); watchdog_file.WriteAll(watchdog_json.c_str(), static_cast<int>(watchdog_json.size())); - - MaybeTriggerCrash(context); } void Watchdog::MaybeTriggerCrash(void* context) { @@ -317,8 +328,8 @@ // If replace is PING or ALL, handles already registered cases. if (replace != NONE) { - auto it = watchdog_index_.find(name); - bool already_registered = it != watchdog_index_.end(); + auto it = client_map_.find(name); + bool already_registered = it != client_map_.end(); if (already_registered) { if (replace == PING) { @@ -344,7 +355,7 @@ client->time_registered_monotonic_microseconds; // Registers. - auto result = watchdog_index_.emplace(name, std::move(client)); + auto result = client_map_.emplace(name, std::move(client)); // Checks for new smallest_time_interval_. smallest_time_interval_ = std::min(smallest_time_interval_, time_interval); @@ -364,10 +375,10 @@ if (lock) SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); // Unregisters. - auto result = watchdog_index_.erase(name); + auto result = client_map_.erase(name); // Sets new smallest_time_interval_. smallest_time_interval_ = kWatchdogSmallestTimeInterval; - for (auto& it : watchdog_index_) { + for (auto& it : client_map_) { smallest_time_interval_ = std::min(smallest_time_interval_, it.second->time_interval_microseconds); } @@ -388,8 +399,8 @@ if (is_stub_) return true; SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); - auto it = watchdog_index_.find(name); - bool client_exists = it != watchdog_index_.end(); + auto it = client_map_.find(name); + bool client_exists = it != client_map_.end(); if (client_exists) { // Updates last ping. @@ -408,29 +419,36 @@ return client_exists; } -std::string Watchdog::GetWatchdogViolations(bool current) { - // Gets the current Watchdog violations file representing ongoing violations - // or gets the previous Watchdog violations file containing violations from - // previous app starts and since the last call (up to a limit). +std::string Watchdog::GetWatchdogViolations() { + // Gets a json string containing the Watchdog violations since the last + // call (up to a limit). // Watchdog stub if (is_stub_) return ""; + std::string watchdog_json = ""; SB_CHECK(SbMutexAcquire(&mutex_) == kSbMutexAcquired); - starboard::ScopedFile read_file(GetWatchdogFilePaths(current).c_str(), + starboard::ScopedFile read_file(GetWatchdogFilePath().c_str(), kSbFileOpenOnly | kSbFileRead); if (read_file.IsValid()) { int64_t kFileSize = read_file.GetSize(); std::string watchdog_content(kFileSize + 1, '\0'); read_file.ReadAll(&watchdog_content[0], kFileSize); - SB_CHECK(SbMutexRelease(&mutex_)); - SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_content; - return watchdog_content; + watchdog_content.erase(watchdog_content.find('\0')); + watchdog_json = "{\n \"watchdog_violations\": [\n"; + watchdog_json += watchdog_content; + watchdog_json += "\n ]\n}"; + + // Removes all Watchdog violations. + watchdog_violations_.clear(); + starboard::SbFileDeleteRecursive(GetWatchdogFilePath().c_str(), true); + starboard::SbFileDeleteRecursive(GetWatchdogFilePath(false).c_str(), true); + SB_LOG(INFO) << "Reading Watchdog violations:\n" << watchdog_json; } else { - SB_CHECK(SbMutexRelease(&mutex_)); SB_LOG(INFO) << "No Watchdog Violations."; - return ""; } + SB_CHECK(SbMutexRelease(&mutex_)); + return watchdog_json; } bool Watchdog::GetPersistentSettingWatchdogCrash() {
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h index d95f1ca..ed14db0 100644 --- a/cobalt/watchdog/watchdog.h +++ b/cobalt/watchdog/watchdog.h
@@ -39,7 +39,12 @@ std::queue<std::string> ping_infos; // Application state to continue monitoring client up to. base::ApplicationState monitor_state; + // Maximum number of microseconds allowed between pings before triggering a + // Watchdog violation. int64_t time_interval_microseconds; + // Number of microseconds to initially wait before Watchdog violations can be + // triggered. Reapplies after client resumes from idle state due to + // application state changes. int64_t time_wait_microseconds; int64_t time_registered_microseconds; // since epoch SbTimeMonotonic time_registered_monotonic_microseconds; // since (relative) @@ -52,16 +57,21 @@ std::string description; // List of strings optionally provided with each Ping. std::queue<std::string> ping_infos; - // Application state to continue monitoring client up to. + // Application state to continue monitoring client up to. Inclusive. base::ApplicationState monitor_state; + // Maximum number of microseconds allowed between pings before triggering a + // Watchdog violation. int64_t time_interval_microseconds; + // Number of microseconds to initially wait before Watchdog violations can be + // triggered. Reapplies after client resumes from idle state due to + // application state changes. int64_t time_wait_microseconds; int64_t time_registered_microseconds; // since epoch int64_t violation_time_microseconds; // since epoch int64_t violation_delta_microseconds; // over time_interval int64_t violation_count; - // Watchdog index as a serialized json string - std::string serialized_watchdog_index; + // Client map as a serialized json string + std::string serialized_client_map; void operator=(const Client& c) { name = c.name; @@ -96,7 +106,7 @@ bool Unregister(const std::string& name, bool lock = true); bool Ping(const std::string& name); bool Ping(const std::string& name, const std::string& info); - std::string GetWatchdogViolations(bool current = false); + std::string GetWatchdogViolations(); bool GetPersistentSettingWatchdogCrash(); void SetPersistentSettingWatchdogCrash(bool can_trigger_crash); @@ -106,22 +116,21 @@ #endif // defined(_DEBUG) private: - std::string GetWatchdogFilePaths(bool current); + std::string GetWatchdogFilePath(bool current = true); void PreservePreviousWatchdogViolations(); static void* Monitor(void* context); - std::string GetSerializedWatchdogIndex(); + std::string GetSerializedClientMap(); static void SerializeWatchdogViolations(void* context); static void MaybeTriggerCrash(void* context); - // Current Watchdog violations file path. + // Watchdog violations file paths. std::string watchdog_file_; - // Previous Watchdog violations file path. std::string watchdog_old_file_; // Creates a lock which ensures that each loop of monitor is atomic in that // modifications to is_monitoring_, state_, smallest_time_interval_, and most - // importantly to the dictionaries containing Watchdog clients, - // watchdog_index_ and watchdog_violations_, only occur in between loops of - // monitor. API functions like Register(), Unregister(), Ping(), and + // importantly to the dictionaries containing Watchdog clients, client_map_ + // and watchdog_violations_, only occur in between loops of monitor. API + // functions like Register(), Unregister(), Ping(), and // GetWatchdogViolations() will be called by various threads and interact // with these class variables. SbMutex mutex_; @@ -134,7 +143,7 @@ // Tracks application state. base::ApplicationState state_ = base::kApplicationStateStarted; // Dictionary of registered Watchdog clients. - std::unordered_map<std::string, std::unique_ptr<Client>> watchdog_index_; + std::unordered_map<std::string, std::unique_ptr<Client>> client_map_; // Dictionary of Watchdog violations. std::unordered_map<std::string, std::unique_ptr<Violation>> watchdog_violations_;
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h index 4e5d4ff..1765ad2 100644 --- a/cobalt/worker/extendable_event.h +++ b/cobalt/worker/extendable_event.h
@@ -15,15 +15,26 @@ #ifndef COBALT_WORKER_EXTENDABLE_EVENT_H_ #define COBALT_WORKER_EXTENDABLE_EVENT_H_ +#include <memory> #include <string> +#include <utility> +#include "base/bind.h" #include "cobalt/base/token.h" #include "cobalt/script/promise.h" #include "cobalt/script/v8c/native_promise.h" #include "cobalt/script/value_handle.h" #include "cobalt/script/wrappable.h" +#include "cobalt/web/context.h" +#include "cobalt/web/dom_exception.h" +#include "cobalt/web/environment_settings.h" #include "cobalt/web/event.h" +#include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/extendable_event_init.h" +#include "cobalt/worker/service_worker_global_scope.h" +#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_object.h" +#include "cobalt/worker/service_worker_registration_object.h" namespace cobalt { namespace worker { @@ -35,16 +46,87 @@ ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict) : Event(type, init_dict) {} - void WaitUntil(script::EnvironmentSettings* settings, - const script::Promise<script::ValueHandle>& promise) { - // TODO(b/228976500): Implement WaitUntil(). - NOTIMPLEMENTED(); + void WaitUntil( + script::EnvironmentSettings* settings, + std::unique_ptr<script::Promise<script::ValueHandle*>>& promise, + script::ExceptionState* exception_state) { + // Algorithm for waitUntil(), to add lifetime promise to event. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + + // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError" + // DOMException. + // 2. If event is not active, throw an "InvalidStateError" DOMException. + if (!IsActive()) { + web::DOMException::Raise(web::DOMException::kInvalidStateErr, + exception_state); + return; + } + // 3. Add promise to event’s extend lifetime promises. + // 4. Increment event’s pending promises count by one. + ++pending_promise_count_; + // 5. Upon fulfillment or rejection of promise, queue a microtask to run + // these substeps: + std::unique_ptr<base::OnceCallback<void()>> callback( + new base::OnceCallback<void()>(std::move( + base::BindOnce(&ExtendableEvent::StateChange, + base::Unretained(this), settings, promise.get())))); + promise->AddStateChangeCallback(std::move(callback)); + promise.release(); + } + + void StateChange(script::EnvironmentSettings* settings, + const script::Promise<script::ValueHandle*>* promise) { + // Implement the microtask called upon fulfillment or rejection of a + // promise, as part of the algorithm for waitUntil(). + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + DCHECK(promise); + has_rejected_promise_ |= + promise->State() == script::PromiseState::kRejected; + // 5.1. Decrement event’s pending promises count by one. + --pending_promise_count_; + // 5.2. If event’s pending promises count is 0, then: + if (0 == pending_promise_count_) { + web::Context* context = + base::polymorphic_downcast<web::EnvironmentSettings*>(settings) + ->context(); + ServiceWorkerJobs* jobs = context->service_worker_jobs(); + DCHECK(jobs); + // 5.2.1. Let registration be the current global object's associated + // service worker's containing service worker registration. + jobs->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&ServiceWorkerJobs::WaitUntilSubSteps, + base::Unretained(jobs), + base::Unretained( + context->GetWindowOrWorkerGlobalScope() + ->AsServiceWorker() + ->service_worker_object() + ->containing_service_worker_registration()))); + } + delete promise; + } + + bool IsActive() { + // An ExtendableEvent object is said to be active when its timed out flag + // is unset and either its pending promises count is greater than zero or + // its dispatch flag is set. + // https://w3c.github.io/ServiceWorker/#extendableevent-active + return !timed_out_flag_ && + ((pending_promise_count_ > 0) || IsBeingDispatched()); } DEFINE_WRAPPABLE_TYPE(ExtendableEvent); protected: ~ExtendableEvent() override {} + + private: + // https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises + // std::list<script::Promise<script::ValueHandle*>> extend_lifetime_promises_; + int pending_promise_count_ = 0; + bool has_rejected_promise_ = false; + // https://w3c.github.io/ServiceWorker/#extendableevent-timed-out-flag + bool timed_out_flag_ = false; }; } // namespace worker
diff --git a/cobalt/worker/extendable_event.idl b/cobalt/worker/extendable_event.idl index 2297cbc..8a12344 100644 --- a/cobalt/worker/extendable_event.idl +++ b/cobalt/worker/extendable_event.idl
@@ -18,5 +18,5 @@ Exposed = ServiceWorker, Constructor(DOMString type, optional ExtendableEventInit eventInitDict) ] interface ExtendableEvent : Event { - [CallWith = EnvironmentSettings] void waitUntil(Promise<any> f); + [RaisesException, CallWith = EnvironmentSettings] void waitUntil(Promise<any> f); };
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc index f901854..9ba0101 100644 --- a/cobalt/worker/service_worker_jobs.cc +++ b/cobalt/worker/service_worker_jobs.cc
@@ -44,6 +44,7 @@ #include "cobalt/worker/client.h" #include "cobalt/worker/client_query_options.h" #include "cobalt/worker/client_type.h" +#include "cobalt/worker/extendable_event.h" #include "cobalt/worker/frame_type.h" #include "cobalt/worker/service_worker.h" #include "cobalt/worker/service_worker_container.h" @@ -891,7 +892,7 @@ // 11.3.1.2. Initialize e’s type attribute to install. // 11.3.1.3. Dispatch e at installingWorker’s global object. installing_worker->worker_global_scope()->DispatchEvent( - new web::Event(base::Tokens::install())); + new ExtendableEvent(base::Tokens::install())); // 11.3.1.4. WaitForAsynchronousExtensions: Run the // following substeps in parallel: // 11.3.1.4.1. Wait until e is not active. @@ -1137,7 +1138,7 @@ // 11.1.1.2. Initialize e’s type attribute to activate. // 11.1.1.3. Dispatch e at activeWorker’s global object. active_worker->worker_global_scope()->DispatchEvent( - new web::Event(base::Tokens::activate())); + new ExtendableEvent(base::Tokens::activate())); // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in // parallel, until e is not active. }, @@ -1837,6 +1838,23 @@ std::move(promise_reference))); } +void ServiceWorkerJobs::WaitUntilSubSteps( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::WaitUntilSubSteps()"); + DCHECK_EQ(message_loop_, base::MessageLoop::current()); + // Sub steps for WaitUntil. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + // 5.2.2. If registration is unregistered, invoke Try Clear Registration + // with registration. + if (scope_to_registration_map_.IsUnregistered(registration)) { + TryClearRegistration(registration); + } + // 5.2.3. If registration is not null, invoke Try Activate with + // registration. + if (registration) { + TryActivate(registration); + } +} void ServiceWorkerJobs::ClientsGetSubSteps( web::EnvironmentSettings* settings, ServiceWorkerObject* associated_service_worker,
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h index 89dd24a..8e09831 100644 --- a/cobalt/worker/service_worker_jobs.h +++ b/cobalt/worker/service_worker_jobs.h
@@ -204,6 +204,10 @@ const base::WeakPtr<ServiceWorkerObject>& service_worker, std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference); + // Sub steps for WaitUntil. + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil + void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration); + // Parallel sub steps (2) for algorithm for Clients.get(id): // https://w3c.github.io/ServiceWorker/#clients-get void ClientsGetSubSteps(
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h index c2b82f7..5a4443b 100644 --- a/net/disk_cache/cobalt/cobalt_backend_impl.h +++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -31,6 +31,8 @@ namespace disk_cache { +const char kCacheEnabledPersistentSettingsKey[] = "cacheEnabled"; + // This class implements the Backend interface. An object of this class handles // the operations of the cache without writing to disk. class NET_EXPORT_PRIVATE CobaltBackendImpl final : public Backend {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index a858ea3..4b05f41 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java
@@ -29,6 +29,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.widget.FrameLayout; +import dev.cobalt.media.AudioOutputManager; import dev.cobalt.media.MediaCodecUtil; import dev.cobalt.media.VideoSurfaceView; import dev.cobalt.util.DisplayUtil; @@ -37,6 +38,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** Native activity that has the required JNI methods called by the Starboard implementation. */ public abstract class CobaltActivity extends NativeActivity { @@ -130,6 +132,8 @@ } DisplayUtil.cacheDefaultDisplay(this); + DisplayUtil.addDisplayListener(this); + AudioOutputManager.addAudioDeviceListener(this); getStarboardBridge().onActivityStart(this, keyboardEditor); super.onStart(); @@ -248,11 +252,12 @@ return; } - String customProxy = String.format("--proxy=\"http=http://%s:%d\"", config.first, port); + String customProxy = + String.format(Locale.US, "--proxy=\"http=http://%s:%d\"", config.first, port); Log.i(TAG, "addCustomProxyArgs: " + customProxy); args.add(customProxy); } catch (NumberFormatException e) { - Log.w(TAG, String.format("http.proxyPort: %s is not valid number", config.second), e); + Log.w(TAG, "http.proxyPort: %s is not valid number", config.second, e); } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java index f131da7..758030d 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
@@ -29,6 +29,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.Locale; /** Loads MediaImage artwork, and caches one image. */ public class ArtworkLoader { @@ -97,7 +98,7 @@ private Size parseImageSize(MediaImage image) { try { String sizeStr = image.sizes.split("\\s+", -1)[0]; - return Size.parseSize(sizeStr.toLowerCase()); + return Size.parseSize(sizeStr.toLowerCase(Locale.US)); } catch (NumberFormatException | NullPointerException e) { return new Size(0, 0); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java index 5a13b01..ebd8967 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; /** Creates and destroys AudioTrackBridge and handles the volume change. */ @@ -103,9 +104,9 @@ // AudioFormat. Log.v( TAG, - String.format( - "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.", - info.getProductName(), getDeviceTypeNameV23(info.getType()))); + "Setting |hasAudioDeviceChanged| to true for audio device %s, %s.", + info.getProductName(), + getDeviceTypeNameV23(info.getType())); hasAudioDeviceChanged.set(true); break; } @@ -116,9 +117,8 @@ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { Log.v( TAG, - String.format( - "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.", - initialDevicesAdded)); + "onAudioDevicesAdded() called, |initialDevicesAdded| is: %b.", + initialDevicesAdded); if (initialDevicesAdded) { handleConnectedDeviceChange(addedDevices); return; @@ -236,7 +236,7 @@ return "TYPE_WIRED_HEADSET"; default: // This may include constants introduced after API 23. - return String.format("TYPE_UNKNOWN (%d)", device_type); + return String.format(Locale.US, "TYPE_UNKNOWN (%d)", device_type); } } @@ -278,7 +278,7 @@ break; default: // This may include constants introduced after API 23. - encodings_in_string.append(String.format("UNKNOWN (%d)", encodings[i])); + encodings_in_string.append(String.format(Locale.US, "UNKNOWN (%d)", encodings[i])); break; } if (i != encodings.length - 1) { @@ -304,12 +304,11 @@ for (AudioDeviceInfo info : deviceInfos) { Log.i( TAG, - String.format( - " Audio Device: %s, channels: %s, sample rates: %s, encodings: %s", - getDeviceTypeNameV23(info.getType()), - Arrays.toString(info.getChannelCounts()), - Arrays.toString(info.getSampleRates()), - getEncodingNames(info.getEncodings()))); + " Audio Device: %s, channels: %s, sample rates: %s, encodings: %s", + getDeviceTypeNameV23(info.getType()), + Arrays.toString(info.getChannelCounts()), + Arrays.toString(info.getSampleRates()), + getEncodingNames(info.getEncodings())); } } @@ -348,10 +347,11 @@ if (AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE % frameSizeInBytes != 0) { Log.w( TAG, - String.format( - "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't" - + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).", - sampleSizeInBytes, numberOfChannels, AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE)); + "Disable tunnel mode due to sampleSizeInBytes (%d) * numberOfChannels (%d) isn't" + + " aligned to AV_SYNC_HEADER_V1_SIZE (%d).", + sampleSizeInBytes, + numberOfChannels, + AudioTrackBridge.AV_SYNC_HEADER_V1_SIZE); return -1; } } @@ -366,10 +366,10 @@ if (Build.VERSION.SDK_INT < 23) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is rejected on api %d, as passthrough is only" - + " supported on api 23 or later.", - encoding, Build.VERSION.SDK_INT)); + "Passthrough on encoding %d is rejected on api %d, as passthrough is only" + + " supported on api 23 or later.", + encoding, + Build.VERSION.SDK_INT); return false; } @@ -382,10 +382,9 @@ if (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is disabled because Bluetooth output device is" - + " connected.", - encoding)); + "Passthrough on encoding %d is disabled because Bluetooth output device is" + + " connected.", + encoding); return false; } } @@ -396,34 +395,31 @@ if (hasPassthroughSupportForV23(deviceInfos, encoding)) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns" - + " true.", - encoding)); + "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns" + + " true.", + encoding); } else { if (Build.VERSION.SDK_INT < 29) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is rejected, as" - + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.", - encoding, Build.VERSION.SDK_INT)); + "Passthrough on encoding %d is rejected, as" + + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.", + encoding, + Build.VERSION.SDK_INT); return false; } if (hasDirectSurroundPlaybackSupportForV29(encoding, DEFAULT_SURROUND_SAMPLE_RATE)) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported, as" - + " hasDirectSurroundPlaybackSupportForV29() returns true.", - encoding)); + "Passthrough on encoding %d is supported, as" + + " hasDirectSurroundPlaybackSupportForV29() returns true.", + encoding); } else { Log.i( TAG, - String.format( - "Passthrough on encoding %d is not supported, as" - + " hasDirectSurroundPlaybackSupportForV29() returns false.", - encoding)); + "Passthrough on encoding %d is not supported, as" + + " hasDirectSurroundPlaybackSupportForV29() returns false.", + encoding); return false; } } @@ -445,10 +441,9 @@ // HDMI and SPDIF are connected, where the output should fallback to AC3. Log.w( TAG, - String.format( - "Passthrough on encoding %d is disabled because creating AudioTrack raises" - + " exception: ", - encoding), + "Passthrough on encoding %d is disabled because creating AudioTrack raises" + + " exception: ", + encoding, e); return false; } @@ -475,31 +470,29 @@ // an empty array indicates that the device supports arbitrary encodings. Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported on %s, because getEncodings() returns" - + " an empty array.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is supported on %s, because getEncodings() returns" + + " an empty array.", + encoding, + getDeviceTypeNameV23(type)); return true; } for (int i = 0; i < encodings.length; ++i) { if (encodings[i] == encoding) { Log.i( TAG, - String.format( - "Passthrough on encoding %d is supported on %s.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is supported on %s.", + encoding, + getDeviceTypeNameV23(type)); return true; } } Log.i( TAG, - String.format( - "Passthrough on encoding %d is not supported on %s.", - encoding, getDeviceTypeNameV23(type))); + "Passthrough on encoding %d is not supported on %s.", + encoding, + getDeviceTypeNameV23(type)); } - Log.i( - TAG, - String.format("Passthrough on encoding %d is not supported on any devices.", encoding)); + Log.i(TAG, "Passthrough on encoding %d is not supported on any devices.", encoding); return false; } @@ -511,19 +504,15 @@ && encoding != AudioFormat.ENCODING_E_AC3_JOC) { Log.w( TAG, - String.format( - "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.", - encoding)); + "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.", + encoding); return false; } boolean supported = AudioTrack.isDirectPlaybackSupported( getPassthroughAudioFormatFor(encoding, sampleRate), getDefaultAudioAttributes()); - Log.i( - TAG, - String.format( - "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported)); + Log.i(TAG, "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported); return supported; } @@ -550,4 +539,31 @@ private boolean getAndResetHasAudioDeviceChanged() { return hasAudioDeviceChanged.getAndSet(false); } + + private static AudioDeviceCallback audioDeviceCallback = + new AudioDeviceCallback() { + @Override + public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { + nativeOnAudioDeviceChanged(); + } + + @Override + public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { + nativeOnAudioDeviceChanged(); + } + }; + + private static boolean audioDeviceListenerAdded = false; + + public static void addAudioDeviceListener(Context context) { + if (audioDeviceListenerAdded) { + return; + } + + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + audioManager.registerAudioDeviceCallback(audioDeviceCallback, null); + audioDeviceListenerAdded = true; + } + + private static native void nativeOnAudioDeviceChanged(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java index 4c11399..5050078 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -27,6 +27,7 @@ import dev.cobalt.util.UsedByNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Locale; /** * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the @@ -96,9 +97,12 @@ audioTrack = null; String errorMessage = String.format( + Locale.US, "Enable tunnel mode when frame size is unaligned, " + "sampleType: %d, channel: %d, sync header size: %d.", - sampleType, channelCount, AV_SYNC_HEADER_V1_SIZE); + sampleType, + channelCount, + AV_SYNC_HEADER_V1_SIZE); Log.e(TAG, errorMessage); throw new RuntimeException(errorMessage); } @@ -160,12 +164,11 @@ } Log.i( TAG, - String.format( - "AudioTrack created with buffer size %d (preferred: %d). The minimum buffer size is" - + " %d.", - audioTrackBufferSize, - preferredBufferSizeInBytes, - AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType))); + "AudioTrack created with buffer size %d (preferred: %d). The minimum buffer size is" + + " %d.", + audioTrackBufferSize, + preferredBufferSizeInBytes, + AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType)); } public Boolean isAudioTrackValid() {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java index a310b15..f36dad3 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -515,9 +515,11 @@ Log.i( TAG, - String.format( - "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms", - stateName, positionMs, speed, duration)); + "MediaSession state: %s, position: %d ms, speed: %f x, duration: %d ms", + stateName, + positionMs, + speed, + duration); playbackStateBuilder = new PlaybackStateCompat.Builder()
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 c5c66cd..eeed7a7 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
@@ -37,6 +37,7 @@ import dev.cobalt.util.UsedByNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Locale; /** A wrapper of the MediaCodec class. */ @SuppressWarnings("unused") @@ -141,7 +142,7 @@ return; } if (presentationTimeUs <= mLastFrameTimestampUs) { - Log.v(TAG, String.format("Invalid output presentation timestamp.")); + Log.v(TAG, "Invalid output presentation timestamp."); return; } @@ -537,13 +538,10 @@ } MediaCodec mediaCodec = null; try { - Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); + Log.i(TAG, "Creating \"%s\" decoder.", decoderName); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { - Log.e( - TAG, - String.format("Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName), - e); + Log.e(TAG, "Failed to create MediaCodec: %s, DecoderName: %s", mime, decoderName, e); return null; } if (mediaCodec == null) { @@ -609,13 +607,16 @@ } try { - Log.i(TAG, String.format("Creating \"%s\" decoder.", decoderName)); + Log.i(TAG, "Creating \"%s\" decoder.", decoderName); mediaCodec = MediaCodec.createByCodecName(decoderName); } catch (Exception e) { String message = String.format( + Locale.US, "Failed to create MediaCodec: %s, mustSupportSecure: %s," + " DecoderName: %s", - mime, crypto != null, decoderName); + mime, + crypto != null, + decoderName); Log.e(TAG, message, e); outCreateMediaCodecBridgeResult.mErrorMessage = message; return; @@ -1189,8 +1190,10 @@ + (configurationData == null ? "|configurationData| is null." : String.format( + Locale.US, "Configuration data size (%d) is less than the required size (%d).", - configurationData.length, MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE))); + configurationData.length, + MIN_OPUS_INITIALIZATION_DATA_BUFFER_SIZE))); return false; } // Both the number of samples to skip from the beginning of the stream and the amount of time
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java index 2d023be..e2e9b43 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -436,7 +436,7 @@ // Filter blacklisted video decoders. String name = codecInfo.getName(); if (!isVp9AllowListed && videoCodecDenyList.contains(name)) { - Log.v(TAG, String.format("Rejecting %s, reason: codec is on deny list", name)); + Log.v(TAG, "Rejecting %s, reason: codec is on deny list", name); continue; } if (name.endsWith(SECURE_DECODER_SUFFIX)) { @@ -446,7 +446,7 @@ name.substring(0, name.length() - SECURE_DECODER_SUFFIX.length()); if (!isVp9AllowListed && videoCodecDenyList.contains(nameWithoutSecureSuffix)) { String format = "Rejecting %s, reason: offpsec denylisted secure decoder"; - Log.v(TAG, String.format(format, name)); + Log.v(TAG, format, name); continue; } } @@ -537,6 +537,7 @@ int fps) { String decoderInfo = String.format( + Locale.US, "Searching for video decoder with parameters mimeType: %s, secure: %b, frameWidth:" + " %d, frameHeight: %d, bitrate: %d, fps: %d, mustSupportHdr: %b," + " mustSupportSoftwareCodec: %b, mustSupportTunnelMode: %b," @@ -554,6 +555,7 @@ Log.v(TAG, decoderInfo); String deviceInfo = String.format( + Locale.US, "brand: %s, model: %s, version: %s, API level: %d, isVp9AllowListed: %b", Build.BRAND, Build.MODEL, @@ -606,9 +608,13 @@ || !mustSupportSecure && requiresSecurePlayback) { String message = String.format( + Locale.US, "Rejecting %s, reason: secure decoder requested: %b, " + "codec FEATURE_SecurePlayback supported: %b, required: %b", - name, mustSupportSecure, supportsSecurePlayback, requiresSecurePlayback); + name, + mustSupportSecure, + supportsSecurePlayback, + requiresSecurePlayback); Log.v(TAG, message); continue; } @@ -623,9 +629,13 @@ || !mustSupportTunnelMode && requiresTunneledPlayback) { String message = String.format( + Locale.US, "Rejecting %s, reason: tunneled playback requested: %b, " + "codec FEATURE_TunneledPlayback supported: %b, required: %b", - name, mustSupportTunnelMode, supportsTunneledPlayback, requiresTunneledPlayback); + name, + mustSupportTunnelMode, + supportsTunneledPlayback, + requiresTunneledPlayback); Log.v(TAG, message); continue; } @@ -652,31 +662,31 @@ if (frameWidth != 0 && frameHeight != 0) { if (!videoCapabilities.isSizeSupported(frameWidth, frameHeight)) { String format = "Rejecting %s, reason: width %s is not compatible with height %d"; - Log.v(TAG, String.format(format, name, frameWidth, frameHeight)); + Log.v(TAG, format, name, frameWidth, frameHeight); continue; } } else if (frameWidth != 0) { if (!supportedWidths.contains(frameWidth)) { String format = "Rejecting %s, reason: supported widths %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth)); + Log.v(TAG, format, name, supportedWidths.toString(), frameWidth); continue; } } else if (frameHeight != 0) { if (!supportedHeights.contains(frameHeight)) { String format = "Rejecting %s, reason: supported heights %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight)); + Log.v(TAG, format, name, supportedHeights.toString(), frameHeight); continue; } } } else { if (frameWidth != 0 && !supportedWidths.contains(frameWidth)) { String format = "Rejecting %s, reason: supported widths %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedWidths.toString(), frameWidth)); + Log.v(TAG, format, name, supportedWidths.toString(), frameWidth); continue; } if (frameHeight != 0 && !supportedHeights.contains(frameHeight)) { String format = "Rejecting %s, reason: supported heights %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedHeights.toString(), frameHeight)); + Log.v(TAG, format, name, supportedHeights.toString(), frameHeight); continue; } } @@ -684,7 +694,7 @@ Range<Integer> bitrates = videoCapabilities.getBitrateRange(); if (bitrate != 0 && !bitrates.contains(bitrate)) { String format = "Rejecting %s, reason: bitrate range %s does not contain %d"; - Log.v(TAG, String.format(format, name, bitrates.toString(), bitrate)); + Log.v(TAG, format, name, bitrates.toString(), bitrate); continue; } @@ -694,14 +704,14 @@ if (frameHeight != 0 && frameWidth != 0) { if (!videoCapabilities.areSizeAndRateSupported(frameWidth, frameHeight, fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } else { // At least one of frameHeight or frameWidth is 0 if (!supportedFrameRates.contains(fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } @@ -709,7 +719,7 @@ } else { if (fps != 0 && !supportedFrameRates.contains(fps)) { String format = "Rejecting %s, reason: supported frame rates %s does not contain %d"; - Log.v(TAG, String.format(format, name, supportedFrameRates.toString(), fps)); + Log.v(TAG, format, name, supportedFrameRates.toString(), fps); continue; } } @@ -855,6 +865,7 @@ for (ResolutionAndFrameRate resolutionAndFrameRate : supported) { frameRateAndResolutionString += String.format( + Locale.US, "[%d x %d, %.3f fps], ", resolutionAndFrameRate.width, resolutionAndFrameRate.height, @@ -878,6 +889,7 @@ String name = info.getName(); decoderDumpString += String.format( + Locale.US, "name: %s (%s, %s): ", name, supportedType, @@ -897,6 +909,7 @@ getSupportedResolutionsAndFrameRates(videoCapabilities, isHdrCapable); decoderDumpString += String.format( + Locale.US, "\n\t\t" + "widths: %s, " + "heights: %s, " @@ -923,6 +936,7 @@ || isTunneledPlaybackSupported) { decoderDumpString += String.format( + Locale.US, "(%s%s%s", isAdaptivePlaybackSupported ? "AdaptivePlayback, " : "", isSecurePlaybackSupported ? "SecurePlayback, " : "", @@ -938,13 +952,12 @@ } Log.v( TAG, - String.format( - " \n" - + "==================================================\n" - + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," - + " TunneledPlayback]\n" - + "Unsupported features for each codec are not listed\n" - + decoderDumpString - + "==================================================")); + " \n" + + "==================================================\n" + + "Full list of decoder features: [AdaptivePlayback, SecurePlayback," + + " TunneledPlayback]\n" + + "Unsupported features for each codec are not listed\n" + + decoderDumpString + + "=================================================="); } }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java index 29a2f80..b96bb87 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -16,6 +16,8 @@ import android.app.Activity; import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.util.DisplayMetrics; import android.util.Size; import android.util.SizeF; @@ -139,4 +141,40 @@ private static DisplayMetrics getDisplayMetrics() { return cachedDisplayMetrics; } + + private static DisplayListener displayerListener = + new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + nativeOnDisplayChanged(); + } + + @Override + public void onDisplayChanged(int displayId) { + nativeOnDisplayChanged(); + } + + @Override + public void onDisplayRemoved(int displayId) { + nativeOnDisplayChanged(); + } + }; + + private static boolean displayerListenerAdded = false; + + public static void addDisplayListener(Context context) { + if (displayerListenerAdded) { + return; + } + + DisplayManager displayManager = context.getSystemService(DisplayManager.class); + displayManager.registerDisplayListener(displayerListener, null); + displayerListenerAdded = true; + + // Call nativeOnDisplayChanged() to reload supported hdr types here after a default + // Display created. + nativeOnDisplayChanged(); + } + + private static native void nativeOnDisplayChanged(); }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java index 0a75c79..cca7c02 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/Log.java
@@ -15,6 +15,7 @@ package dev.cobalt.util; import java.lang.reflect.Method; +import java.util.Locale; /** * Logging wrapper to allow for better control of Proguard log stripping. Many dependent @@ -59,9 +60,27 @@ } } - private static int logWithMethod(Method logMethod, String tag, String msg, Throwable tr) { + private static Throwable getThrowableToLog(Object[] args) { + if (args == null || args.length == 0) return null; + Object lastArg = args[args.length - 1]; + if (!(lastArg instanceof Throwable)) return null; + return (Throwable) lastArg; + } + + /** Returns a formatted log message, using the supplied format and arguments. */ + private static String formatLog(String messageTemplate, Throwable tr, Object... params) { + if ((params != null) && ((tr == null && params.length > 0) || params.length > 1)) { + messageTemplate = String.format(Locale.US, messageTemplate, params); + } + return messageTemplate; + } + + private static int logWithMethod( + Method logMethod, String tag, String messageTemplate, Object... args) { try { if (logMethod != null) { + Throwable tr = getThrowableToLog(args); + String msg = formatLog(messageTemplate, tr, args); return (int) logMethod.invoke(null, tag, msg, tr); } } catch (Throwable e) { @@ -70,47 +89,27 @@ return 0; } - public static int v(String tag, String msg) { - return logWithMethod(logV, tag, msg, null); + public static int v(String tag, String messageTemplate, Object... args) { + return logWithMethod(logV, tag, messageTemplate, args); } - public static int v(String tag, String msg, Throwable tr) { - return logWithMethod(logV, tag, msg, tr); + public static int d(String tag, String messageTemplate, Object... args) { + return logWithMethod(logD, tag, messageTemplate, args); } - public static int d(String tag, String msg) { - return logWithMethod(logD, tag, msg, null); + public static int i(String tag, String messageTemplate, Object... args) { + return logWithMethod(logI, tag, messageTemplate, args); } - public static int d(String tag, String msg, Throwable tr) { - return logWithMethod(logD, tag, msg, tr); - } - - public static int i(String tag, String msg) { - return logWithMethod(logI, tag, msg, null); - } - - public static int i(String tag, String msg, Throwable tr) { - return logWithMethod(logI, tag, msg, tr); - } - - public static int w(String tag, String msg) { - return logWithMethod(logW, tag, msg, null); - } - - public static int w(String tag, String msg, Throwable tr) { - return logWithMethod(logW, tag, msg, tr); + public static int w(String tag, String messageTemplate, Object... args) { + return logWithMethod(logW, tag, messageTemplate, args); } public static int w(String tag, Throwable tr) { return logWithMethod(logW, tag, "", tr); } - public static int e(String tag, String msg) { - return logWithMethod(logE, tag, msg, null); - } - - public static int e(String tag, String msg, Throwable tr) { - return logWithMethod(logE, tag, msg, tr); + public static int e(String tag, String messageTemplate, Object... args) { + return logWithMethod(logE, tag, messageTemplate, args); } }
diff --git a/starboard/android/shared/file_open.cc b/starboard/android/shared/file_open.cc index 83d924a..0f398a4 100644 --- a/starboard/android/shared/file_open.cc +++ b/starboard/android/shared/file_open.cc
@@ -30,7 +30,12 @@ // font file of the same name. const std::string kFontsXml("fonts.xml"); const std::string kSystemFontsDir("/system/fonts/"); + +#if SB_IS(EVERGREEN_COMPATIBLE) +const std::string kCobaltFontsDir("/cobalt/assets/app/cobalt/content/fonts/"); +#else const std::string kCobaltFontsDir("/cobalt/assets/fonts/"); +#endif // Returns the fallback for the given asset path, or an empty string if none. // NOTE: While Cobalt now provides a mechanism for loading system fonts through @@ -67,8 +72,8 @@ bool* out_created, SbFileError* out_error) { if (!IsAndroidAssetPath(path)) { - return ::starboard::shared::posix::impl::FileOpen( - path, flags, out_created, out_error); + return ::starboard::shared::posix::impl::FileOpen(path, flags, out_created, + out_error); } // Assets are never created and are always read-only, whether it's actually an
diff --git a/starboard/android/shared/media_capabilities_cache.cc b/starboard/android/shared/media_capabilities_cache.cc index 53360ea..e8090b5 100644 --- a/starboard/android/shared/media_capabilities_cache.cc +++ b/starboard/android/shared/media_capabilities_cache.cc
@@ -20,12 +20,17 @@ #include "starboard/android/shared/media_common.h" #include "starboard/common/log.h" #include "starboard/once.h" +#include "starboard/shared/starboard/media/key_system_supportability_cache.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" namespace starboard { namespace android { namespace shared { namespace { +using ::starboard::shared::starboard::media::KeySystemSupportabilityCache; +using ::starboard::shared::starboard::media::MimeSupportabilityCache; + // https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10 const jint HDR_TYPE_DOLBY_VISION = 1; const jint HDR_TYPE_HDR10 = 2; @@ -79,6 +84,13 @@ JniEnvExt* env = JniEnvExt::Get(); jintArray j_supported_hdr_types = static_cast<jintArray>( env->CallStarboardObjectMethodOrAbort("getSupportedHdrTypes", "()[I")); + + if (!j_supported_hdr_types) { + // Failed to get supported hdr types. + SB_LOG(ERROR) << "Failed to load supported hdr types."; + return std::set<SbMediaTransferId>(); + } + jsize length = env->GetArrayLength(j_supported_hdr_types); jint* numbers = env->GetIntArrayElements(j_supported_hdr_types, 0); for (int i = 0; i < length; i++) { @@ -461,6 +473,31 @@ max_audio_output_channels_ = -1; } +void MediaCapabilitiesCache::ReloadSupportedHdrTypes() { + ScopedLock scoped_lock(mutex_); + if (!is_initialized_) { + LazyInitialize_Locked(); + return; + } + supported_transfer_ids_ = GetSupportedHdrTypes(); +} + +void MediaCapabilitiesCache::ReloadAudioOutputChannels() { + ScopedLock scoped_lock(mutex_); + if (!is_initialized_) { + LazyInitialize_Locked(); + return; + } + max_audio_output_channels_ = + ::starboard::android::shared::GetMaxAudioOutputChannels(); +} + +MediaCapabilitiesCache::MediaCapabilitiesCache() { + // Enable mime and key system caches. + MimeSupportabilityCache::GetInstance()->SetCacheEnabled(true); + KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(true); +} + void MediaCapabilitiesCache::LazyInitialize_Locked() { mutex_.DCheckAcquired(); @@ -526,6 +563,20 @@ } } +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_util_DisplayUtil_nativeOnDisplayChanged() { + SB_DLOG(INFO) << "Display device has changed."; + MediaCapabilitiesCache::GetInstance()->ReloadSupportedHdrTypes(); + MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); +} + +extern "C" SB_EXPORT_PLATFORM void +Java_dev_cobalt_media_AudioOutputManager_nativeOnAudioDeviceChanged() { + SB_DLOG(INFO) << "Audio device has changed."; + MediaCapabilitiesCache::GetInstance()->ReloadAudioOutputChannels(); + MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities(); +} + } // namespace shared } // namespace android } // namespace starboard
diff --git a/starboard/android/shared/media_capabilities_cache.h b/starboard/android/shared/media_capabilities_cache.h index 46da91a..21d735b 100644 --- a/starboard/android/shared/media_capabilities_cache.h +++ b/starboard/android/shared/media_capabilities_cache.h
@@ -160,8 +160,11 @@ void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } void ClearCache(); + void ReloadSupportedHdrTypes(); + void ReloadAudioOutputChannels(); + private: - MediaCapabilitiesCache() {} + MediaCapabilitiesCache(); ~MediaCapabilitiesCache() {} MediaCapabilitiesCache(const MediaCapabilitiesCache&) = delete; @@ -183,7 +186,7 @@ std::map<std::string, AudioCodecCapabilities> audio_codec_capabilities_map_; std::map<std::string, VideoCodecCapabilities> video_codec_capabilities_map_; - std::atomic_bool is_enabled_{false}; + std::atomic_bool is_enabled_{true}; bool is_initialized_ = false; bool is_widevine_supported_ = false; bool is_cbcs_supported_ = false;
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc index e80c61d..46b73e5 100644 --- a/starboard/android/shared/media_is_audio_supported.cc +++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -21,16 +21,13 @@ #include "starboard/configuration.h" #include "starboard/configuration_constants.h" #include "starboard/media.h" -#include "starboard/shared/starboard/media/mime_type.h" -using starboard::android::shared::JniEnvExt; using starboard::android::shared::MediaCapabilitiesCache; -using starboard::android::shared::ScopedLocalJavaRef; using starboard::android::shared::SupportedAudioCodecToMimeType; using starboard::shared::starboard::media::MimeType; bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (bitrate >= kSbMediaMaxAudioBitrateInBitsPerSecond) { return false; @@ -43,28 +40,41 @@ return false; } - MimeType mime_type(content_type); - if (strlen(content_type) > 0) { + bool enable_tunnel_mode = false; + bool enable_audio_passthrough = true; + if (mime_type) { + if (!mime_type->is_valid()) { + return false; + } // Allows for disabling the use of the AudioDeviceCallback API to detect // when audio peripherals are connected. Enabled by default. // (https://developer.android.com/reference/android/media/AudioDeviceCallback) - mime_type.RegisterBoolParameter("enableaudiodevicecallback"); + if (!mime_type->ValidateBoolParameter("enableaudiodevicecallback")) { + return false; + } + // Allows for enabling tunneled playback. Disabled by default. // (https://source.android.com/devices/tv/multimedia-tunneling) - mime_type.RegisterBoolParameter("tunnelmode"); + if (!mime_type->ValidateBoolParameter("tunnelmode")) { + return false; + } + enable_tunnel_mode = mime_type->GetParamBoolValue("tunnelmode", false); + // Enables audio passthrough if the codec supports it. - mime_type.RegisterBoolParameter("audiopassthrough"); + if (!mime_type->ValidateBoolParameter("audiopassthrough")) { + return false; + } + enable_audio_passthrough = + mime_type->GetParamBoolValue("audiopassthrough", true); + // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for // non-tunneled playbacks with PCM audio. Enabled by default. // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE) - mime_type.RegisterBoolParameter("enablepcmcontenttypemovie"); - - if (!mime_type.is_valid()) { + if (!mime_type->ValidateBoolParameter("enablepcmcontenttypemovie")) { return false; } } - bool enable_tunnel_mode = mime_type.GetParamBoolValue("tunnelmode", false); if (enable_tunnel_mode && !SbAudioSinkIsAudioSampleTypeSupported( kSbMediaAudioSampleTypeInt16Deprecated)) { SB_LOG(WARNING) @@ -91,7 +101,7 @@ return true; } - if (!mime_type.GetParamBoolValue("audiopassthrough", true)) { + if (!enable_audio_passthrough) { SB_LOG(INFO) << "Passthrough codec is rejected because passthrough is " "disabled through mime param."; return false;
diff --git a/starboard/android/shared/media_is_supported.cc b/starboard/android/shared/media_is_supported.cc index feaae31..fcd6806 100644 --- a/starboard/android/shared/media_is_supported.cc +++ b/starboard/android/shared/media_is_supported.cc
@@ -33,8 +33,8 @@ // `com.widevine.alpha; encryptionscheme="cenc"`. We prepend "key_system/" // to it, so it can be parsed by MimeType. MimeType mime_type(std::string("key_system/") + key_system); - mime_type.RegisterStringParameter("encryptionscheme", "cenc|cbcs|cbcs-1-9"); - if (!mime_type.is_valid()) { + if (!mime_type.is_valid() || !mime_type.ValidateStringParameter( + "encryptionscheme", "cenc|cbcs|cbcs-1-9")) { return false; }
diff --git a/starboard/android/shared/media_is_video_supported.cc b/starboard/android/shared/media_is_video_supported.cc index 12c2f9f..9b8fcf5 100644 --- a/starboard/android/shared/media_is_video_supported.cc +++ b/starboard/android/shared/media_is_video_supported.cc
@@ -19,7 +19,6 @@ #include "starboard/configuration.h" #include "starboard/media.h" #include "starboard/shared/starboard/media/media_util.h" -#include "starboard/shared/starboard/media/mime_type.h" using starboard::android::shared::MediaCapabilitiesCache; using starboard::android::shared::SupportedVideoCodecToMimeType; @@ -27,7 +26,7 @@ using starboard::shared::starboard::media::MimeType; bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -49,26 +48,48 @@ // While not necessarily true, for now we assume that all Android devices // can play decode-to-texture video just as well as normal video. - // Check extended parameters for correctness and return false if any invalid - // invalid params are found. - MimeType mime_type(content_type); - if (strlen(content_type) > 0) { + bool must_support_tunnel_mode = false; + bool force_improved_support_check = true; + int decoder_cache_ttl_ms = -1; + if (mime_type) { + if (!mime_type->is_valid()) { + return false; + } + // Allows for enabling tunneled playback. Disabled by default. // https://source.android.com/devices/tv/multimedia-tunneling - mime_type.RegisterBoolParameter("tunnelmode"); + if (!mime_type->ValidateBoolParameter("tunnelmode")) { + return false; + } + must_support_tunnel_mode = + mime_type->GetParamBoolValue("tunnelmode", false); + // Override endianness on HDR Info header. Defaults to little. - mime_type.RegisterStringParameter("hdrinfoendianness", "big|little"); + if (!mime_type->ValidateStringParameter("hdrinfoendianness", + "big|little")) { + return false; + } + // Forces the use of specific Android APIs (isSizeSupported() and // areSizeAndRateSupported()) to determine format support. - mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - - if (!mime_type.is_valid()) { + if (!mime_type->ValidateBoolParameter("forceimprovedsupportcheck")) { return false; } + force_improved_support_check = + mime_type->GetParamBoolValue("forceimprovedsupportcheck", true); + + decoder_cache_ttl_ms = + mime_type->GetParamIntValue("decoder_cache_ttl_ms", -1); + + // Disable MediaCapabilitiesCache if "disablecache" option presented. + if (!mime_type->ValidateBoolParameter("disablecache")) { + return false; + } + if (mime_type->GetParamBoolValue("disablecache", false)) { + MediaCapabilitiesCache::GetInstance()->SetCacheEnabled(false); + } } - bool must_support_tunnel_mode = - mime_type.GetParamBoolValue("tunnelmode", false); if (must_support_tunnel_mode && decode_to_texture_required) { SB_LOG(WARNING) << "Tunnel mode is rejected because output mode decode to " "texture is required but not supported."; @@ -85,8 +106,6 @@ // tunneled playback to be encrypted, so we must align the tunnel mode // requirement with the secure playback requirement. const bool require_secure_playback = must_support_tunnel_mode; - const bool force_improved_support_check = - mime_type.GetParamBoolValue("forceimprovedsupportcheck", true); return MediaCapabilitiesCache::GetInstance()->HasVideoDecoderFor( mime, require_secure_playback, must_support_hdr, must_support_tunnel_mode,
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index e356269..6d26909 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h
@@ -207,25 +207,26 @@ error_message); } - MimeType audio_mime_type(creation_parameters.audio_mime()); + bool enable_audio_device_callback = true; if (strlen(creation_parameters.audio_mime()) > 0) { - audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); - audio_mime_type.RegisterBoolParameter("audiopassthrough"); - if (!audio_mime_type.is_valid()) { + MimeType audio_mime_type(creation_parameters.audio_mime()); + if (!audio_mime_type.is_valid() || + !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") || + !audio_mime_type.ValidateBoolParameter("audiopassthrough")) { return scoped_ptr<PlayerComponents>(); } - } - bool enable_audio_device_callback = - audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); - SB_LOG(INFO) << "AudioDeviceCallback is " - << (enable_audio_device_callback ? "enabled." : "disabled."); + enable_audio_device_callback = + audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true); + SB_LOG(INFO) << "AudioDeviceCallback is " + << (enable_audio_device_callback ? "enabled." : "disabled."); - if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) { - SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: " - "false. Passthrough is disabled."; - return scoped_ptr<PlayerComponents>(); + if (!audio_mime_type.GetParamBoolValue("audiopassthrough", true)) { + SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: " + "false. Passthrough is disabled."; + return scoped_ptr<PlayerComponents>(); + } } SB_LOG(INFO) << "Creating passthrough components."; @@ -245,15 +246,20 @@ constexpr int kTunnelModeAudioSessionId = -1; constexpr bool kForceSecurePipelineUnderTunnelMode = false; - MimeType video_mime_type(creation_parameters.video_mime()); - video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - if (!video_mime_type.is_valid()) { - return scoped_ptr<PlayerComponents>(); + bool force_improved_support_check = true; + + if (strlen(creation_parameters.video_mime()) > 0) { + MimeType video_mime_type(creation_parameters.video_mime()); + if (!video_mime_type.is_valid() || + !video_mime_type.ValidateBoolParameter( + "forceimprovedsupportcheck")) { + return scoped_ptr<PlayerComponents>(); + } + force_improved_support_check = video_mime_type.GetParamBoolValue( + "forceimprovedsupportcheck", true); + SB_LOG_IF(INFO, !force_improved_support_check) + << "Improved support check is disabled for queries under 4K."; } - const bool force_improved_support_check = - video_mime_type.GetParamBoolValue("forceimprovedsupportcheck", true); - SB_LOG_IF(INFO, !force_improved_support_check) - << "Improved support check is disabled for queries under 4K."; scoped_ptr<VideoDecoder> video_decoder = CreateVideoDecoder(creation_parameters, kTunnelModeAudioSessionId, @@ -293,13 +299,11 @@ ? creation_parameters.audio_mime() : ""; MimeType audio_mime_type(audio_mime); - if (creation_parameters.audio_codec() != kSbMediaAudioCodecNone && - strlen(creation_parameters.audio_mime()) > 0) { - audio_mime_type.RegisterBoolParameter("tunnelmode"); - audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback"); - audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie"); - - if (!audio_mime_type.is_valid()) { + if (strlen(audio_mime) > 0) { + if (!audio_mime_type.is_valid() || + !audio_mime_type.ValidateBoolParameter("tunnelmode") || + !audio_mime_type.ValidateBoolParameter("enableaudiodevicecallback") || + !audio_mime_type.ValidateBoolParameter("enablepcmcontenttypemovie")) { *error_message = "Invalid audio MIME: '" + std::string(audio_mime) + "'"; return false; @@ -311,12 +315,10 @@ ? creation_parameters.video_mime() : ""; MimeType video_mime_type(video_mime); - if (creation_parameters.video_codec() != kSbMediaVideoCodecNone && - strlen(creation_parameters.video_mime()) > 0) { - video_mime_type.RegisterBoolParameter("tunnelmode"); - video_mime_type.RegisterBoolParameter("forceimprovedsupportcheck"); - - if (!video_mime_type.is_valid()) { + if (strlen(video_mime) > 0) { + if (!video_mime_type.is_valid() || + !video_mime_type.ValidateBoolParameter("tunnelmode") || + !video_mime_type.ValidateBoolParameter("forceimprovedsupportcheck")) { *error_message = "Invalid video MIME: '" + std::string(video_mime) + "'"; return false; @@ -497,18 +499,18 @@ bool force_secure_pipeline_under_tunnel_mode, bool force_improved_support_check, std::string* error_message) { - // Use mime param to determine endianness of HDR metadata. If param is - // missing or invalid it defaults to Little Endian. - MimeType video_mime_type(creation_parameters.video_mime()); - + bool force_big_endian_hdr_metadata = false; if (strlen(creation_parameters.video_mime()) > 0) { - video_mime_type.RegisterStringParameter("hdrinfoendianness", + // Use mime param to determine endianness of HDR metadata. If param is + // missing or invalid it defaults to Little Endian. + MimeType video_mime_type(creation_parameters.video_mime()); + video_mime_type.ValidateStringParameter("hdrinfoendianness", "big|little"); + const std::string& hdr_info_endianness = + video_mime_type.GetParamStringValue("hdrinfoendianness", + /*default=*/"little"); + force_big_endian_hdr_metadata = hdr_info_endianness == "big"; } - const std::string& hdr_info_endianness = - video_mime_type.GetParamStringValue("hdrinfoendianness", - /*default=*/"little"); - bool force_big_endian_hdr_metadata = hdr_info_endianness == "big"; scoped_ptr<VideoDecoder> video_decoder(new VideoDecoder( creation_parameters.video_codec(),
diff --git a/starboard/android/shared/system_get_path.cc b/starboard/android/shared/system_get_path.cc index 2ca0815..5e4610a 100644 --- a/starboard/android/shared/system_get_path.cc +++ b/starboard/android/shared/system_get_path.cc
@@ -25,10 +25,35 @@ #include "starboard/common/string.h" #include "starboard/directory.h" +#if SB_IS(EVERGREEN_COMPATIBLE) +#include "starboard/elf_loader/evergreen_config.h" // nogncheck +#endif + using ::starboard::android::shared::g_app_assets_dir; using ::starboard::android::shared::g_app_cache_dir; +using ::starboard::android::shared::g_app_files_dir; using ::starboard::android::shared::g_app_lib_dir; +#if SB_IS(EVERGREEN_COMPATIBLE) +bool GetEvergreenContentPathOverride(char* out_path, int path_size) { + const starboard::elf_loader::EvergreenConfig* evergreen_config = + starboard::elf_loader::EvergreenConfig::GetInstance(); + if (!evergreen_config) { + return true; + } + if (evergreen_config->content_path_.empty()) { + return true; + } + + if (starboard::strlcpy(out_path, evergreen_config->content_path_.c_str(), + path_size) >= path_size) { + return false; + } + + return true; +} +#endif + bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) { if (!out_path || !path_size) { return false; @@ -43,9 +68,25 @@ if (starboard::strlcat(path, g_app_assets_dir, kPathSize) >= kPathSize) { return false; } + +#if SB_IS(EVERGREEN_COMPATIBLE) + if (!GetEvergreenContentPathOverride(path, kPathSize)) { + return false; + } +#endif break; } + case kSbSystemPathStorageDirectory: { + if (starboard::strlcpy(path, g_app_files_dir, kPathSize) >= kPathSize) { + return false; + } + if (starboard::strlcat(path, "/storage", kPathSize) >= kPathSize) { + return false; + } + SbDirectoryCreate(path); + break; + } case kSbSystemPathCacheDirectory: { if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) { return false;
diff --git a/starboard/evergreen/shared/launcher.py b/starboard/evergreen/shared/launcher.py index c8bf2d5..e6f331d 100644 --- a/starboard/evergreen/shared/launcher.py +++ b/starboard/evergreen/shared/launcher.py
@@ -75,6 +75,8 @@ self.loader_out_directory = paths.BuildOutputDirectory( self.loader_platform, self.loader_config) + self.use_compressed_library = kwargs.get('use_compressed_library') + # The relationship of loader platforms and configurations to evergreen # platforms and configurations is many-to-many. We need a separate directory # for each of them, i.e. linux-x64x11_debug__evergreen-x64_gold. @@ -92,9 +94,18 @@ # Ensure the path, relative to the content of the ELF Loader, to the # Evergreen target and its content are passed as command line switches. + library_path_param = '--evergreen_library=app/{}/lib/lib{}'.format( + self.target_name, self.target_name) + if self.use_compressed_library: + if self.target_name != 'cobalt': + raise ValueError( + '|use_compressed_library| only expected with |target_name| cobalt') + library_path_param += '.lz4' + else: + library_path_param += '.so' + target_command_line_params = [ - '--evergreen_library=app/{}/lib/lib{}.so'.format( - self.target_name, self.target_name), + library_path_param, '--evergreen_content=app/{}/content'.format(self.target_name) ] @@ -171,6 +182,7 @@ self._StageTargetsAndContentsGyp() def _StageTargetsAndContentsGnLinux(self): + """Stage targets and their contents for GN builds for Linux platforms.""" content_subdir = os.path.join('usr', 'share', 'cobalt') # Copy loader content and binaries @@ -199,13 +211,15 @@ target_content_dst = os.path.join(target_staging_dir, 'content') shutil.copytree(target_content_src, target_content_dst) - shlib_name = 'lib{}.so'.format(self.target_name) + shlib_name = 'lib{}'.format(self.target_name) + shlib_name += '.lz4' if self.use_compressed_library else '.so' target_binary_src = os.path.join(target_install_path, 'lib', shlib_name) target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name) os.makedirs(os.path.join(target_staging_dir, 'lib')) shutil.copy(target_binary_src, target_binary_dst) def _StageTargetsAndContentsGnRaspi(self): + """Stage targets and their contents for GN builds for Raspi platforms.""" # TODO(b/218889313): `content` is hardcoded on raspi and must be in the same # directory as the binaries. if 'raspi' in self.loader_platform: @@ -251,13 +265,15 @@ target_content_dst = os.path.join(target_staging_dir, 'content') shutil.copytree(target_content_src, target_content_dst) - shlib_name = 'lib{}.so'.format(self.target_name) + shlib_name = 'lib{}'.format(self.target_name) + shlib_name += '.lz4' if self.use_compressed_library else '.so' target_binary_src = os.path.join(target_install_path, 'lib', shlib_name) target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name) os.makedirs(os.path.join(target_staging_dir, 'lib')) shutil.copy(target_binary_src, target_binary_dst) def _StageTargetsAndContentsGyp(self): + """Stage targets and their contents for GYP builds.""" # <outpath>/deploy/elf_loader_sandbox staging_directory_loader = os.path.join(self.staging_directory, 'deploy', self.loader_target)
diff --git a/starboard/evergreen/testing/README.md b/starboard/evergreen/testing/README.md index bbd33fe..e401325 100644 --- a/starboard/evergreen/testing/README.md +++ b/starboard/evergreen/testing/README.md
@@ -121,9 +121,9 @@ +-- content <-- loader content +-- app +-- cobalt - +-- content <-- cobalt content + +-- content <-- cobalt content +-- lib - +-- libcobalt.so <-- cobalt binary + +-- libcobalt.{so,lz4} <-- cobalt binary ``` Note: This directory structure is the same as what would be generated by
diff --git a/starboard/evergreen/testing/linux/deploy_cobalt.sh b/starboard/evergreen/testing/linux/deploy_cobalt.sh index 6dc6e59..a461fae 100755 --- a/starboard/evergreen/testing/linux/deploy_cobalt.sh +++ b/starboard/evergreen/testing/linux/deploy_cobalt.sh
@@ -31,8 +31,8 @@ echo " Checking '${staging_dir}'" - PATHS=("${staging_dir}/loader_app" \ - "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \ + PATHS=("${staging_dir}/loader_app" \ + "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \ "${staging_dir}/content/app/cobalt/content/") for file in "${PATHS[@]}"; do
diff --git a/starboard/evergreen/testing/raspi/deploy_cobalt.sh b/starboard/evergreen/testing/raspi/deploy_cobalt.sh index 8fdbc22..9012f3c 100755 --- a/starboard/evergreen/testing/raspi/deploy_cobalt.sh +++ b/starboard/evergreen/testing/raspi/deploy_cobalt.sh
@@ -31,8 +31,8 @@ echo " Checking '${staging_dir}'" - PATHS=("${staging_dir}/loader_app" \ - "${staging_dir}/content/app/cobalt/lib/libcobalt.so" \ + PATHS=("${staging_dir}/loader_app" \ + "${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION}" \ "${staging_dir}/content/app/cobalt/content/") for file in "${PATHS[@]}"; do @@ -60,7 +60,7 @@ eval "${SSH} \"mkdir -p /home/pi/coeg/content/app/cobalt/lib\"" echo " Copying cobalt to system image directory" - eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt.so pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\"" + eval "${SCP} \"${staging_dir}/content/app/cobalt/lib/libcobalt${SYSTEM_IMAGE_EXTENSION} pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/\"" echo " Copying content to system image directory" eval "${SCP} \"-r ${staging_dir}/content/app/cobalt/content/ pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/\""
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh index efb9d0b..91c53fb 100755 --- a/starboard/evergreen/testing/run_all_tests.sh +++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,7 +21,9 @@ DIR="$(dirname "${0}")" AUTH_METHOD="public-key" -while getopts "d:a:" o; do +USE_COMPRESSED_SYSTEM_IMAGE="false" +SYSTEM_IMAGE_EXTENSION=".so" +while getopts "d:a:c" o; do case "${o}" in d) DEVICE_ID=${OPTARG} @@ -29,6 +31,10 @@ a) AUTH_METHOD=${OPTARG} ;; + c) + USE_COMPRESSED_SYSTEM_IMAGE="true" + SYSTEM_IMAGE_EXTENSION=".lz4" + ;; esac done shift $((OPTIND-1)) @@ -40,8 +46,14 @@ source $DIR/setup.sh -# Find all of the test files within the 'test' subdirectory. -TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) +if [[ "${USE_COMPRESSED_SYSTEM_IMAGE}" == "true" ]]; then + # It would be valid to run all test cases using a compressed system image but + # is probably excessive. Instead, just the Evergreen Lite case is run to test + # that the compressed system image can be successfully loaded. + TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name 'evergreen_lite_test.sh'")) +else + TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'")) +fi COUNT=0 RETRIED=() @@ -145,7 +157,7 @@ clean_up -log "info" " [==========] Finished." +log "info" " [==========] Finished testing with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}." if [[ "${#FAILED[@]}" -eq 0 ]]; then exit 0
diff --git a/starboard/evergreen/testing/setup.sh b/starboard/evergreen/testing/setup.sh index 57bdbc4..393ea6d 100755 --- a/starboard/evergreen/testing/setup.sh +++ b/starboard/evergreen/testing/setup.sh
@@ -24,7 +24,7 @@ source $DIR/pprint.sh -log "info" " [==========] Preparing Cobalt." +log "info" " [==========] Preparing to test with USE_COMPRESSED_SYSTEM_IMAGE=${USE_COMPRESSED_SYSTEM_IMAGE}." if [[ -z ${1} ]]; then log "error" "A platform must be provided"
diff --git a/starboard/linux/shared/media_is_audio_supported.cc b/starboard/linux/shared/media_is_audio_supported.cc index 30d71e5..bedb0c7 100644 --- a/starboard/linux/shared/media_is_audio_supported.cc +++ b/starboard/linux/shared/media_is_audio_supported.cc
@@ -19,14 +19,11 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" -bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, - int64_t bitrate) { - if (!content_type) { - SB_LOG(WARNING) << "|content_type| cannot be nullptr."; - return false; - } +using ::starboard::shared::starboard::media::MimeType; +bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, + const MimeType* mime_type, + int64_t bitrate) { if (audio_codec == kSbMediaAudioCodecAac) { return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond; }
diff --git a/starboard/linux/shared/media_is_video_supported.cc b/starboard/linux/shared/media_is_video_supported.cc index b05bfe4..e932cb1 100644 --- a/starboard/linux/shared/media_is_video_supported.cc +++ b/starboard/linux/shared/media_is_video_supported.cc
@@ -22,11 +22,12 @@ #include "starboard/shared/libde265/de265_library_loader.h" #include "starboard/shared/starboard/media/media_util.h" -using starboard::shared::de265::is_de265_supported; -using starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::de265::is_de265_supported; +using ::starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::starboard::media::MimeType; bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -38,11 +39,6 @@ int64_t bitrate, int fps, bool decode_to_texture_required) { - if (!content_type) { - SB_LOG(WARNING) << "|content_type| cannot be nullptr."; - return false; - } - if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) { if (bit_depth != 10 && bit_depth != 12) { return false;
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc index beea8ac..af93b99 100644 --- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc +++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -74,6 +74,10 @@ result = SbMediaCanPlayMimeAndKeySystem( "audio/webm; codecs=\"opus\"; channels=2", ""); ASSERT_EQ(result, kSbMediaSupportTypeProbably); + // Two codecs + result = SbMediaCanPlayMimeAndKeySystem( + "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"", ""); + ASSERT_EQ(result, kSbMediaSupportTypeProbably); } TEST(SbMediaCanPlayMimeAndKeySystem, Invalid) {
diff --git a/starboard/nplb/media_set_audio_write_duration_test.cc b/starboard/nplb/media_set_audio_write_duration_test.cc index 237fe58..84ff9b2 100644 --- a/starboard/nplb/media_set_audio_write_duration_test.cc +++ b/starboard/nplb/media_set_audio_write_duration_test.cc
@@ -31,7 +31,7 @@ namespace { using ::starboard::testing::FakeGraphicsContextProvider; -using shared::starboard::player::video_dmp::VideoDmpReader; +using ::shared::starboard::player::video_dmp::VideoDmpReader; using ::testing::ValuesIn; const SbTime kDuration = kSbTimeSecond / 2; @@ -261,8 +261,7 @@ const SbMediaAudioSampleInfo* audio_sample_info = &dmp_reader.audio_sample_info(); - if (SbMediaIsAudioSupported(dmp_reader.audio_codec(), - "", // content_type + if (SbMediaIsAudioSupported(dmp_reader.audio_codec(), nullptr, dmp_reader.audio_bitrate())) { test_params.push_back(filename); }
diff --git a/starboard/raspi/shared/media_is_video_supported.cc b/starboard/raspi/shared/media_is_video_supported.cc index 704faea..16a7088 100644 --- a/starboard/raspi/shared/media_is_video_supported.cc +++ b/starboard/raspi/shared/media_is_video_supported.cc
@@ -19,8 +19,11 @@ #include "starboard/media.h" #include "starboard/shared/starboard/media/media_util.h" +using ::starboard::shared::starboard::media::IsSDRVideo; +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -32,8 +35,6 @@ int64_t bitrate, int fps, bool decode_to_texture_required) { - using starboard::shared::starboard::media::IsSDRVideo; - if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) { return false; }
diff --git a/starboard/shared/starboard/media/BUILD.gn b/starboard/shared/starboard/media/BUILD.gn index 1e7355b..7baaf99 100644 --- a/starboard/shared/starboard/media/BUILD.gn +++ b/starboard/shared/starboard/media/BUILD.gn
@@ -19,8 +19,16 @@ "//starboard/shared/starboard/media/avc_util.h", "//starboard/shared/starboard/media/codec_util.cc", "//starboard/shared/starboard/media/codec_util.h", + "//starboard/shared/starboard/media/key_system_supportability_cache.cc", + "//starboard/shared/starboard/media/key_system_supportability_cache.h", "//starboard/shared/starboard/media/media_util.cc", "//starboard/shared/starboard/media/media_util.h", + "//starboard/shared/starboard/media/mime_supportability_cache.cc", + "//starboard/shared/starboard/media/mime_supportability_cache.h", + "//starboard/shared/starboard/media/mime_util.cc", + "//starboard/shared/starboard/media/mime_util.h", + "//starboard/shared/starboard/media/parsed_mime_info.cc", + "//starboard/shared/starboard/media/parsed_mime_info.h", "//starboard/shared/starboard/media/video_capabilities.cc", "//starboard/shared/starboard/media/video_capabilities.h", "//starboard/shared/starboard/media/vp9_util.cc",
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.cc b/starboard/shared/starboard/media/key_system_supportability_cache.cc new file mode 100644 index 0000000..6ee6532 --- /dev/null +++ b/starboard/shared/starboard/media/key_system_supportability_cache.cc
@@ -0,0 +1,169 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/key_system_supportability_cache.h" + +#include <cstring> +#include <map> +#include <string> + +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/media.h" +#include "starboard/once.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +template <typename T> +class KeySystemSupportabilityContainer { + public: + Supportability GetKeySystemSupportability(T codec, const char* key_system) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + + ScopedLock scoped_lock(mutex_); + auto map_iter = key_system_supportabilities_.find(codec); + if (map_iter == key_system_supportabilities_.end()) { + return kSupportabilityUnknown; + } + KeySystemToSupportabilityMap& map = map_iter->second; + auto supportability_iter = map.find(std::string(key_system)); + if (supportability_iter == map.end()) { + return kSupportabilityUnknown; + } + return supportability_iter->second; + } + + void CacheKeySystemSupportability(T codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + ScopedLock scoped_lock(mutex_); + key_system_supportabilities_[codec][key_system] = supportability; + } + + void ClearContainer() { + ScopedLock scoped_lock(mutex_); + key_system_supportabilities_.clear(); + } + + private: + typedef std::map<std::string, Supportability> KeySystemToSupportabilityMap; + + Mutex mutex_; + std::map<T, KeySystemToSupportabilityMap> key_system_supportabilities_; +}; + +template <typename T> +SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityContainer<T>, GetContainer); + +} // namespace + +// static +SB_ONCE_INITIALIZE_FUNCTION(KeySystemSupportabilityCache, + KeySystemSupportabilityCache::GetInstance); + +Supportability KeySystemSupportabilityCache::GetKeySystemSupportability( + SbMediaAudioCodec codec, + const char* key_system) { + SB_DCHECK(key_system); + + // Empty key system is always supported. + if (strlen(key_system) == 0) { + return kSupportabilitySupported; + } + + if (!is_enabled_) { + return kSupportabilityUnknown; + } + + return GetContainer<SbMediaAudioCodec>()->GetKeySystemSupportability( + codec, key_system); +} + +Supportability KeySystemSupportabilityCache::GetKeySystemSupportability( + SbMediaVideoCodec codec, + const char* key_system) { + SB_DCHECK(key_system); + + // Empty key system is always supported. + if (strlen(key_system) == 0) { + return kSupportabilitySupported; + } + + if (!is_enabled_) { + return kSupportabilityUnknown; + } + + return GetContainer<SbMediaVideoCodec>()->GetKeySystemSupportability( + codec, key_system); +} + +void KeySystemSupportabilityCache::CacheKeySystemSupportability( + SbMediaAudioCodec codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (!is_enabled_) { + return; + } + + if (strlen(key_system) == 0) { + SB_LOG(WARNING) << "Rejected empty key system as it's always supported."; + } + + GetContainer<SbMediaAudioCodec>()->CacheKeySystemSupportability( + codec, key_system, supportability); +} + +void KeySystemSupportabilityCache::CacheKeySystemSupportability( + SbMediaVideoCodec codec, + const char* key_system, + Supportability supportability) { + SB_DCHECK(key_system); + SB_DCHECK(strlen(key_system) > 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (!is_enabled_) { + return; + } + + if (strlen(key_system) == 0) { + SB_LOG(WARNING) << "Rejected empty key system as it's always supported."; + return; + } + + GetContainer<SbMediaVideoCodec>()->CacheKeySystemSupportability( + codec, key_system, supportability); +} + +void KeySystemSupportabilityCache::ClearCache() { + GetContainer<SbMediaAudioCodec>()->ClearContainer(); + GetContainer<SbMediaVideoCodec>()->ClearContainer(); +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/key_system_supportability_cache.h b/starboard/shared/starboard/media/key_system_supportability_cache.h new file mode 100644 index 0000000..e9ad962 --- /dev/null +++ b/starboard/shared/starboard/media/key_system_supportability_cache.h
@@ -0,0 +1,77 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_ + +#include <atomic> + +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// KeySystemSupportabilityCache caches the supportabilities of the combinations +// of codec and key system. +// +// Note: anytime the platform key system capabilities have changed, please +// call KeySystemSupportabilityCache::ClearCache() to clear the outdated +// results. +// +// TODO: add unit tests for KeySystemSupportabilityCache. +class KeySystemSupportabilityCache { + public: + static KeySystemSupportabilityCache* GetInstance(); + + // When cache is not enabled, GetKeySystemSupportability() will always return + // kSupportabilityUnknown, and CacheKeySystemSupportability() will do nothing. + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + + // Get & cache key system supportability. + Supportability GetKeySystemSupportability(SbMediaAudioCodec codec, + const char* key_system); + Supportability GetKeySystemSupportability(SbMediaVideoCodec codec, + const char* key_system); + void CacheKeySystemSupportability(SbMediaAudioCodec codec, + const char* key_system, + Supportability supportability); + void CacheKeySystemSupportability(SbMediaVideoCodec codec, + const char* key_system, + Supportability supportability); + + // Clear all cached supportabilities. + void ClearCache(); + + private: + // Class can only be instanced via the singleton + KeySystemSupportabilityCache() {} + ~KeySystemSupportabilityCache() {} + + KeySystemSupportabilityCache(const KeySystemSupportabilityCache&) = delete; + KeySystemSupportabilityCache& operator=(const KeySystemSupportabilityCache&) = + delete; + + std::atomic_bool is_enabled_{false}; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_KEY_SYSTEM_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc index b3c5723..8e5bae0 100644 --- a/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc +++ b/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -12,12 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "starboard/media.h" - #include "starboard/common/log.h" -#include "starboard/common/string.h" -#include "starboard/shared/starboard/media/media_util.h" -#include "starboard/shared/starboard/media/mime_type.h" +#include "starboard/media.h" +#include "starboard/shared/starboard/media/mime_util.h" SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system) { @@ -31,11 +28,6 @@ return kSbMediaSupportTypeNotSupported; } - starboard::shared::starboard::media::MimeType mime_type(mime); - if (!mime_type.is_valid()) { - SB_DLOG(WARNING) << mime << " is not a valid mime type"; - return kSbMediaSupportTypeNotSupported; - } - - return CanPlayMimeAndKeySystem(mime_type, key_system); + return starboard::shared::starboard::media::CanPlayMimeAndKeySystem( + mime, key_system); }
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc index 6d2c701..e4689ca 100644 --- a/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc +++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (audio_codec == kSbMediaAudioCodecAac) { return bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc index fc72934..83dbd14 100644 --- a/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc +++ b/starboard/shared/starboard/media/media_is_audio_supported_aac_only.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { return audio_codec == kSbMediaAudioCodecAac && bitrate <= kSbMediaMaxAudioBitrateInBitsPerSecond;
diff --git a/starboard/shared/starboard/media/media_support_internal.h b/starboard/shared/starboard/media/media_support_internal.h index bf5fe78..6fcc832 100644 --- a/starboard/shared/starboard/media/media_support_internal.h +++ b/starboard/shared/starboard/media/media_support_internal.h
@@ -18,6 +18,7 @@ #include "starboard/configuration.h" #include "starboard/media.h" #include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_type.h" #ifdef __cplusplus extern "C" { @@ -31,9 +32,9 @@ // the platform to decode any supported input formats. // // |video_codec|: The |SbMediaVideoCodec| being checked for platform -// compatibility. +// compatibility. // |audio_codec|: The |SbMediaAudioCodec| being checked for platform -// compatibility. +// compatibility. // |key_system|: The key system being checked for platform compatibility. SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, @@ -46,9 +47,8 @@ // function returns |false|. // // |video_codec|: The video codec used in the media content. -// |content_type|: The full content type passed to the corresponding dom -// interface if there is any. Otherwise it will be set to "". -// It should never to set to NULL. +// |mime_type|: The parsed mime type passed to the corresponding interface. +// Note that |mime_type| can be NULL. // |profile|: The profile in the context of |video_codec|. It should be set to // -1 when it is unknown or not applicable. // |level|: The level in the context of |video_codec|. It should be set to -1 @@ -75,32 +75,33 @@ // it indicates that the fps shouldn't be considered. // |decode_to_texture_required|: Whether or not the resulting video frames can // be decoded and used as textures by the GPU. -bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, - int profile, - int level, - int bit_depth, - SbMediaPrimaryId primary_id, - SbMediaTransferId transfer_id, - SbMediaMatrixId matrix_id, - int frame_width, - int frame_height, - int64_t bitrate, - int fps, - bool decode_to_texture_required); +bool SbMediaIsVideoSupported( + SbMediaVideoCodec video_codec, + const starboard::shared::starboard::media::MimeType* mime_type, + int profile, + int level, + int bit_depth, + SbMediaPrimaryId primary_id, + SbMediaTransferId transfer_id, + SbMediaMatrixId matrix_id, + int frame_width, + int frame_height, + int64_t bitrate, + int fps, + bool decode_to_texture_required); // Indicates whether this platform supports |audio_codec| at |bitrate|. // If |audio_codec| is not supported under any condition, this function // returns |false|. // // |audio_codec|: The media's audio codec (|SbMediaAudioCodec|). -// |content_type|: The full content type passed to the corresponding dom -// interface if there is any. Otherwise it will be set to "". -// It should never to set to NULL. +// |mime_type|: The parsed mime type passed to the corresponding interface. +// Note that |mime_type| can be NULL. // |bitrate|: The media's bitrate. -bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, - int64_t bitrate); +bool SbMediaIsAudioSupported( + SbMediaAudioCodec audio_codec, + const starboard::shared::starboard::media::MimeType* mime_type, + int64_t bitrate); #ifdef __cplusplus } // extern "C"
diff --git a/starboard/shared/starboard/media/media_util.cc b/starboard/shared/starboard/media/media_util.cc index a5a135c..58bb7bf 100644 --- a/starboard/shared/starboard/media/media_util.cc +++ b/starboard/shared/starboard/media/media_util.cc
@@ -21,9 +21,7 @@ #include "starboard/common/media.h" #include "starboard/common/string.h" #include "starboard/log.h" -#include "starboard/memory.h" #include "starboard/shared/starboard/media/codec_util.h" -#include "starboard/shared/starboard/media/media_support_internal.h" #include "starboard/shared/starboard/media/mime_type.h" namespace starboard { @@ -36,165 +34,6 @@ const int64_t kDefaultBitRate = 0; const int64_t kDefaultAudioChannels = 2; -bool IsSupportedAudioCodec(const MimeType& mime_type, - const std::string& codec, - const char* key_system) { - SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str()); - if (audio_codec == kSbMediaAudioCodecNone) { - return false; - } - - // TODO: allow platform-specific rejection of a combination of codec & - // number of channels, by passing channels to SbMediaAudioIsSupported and / - // or SbMediaIsSupported. - - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(kSbMediaVideoCodecNone, audio_codec, key_system)) { - return false; - } - } - - int channels = mime_type.GetParamIntValue("channels", kDefaultAudioChannels); - if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm, channels)) { - return false; - } - - int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate); - - if (!SbMediaIsAudioSupported(audio_codec, - mime_type.raw_content_type().c_str(), bitrate)) { - return false; - } - - switch (audio_codec) { - case kSbMediaAudioCodecNone: - SB_NOTREACHED(); - return false; - case kSbMediaAudioCodecAac: - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecAc3: - if (!kSbHasAc3Audio) { - SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To " - << "enable it, set kSbHasAc3Audio to |true|."; - return false; - } - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecEac3: - if (!kSbHasAc3Audio) { - SB_NOTREACHED() << "AC3 audio is not enabled on this platform. To " - << "enable it, set kSbHasAc3Audio to |true|."; - return false; - } - return mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecOpus: - case kSbMediaAudioCodecVorbis: - return mime_type.subtype() == "webm"; -#if SB_API_VERSION >= 14 - case kSbMediaAudioCodecMp3: - return mime_type.subtype() == "mpeg" || mime_type.subtype() == "mp3" || - mime_type.subtype() == "mp4"; - case kSbMediaAudioCodecPcm: - return mime_type.subtype() == "wav" || mime_type.subtype() == "wave" || - mime_type.subtype() == "x-wav" || - mime_type.subtype() == "x-pn-wav"; - case kSbMediaAudioCodecFlac: - return mime_type.subtype() == "ogg"; -#endif // SB_API_VERSION >= 14 - } - - SB_NOTREACHED(); - return false; -} - -bool IsSupportedVideoCodec(const MimeType& mime_type, - const std::string& codec, - const char* key_system, - bool decode_to_texture_required) { - SbMediaVideoCodec video_codec; - int profile = -1; - int level = -1; - int bit_depth = 8; - SbMediaPrimaryId primary_id = kSbMediaPrimaryIdUnspecified; - SbMediaTransferId transfer_id = kSbMediaTransferIdUnspecified; - SbMediaMatrixId matrix_id = kSbMediaMatrixIdUnspecified; - - if (!ParseVideoCodec(codec.c_str(), &video_codec, &profile, &level, - &bit_depth, &primary_id, &transfer_id, &matrix_id)) { - return false; - } - SB_DCHECK(video_codec != kSbMediaVideoCodecNone); - - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(video_codec, kSbMediaAudioCodecNone, key_system)) { - return false; - } - } - - std::string eotf = mime_type.GetParamStringValue("eotf", ""); - if (!eotf.empty()) { - SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf); - // If the eotf is not known, reject immediately - without checking with - // the platform. - if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) { - return false; - } - if (transfer_id != kSbMediaTransferIdUnspecified && - transfer_id != transfer_id_from_eotf) { - SB_LOG_IF(WARNING, transfer_id != kSbMediaTransferIdUnspecified) - << "transfer_id " << transfer_id << " set by the codec string \"" - << codec << "\" will be overwritten by the eotf attribute " << eotf; - } - transfer_id = transfer_id_from_eotf; - } - - std::string cryptoblockformat = - mime_type.GetParamStringValue("cryptoblockformat", ""); - if (!cryptoblockformat.empty()) { - if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") { - return false; - } - } - - int width = mime_type.GetParamIntValue("width", 0); - int height = mime_type.GetParamIntValue("height", 0); - int fps = mime_type.GetParamIntValue("framerate", 0); - - int bitrate = mime_type.GetParamIntValue("bitrate", kDefaultBitRate); - - if (width < 0 || height < 0 || fps < 0 || bitrate < 0) { - return false; - } - - if (!SbMediaIsVideoSupported( - video_codec, mime_type.raw_content_type().c_str(), profile, level, - bit_depth, primary_id, transfer_id, matrix_id, width, height, bitrate, - fps, decode_to_texture_required)) { - return false; - } - - switch (video_codec) { - case kSbMediaVideoCodecNone: - SB_NOTREACHED(); - return false; - case kSbMediaVideoCodecH264: - case kSbMediaVideoCodecH265: - return mime_type.subtype() == "mp4"; - case kSbMediaVideoCodecMpeg2: - case kSbMediaVideoCodecTheora: - return false; // No associated container in YT. - case kSbMediaVideoCodecVc1: - case kSbMediaVideoCodecAv1: - return mime_type.subtype() == "mp4"; - case kSbMediaVideoCodecVp8: - return mime_type.subtype() == "webm"; - case kSbMediaVideoCodecVp9: - return mime_type.subtype() == "mp4" || mime_type.subtype() == "webm"; - } - - SB_NOTREACHED(); - return false; -} - } // namespace AudioSampleInfo::AudioSampleInfo() { @@ -254,24 +93,6 @@ return *this; } -bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { - int count = SbMediaGetAudioOutputCount(); - - for (int output_index = 0; output_index < count; ++output_index) { - SbMediaAudioConfiguration configuration; - if (!SbMediaGetAudioConfiguration(output_index, &configuration)) { - continue; - } - - if (configuration.coding_type == coding_type && - configuration.number_of_channels >= channels) { - return true; - } - } - - return false; -} - bool IsSDRVideo(int bit_depth, SbMediaPrimaryId primary_id, SbMediaTransferId transfer_id, @@ -346,17 +167,6 @@ return bit_depth == 8; } -SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) { - if (transfer_id == "bt709") { - return kSbMediaTransferIdBt709; - } else if (transfer_id == "smpte2084") { - return kSbMediaTransferIdSmpteSt2084; - } else if (transfer_id == "arib-std-b67") { - return kSbMediaTransferIdAribStdB67; - } - return kSbMediaTransferIdUnknown; -} - int GetBytesPerSample(SbMediaAudioSampleType sample_type) { switch (sample_type) { case kSbMediaAudioSampleTypeInt16Deprecated: @@ -369,84 +179,6 @@ return 4; } -SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type, - const char* key_system) { - SB_DCHECK(mime_type.is_valid()); - - if (mime_type.type() != "audio" && mime_type.type() != "video") { - return kSbMediaSupportTypeNotSupported; - } - - auto codecs = mime_type.GetCodecs(); - - // Pre-filter for |key_system|. - if (strlen(key_system) != 0) { - if (!SbMediaIsSupported(kSbMediaVideoCodecNone, kSbMediaAudioCodecNone, - key_system)) { - return kSbMediaSupportTypeNotSupported; - } - } - - bool decode_to_texture_required = false; - std::string decode_to_texture_value = - mime_type.GetParamStringValue("decode-to-texture", "false"); - if (decode_to_texture_value == "true") { - decode_to_texture_required = true; - } else if (decode_to_texture_value != "false") { - // If an invalid value (e.g. not "true" or "false") is passed in for - // decode-to-texture, trivially reject. - return kSbMediaSupportTypeNotSupported; - } - - if (codecs.size() == 0) { - // This happens when the H5 player is either querying for progressive - // playback support, or probing for generic mp4 support without specific - // codecs. We only support "audio/mp4" and "video/mp4" for these cases. - if ((mime_type.type() == "audio" || mime_type.type() == "video") && - mime_type.subtype() == "mp4") { - return kSbMediaSupportTypeMaybe; - } - return kSbMediaSupportTypeNotSupported; - } - - if (codecs.size() > 2) { - return kSbMediaSupportTypeNotSupported; - } - - bool has_audio_codec = false; - bool has_video_codec = false; - for (const auto& codec : codecs) { - if (IsSupportedAudioCodec(mime_type, codec, key_system)) { - if (has_audio_codec) { - // We don't support two audio codecs in one stream. - return kSbMediaSupportTypeNotSupported; - } - has_audio_codec = true; - continue; - } - if (IsSupportedVideoCodec(mime_type, codec, key_system, - decode_to_texture_required)) { - if (mime_type.type() != "video") { - // Video can only be contained in "video/*", while audio can be - // contained in both "audio/*" and "video/*". - return kSbMediaSupportTypeNotSupported; - } - if (has_video_codec) { - // We don't support two video codecs in one stream. - return kSbMediaSupportTypeNotSupported; - } - has_video_codec = true; - continue; - } - return kSbMediaSupportTypeNotSupported; - } - - if (has_audio_codec || has_video_codec) { - return kSbMediaSupportTypeProbably; - } - return kSbMediaSupportTypeNotSupported; -} - std::string GetStringRepresentation(const uint8_t* data, const int size) { std::string result;
diff --git a/starboard/shared/starboard/media/media_util.h b/starboard/shared/starboard/media/media_util.h index aef3cb8..8a97986 100644 --- a/starboard/shared/starboard/media/media_util.h +++ b/starboard/shared/starboard/media/media_util.h
@@ -21,7 +21,6 @@ #include "starboard/media.h" #include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/media/mime_type.h" namespace starboard { namespace shared { @@ -48,49 +47,14 @@ std::string max_video_capabilities_storage; }; -bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels); - bool IsSDRVideo(int bit_depth, SbMediaPrimaryId primary_id, SbMediaTransferId transfer_id, SbMediaMatrixId matrix_id); bool IsSDRVideo(const char* mime); -// Turns |eotf| into value of SbMediaTransferId. If |eotf| isn't recognized the -// function returns kSbMediaTransferIdReserved0. -// This function supports all eotfs required by YouTube TV HTML5 Technical -// Requirements (2018). -SbMediaTransferId GetTransferIdFromString(const std::string& eotf); - int GetBytesPerSample(SbMediaAudioSampleType sample_type); -// Calls to canPlayType() and isTypeSupported() are redirected to this function. -// Following are some example inputs: -// canPlayType(video/mp4) -// canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2") -// canPlayType(video/webm) -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000) -// isTypeSupported(audio/mp4; codecs="mp4a.40.2") -// isTypeSupported(audio/webm; codecs="vorbis") -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(video/webm; codecs="vp9") -// isTypeSupported(audio/webm; codecs="opus") -// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2) -// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false) -// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid) -SbMediaSupportType CanPlayMimeAndKeySystem(const MimeType& mime_type, - const char* key_system); - std::string GetStringRepresentation(const uint8_t* data, const int size); std::string GetMixedRepresentation(const uint8_t* data, const int size,
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.cc b/starboard/shared/starboard/media/mime_supportability_cache.cc new file mode 100644 index 0000000..d52761c --- /dev/null +++ b/starboard/shared/starboard/media/mime_supportability_cache.cc
@@ -0,0 +1,336 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/mime_supportability_cache.h" + +#include <cstring> +#include <queue> +#include <sstream> +#include <string> +#include <unordered_map> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/common/mutex.h" +#include "starboard/log.h" +#include "starboard/media.h" +#include "starboard/once.h" +#include "starboard/shared/starboard/media/mime_type.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +// RemoveAttributeFromMime() will return a new mime string with the specified +// attribute removed. If |attribute_string| is not null, the removed attribute +// string will be returned via |attribute_string|. Following are some examples: +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"" +// attribute_string: "bitrate=300000" +// +// mime: "video/webm; codecs=\"vp9\"; bitrate=300000; eotf=bt709" +// attribute_name: "bitrate" +// return: "video/webm; codecs=\"vp9\"; eotf=bt709" +// attribute_string: "bitrate=300000" +// +// mime: "bitrate=300000" +// attribute_name: "bitrate" +// return: "" +// attribute_string: "bitrate=300000" +std::string RemoveAttributeFromMime(const char* mime, + const char* attribute_name, + std::string* attribute_string) { + size_t name_length = strlen(attribute_name); + if (name_length == 0) { + return mime; + } + + std::string mime_without_attribute; + const char* start_pos = strstr(mime, attribute_name); + while (start_pos) { + if ((start_pos == mime || start_pos[-1] == ';' || isspace(start_pos[-1])) && + (start_pos[name_length] && + (start_pos[name_length] == '=' || isspace(start_pos[name_length])))) { + break; + } + start_pos += name_length; + start_pos = strstr(start_pos, attribute_name); + } + + if (!start_pos) { + // Target attribute is not found. + return std::string(mime); + } + const char* end_pos = strstr(start_pos, ";"); + if (end_pos) { + // There may be other attribute after target attribute. + if (attribute_string) { + // Returned |attribute_string| will not have a trailing ';'. + attribute_string->assign(start_pos, end_pos - start_pos); + } + + end_pos++; + // Remove leading spaces. + while (*end_pos && isspace(*end_pos)) { + end_pos++; + } + if (*end_pos) { + // Append the string after target attribute. + mime_without_attribute = std::string(mime, start_pos - mime); + mime_without_attribute.append(end_pos); + } else { + // Target attribute is the last one. Remove trailing spaces. + size_t mime_length = start_pos - mime; + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; + } + mime_without_attribute = std::string(mime, mime_length); + } + } else { + // It can't find a trailing ';'. The target attribute must be the last one. + size_t mime_length = start_pos - mime; + // Remove trailing spaces. + while (mime_length > 0 && (isspace(mime[mime_length - 1]))) { + mime_length--; + } + // Remove the trailing ';'. + if (mime_length > 0 && mime[mime_length - 1] == ';') { + mime_length--; + } + mime_without_attribute = std::string(mime, mime_length); + if (attribute_string) { + *attribute_string = std::string(start_pos); + } + } + return mime_without_attribute; +} + +// Note that if bitrate parsing failed, |bitrate| will be set to -1. +void StripAndParseBitrate(const char* mime, + std::string* mime_without_bitrate, + int* bitrate) { + SB_DCHECK(mime_without_bitrate); + SB_DCHECK(bitrate); + + std::string bitrate_string; + *mime_without_bitrate = + RemoveAttributeFromMime(mime, "bitrate", &bitrate_string); + + if (bitrate_string.empty()) { + *bitrate = 0; + return; + } + + MimeType::Param param; + if (!MimeType::ParseParamString(bitrate_string, ¶m) || + param.type != MimeType::kParamTypeInteger) { + *bitrate = -1; + return; + } + SB_DCHECK(param.name == "bitrate"); + *bitrate = param.int_value; +} + +} // namespace + +// static +SB_ONCE_INITIALIZE_FUNCTION(MimeSupportabilityCache, + MimeSupportabilityCache::GetInstance); + +Supportability MimeSupportabilityCache::GetMimeSupportability( + const char* mime, + ParsedMimeInfo* mime_info) { + SB_DCHECK(mime); + SB_DCHECK(mime_info); + + // Strip the bitrate from mime string and check it separately. + std::string mime_without_bitrate; + int bitrate; + StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate); + + if (bitrate < 0) { + // The mime string contains an invalid bitrate attribute. In that case, we + // return an invalid ParsedMimeInfo with kSbMediaSupportTypeNotSupported. + *mime_info = ParsedMimeInfo(mime); + return kSupportabilityNotSupported; + } + + ScopedLock scoped_lock(mutex_); + Entry& entry = GetEntry_Locked(mime_without_bitrate); + + // Return cached ParsedMimeInfo with real bitrate. + *mime_info = entry.mime_info; + mime_info->SetBitrate(bitrate); + + if (!mime_info->is_valid()) { + // Return kSupportabilityNotSupported if we can't get a valid + // ParsedMimeInfo. + return kSupportabilityNotSupported; + } + + return is_enabled_ ? IsBitrateSupported_Locked(entry, bitrate) + : kSupportabilityUnknown; +} + +void MimeSupportabilityCache::CacheMimeSupportability( + const char* mime, + Supportability supportability) { + SB_DCHECK(mime); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (!is_enabled_) { + return; + } + + // Strip bitrate as what we do in GetMimeSupportability(). + std::string mime_without_bitrate; + int bitrate; + StripAndParseBitrate(mime, &mime_without_bitrate, &bitrate); + + if (bitrate < 0) { + // The mime string contains an invalid bitrate attribute. + return; + } + + ScopedLock scoped_lock(mutex_); + Entry& entry = GetEntry_Locked(mime_without_bitrate); + + if (entry.mime_info.is_valid()) { + UpdateBitrateSupportability_Locked(&entry, bitrate, supportability); + } +} + +void MimeSupportabilityCache::ClearCachedMimeSupportabilities() { + ScopedLock scoped_lock(mutex_); + for (auto& iter : entries_) { + iter.second.max_supported_bitrate = -1; + iter.second.min_unsupported_bitrate = INT_MAX; + } +} + +void MimeSupportabilityCache::DumpCache() { + ScopedLock scoped_lock(mutex_); + std::stringstream ss; + ss << "\n========Dumping MimeSupportabilityCache========"; + for (const auto& entry_iter : entries_) { + const ParsedMimeInfo& mime_info = entry_iter.second.mime_info; + ss << "\nMime: " << entry_iter.first; + ss << "\n ParsedMimeInfo:"; + ss << "\n MimeType : " << mime_info.mime_type().ToString(); + if (mime_info.is_valid()) { + if (mime_info.has_audio_info()) { + const ParsedMimeInfo::AudioCodecInfo& audio_info = + mime_info.audio_info(); + ss << "\n Audio Codec : " + << GetMediaAudioCodecName(audio_info.codec); + ss << "\n Channels : " << audio_info.channels; + } + if (mime_info.has_video_info()) { + const ParsedMimeInfo::VideoCodecInfo& video_info = + mime_info.video_info(); + ss << "\n Video Codec : " + << GetMediaVideoCodecName(video_info.codec); + ss << "\n Profile : " << video_info.profile; + ss << "\n Level : " << video_info.level; + ss << "\n BitDepth : " << video_info.bit_depth; + ss << "\n PrimaryId : " + << GetMediaPrimaryIdName(video_info.primary_id); + ss << "\n TransferId : " + << GetMediaTransferIdName(video_info.transfer_id); + ss << "\n MatrixId : " << GetMediaMatrixIdName(video_info.matrix_id); + ss << "\n Width : " << video_info.frame_width; + ss << "\n Height : " << video_info.frame_height; + ss << "\n Fps : " << video_info.fps; + ss << "\n DecodeToTexture : " + << (video_info.decode_to_texture_required ? "true" : "false"); + } + } else { + ss << "\n Mime info is not valid"; + } + + ss << "\n MaxSupportedBitrate: " + << entry_iter.second.max_supported_bitrate; + ss << "\n MinUnsupportedBitrate: " + << entry_iter.second.min_unsupported_bitrate; + } + ss << "\n========End of Dumping========"; + + SB_DLOG(INFO) << ss.str(); +} + +MimeSupportabilityCache::Entry& MimeSupportabilityCache::GetEntry_Locked( + const std::string& mime_string) { + auto entry_iter = entries_.find(mime_string); + if (entry_iter != entries_.end()) { + return entry_iter->second; + } + + // We can't find anything from the cache. Parse mime string and cache + // parsed ParsedMimeInfo. + auto insert_result = entries_.insert({mime_string, Entry(mime_string)}); + + // Keep cached items not exceeding max size. + fifo_queue_.push(insert_result.first); + while (fifo_queue_.size() > max_size_) { + entries_.erase(fifo_queue_.front()); + fifo_queue_.pop(); + } + SB_DCHECK(entries_.size() == fifo_queue_.size()); + + return insert_result.first->second; +} + +Supportability MimeSupportabilityCache::IsBitrateSupported_Locked( + const Entry& entry, + int bitrate) const { + SB_DCHECK(bitrate >= 0); + + if (bitrate <= entry.max_supported_bitrate) { + return kSupportabilitySupported; + } + if (bitrate >= entry.min_unsupported_bitrate) { + return kSupportabilityNotSupported; + } + return kSupportabilityUnknown; +} + +void MimeSupportabilityCache::UpdateBitrateSupportability_Locked( + Entry* entry, + int bitrate, + Supportability supportability) { + SB_DCHECK(entry); + SB_DCHECK(bitrate >= 0); + SB_DCHECK(supportability != kSupportabilityUnknown); + + if (supportability == kSupportabilitySupported) { + SB_DCHECK(bitrate < entry->min_unsupported_bitrate); + if (bitrate > entry->max_supported_bitrate) { + entry->max_supported_bitrate = bitrate; + } + } else if (supportability == kSupportabilityNotSupported) { + SB_DCHECK(bitrate > entry->max_supported_bitrate); + if (bitrate < entry->min_unsupported_bitrate) { + entry->min_unsupported_bitrate = bitrate; + } + } +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_supportability_cache.h b/starboard/shared/starboard/media/mime_supportability_cache.h new file mode 100644 index 0000000..c1e560b --- /dev/null +++ b/starboard/shared/starboard/media/mime_supportability_cache.h
@@ -0,0 +1,128 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_ + +#include <atomic> +#include <queue> +#include <string> +#include <unordered_map> + +#include "starboard/common/mutex.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +typedef enum Supportability { + kSupportabilityUnknown, + kSupportabilitySupported, + kSupportabilityNotSupported, +} Supportability; + +// MimeSupportabilityCache caches the supportabilities of raw mime strings. +// To increase cache hit rate, it strips bitrate from the raw mime string, and +// stores a supported bitrate range for mime strings with same other attributes. +// +// Note: MimeSupportabilityCache leverage the assumption that if the +// platform can support a codec with bitrate of n, the codec should also support +// any bitrate less than n. If that assumption is not true, please do NOT enable +// MimeSupportabilityCache. +// +// Note: anytime the platform codec capabilities have changed, please call +// MimeSupportabilityCache::ClearCachedMimeSupportabilities() to clear the +// outdated results. +// +// TODO: add unit tests for MimeSupportabilityCache. +class MimeSupportabilityCache { + public: + static MimeSupportabilityCache* GetInstance(); + + // When cache is not enabled, GetMimeSupportability() will return cached + // ParsedMimeInfo with kSupportabilityUnknown, and CacheMimeSupportability() + // will do nothing. + bool IsEnabled() const { return is_enabled_; } + void SetCacheEnabled(bool enabled) { is_enabled_ = enabled; } + + // Set the max number of the cached ParsedMimeInfos and its supportabilities. + void SetCacheMaxSize(size_t size) { max_size_ = size; } + + // Get cached ParsedMimeInfo and mime supportability. If there's no cached + // ParsedMimeInfo, it will parse the mime string and cache the result. + // If we cannot get a valid ParsedMimeInfo from |mime|, + // GetMimeSupportability() will return kSupportabilityNotSupported with an + // invalid ParsedMimeInfo. Ideally, we should decouple mime parsing and + // supportability cache, but considering that the cache is only for internal + // use, to avoid repeated lookups, we do parsing in this function for now. + // Note that |mime| and |mime_info| cannot be null. + Supportability GetMimeSupportability(const char* mime, + ParsedMimeInfo* mime_info); + + // Update cached supportability of the mime string. + // Note that if |supportability| is kSupportabilityUnknown or we cannot + // get a valid ParsedMimeInfo from |mime|, CacheMimeSupportability() + // will not cache the supportability. + void CacheMimeSupportability(const char* mime, Supportability supportability); + + // Clear all cached supportabilities. But it will not remove cached + // ParsedMimeInfos, as for the same mime string, the parsed results should be + // always the same. + void ClearCachedMimeSupportabilities(); + + void DumpCache(); + + private: + const int kDefaultCacheMaxSize = 2000; + + struct Entry { + ParsedMimeInfo mime_info; + int max_supported_bitrate = -1; + int min_unsupported_bitrate = INT_MAX; + + explicit Entry(const std::string& mime) : mime_info(mime) {} + }; + + // Class can only be instanced via the singleton + MimeSupportabilityCache() {} + ~MimeSupportabilityCache() {} + + MimeSupportabilityCache(const MimeSupportabilityCache&) = delete; + MimeSupportabilityCache& operator=(const MimeSupportabilityCache&) = delete; + + Entry& GetEntry_Locked(const std::string& mime_string); + Supportability IsBitrateSupported_Locked(const Entry& entry, + int bitrate) const; + void UpdateBitrateSupportability_Locked(Entry* entry, + int bitrate, + Supportability supportability); + + typedef std::unordered_map<std::string, Entry> Entries; + + Mutex mutex_; + Entries entries_; + std::queue<Entries::iterator> fifo_queue_; + std::atomic_int max_size_{kDefaultCacheMaxSize}; + std::atomic_bool is_enabled_{false}; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_SUPPORTABILITY_CACHE_H_
diff --git a/starboard/shared/starboard/media/mime_type.cc b/starboard/shared/starboard/media/mime_type.cc index 035de69..4954cee 100644 --- a/starboard/shared/starboard/media/mime_type.cc +++ b/starboard/shared/starboard/media/mime_type.cc
@@ -18,6 +18,7 @@ #include <iosfwd> #include <locale> #include <numeric> +#include <sstream> #include <string> #include <vector> @@ -33,26 +34,44 @@ typedef std::vector<std::string> Strings; -MimeType::ParamType GetParamTypeByValue(const std::string& value) { +void ParseParamTypeAndValue(const std::string& name, + const std::string& value, + MimeType::Param* param) { + SB_DCHECK(param); + + param->name = name; + if (value.size() >= 2 && value[0] == '\"' && value.back() == '\"') { + param->type = MimeType::kParamTypeString; + param->string_value = value.substr(1, value.size() - 2); + return; + } + + param->string_value = value; + int count; int i; if (SbStringScanF(value.c_str(), "%d%n", &i, &count) == 1 && count == value.size()) { - return MimeType::kParamTypeInteger; + param->type = MimeType::kParamTypeInteger; + param->int_value = i; + return; } float f; std::stringstream buffer(value); buffer.imbue(std::locale::classic()); buffer >> f; if (!buffer.fail() && buffer.rdbuf()->in_avail() == 0) { - return MimeType::kParamTypeFloat; + param->type = MimeType::kParamTypeFloat; + param->float_value = f; + return; } - if (value == "true" || value == "false") { - return MimeType::kParamTypeBoolean; + param->type = MimeType::kParamTypeBoolean; + param->bool_value = value == "true"; + return; } - return MimeType::kParamTypeString; + param->type = MimeType::kParamTypeString; } bool ContainsSpace(const std::string& str) { @@ -61,7 +80,6 @@ return true; } } - return false; } @@ -94,28 +112,29 @@ return result; } -const char* ParamTypeToString(MimeType::ParamType param_type) { - switch (param_type) { - case MimeType::kParamTypeInteger: - return "Integer"; - case MimeType::kParamTypeFloat: - return "Float"; - case MimeType::kParamTypeString: - return "String"; - case MimeType::kParamTypeBoolean: - return "Boolean"; - default: - SB_NOTREACHED(); - return "Unknown"; - } -} - } // namespace const int MimeType::kInvalidParamIndex = -1; -MimeType::MimeType(const std::string& content_type) - : raw_content_type_(content_type), is_valid_(false) { +// static +bool MimeType::ParseParamString(const std::string& param_string, Param* param) { + std::vector<std::string> name_and_value = SplitAndTrim(param_string, '='); + // The parameter must be on the format 'name=value' and neither |name| nor + // |value| can be empty. |value| must also not contain '|' and ';'. + if (name_and_value.size() != 2 || name_and_value[0].empty() || + name_and_value[1].empty() || + name_and_value[1].find('|') != std::string::npos || + name_and_value[1].find(';') != std::string::npos) { + return false; + } + + if (param) { + ParseParamTypeAndValue(name_and_value[0], name_and_value[1], param); + } + return true; +} + +MimeType::MimeType(const std::string& content_type) { Strings components = SplitAndTrim(content_type, ';'); if (components.empty()) { @@ -135,38 +154,23 @@ } type_ = type_and_container[0]; subtype_ = type_and_container[1]; + components.erase(components.begin()); // 2. Verify the parameters have valid formats, we want to be strict here. - bool has_codecs = false; for (Strings::iterator iter = components.begin(); iter != components.end(); ++iter) { - std::vector<std::string> name_and_value = SplitAndTrim(*iter, '='); - // The parameter must be on the format 'name=value' and neither |name| nor - // |value| can be empty. |value| must also not contain '|'. - if (name_and_value.size() != 2 || name_and_value[0].empty() || - name_and_value[1].empty() || - name_and_value[1].find('|') != std::string::npos) { + Param param; + if (!ParseParamString(*iter, ¶m)) { return; } - Param param; - if (name_and_value[1].size() > 2 && name_and_value[1][0] == '\"' && - *name_and_value[1].rbegin() == '\"') { - param.type = kParamTypeString; - param.value = name_and_value[1].substr(1, name_and_value[1].size() - 2); - } else { - param.type = GetParamTypeByValue(name_and_value[1]); - param.value = name_and_value[1]; - } - param.name = name_and_value[0]; + // There can only be no more than one codecs parameter and it has to be + // the first parameter if it is present. if (param.name == "codecs") { - // There can only be no more than one codecs parameter and it has to be - // the first parameter if it is present. - if (!params_.empty() || has_codecs) { + if (!params_.empty()) { return; - } else { - has_codecs = true; } + codecs_ = SplitAndTrim(param.string_value, ','); } params_.push_back(param); } @@ -174,84 +178,65 @@ is_valid_ = true; } -const std::vector<std::string>& MimeType::GetCodecs() const { - if (!codecs_.empty()) { - return codecs_; - } - int codecs_index = GetParamIndexByName("codecs"); - if (codecs_index != 0) { - return codecs_; - } - codecs_ = SplitAndTrim(params_[0].value, ','); - return codecs_; -} - int MimeType::GetParamCount() const { - SB_DCHECK(is_valid()); - return static_cast<int>(params_.size()); } MimeType::ParamType MimeType::GetParamType(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); return params_[index].type; } const std::string& MimeType::GetParamName(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); return params_[index].name; } +int MimeType::GetParamIndexByName(const char* name) const { + for (size_t i = 0; i < params_.size(); ++i) { + if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { + return static_cast<int>(i); + } + } + return kInvalidParamIndex; +} + int MimeType::GetParamIntValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeInteger) { - return 0; + if (params_[index].type == kParamTypeInteger) { + return params_[index].int_value; } - - int i; - SbStringScanF(params_[index].value.c_str(), "%d", &i); - return i; + return 0; } float MimeType::GetParamFloatValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeInteger && - GetParamType(index) != kParamTypeFloat) { - return 0.0f; + if (params_[index].type == kParamTypeInteger) { + return params_[index].int_value; } - - float f; - std::stringstream buffer(params_[index].value.c_str()); - buffer.imbue(std::locale::classic()); - buffer >> f; - - return f; + if (params_[index].type == kParamTypeFloat) { + return params_[index].float_value; + } + return 0.0f; } const std::string& MimeType::GetParamStringValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - return params_[index].value; + return params_[index].string_value; } bool MimeType::GetParamBoolValue(int index) const { - SB_DCHECK(is_valid()); SB_DCHECK(index < GetParamCount()); - if (GetParamType(index) != kParamTypeBoolean) { - return false; + if (params_[index].type == kParamTypeBoolean) { + return params_[index].bool_value; } - - return params_[index].value == "true"; + return false; } int MimeType::GetParamIntValue(const char* name, int default_value) const { @@ -289,23 +274,44 @@ return default_value; } -bool MimeType::RegisterBoolParameter(const char* name) { - return RegisterParameter(name, kParamTypeBoolean); -} - -bool MimeType::RegisterStringParameter(const char* name, - const std::string& pattern /* = "" */) { - if (!RegisterParameter(name, kParamTypeString)) { +bool MimeType::ValidateIntParameter(const char* name) const { + if (!is_valid()) { return false; } - int param_index = GetParamIndexByName(name); - if (param_index == kInvalidParamIndex || pattern.empty()) { + int index = GetParamIndexByName(name); + if (index == kInvalidParamIndex) { + return true; + } + return GetParamType(index) == kParamTypeInteger; +} + +bool MimeType::ValidateFloatParameter(const char* name) const { + if (!is_valid()) { + return false; + } + + int index = GetParamIndexByName(name); + if (index == kInvalidParamIndex) { + return true; + } + ParamType type = GetParamType(index); + return type == kParamTypeInteger || type == kParamTypeFloat; +} + +bool MimeType::ValidateStringParameter(const char* name, + const std::string& pattern) const { + if (!is_valid()) { + return false; + } + + int index = GetParamIndexByName(name); + if (pattern.empty() || index == kInvalidParamIndex) { return true; } - // Compare the parameter value with the provided pattern. - const std::string& param_value = GetParamStringValue(param_index); + const std::string& param_value = params_[index].string_value; + bool matches = false; size_t match_start = 0; while (!matches) { @@ -324,27 +330,10 @@ (match_end >= pattern.length() || pattern[match_end] == '|'); match_start = match_end + 1; } - - if (matches) { - return true; - } - - SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value - << "' does not match the supplied pattern: '" << pattern << "'"; - is_valid_ = false; - return false; + return matches; } -int MimeType::GetParamIndexByName(const char* name) const { - for (size_t i = 0; i < params_.size(); ++i) { - if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { - return static_cast<int>(i); - } - } - return kInvalidParamIndex; -} - -bool MimeType::RegisterParameter(const char* name, ParamType param_type) { +bool MimeType::ValidateBoolParameter(const char* name) const { if (!is_valid()) { return false; } @@ -353,25 +342,56 @@ if (index == kInvalidParamIndex) { return true; } + ParamType type = GetParamType(index); + return type == kParamTypeBoolean; +} - const std::string& param_value = GetParamStringValue(index); - ParamType parsed_type = GetParamType(index); - - // Check that the parameter can be returned as the requested type. - // Allowed conversions: - // Any Type -> String, Int -> Float - bool convertible = - param_type == parsed_type || param_type == kParamTypeString || - (param_type == kParamTypeFloat && parsed_type == kParamTypeInteger); - if (!convertible) { - SB_LOG(INFO) << "Extended Parameter '" << name << "=" << param_value - << "' can't be converted to " << ParamTypeToString(param_type); - is_valid_ = false; - return false; +std::string MimeType::ToString() const { + if (!is_valid()) { + return "{ InvalidMimeType }; "; } - - // All validations succeeded. - return true; + std::stringstream ss; + ss << "{ type: " << type(); + ss << ", subtype: " << subtype(); + ss << ", codecs: "; + if (codecs_.empty()) { + ss << "null"; + } else { + ss << codecs_[0]; + for (size_t i = 1; i < codecs_.size(); i++) { + ss << "|" << codecs_[i]; + } + } + ss << ", params: "; + if (params_.empty()) { + ss << "null"; + } else { + ss << "{ "; + for (size_t i = 0; i < params_.size(); i++) { + const Param& param = params_[i]; + if (i != 0) { + ss << ","; + } + ss << param.name << "="; + switch (param.type) { + case kParamTypeInteger: + ss << "(int)" << param.int_value; + break; + case kParamTypeFloat: + ss << "(float)" << param.float_value; + break; + case kParamTypeString: + ss << "(string)" << param.string_value; + break; + case kParamTypeBoolean: + ss << "(bool)" << (param.bool_value ? "true" : "false"); + break; + } + } + ss << " }"; + } + ss << " }"; + return ss.str(); } } // namespace media
diff --git a/starboard/shared/starboard/media/mime_type.h b/starboard/shared/starboard/media/mime_type.h index a05a301..5390ed0 100644 --- a/starboard/shared/starboard/media/mime_type.h +++ b/starboard/shared/starboard/media/mime_type.h
@@ -53,21 +53,36 @@ kParamTypeBoolean, }; + struct Param { + ParamType type; + std::string name; + std::string string_value; + union { + int int_value; + float float_value; + bool bool_value; + }; + }; + static const int kInvalidParamIndex; + // Expose the function as a helper function to parse a mime attribute. + static bool ParseParamString(const std::string& param_string, Param* param); + explicit MimeType(const std::string& content_type); - const std::string& raw_content_type() const { return raw_content_type_; } bool is_valid() const { return is_valid_; } const std::string& type() const { return type_; } const std::string& subtype() const { return subtype_; } - - const std::vector<std::string>& GetCodecs() const; + const std::vector<std::string>& GetCodecs() const { return codecs_; } int GetParamCount() const; ParamType GetParamType(int index) const; const std::string& GetParamName(int index) const; + // GetParamIndexByName() will return |kInvalidParamIndex| if the param name is + // not found. + int GetParamIndexByName(const char* name) const; int GetParamIntValue(int index) const; float GetParamFloatValue(int index) const; @@ -81,42 +96,29 @@ const std::string& default_value) const; bool GetParamBoolValue(const char* name, bool default_value) const; - // Pre-register a mime parameter of type boolean. - // Returns true if the mime type is valid and the value passes validation. - // If the parameter validation fails this MimeType will be marked invalid. - // NOTE: The function returns true for missing parameters. - bool RegisterBoolParameter(const char* name); - - // Pre-register a mime parameter of type string. - // Returns true if the mime type is valid and the value passes validation. + // Validate functions will return true if the param contains a valid value or + // if param name is not found. + bool ValidateIntParameter(const char* name) const; + bool ValidateFloatParameter(const char* name) const; // Allows passing a pattern on the format "value_1|...|value_n" // where the parameter value must match one of the values in the pattern in // order to be considered valid. - // If the parameter validation fails this MimeType will be marked invalid. - // NOTE: The function returns true for missing parameters. - bool RegisterStringParameter(const char* name, - const std::string& pattern = ""); + bool ValidateStringParameter(const char* name, + const std::string& pattern = "") const; + bool ValidateBoolParameter(const char* name) const; + + std::string ToString() const; private: - struct Param { - ParamType type; - std::string name; - std::string value; - }; - // Use std::vector as the number of components are usually small and we'd like // to keep the order of components. typedef std::vector<Param> Params; - int GetParamIndexByName(const char* name) const; - bool RegisterParameter(const char* name, ParamType type); - - const std::string raw_content_type_; - bool is_valid_; + bool is_valid_ = false; std::string type_; std::string subtype_; + std::vector<std::string> codecs_; Params params_; - mutable std::vector<std::string> codecs_; }; } // namespace media
diff --git a/starboard/shared/starboard/media/mime_type_test.cc b/starboard/shared/starboard/media/mime_type_test.cc index 854444d..52705f3 100644 --- a/starboard/shared/starboard/media/mime_type_test.cc +++ b/starboard/shared/starboard/media/mime_type_test.cc
@@ -22,19 +22,6 @@ namespace media { namespace { -TEST(MimeTypeTest, RawContentType) { - { - const char kContentTypeWithSpace[] = " video/mp4; name0=123; name1=123.4 "; - MimeType mime_type(kContentTypeWithSpace); - EXPECT_EQ(mime_type.raw_content_type(), kContentTypeWithSpace); - } - { - const char kInvalidContentType[] = "video /mp4"; - MimeType mime_type(kInvalidContentType); - EXPECT_EQ(mime_type.raw_content_type(), kInvalidContentType); - } -} - TEST(MimeTypeTest, EmptyString) { MimeType mime_type(""); EXPECT_FALSE(mime_type.is_valid()); @@ -343,69 +330,116 @@ EXPECT_FALSE(mime_type.GetParamBoolValue("float", false)); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithPatterns) { +TEST(MimeTypeTest, ParseInvalidParamString) { + EXPECT_FALSE(MimeType::ParseParamString("", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("invalid", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=0;", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=a|b", nullptr)); + EXPECT_FALSE(MimeType::ParseParamString("val=", nullptr)); +} + +TEST(MimeTypeTest, ParseValidParamString) { + MimeType::Param result; + + EXPECT_TRUE(MimeType::ParseParamString("val=0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, 0); + + EXPECT_TRUE(MimeType::ParseParamString("val=1", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, 1); + + EXPECT_TRUE(MimeType::ParseParamString("val=-1", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeInteger); + EXPECT_EQ(result.int_value, -1); + + EXPECT_TRUE(MimeType::ParseParamString("val=0.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, 0.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=1.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, 1.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=-1.0", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeFloat); + EXPECT_EQ(result.float_value, -1.0f); + + EXPECT_TRUE(MimeType::ParseParamString("val=true", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeBoolean); + EXPECT_EQ(result.bool_value, true); + + EXPECT_TRUE(MimeType::ParseParamString("val=false", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeBoolean); + EXPECT_EQ(result.bool_value, false); + + EXPECT_TRUE(MimeType::ParseParamString("val=\"\"", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, ""); + + EXPECT_TRUE(MimeType::ParseParamString("val=\"abc\"", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, "abc"); + + EXPECT_TRUE(MimeType::ParseParamString("val=abc", &result)); + EXPECT_EQ(result.name, "val"); + EXPECT_EQ(result.type, MimeType::kParamTypeString); + EXPECT_EQ(result.string_value, "abc"); +} + +TEST(MimeTypeTest, ValidateParamsWithPatterns) { MimeType mime_type("video/mp4; string=yes"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "yes|no")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|yes|no")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "no|no|yes")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "noyes|yes")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "yes|no")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|yes|no")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "no|no|yes")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "noyes|yes")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no")); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithShortPatterns) { +TEST(MimeTypeTest, ValidateParamsWithShortPatterns) { MimeType mime_type("video/mp4; string=y"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "y|n")); - EXPECT_TRUE(mime_type.RegisterStringParameter("string", "n|y")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "y|n")); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "n|y")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "n")); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithPartialMatches) { - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "yesno|no")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "noyes|no")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|yesno")); - EXPECT_FALSE(mime_type.is_valid()); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "no|noyes")); - EXPECT_FALSE(mime_type.is_valid()); - } +TEST(MimeTypeTest, ValidateParamsWithPartialMatches) { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "yesno|no")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "noyes|no")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|yesno")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "no|noyes")); } -TEST(MimeTypeTest, MissingParamReturnsTrueOnRegistration) { +TEST(MimeTypeTest, ValidateMissingParam) { MimeType mime_type("video/mp4"); - EXPECT_TRUE(mime_type.RegisterStringParameter("string")); - EXPECT_TRUE(mime_type.is_valid()); + EXPECT_TRUE(mime_type.ValidateStringParameter("string")); EXPECT_EQ(mime_type.GetParamStringValue("string", "default"), "default"); } -TEST(MimeTypeTest, RegisterAndValidateParamsWithEmptyishPattern) { - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "|")); - } - { - MimeType mime_type("video/mp4; string=yes"); - EXPECT_FALSE(mime_type.RegisterStringParameter("string", "||")); - } +TEST(MimeTypeTest, ValidateParamsWithEmptyishPattern) { + MimeType mime_type("video/mp4; string=yes"); + EXPECT_TRUE(mime_type.ValidateStringParameter("string", "")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "|")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string", "||")); } -TEST(MimeTypeTest, CannotRegisterParamWithInvalidMimeType) { +TEST(MimeTypeTest, ValidateParamWithInvalidMimeType) { MimeType mime_type("video/mp4; string="); ASSERT_FALSE(mime_type.is_valid()); - EXPECT_FALSE(mime_type.RegisterStringParameter("string")); + EXPECT_FALSE(mime_type.ValidateStringParameter("string")); } } // namespace
diff --git a/starboard/shared/starboard/media/mime_util.cc b/starboard/shared/starboard/media/mime_util.cc new file mode 100644 index 0000000..6c53aa7 --- /dev/null +++ b/starboard/shared/starboard/media/mime_util.cc
@@ -0,0 +1,289 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/mime_util.h" + +#include <cstring> +#include <string> +#include <vector> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/log.h" +#include "starboard/shared/starboard/media/key_system_supportability_cache.h" +#include "starboard/shared/starboard/media/media_support_internal.h" +#include "starboard/shared/starboard/media/mime_supportability_cache.h" +#include "starboard/shared/starboard/media/mime_type.h" +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +// Use SbMediaGetAudioConfiguration() to check if the platform can support +// |channels|. +bool IsAudioOutputSupported(SbMediaAudioCodingType coding_type, int channels) { + int count = SbMediaGetAudioOutputCount(); + + for (int output_index = 0; output_index < count; ++output_index) { + SbMediaAudioConfiguration configuration; + if (!SbMediaGetAudioConfiguration(output_index, &configuration)) { + continue; + } + + if (configuration.coding_type == coding_type && + configuration.number_of_channels >= channels) { + return true; + } + } + + return false; +} + +bool IsSupportedKeySystem(SbMediaAudioCodec codec, const char* key_system) { + SB_DCHECK(key_system); + // KeySystemSupportabilityCache() should always return supported for empty + // |key_system|, so here it should always be non empty. + SB_DCHECK(strlen(key_system) > 0); + + return SbMediaIsSupported(kSbMediaVideoCodecNone, codec, key_system); +} + +bool IsSupportedKeySystem(SbMediaVideoCodec codec, const char* key_system) { + SB_DCHECK(key_system); + // KeySystemSupportabilityCache() should always return supported for empty + // |key_system|, so here it should always be non empty. + SB_DCHECK(strlen(key_system) > 0); + + return SbMediaIsSupported(codec, kSbMediaAudioCodecNone, key_system); +} + +bool IsSupportedAudioCodec(const ParsedMimeInfo& mime_info) { + SB_DCHECK(mime_info.is_valid()); + SB_DCHECK(mime_info.mime_type().is_valid()); + SB_DCHECK(mime_info.has_audio_info()); + + const MimeType& mime_type = mime_info.mime_type(); + const ParsedMimeInfo::AudioCodecInfo& audio_info = mime_info.audio_info(); + + switch (audio_info.codec) { + case kSbMediaAudioCodecNone: + SB_NOTREACHED(); + return false; + case kSbMediaAudioCodecAac: + case kSbMediaAudioCodecAc3: + case kSbMediaAudioCodecEac3: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaAudioCodecOpus: + case kSbMediaAudioCodecVorbis: + if (mime_type.subtype() != "webm") { + return false; + } + break; +#if SB_API_VERSION >= 14 + case kSbMediaAudioCodecMp3: + case kSbMediaAudioCodecFlac: + case kSbMediaAudioCodecPcm: + return false; +#endif // SB_API_VERSION >= 14 + } + + if (!IsAudioOutputSupported(kSbMediaAudioCodingTypePcm, + audio_info.channels)) { + return false; + } + + return SbMediaIsAudioSupported(audio_info.codec, &mime_type, + audio_info.bitrate); +} + +bool IsSupportedVideoCodec(const ParsedMimeInfo& mime_info) { + SB_DCHECK(mime_info.is_valid()); + SB_DCHECK(mime_info.mime_type().is_valid()); + SB_DCHECK(mime_info.has_video_info()); + + const MimeType& mime_type = mime_info.mime_type(); + const ParsedMimeInfo::VideoCodecInfo& video_info = mime_info.video_info(); + + switch (video_info.codec) { + case kSbMediaVideoCodecNone: + SB_NOTREACHED(); + return false; + case kSbMediaVideoCodecH264: + case kSbMediaVideoCodecH265: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaVideoCodecMpeg2: + case kSbMediaVideoCodecTheora: + return false; // No associated container in YT. + case kSbMediaVideoCodecVc1: + case kSbMediaVideoCodecAv1: + if (mime_type.subtype() != "mp4") { + return false; + } + break; + case kSbMediaVideoCodecVp8: + if (mime_type.subtype() != "webm") { + return false; + } + break; + case kSbMediaVideoCodecVp9: + if (mime_type.subtype() != "mp4" && mime_type.subtype() != "webm") { + return false; + } + break; + } + + std::string cryptoblockformat = + mime_type.GetParamStringValue("cryptoblockformat", ""); + if (!cryptoblockformat.empty()) { + if (mime_type.subtype() != "webm" || cryptoblockformat != "subsample") { + return false; + } + } + + return SbMediaIsVideoSupported( + video_info.codec, &mime_type, video_info.profile, video_info.level, + video_info.bit_depth, video_info.primary_id, video_info.transfer_id, + video_info.matrix_id, video_info.frame_width, video_info.frame_height, + video_info.bitrate, video_info.fps, + video_info.decode_to_texture_required); +} + +} // namespace + +SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime, + const char* key_system) { + SB_DCHECK(mime); + SB_DCHECK(key_system); + + // Get cached ParsedMimeInfo with its supportability. If it is not found in + // the cache, MimeSupportabilityCache would parse the mime string and return + // the ParsedMimeInfo with kSupportabilityUnknown. + ParsedMimeInfo mime_info; + Supportability mime_supportability = + MimeSupportabilityCache::GetInstance()->GetMimeSupportability(mime, + &mime_info); + + if (mime_info.disable_cache()) { + // Disable all caches if required. + mime_supportability = kSupportabilityUnknown; + MimeSupportabilityCache::GetInstance()->SetCacheEnabled(false); + KeySystemSupportabilityCache::GetInstance()->SetCacheEnabled(false); + } + + // Reject mime if cached result is not supported. + if (mime_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + + // MimeSupportabilityCache::GetMimeSupportability() returns + // kSupportabilityNotSupported if ParsedMimeInfo is not valid, so |mime_info| + // must be valid here. + SB_DCHECK(mime_info.is_valid()); + + const MimeType& mime_type = mime_info.mime_type(); + const std::vector<std::string>& codecs = mime_type.GetCodecs(); + + // Quick check for mp4 format. + if (codecs.size() == 0) { + // This happens when the H5 player is either querying for progressive + // playback support, or probing for generic mp4 support without specific + // codecs. + if (mime_type.subtype() == "mp4") { + return kSbMediaSupportTypeMaybe; + } else { + return kSbMediaSupportTypeNotSupported; + } + } + + // Reject mime if it doesn't have any valid codec info. + if (!mime_info.has_audio_info() && !mime_info.has_video_info()) { + return kSbMediaSupportTypeNotSupported; + } + + // Get cached key system supportability. Note that we check if audio or video + // codec supports key system separately. + if (mime_info.has_audio_info()) { + Supportability key_system_supportability = + KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability( + mime_info.audio_info().codec, key_system); + if (key_system_supportability == kSupportabilityUnknown) { + key_system_supportability = + IsSupportedKeySystem(mime_info.audio_info().codec, key_system) + ? kSupportabilitySupported + : kSupportabilityNotSupported; + KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability( + mime_info.audio_info().codec, key_system, key_system_supportability); + } + // Reject mime if audio codec doesn't support the key system. + if (key_system_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + } + if (mime_info.has_video_info()) { + Supportability key_system_supportability = + KeySystemSupportabilityCache::GetInstance()->GetKeySystemSupportability( + mime_info.video_info().codec, key_system); + if (key_system_supportability == kSupportabilityUnknown) { + key_system_supportability = + IsSupportedKeySystem(mime_info.video_info().codec, key_system) + ? kSupportabilitySupported + : kSupportabilityNotSupported; + KeySystemSupportabilityCache::GetInstance()->CacheKeySystemSupportability( + mime_info.video_info().codec, key_system, key_system_supportability); + } + // Reject mime if video codec doesn't the key system. + if (key_system_supportability == kSupportabilityNotSupported) { + return kSbMediaSupportTypeNotSupported; + } + } + + // At this point, |key_system| is supported. Return supported here if + // mime is also supported. Otherwise, |mime_supportability| must be unknown. + if (mime_supportability == kSupportabilitySupported) { + return kSbMediaSupportTypeProbably; + } + SB_DCHECK(mime_supportability == kSupportabilityUnknown); + + // Call platform functions to check if it's supported. + if (mime_info.has_audio_info() && !IsSupportedAudioCodec(mime_info)) { + mime_supportability = kSupportabilityNotSupported; + } else if (mime_info.has_video_info() && !IsSupportedVideoCodec(mime_info)) { + mime_supportability = kSupportabilityNotSupported; + } else { + mime_supportability = kSupportabilitySupported; + } + + // Cache mime supportability. + MimeSupportabilityCache::GetInstance()->CacheMimeSupportability( + mime, mime_supportability); + + return mime_supportability == kSupportabilitySupported + ? kSbMediaSupportTypeProbably + : kSbMediaSupportTypeNotSupported; +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/mime_util.h b/starboard/shared/starboard/media/mime_util.h new file mode 100644 index 0000000..e79dac1 --- /dev/null +++ b/starboard/shared/starboard/media/mime_util.h
@@ -0,0 +1,60 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_ + +#include <string> + +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// Calls to canPlayType() and isTypeSupported() are redirected to this function. +// Following are some example inputs: +// canPlayType(video/mp4) +// canPlayType(video/mp4; codecs="avc1.42001E, mp4a.40.2") +// canPlayType(video/webm) +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=640) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; width=99999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=360) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; height=99999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=30) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; framerate=9999) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=300000) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; bitrate=2000000000) +// isTypeSupported(audio/mp4; codecs="mp4a.40.2") +// isTypeSupported(audio/webm; codecs="vorbis") +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(video/webm; codecs="vp9") +// isTypeSupported(audio/webm; codecs="opus") +// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=2) +// isTypeSupported(audio/mp4; codecs="mp4a.40.2"; channels=99) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=true) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=false) +// isTypeSupported(video/mp4; codecs="avc1.4d401e"; decode-to-texture=invalid) +SbMediaSupportType CanPlayMimeAndKeySystem(const char* mime, + const char* key_system); + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_UTIL_H_
diff --git a/starboard/shared/starboard/media/parsed_mime_info.cc b/starboard/shared/starboard/media/parsed_mime_info.cc new file mode 100644 index 0000000..8894289 --- /dev/null +++ b/starboard/shared/starboard/media/parsed_mime_info.cc
@@ -0,0 +1,178 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/parsed_mime_info.h" + +#include <string> + +#include "starboard/common/log.h" +#include "starboard/common/media.h" +#include "starboard/shared/starboard/media/codec_util.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +const int64_t kDefaultAudioChannels = 2; + +// Turns |eotf| into value of SbMediaTransferId. If |eotf| isn't recognized the +// function returns kSbMediaTransferIdUnknown. +// This function supports all eotfs required by YouTube TV HTML5 Technical +// Requirements. +SbMediaTransferId GetTransferIdFromString(const std::string& transfer_id) { + if (transfer_id == "bt709") { + return kSbMediaTransferIdBt709; + } else if (transfer_id == "smpte2084") { + return kSbMediaTransferIdSmpteSt2084; + } else if (transfer_id == "arib-std-b67") { + return kSbMediaTransferIdAribStdB67; + } + return kSbMediaTransferIdUnknown; +} + +} // namespace + +ParsedMimeInfo::ParsedMimeInfo(const std::string& mime_string) + : mime_type_(mime_string) { + ParseMimeInfo(); +} + +void ParsedMimeInfo::SetBitrate(int bitrate) { + audio_info_.bitrate = bitrate; + video_info_.bitrate = bitrate; +} + +void ParsedMimeInfo::ParseMimeInfo() { + if (!mime_type_.is_valid()) { + is_valid_ = false; + return; + } + + // Read "disablecache". + if (!mime_type_.ValidateBoolParameter("disablecache")) { + is_valid_ = false; + return; + } + disable_cache_ = mime_type_.GetParamBoolValue("disablecache", false); + + // We only support audio or video type. + if (mime_type_.type() != "audio" && mime_type_.type() != "video") { + is_valid_ = false; + return; + } + + auto codecs = mime_type_.GetCodecs(); + // We only support up to one audio codec and one video codec. + if (codecs.size() > 2) { + is_valid_ = false; + return; + } + + for (const auto& codec : codecs) { + if (!has_audio_info() && ParseAudioInfo(codec)) { + continue; + } + if (!has_video_info() && ParseVideoInfo(codec)) { + continue; + } + // It either has an invalid codec or has two codecs of same type. + ResetCodecInfos(); + is_valid_ = false; + return; + } +} + +bool ParsedMimeInfo::ParseAudioInfo(const std::string& codec) { + SB_DCHECK(mime_type_.is_valid()); + SB_DCHECK(!has_audio_info()); + + SbMediaAudioCodec audio_codec = GetAudioCodecFromString(codec.c_str()); + if (audio_codec == kSbMediaAudioCodecNone) { + return false; + } + if (!mime_type_.ValidateIntParameter("channels") || + !mime_type_.ValidateIntParameter("bitrate")) { + return false; + } + audio_info_.codec = audio_codec; + audio_info_.channels = + mime_type_.GetParamIntValue("channels", kDefaultAudioChannels); + audio_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0); + + return audio_info_.channels >= 0 && audio_info_.bitrate >= 0; +} + +bool ParsedMimeInfo::ParseVideoInfo(const std::string& codec) { + SB_DCHECK(mime_type_.is_valid()); + SB_DCHECK(!has_video_info()); + + if (!ParseVideoCodec(codec.c_str(), &video_info_.codec, &video_info_.profile, + &video_info_.level, &video_info_.bit_depth, + &video_info_.primary_id, &video_info_.transfer_id, + &video_info_.matrix_id)) { + return false; + } + + if (video_info_.codec == kSbMediaVideoCodecNone) { + return false; + } + + std::string eotf = mime_type_.GetParamStringValue("eotf", ""); + if (!eotf.empty()) { + SbMediaTransferId transfer_id_from_eotf = GetTransferIdFromString(eotf); + if (transfer_id_from_eotf == kSbMediaTransferIdUnknown) { + // The eotf is an unknown value, mark the codec info as invalid. + SB_LOG(WARNING) << "Unknown eotf " << eotf << "."; + return false; + } + SB_LOG_IF(WARNING, + video_info_.transfer_id != kSbMediaTransferIdUnspecified && + video_info_.transfer_id != transfer_id_from_eotf) + << "transfer_id " << video_info_.transfer_id + << " set by the codec string \"" << video_info_.codec + << "\" will be overwritten by the eotf attribute " << eotf; + video_info_.transfer_id = transfer_id_from_eotf; + } + + if (!mime_type_.ValidateIntParameter("width") || + !mime_type_.ValidateIntParameter("height") || + !mime_type_.ValidateIntParameter("framerate") || + !mime_type_.ValidateIntParameter("bitrate") || + !mime_type_.ValidateBoolParameter("decode-to-texture")) { + return false; + } + + video_info_.frame_width = mime_type_.GetParamIntValue("width", 0); + video_info_.frame_height = mime_type_.GetParamIntValue("height", 0); + video_info_.fps = mime_type_.GetParamIntValue("framerate", 0); + video_info_.bitrate = mime_type_.GetParamIntValue("bitrate", 0); + video_info_.decode_to_texture_required = + mime_type_.GetParamBoolValue("decode-to-texture", false); + + return video_info_.frame_width >= 0 && video_info_.frame_height >= 0 && + video_info_.fps >= 0 && video_info_.bitrate >= 0; +} + +void ParsedMimeInfo::ResetCodecInfos() { + audio_info_.codec = kSbMediaAudioCodecNone; + video_info_.codec = kSbMediaVideoCodecNone; +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/starboard/shared/starboard/media/parsed_mime_info.h b/starboard/shared/starboard/media/parsed_mime_info.h new file mode 100644 index 0000000..5be03a5 --- /dev/null +++ b/starboard/shared/starboard/media/parsed_mime_info.h
@@ -0,0 +1,106 @@ +// Copyright 2022 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_ + +#include <string> + +#include "starboard/common/log.h" +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/media/mime_type.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// TODO: add unit tests for ParsedMimeInfo +class ParsedMimeInfo { + public: + struct AudioCodecInfo { + SbMediaAudioCodec codec = kSbMediaAudioCodecNone; + int channels; + int bitrate; + }; + + struct VideoCodecInfo { + SbMediaVideoCodec codec = kSbMediaVideoCodecNone; + int profile; + int level; + int bit_depth; + SbMediaPrimaryId primary_id; + SbMediaTransferId transfer_id; + SbMediaMatrixId matrix_id; + int frame_width; + int frame_height; + int fps; + int bitrate; + bool decode_to_texture_required; + }; + + ParsedMimeInfo() : mime_type_("") {} + explicit ParsedMimeInfo(const std::string& mime_string); + + const MimeType& mime_type() const { return mime_type_; } + + bool is_valid() const { return is_valid_; } + + // A switch in the mime string to disable caches. + bool disable_cache() const { return disable_cache_; } + + bool has_audio_info() const { + return audio_info_.codec != kSbMediaAudioCodecNone; + } + // Extra information for audio codec. Note that audio_info() can only be + // used when has_audio_info() returns true. + const AudioCodecInfo& audio_info() const { + SB_DCHECK(has_audio_info()); + return audio_info_; + } + + bool has_video_info() const { + return video_info_.codec != kSbMediaVideoCodecNone; + } + // Extra information for video codec. Note that video_info() can only be + // used when has_video_info() returns true. + const VideoCodecInfo& video_info() const { + SB_DCHECK(has_video_info()); + return video_info_; + } + + // Allow to overwrite the bitrate. + void SetBitrate(int bitrate); + + private: + void ParseMimeInfo(); + bool ParseAudioInfo(const std::string& codec); + bool ParseVideoInfo(const std::string& codec); + + void ResetCodecInfos(); + + MimeType mime_type_; + bool is_valid_ = true; + bool disable_cache_ = false; + AudioCodecInfo audio_info_; + VideoCodecInfo video_info_; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_PARSED_MIME_INFO_H_
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc index 9bfdb7e..d87f71d 100644 --- a/starboard/shared/starboard/player/filter/testing/test_util.cc +++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -18,6 +18,7 @@ #include "starboard/common/log.h" #include "starboard/directory.h" #include "starboard/shared/starboard/media/media_support_internal.h" +#include "starboard/shared/starboard/media/mime_type.h" #include "starboard/shared/starboard/player/filter/player_components.h" #include "starboard/shared/starboard/player/filter/stub_player_components_factory.h" #include "starboard/shared/starboard/player/filter/video_decoder_internal.h" @@ -33,6 +34,7 @@ namespace testing { namespace { +using ::starboard::shared::starboard::media::MimeType; using ::testing::AssertionFailure; using ::testing::AssertionResult; using ::testing::AssertionSuccess; @@ -155,12 +157,11 @@ } // Filter files of unsupported codec. - if (!SbMediaIsAudioSupported( - audio_file_info.audio_codec, - GetContentTypeFromAudioCodec(audio_file_info.audio_codec, - extra_mime_attributes) - .c_str(), - audio_file_info.bitrate)) { + const std::string audio_mime = GetContentTypeFromAudioCodec( + audio_file_info.audio_codec, extra_mime_attributes); + const MimeType audio_mime_type(audio_mime.c_str()); + if (!SbMediaIsAudioSupported(audio_file_info.audio_codec, &audio_mime_type, + audio_file_info.bitrate)) { continue; } @@ -203,13 +204,15 @@ const auto& video_sample_info = dmp_reader.GetPlayerSampleInfo(kSbMediaTypeVideo, 0) .video_sample_info; - + const std::string video_mime = dmp_reader.video_mime_type(); + const MimeType video_mime_type(video_mime.c_str()); if (SbMediaIsVideoSupported( - dmp_reader.video_codec(), dmp_reader.video_mime_type().c_str(), - -1, -1, 8, kSbMediaPrimaryIdUnspecified, - kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, - video_sample_info.frame_width, video_sample_info.frame_height, - dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) { + dmp_reader.video_codec(), + video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8, + kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified, + kSbMediaMatrixIdUnspecified, video_sample_info.frame_width, + video_sample_info.frame_height, dmp_reader.video_bitrate(), + dmp_reader.video_fps(), false)) { test_params.push_back(std::make_tuple(filename, output_mode)); } }
diff --git a/starboard/shared/starboard/player/player_create.cc b/starboard/shared/starboard/player/player_create.cc index c2e0a07..16bc57f 100644 --- a/starboard/shared/starboard/player/player_create.cc +++ b/starboard/shared/starboard/player/player_create.cc
@@ -30,12 +30,13 @@ #include "starboard/shared/starboard/player/video_dmp_writer.h" #endif // SB_PLAYER_ENABLE_VIDEO_DUMPER -using starboard::shared::media_session:: +using ::starboard::shared::media_session:: UpdateActiveSessionPlatformPlaybackState; -using starboard::shared::media_session::kPlaying; -using starboard::shared::starboard::player::filter:: +using ::starboard::shared::media_session::kPlaying; +using ::starboard::shared::starboard::media::MimeType; +using ::starboard::shared::starboard::player::filter:: FilterBasedPlayerWorkerHandler; -using starboard::shared::starboard::player::PlayerWorker; +using ::starboard::shared::starboard::player::PlayerWorker; SbPlayer SbPlayerCreate(SbWindow window, const SbPlayerCreationParam* creation_param, @@ -135,16 +136,19 @@ } const int64_t kDefaultBitRate = 0; - if (audio_codec != kSbMediaAudioCodecNone && - !SbMediaIsAudioSupported(audio_codec, audio_mime, kDefaultBitRate)) { - SB_LOG(ERROR) << "Unsupported audio codec " - << starboard::GetMediaAudioCodecName(audio_codec) << "."; - player_error_func( - kSbPlayerInvalid, context, kSbPlayerErrorDecode, - starboard::FormatString("Unsupported audio codec: %s", - starboard::GetMediaAudioCodecName(audio_codec)) - .c_str()); - return kSbPlayerInvalid; + if (audio_codec != kSbMediaAudioCodecNone) { + const MimeType audio_mime_type(audio_mime); + if (!SbMediaIsAudioSupported(audio_codec, &audio_mime_type, + kDefaultBitRate)) { + SB_LOG(ERROR) << "Unsupported audio codec " + << starboard::GetMediaAudioCodecName(audio_codec) << "."; + player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode, + starboard::FormatString( + "Unsupported audio codec: %s", + starboard::GetMediaAudioCodecName(audio_codec)) + .c_str()); + return kSbPlayerInvalid; + } } const int kDefaultProfile = -1; @@ -153,22 +157,24 @@ const int kDefaultFrameWidth = 0; const int kDefaultFrameHeight = 0; const int kDefaultFrameRate = 0; - if (video_codec != kSbMediaVideoCodecNone && - !SbMediaIsVideoSupported( - video_codec, video_mime, kDefaultProfile, kDefaultLevel, - kDefaultColorDepth, kSbMediaPrimaryIdUnspecified, - kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, - kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate, - kDefaultFrameRate, - output_mode == kSbPlayerOutputModeDecodeToTexture)) { - SB_LOG(ERROR) << "Unsupported video codec " - << starboard::GetMediaVideoCodecName(video_codec) << "."; - player_error_func( - kSbPlayerInvalid, context, kSbPlayerErrorDecode, - starboard::FormatString("Unsupported video codec: %s", - starboard::GetMediaVideoCodecName(video_codec)) - .c_str()); - return kSbPlayerInvalid; + if (video_codec != kSbMediaVideoCodecNone) { + const MimeType video_mime_type(video_mime); + if (!SbMediaIsVideoSupported( + video_codec, &video_mime_type, kDefaultProfile, kDefaultLevel, + kDefaultColorDepth, kSbMediaPrimaryIdUnspecified, + kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified, + kDefaultFrameWidth, kDefaultFrameHeight, kDefaultBitRate, + kDefaultFrameRate, + output_mode == kSbPlayerOutputModeDecodeToTexture)) { + SB_LOG(ERROR) << "Unsupported video codec " + << starboard::GetMediaVideoCodecName(video_codec) << "."; + player_error_func(kSbPlayerInvalid, context, kSbPlayerErrorDecode, + starboard::FormatString( + "Unsupported video codec: %s", + starboard::GetMediaVideoCodecName(video_codec)) + .c_str()); + return kSbPlayerInvalid; + } } if (audio_codec != kSbMediaAudioCodecNone && !audio_sample_info) {
diff --git a/starboard/shared/stub/media_is_audio_supported.cc b/starboard/shared/stub/media_is_audio_supported.cc index d9c70c5..853eca0 100644 --- a/starboard/shared/stub/media_is_audio_supported.cc +++ b/starboard/shared/stub/media_is_audio_supported.cc
@@ -16,8 +16,10 @@ #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { return false; }
diff --git a/starboard/shared/stub/media_is_video_supported.cc b/starboard/shared/stub/media_is_video_supported.cc index e16ee53..1049791 100644 --- a/starboard/shared/stub/media_is_video_supported.cc +++ b/starboard/shared/stub/media_is_video_supported.cc
@@ -16,8 +16,10 @@ #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth,
diff --git a/starboard/shared/win32/media_is_audio_supported.cc b/starboard/shared/win32/media_is_audio_supported.cc index b1c99f3..f838ac3 100644 --- a/starboard/shared/win32/media_is_audio_supported.cc +++ b/starboard/shared/win32/media_is_audio_supported.cc
@@ -18,8 +18,10 @@ #include "starboard/configuration_constants.h" #include "starboard/media.h" +using ::starboard::shared::starboard::media::MimeType; + bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec, - const char* content_type, + const MimeType* mime_type, int64_t bitrate) { if (audio_codec != kSbMediaAudioCodecAac && audio_codec != kSbMediaAudioCodecOpus) {
diff --git a/starboard/shared/win32/media_is_video_supported.cc b/starboard/shared/win32/media_is_video_supported.cc index 4b49b55..b7af5ce 100644 --- a/starboard/shared/win32/media_is_video_supported.cc +++ b/starboard/shared/win32/media_is_video_supported.cc
@@ -22,16 +22,14 @@ #include "starboard/configuration_constants.h" #include "starboard/shared/starboard/media/media_util.h" +using ::starboard::shared::starboard::media::MimeType; + namespace { #if SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION || \ defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) // Cache the VP9 support status since the check may be expensive. -enum Vp9Support { - kVp9SupportUnknown, - kVp9SupportYes, - kVp9SupportNo -}; +enum Vp9Support { kVp9SupportUnknown, kVp9SupportYes, kVp9SupportNo }; Vp9Support s_vp9_support = kVp9SupportUnknown; // Check for VP9 support. Since this is used by a starboard function, it @@ -76,7 +74,7 @@ return s_vp9_support == kVp9SupportYes; } #else // SB_API_VERSION >= SB_RUNTIME_CONFIGS_VERSION || - // defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) +// defined(SB_HAS_MEDIA_WEBM_VP9_SUPPORT) bool IsVp9Supported() { return false; } @@ -86,7 +84,7 @@ } // namespace bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, - const char* content_type, + const MimeType* mime_type, int profile, int level, int bit_depth, @@ -105,18 +103,18 @@ int max_height = 1080; if (video_codec == kSbMediaVideoCodecVp9) { - // Vp9 supports 8k only in whitelisted platforms, up to 4k in the others. +// Vp9 supports 8k only in whitelisted platforms, up to 4k in the others. #ifdef ENABLE_VP9_8K_SUPPORT max_width = 7680; max_height = 4320; -#else // ENABLE_VP9_8K_SUPPORT +#else // ENABLE_VP9_8K_SUPPORT max_width = 3840; max_height = 2160; #endif // ENABLE_VP9_8K_SUPPORT } else if (video_codec == kSbMediaVideoCodecH264) { - // Not all devices can support 4k H264; some (e.g. xb1) may crash in - // the decoder if provided too high of a resolution. Therefore - // platforms must explicitly opt-in to support 4k H264. +// Not all devices can support 4k H264; some (e.g. xb1) may crash in +// the decoder if provided too high of a resolution. Therefore +// platforms must explicitly opt-in to support 4k H264. #ifdef ENABLE_H264_4K_SUPPORT max_width = 3840; max_height = 2160;