Import Cobalt 21.lts.4.301329
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index b201080..dc2d88d 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -137,6 +137,11 @@
rendering; the web app does not know about the custom transform so may not
layout elements appropriately.
+ - **Added support for javascript code caching.**
+
+ Platforms can provide javascript code caching by implementing
+ CobaltExtensionJavaScriptCacheApi.
+
## Version 20
- **Support for QUIC and SPDY is now enabled.**
diff --git a/src/cobalt/audio/audio_destination_node.cc b/src/cobalt/audio/audio_destination_node.cc
index e35f1f9..6f82ba8 100644
--- a/src/cobalt/audio/audio_destination_node.cc
+++ b/src/cobalt/audio/audio_destination_node.cc
@@ -59,6 +59,7 @@
if (!audio_device_) {
audio_device_.reset(
new AudioDevice(static_cast<int>(channel_count(NULL)), this));
+ SB_LOG(INFO) << "Created audio device " << audio_device_.get() << '.';
context()->PreventGarbageCollection();
}
audio_device_to_delete_ = NULL;
@@ -73,7 +74,10 @@
DCHECK_EQ(number_of_inputs(), 1u);
bool all_finished = true;
Input(0)->FillAudioBus(audio_bus, silence, &all_finished);
- if (all_consumed && all_finished) {
+ if (all_consumed && all_finished &&
+ audio_device_to_delete_ != audio_device_.get()) {
+ SB_LOG(INFO) << "Schedule to destroy audio device " << audio_device_.get()
+ << '.';
audio_device_to_delete_ = audio_device_.get();
message_loop_->task_runner()->PostTask(
FROM_HERE, base::Bind(&AudioDestinationNode::DestroyAudioDevice,
@@ -82,7 +86,12 @@
}
void AudioDestinationNode::DestroyAudioDevice() {
+ AudioLock::AutoLock lock(audio_lock());
+ if (!audio_device_.get()) {
+ return;
+ }
if (audio_device_.get() == audio_device_to_delete_) {
+ SB_LOG(INFO) << "Destroying audio device " << audio_device_.get() << '.';
audio_device_.reset();
context()->AllowGarbageCollection();
}
diff --git a/src/cobalt/base/message_queue.h b/src/cobalt/base/message_queue.h
index 60e72ea..be36eea 100644
--- a/src/cobalt/base/message_queue.h
+++ b/src/cobalt/base/message_queue.h
@@ -58,6 +58,14 @@
}
}
+ // Clear all the messages in the queue.
+ void ClearAll() {
+ TRACE_EVENT0("cobalt::base", "MessageQueue::ClearAll()");
+ base::AutoLock lock(mutex_);
+ std::queue<base::Closure> empty_queue;
+ empty_queue.swap(queue_);
+ }
+
private:
base::Lock mutex_;
std::queue<base::Closure> queue_;
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index f2ba949..27fab77 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -765,7 +765,13 @@
TRACE_EVENT0("cobalt::browser",
"BrowserModule::ProcessRenderTreeSubmissionQueue()");
DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
- render_tree_submission_queue_.ProcessAll();
+ // If the app is preloaded, clear the render tree queue to avoid unnecessary
+ // rendering overhead.
+ if (application_state_ == base::kApplicationStatePreloading) {
+ render_tree_submission_queue_.ClearAll();
+ } else {
+ render_tree_submission_queue_.ProcessAll();
+ }
}
void BrowserModule::QueueOnRenderTreeProduced(
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 4021ee4..189d70d 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-300899
\ No newline at end of file
+301329
\ No newline at end of file
diff --git a/src/cobalt/cssom/viewport_size.h b/src/cobalt/cssom/viewport_size.h
index 6efe128..040d414 100644
--- a/src/cobalt/cssom/viewport_size.h
+++ b/src/cobalt/cssom/viewport_size.h
@@ -53,7 +53,7 @@
// Ratio of CSS pixels per device pixel, matching the devicePixelRatio
// attribute.
- // https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-window-devicepixelratio
+ // https://www.w3.org/TR/cssom-view-1/#dom-window-devicepixelratio
float device_pixel_ratio_ = 1.0f;
};
diff --git a/src/cobalt/extension/extension_test.cc b/src/cobalt/extension/extension_test.cc
index 1bbc6c8..989ccac 100644
--- a/src/cobalt/extension/extension_test.cc
+++ b/src/cobalt/extension/extension_test.cc
@@ -19,6 +19,7 @@
#include "cobalt/extension/font.h"
#include "cobalt/extension/graphics.h"
#include "cobalt/extension/installation_manager.h"
+#include "cobalt/extension/javascript_cache.h"
#include "cobalt/extension/platform_service.h"
#include "starboard/system.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -219,6 +220,29 @@
EXPECT_EQ(second_extension_api, extension_api)
<< "Extension struct should be a singleton";
}
+
+TEST(ExtensionTest, JavaScriptCache) {
+ typedef CobaltExtensionJavaScriptCacheApi ExtensionApi;
+ const char* kExtensionName = kCobaltExtensionJavaScriptCacheName;
+
+ const ExtensionApi* extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ if (!extension_api) {
+ return;
+ }
+
+ EXPECT_STREQ(extension_api->name, kExtensionName);
+ EXPECT_EQ(extension_api->version, 1u);
+ EXPECT_NE(extension_api->GetCachedScript, nullptr);
+ EXPECT_NE(extension_api->ReleaseCachedScriptData, nullptr);
+ EXPECT_NE(extension_api->StoreCachedScript, nullptr);
+
+ const ExtensionApi* second_extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ EXPECT_EQ(second_extension_api, extension_api)
+ << "Extension struct should be a singleton";
+}
+
} // namespace extension
} // namespace cobalt
#endif // SB_API_VERSION >= 11
diff --git a/src/cobalt/extension/javascript_cache.h b/src/cobalt/extension/javascript_cache.h
new file mode 100644
index 0000000..660c578
--- /dev/null
+++ b/src/cobalt/extension/javascript_cache.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_EXTENSION_JAVASCRIPT_CACHE_H_
+#define COBALT_EXTENSION_JAVASCRIPT_CACHE_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define kCobaltExtensionJavaScriptCacheName \
+ "dev.cobalt.extension.JavaScriptCache"
+
+// The implementation must be thread-safe as the extension would
+// be called from different threads. Also all storage management
+// is delegated to the platform.
+typedef struct CobaltExtensionJavaScriptCacheApi {
+ // Name should be the string |kCobaltExtensionJavaScriptCacheName|.
+ // This helps to validate that the extension API is correct.
+ const char* name;
+
+ // This specifies the version of the API that is implemented.
+ uint32_t version;
+
+ // The fields below this point were added in version 1 or later.
+
+ // Retrieves the cached data for a script using |key|. The |source_length|
+ // provides the actual size of the script source in bytes. The cached script
+ // bytes will be returned in |cache_data_out|. After the |cache_data| is
+ // processed the memory should be released by calling
+ // |ReleaseScriptCacheData|.
+ bool (*GetCachedScript)(uint32_t key, int source_length,
+ const uint8_t** cache_data_out,
+ int* cache_data_length);
+
+ // Releases the memory allocated for the |cache_data| by |GetCachedScript|.
+ void (*ReleaseCachedScriptData)(const uint8_t* cache_data);
+
+ // Stores the cached data for |key|.
+ bool (*StoreCachedScript)(uint32_t key, int source_length,
+ const uint8_t* cache_data, int cache_data_length);
+} CobaltExtensionJavaScriptCacheApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // COBALT_EXTENSION_JAVASCRIPT_CACHE_H_
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
index 1a36b31..9a77757 100644
--- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
+++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
@@ -30,6 +30,5 @@
}
vec4 untransformed_color = vec4(y_value, uv_value.r, uv_value.g, 1.0);
- vec4 color = untransformed_color * to_rgb_color_matrix;
- gl_FragColor = color;
+ gl_FragColor = untransformed_color * to_rgb_color_matrix;
}
diff --git a/src/cobalt/script/v8c/v8c_global_environment.cc b/src/cobalt/script/v8c/v8c_global_environment.cc
index b19174c..044ea64 100644
--- a/src/cobalt/script/v8c/v8c_global_environment.cc
+++ b/src/cobalt/script/v8c/v8c_global_environment.cc
@@ -25,6 +25,7 @@
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/script/javascript_engine.h"
#include "cobalt/script/v8c/embedded_resources.h"
#include "cobalt/script/v8c/entry_scope.h"
#include "cobalt/script/v8c/v8c_script_value_factory.h"
@@ -32,6 +33,8 @@
#include "cobalt/script/v8c/v8c_user_object_holder.h"
#include "cobalt/script/v8c/v8c_value_handle.h"
#include "nb/memory_scope.h"
+#include "starboard/common/murmurhash2.h"
+
namespace cobalt {
namespace script {
@@ -71,6 +74,18 @@
return *v8::String::Utf8Value(isolate, value.As<v8::String>());
}
+uint32_t CreateJavaScriptCacheKey(const std::string& javascript_engine_version,
+ uint32_t cached_data_version_tag,
+ const std::string& source,
+ const std::string& origin) {
+ uint32_t res = starboard::MurmurHash2_32(javascript_engine_version.c_str(),
+ javascript_engine_version.size(),
+ cached_data_version_tag);
+ res = starboard::MurmurHash2_32(source.c_str(), source.size(), res);
+ res = starboard::MurmurHash2_32(origin.c_str(), origin.size(), res);
+ return res;
+}
+
} // namespace
V8cGlobalEnvironment::V8cGlobalEnvironment(v8::Isolate* isolate)
@@ -229,7 +244,11 @@
}
void V8cGlobalEnvironment::RemoveRoot(Traceable* traceable) {
- V8cEngine::GetFromIsolate(isolate_)->heap_tracer()->RemoveRoot(traceable);
+ CHECK(isolate_);
+ V8cEngine* v8c_engine = V8cEngine::GetFromIsolate(isolate_);
+ CHECK(v8c_engine);
+ CHECK(v8c_engine->heap_tracer());
+ v8c_engine->heap_tracer()->RemoveRoot(traceable);
}
void V8cGlobalEnvironment::PreventGarbageCollection(
@@ -415,7 +434,29 @@
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::Local<v8::Script> script;
- {
+
+ bool used_cache = false;
+#if SB_API_VERSION >= 11
+ const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
+ static_cast<const CobaltExtensionJavaScriptCacheApi*>(
+ SbSystemGetExtension(kCobaltExtensionJavaScriptCacheName));
+ if (javascript_cache_extension &&
+ SbStringCompareAll(javascript_cache_extension->name,
+ kCobaltExtensionJavaScriptCacheName) == 0 &&
+ javascript_cache_extension->version >= 1) {
+ TRACE_EVENT0("cobalt::script",
+ "V8cGlobalEnvironment::CompileWithCaching()");
+ if (CompileWithCaching(javascript_cache_extension, context, v8c_source_code,
+ source, &script_origin)
+ .ToLocal(&script)) {
+ used_cache = true;
+ } else {
+ LOG(WARNING) << "Failed to compile script.";
+ return {};
+ }
+ }
+#endif
+ if (!used_cache) {
TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
if (!v8::Script::Compile(context, source, &script_origin)
.ToLocal(&script)) {
@@ -436,6 +477,78 @@
return result;
}
+v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::CompileWithCaching(
+ const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension,
+ v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code,
+ v8::Local<v8::String> source, v8::ScriptOrigin* script_origin) {
+ const base::SourceLocation& source_location = v8c_source_code->location();
+
+ DLOG(INFO) << "CompileWithCaching: " << source_location.file_path.c_str()
+ << " " << v8c_source_code->location().file_path;
+
+ bool successful_cache = false;
+
+ std::string javascript_engine_version =
+ script::GetJavaScriptEngineNameAndVersion();
+ uint32_t javascript_cache_key = CreateJavaScriptCacheKey(
+ javascript_engine_version, v8::ScriptCompiler::CachedDataVersionTag(),
+ v8c_source_code->source_utf8(), source_location.file_path);
+ const uint8_t* cache_data_buf = nullptr;
+ int cache_data_size = -1;
+ v8::Local<v8::Script> script;
+ if (javascript_cache_extension->GetCachedScript(
+ javascript_cache_key, v8c_source_code->source_utf8().size(),
+ &cache_data_buf, &cache_data_size)) {
+ DLOG(INFO) << "CompileWithCaching: Using cached resource";
+ v8::ScriptCompiler::CachedData* cached_code =
+ new v8::ScriptCompiler::CachedData(
+ cache_data_buf, cache_data_size,
+ v8::ScriptCompiler::CachedData::BufferNotOwned);
+ // The script_source owns the cached_code object.
+ v8::ScriptCompiler::Source script_source(source, *script_origin,
+ cached_code);
+
+ {
+ TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
+ successful_cache =
+ v8::ScriptCompiler::Compile(context, &script_source,
+ v8::ScriptCompiler::kConsumeCodeCache)
+ .ToLocal(&script) &&
+ !cached_code->rejected;
+ if (!successful_cache) {
+ LOG(WARNING)
+ << "CompileWithCaching: Failed to reuse the cached script rejected="
+ << cached_code->rejected;
+ }
+ }
+ }
+
+ if (cache_data_buf != nullptr) {
+ javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf);
+ cache_data_buf = nullptr;
+ }
+
+ if (!successful_cache) {
+ DLOG(INFO) << "CompileWithCaching: compile";
+ {
+ TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
+ if (!v8::Script::Compile(context, source, script_origin)
+ .ToLocal(&script)) {
+ LOG(WARNING) << "CompileWithCaching: Failed to compile script.";
+ return {};
+ }
+ }
+ std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data(
+ v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
+ if (!javascript_cache_extension->StoreCachedScript(
+ javascript_cache_key, v8c_source_code->source_utf8().size(),
+ cached_data->data, cached_data->length)) {
+ LOG(WARNING) << "CompileWithCaching: Failed to store cached script";
+ }
+ }
+ return script;
+}
+
void V8cGlobalEnvironment::EvaluateEmbeddedScript(const unsigned char* data,
size_t size,
const char* filename) {
diff --git a/src/cobalt/script/v8c/v8c_global_environment.h b/src/cobalt/script/v8c/v8c_global_environment.h
index c131a8d..6d6c9c5 100644
--- a/src/cobalt/script/v8c/v8c_global_environment.h
+++ b/src/cobalt/script/v8c/v8c_global_environment.h
@@ -26,9 +26,11 @@
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/threading/thread_checker.h"
+#include "cobalt/extension/javascript_cache.h"
#include "cobalt/script/global_environment.h"
#include "cobalt/script/javascript_engine.h"
#include "cobalt/script/v8c/v8c_heap_tracer.h"
+#include "cobalt/script/v8c/v8c_source_code.h"
#include "cobalt/script/v8c/wrapper_factory.h"
#include "v8/include/libplatform/libplatform.h"
#include "v8/include/v8.h"
@@ -145,6 +147,11 @@
v8::MaybeLocal<v8::Value> EvaluateScriptInternal(
const scoped_refptr<SourceCode>& source_code);
+ v8::MaybeLocal<v8::Script> CompileWithCaching(
+ const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension,
+ v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code,
+ v8::Local<v8::String> source, v8::ScriptOrigin* script_origin);
+
void EvaluateEmbeddedScript(const unsigned char* data, size_t size,
const char* filename);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 048ecfa..ac387f5 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -74,6 +74,9 @@
int preferredBufferSizeInBytes,
boolean enableAudioRouting,
int tunnelModeAudioSessionId) {
+ // TODO: Re-enable audio routing when all related bugs are fixed.
+ enableAudioRouting = false;
+
tunnelModeEnabled = tunnelModeAudioSessionId != -1;
int channelConfig;
switch (channelCount) {
@@ -166,6 +169,7 @@
preferredBufferSizeInBytes,
AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType)));
if (audioTrack != null && enableAudioRouting && Build.VERSION.SDK_INT >= 24) {
+ Log.i(TAG, "Audio routing enabled.");
currentRoutedDevice = audioTrack.getRoutedDevice();
onRoutingChangedListener =
new AudioRouting.OnRoutingChangedListener() {
@@ -186,6 +190,8 @@
}
};
audioTrack.addOnRoutingChangedListener(onRoutingChangedListener, null);
+ } else {
+ Log.i(TAG, "Audio routing disabled.");
}
}
@@ -195,7 +201,7 @@
public void release() {
if (audioTrack != null) {
- if (Build.VERSION.SDK_INT >= 24) {
+ if (Build.VERSION.SDK_INT >= 24 && onRoutingChangedListener != null) {
audioTrack.removeOnRoutingChangedListener(onRoutingChangedListener);
}
audioTrack.release();
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index 5318110..cdbbf9a 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -120,6 +120,7 @@
// On the emulator it fails with the log: "storeMetaDataInBuffers failed w/ err -1010"
codecBlackList.add("OMX.google.vp9.decoder");
+ vp9WhiteList.put("Amazon", new HashSet<String>());
vp9WhiteList.put("Amlogic", new HashSet<String>());
vp9WhiteList.put("Arcadyan", new HashSet<String>());
vp9WhiteList.put("arcelik", new HashSet<String>());
@@ -159,6 +160,7 @@
vp9WhiteList.put("Xiaomi", new HashSet<String>());
vp9WhiteList.put("ZTE TV", new HashSet<String>());
+ vp9WhiteList.get("Amazon").add("AFTS");
vp9WhiteList.get("Amlogic").add("p212");
vp9WhiteList.get("Arcadyan").add("Bouygtel4K");
vp9WhiteList.get("Arcadyan").add("HMB2213PW22TS");
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
index c49cacb..9ec58e5 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -15,7 +15,6 @@
package dev.cobalt.util;
import android.content.Context;
-import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.Size;
import android.util.SizeF;
@@ -134,7 +133,6 @@
private static DisplayMetrics cachedDisplayMetrics = null;
private static DisplayMetrics getDisplayMetrics() {
- Resources.getSystem().getDisplayMetrics();
if (cachedDisplayMetrics == null) {
cachedDisplayMetrics = new DisplayMetrics();
Display display = getDefaultDisplay();
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id
new file mode 100644
index 0000000..13984bc
--- /dev/null
+++ b/src/starboard/android/apk/build.id
@@ -0,0 +1 @@
+301279
\ No newline at end of file
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index e28eabe..1c7d413 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -14,6 +14,7 @@
#include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
+#include <string>
#include <vector>
#include "starboard/android/shared/audio_track_audio_sink_type.h"
@@ -177,7 +178,9 @@
// static
void MinRequiredFramesTester::ErrorFunc(bool capability_changed,
+ const std::string& error_message,
void* context) {
+ SB_LOG(ERROR) << "Error occurred while writing frames: " << error_message;
// TODO: Handle errors during minimum frames test, maybe by terminating the
// test earlier.
SB_NOTREACHED();
diff --git a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
index e689e96..669217a 100644
--- a/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
+++ b/src/starboard/android/shared/audio_sink_min_required_frames_tester.h
@@ -17,6 +17,7 @@
#include <atomic>
#include <functional>
+#include <string>
#include <vector>
#include "starboard/common/condition_variable.h"
@@ -85,7 +86,9 @@
static void ConsumeFramesFunc(int frames_consumed,
SbTime frames_consumed_at,
void* context);
- static void ErrorFunc(bool capability_changed, void* context);
+ static void ErrorFunc(bool capability_changed,
+ const std::string& error_message,
+ void* context);
void UpdateSourceStatus(int* frames_in_buffer,
int* offset_in_frames,
bool* is_playing,
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 3f5b5b8..4c24e5b 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -15,8 +15,10 @@
#include "starboard/android/shared/audio_track_audio_sink_type.h"
#include <algorithm>
+#include <string>
#include <vector>
+#include "starboard/common/string.h"
#include "starboard/shared/starboard/player/filter/common.h"
namespace {
@@ -246,7 +248,8 @@
j_audio_track_bridge_, "getAndResetHasNewAudioDeviceAdded", "()Z");
if (new_audio_device_added) {
SB_LOG(INFO) << "New audio device added.";
- error_func_(kSbPlayerErrorCapabilityChanged, context_);
+ error_func_(kSbPlayerErrorCapabilityChanged, "New audio device added.",
+ context_);
break;
}
@@ -395,10 +398,12 @@
// Take all |frames_in_audio_track| as consumed since audio track could be
// dead.
consume_frames_func_(frames_in_audio_track, now, context_);
- error_func_(written_frames == kAudioTrackErrorDeadObject
- ? kSbPlayerErrorCapabilityChanged
- : kSbPlayerErrorDecode,
- context_);
+
+ bool capabilities_changed = written_frames == kAudioTrackErrorDeadObject;
+ error_func_(
+ capabilities_changed,
+ FormatString("Error while writing frames: %d", written_frames),
+ context_);
break;
} else if (written_frames > 0) {
last_written_succeeded_at = now;
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index 5e1b078..7caf57b 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -49,6 +49,11 @@
using starboard::shared::starboard::media::MimeType;
+// Tunnel mode has to be enabled explicitly by the web app via mime attributes
+// "tunnelmode", set the following variable to true to force enabling tunnel
+// mode on all playbacks.
+constexpr bool kForceTunnelMode = false;
+
// On some platforms tunnel mode is only supported in the secure pipeline. Set
// the following variable to true to force creating a secure pipeline in tunnel
// mode, even for clear content.
@@ -113,7 +118,8 @@
SB_DCHECK(frames_consumed == 0);
}
- void OnError(bool capability_changed) override {
+ void OnError(bool capability_changed,
+ const std::string& error_message) override {
error_occurred_.store(true);
}
@@ -196,6 +202,12 @@
<< ". Tunnel mode is disabled.";
}
+ if (kForceTunnelMode && !enable_tunnel_mode) {
+ SB_LOG(INFO) << "`kForceTunnelMode` is set to true, force enabling tunnel"
+ << " mode.";
+ enable_tunnel_mode = true;
+ }
+
bool force_secure_pipeline_under_tunnel_mode = false;
if (enable_tunnel_mode &&
IsTunnelModeSupported(creation_parameters,
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 60e8129..97371fe 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -164,6 +164,8 @@
'trace_util.h',
'video_decoder.cc',
'video_decoder.h',
+ 'video_frame_tracker.cc',
+ 'video_frame_tracker.h',
'video_render_algorithm.cc',
'video_render_algorithm.h',
'video_window.cc',
diff --git a/src/starboard/android/shared/starboard_platform_tests.gypi b/src/starboard/android/shared/starboard_platform_tests.gypi
index 5b40e63..5b8fe50 100644
--- a/src/starboard/android/shared/starboard_platform_tests.gypi
+++ b/src/starboard/android/shared/starboard_platform_tests.gypi
@@ -23,6 +23,7 @@
'<(DEPTH)/starboard/common/test_main.cc',
'<@(media_tests_sources)',
'jni_env_ext_test.cc',
+ 'video_frame_tracker_test.cc',
],
'defines': [
# This allows the tests to include internal only header files.
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index ff53ef1..267285d 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -20,7 +20,6 @@
#include <cmath>
#include <functional>
#include <list>
-#include <vector>
#include "starboard/android/shared/application_android.h"
#include "starboard/android/shared/decode_target_create.h"
@@ -36,7 +35,6 @@
#include "starboard/drm.h"
#include "starboard/memory.h"
#include "starboard/shared/starboard/player/filter/video_frame_internal.h"
-#include "starboard/shared/starboard/thread_checker.h"
#include "starboard/string.h"
#include "starboard/thread.h"
@@ -154,125 +152,7 @@
} // namespace
-// TODO: Move into a separate file and write unit tests for it.
-class VideoFrameTracker {
- public:
- SbTime seek_to_time() const { return seek_to_time_; }
-
- void OnInputBuffer(SbTime timestamp) {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
- if (frames_to_be_rendered_.empty()) {
- frames_to_be_rendered_.push_back(timestamp);
- return;
- }
-
- if (frames_to_be_rendered_.size() > kMaxPendingWorkSize * 2) {
- // OnFrameRendered() is only available after API level 23. Cap the size
- // of |frames_to_be_rendered_| in case OnFrameRendered() is not available.
- frames_to_be_rendered_.pop_front();
- }
-
- // Sort by |timestamp|, because |timestamp| won't be monotonic if there are
- // B frames.
- for (auto it = frames_to_be_rendered_.end();
- it != frames_to_be_rendered_.begin();) {
- it--;
- if (*it < timestamp) {
- frames_to_be_rendered_.emplace(++it, timestamp);
- return;
- } else if (*it == timestamp) {
- SB_LOG(WARNING) << "feed video AU with same time stamp " << timestamp;
- return;
- }
- }
-
- frames_to_be_rendered_.emplace_front(timestamp);
- }
-
- void OnFrameRendered(int64_t frame_timestamp) {
- ScopedLock lock(rendered_frames_mutex_);
- rendered_frames_on_decoder_thread_.push_back(frame_timestamp);
- }
-
- void Seek(SbTime seek_to_time) {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
- // Ensure that all dropped frames before seeking are captured.
- UpdateDroppedFrames();
-
- frames_to_be_rendered_.clear();
- seek_to_time_ = seek_to_time;
- }
-
- int UpdateAndGetDroppedFrames() {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
- UpdateDroppedFrames();
- return dropped_frames_;
- }
-
- private:
- // TODO:
- // * It is possible that the initial frame rendered time is before the seek to
- // time, when the platform decides to render a frame earlier than the seek
- // to time during preroll. This shouldn't be an issue after we align seek
- // time to the next video key frame.
- // * The reported frame rendering time is the real time the frame is rendered.
- // It can be slightly different than the timestamp associated with the
- // InputBuffer. For example, the frame with timestamp 120000 may be
- // rendered at 120020. We have to account for this difference, as otherwise
- // lots of frames will be reported as being dropped.
- void UpdateDroppedFrames() {
- SB_DCHECK(thread_checker_.CalledOnValidThread());
-
- {
- ScopedLock lock(rendered_frames_mutex_);
- rendered_frames_on_tracker_thread_.swap(
- rendered_frames_on_decoder_thread_);
- }
-
- // TODO: Refine the algorithm, and consider using std::set<> for
- // |frames_to_be_rendered_|.
- for (auto timestamp : rendered_frames_on_tracker_thread_) {
- auto it = frames_to_be_rendered_.begin();
- while (it != frames_to_be_rendered_.end()) {
- if (*it > timestamp) {
- break;
- }
-
- if (*it < seek_to_time_) {
- it = frames_to_be_rendered_.erase(it);
- } else if (*it < timestamp) {
- SB_LOG(WARNING) << "Video frame dropped:" << *it
- << ", current frame timestamp:" << timestamp
- << ", frames in the backlog:"
- << frames_to_be_rendered_.size();
- ++dropped_frames_;
- it = frames_to_be_rendered_.erase(it);
- } else if (*it == timestamp) {
- it = frames_to_be_rendered_.erase(it);
- } else {
- ++it;
- }
- }
- }
-
- rendered_frames_on_tracker_thread_.clear();
- }
-
- ::starboard::shared::starboard::ThreadChecker thread_checker_;
-
- std::list<SbTime> frames_to_be_rendered_;
-
- int dropped_frames_ = 0;
- SbTime seek_to_time_ = 0;
-
- std::vector<SbTime> rendered_frames_on_tracker_thread_;
- Mutex rendered_frames_mutex_;
- std::vector<SbTime> rendered_frames_on_decoder_thread_;
-};
-
-// TODO: Merge this with VideoFrameTracker
+// TODO: Merge this with VideoFrameTracker, maybe?
class VideoRenderAlgorithmTunneled : public VideoRenderAlgorithmBase {
public:
explicit VideoRenderAlgorithmTunneled(VideoFrameTracker* frame_tracker)
@@ -352,7 +232,7 @@
SB_DCHECK(error_message);
if (tunnel_mode_audio_session_id != -1) {
- video_frame_tracker_.reset(new VideoFrameTracker);
+ video_frame_tracker_.reset(new VideoFrameTracker(kMaxPendingWorkSize * 2));
}
if (force_secure_pipeline_under_tunnel_mode) {
SB_DCHECK(tunnel_mode_audio_session_id != -1);
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h
index 42d6901..5297bb5 100644
--- a/src/starboard/android/shared/video_decoder.h
+++ b/src/starboard/android/shared/video_decoder.h
@@ -22,6 +22,7 @@
#include "starboard/android/shared/drm_system.h"
#include "starboard/android/shared/media_codec_bridge.h"
#include "starboard/android/shared/media_decoder.h"
+#include "starboard/android/shared/video_frame_tracker.h"
#include "starboard/android/shared/video_window.h"
#include "starboard/atomic.h"
#include "starboard/common/condition_variable.h"
@@ -41,8 +42,6 @@
namespace android {
namespace shared {
-class VideoFrameTracker;
-
class VideoDecoder
: public ::starboard::shared::starboard::player::filter::VideoDecoder,
private MediaDecoder::Host,
diff --git a/src/starboard/android/shared/video_frame_tracker.cc b/src/starboard/android/shared/video_frame_tracker.cc
new file mode 100644
index 0000000..3e3349f
--- /dev/null
+++ b/src/starboard/android/shared/video_frame_tracker.cc
@@ -0,0 +1,144 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/video_frame_tracker.h"
+
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <vector>
+
+#include "starboard/common/log.h"
+#include "starboard/common/mutex.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+const SbTime kMaxAllowedSkew = 5000;
+
+} // namespace
+
+SbTime VideoFrameTracker::seek_to_time() const {
+ return seek_to_time_;
+}
+
+void VideoFrameTracker::OnInputBuffer(SbTime timestamp) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (frames_to_be_rendered_.empty()) {
+ frames_to_be_rendered_.push_back(timestamp);
+ return;
+ }
+
+ if (frames_to_be_rendered_.size() > max_pending_frames_size_) {
+ // OnFrameRendered() is only available after API level 23. Cap the size
+ // of |frames_to_be_rendered_| in case OnFrameRendered() is not available.
+ frames_to_be_rendered_.pop_front();
+ }
+
+ // Sort by |timestamp|, because |timestamp| won't be monotonic if there are
+ // B frames.
+ for (auto it = frames_to_be_rendered_.end();
+ it != frames_to_be_rendered_.begin();) {
+ it--;
+ if (*it < timestamp) {
+ frames_to_be_rendered_.emplace(++it, timestamp);
+ return;
+ } else if (*it == timestamp) {
+ SB_LOG(WARNING) << "feed video AU with same time stamp " << timestamp;
+ return;
+ }
+ }
+
+ frames_to_be_rendered_.emplace_front(timestamp);
+}
+
+void VideoFrameTracker::OnFrameRendered(int64_t frame_timestamp) {
+ ScopedLock lock(rendered_frames_mutex_);
+ rendered_frames_on_decoder_thread_.push_back(frame_timestamp);
+}
+
+void VideoFrameTracker::Seek(SbTime seek_to_time) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Ensure that all dropped frames before seeking are captured.
+ UpdateDroppedFrames();
+
+ frames_to_be_rendered_.clear();
+ seek_to_time_ = seek_to_time;
+}
+
+int VideoFrameTracker::UpdateAndGetDroppedFrames() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ UpdateDroppedFrames();
+ return dropped_frames_;
+}
+
+void VideoFrameTracker::UpdateDroppedFrames() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ {
+ ScopedLock lock(rendered_frames_mutex_);
+ rendered_frames_on_tracker_thread_.swap(rendered_frames_on_decoder_thread_);
+ }
+
+ while (frames_to_be_rendered_.front() < seek_to_time_) {
+ // It is possible that the initial frame rendered time is before the
+ // seek to time, when the platform decides to render a frame earlier
+ // than the seek to time during preroll. This shouldn't be an issue
+ // after we align seek time to the next video key frame.
+ frames_to_be_rendered_.pop_front();
+ }
+
+ // Loop over all timestamps from OnFrameRendered and compare against ones from
+ // OnInputBuffer.
+ for (auto rendered_timestamp : rendered_frames_on_tracker_thread_) {
+ auto to_render_timestamp = frames_to_be_rendered_.begin();
+ // Loop over all frames to render until we've caught up to the timestamp of
+ // the last rendered frame.
+ while (to_render_timestamp != frames_to_be_rendered_.end() &&
+ !(*to_render_timestamp - rendered_timestamp > kMaxAllowedSkew)) {
+ if (std::abs(*to_render_timestamp - rendered_timestamp) <=
+ kMaxAllowedSkew) {
+ // This frame was rendered, remove it from frames_to_be_rendered_.
+ to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp);
+ } else if (rendered_timestamp - *to_render_timestamp > kMaxAllowedSkew) {
+ // The rendered frame is too far ahead. The to_render_timestamp frame
+ // was dropped.
+ SB_LOG(WARNING) << "Video frame dropped:" << *to_render_timestamp
+ << ", current frame timestamp:" << rendered_timestamp
+ << ", frames in the backlog:"
+ << frames_to_be_rendered_.size();
+ ++dropped_frames_;
+ to_render_timestamp = frames_to_be_rendered_.erase(to_render_timestamp);
+ } else {
+ // The rendered frame is too early to match the next frame to render.
+ // This could happen if a frame is reported to be rendered twice or if
+ // it is rendered more than kMaxAllowedSkew early. In the latter
+ // scenario the frame will be reported dropped in the next iteration of
+ // the outer loop.
+ ++to_render_timestamp;
+ }
+ }
+ }
+
+ rendered_frames_on_tracker_thread_.clear();
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/video_frame_tracker.h b/src/starboard/android/shared/video_frame_tracker.h
new file mode 100644
index 0000000..8b88f59
--- /dev/null
+++ b/src/starboard/android/shared/video_frame_tracker.h
@@ -0,0 +1,64 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_
+#define STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_
+
+#include <list>
+#include <vector>
+
+#include "starboard/common/mutex.h"
+#include "starboard/shared/starboard/thread_checker.h"
+#include "starboard/time.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class VideoFrameTracker {
+ public:
+ explicit VideoFrameTracker(int max_pending_frames_size)
+ : max_pending_frames_size_(max_pending_frames_size) {}
+
+ SbTime seek_to_time() const;
+
+ void OnInputBuffer(SbTime timestamp);
+
+ void OnFrameRendered(int64_t frame_timestamp);
+
+ void Seek(SbTime seek_to_time);
+
+ int UpdateAndGetDroppedFrames();
+
+ private:
+ void UpdateDroppedFrames();
+
+ ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
+ std::list<SbTime> frames_to_be_rendered_;
+
+ const int max_pending_frames_size_;
+ int dropped_frames_ = 0;
+ SbTime seek_to_time_ = 0;
+
+ Mutex rendered_frames_mutex_;
+ std::vector<SbTime> rendered_frames_on_tracker_thread_;
+ std::vector<SbTime> rendered_frames_on_decoder_thread_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_VIDEO_FRAME_TRACKER_H_
diff --git a/src/starboard/android/shared/video_frame_tracker_test.cc b/src/starboard/android/shared/video_frame_tracker_test.cc
new file mode 100644
index 0000000..5e61b00
--- /dev/null
+++ b/src/starboard/android/shared/video_frame_tracker_test.cc
@@ -0,0 +1,167 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "starboard/android/shared/video_frame_tracker.h"
+#include "starboard/time.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+TEST(VideoFrameTrackerTest, DroppedFrameCountIsCumulative) {
+ VideoFrameTracker video_frame_tracker(100);
+
+ video_frame_tracker.OnInputBuffer(10000);
+ video_frame_tracker.OnInputBuffer(20000);
+ video_frame_tracker.OnInputBuffer(30000);
+ video_frame_tracker.OnInputBuffer(40000);
+ video_frame_tracker.OnInputBuffer(50000);
+ video_frame_tracker.OnInputBuffer(60000);
+ video_frame_tracker.OnInputBuffer(70000);
+ video_frame_tracker.OnInputBuffer(80000);
+
+ video_frame_tracker.OnFrameRendered(10000);
+ video_frame_tracker.OnFrameRendered(20000);
+ // Frame with timestamp 30000 was dropped
+ video_frame_tracker.OnFrameRendered(40000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 1);
+ video_frame_tracker.OnFrameRendered(50000);
+ // Frame with timestamp 60000 was dropped
+ video_frame_tracker.OnFrameRendered(70000);
+ // Frame with timestamp 80000 is not rendered and not dropped.
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 2);
+}
+
+TEST(VideoFrameTrackerTest, OnlySkewOverMaxAllowedShouldResultInDroppedFrame) {
+ VideoFrameTracker video_frame_tracker(100);
+
+ video_frame_tracker.OnInputBuffer(100000);
+ video_frame_tracker.OnInputBuffer(200000);
+ video_frame_tracker.OnInputBuffer(300000);
+ video_frame_tracker.OnInputBuffer(400000);
+ video_frame_tracker.OnInputBuffer(500000);
+ video_frame_tracker.OnInputBuffer(600000);
+
+ video_frame_tracker.OnFrameRendered(104999); // 4999us late -> not dropped
+ video_frame_tracker.OnFrameRendered(195001); // 4999us early -> not dropped
+ video_frame_tracker.OnFrameRendered(300000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0);
+
+ video_frame_tracker.OnFrameRendered(394999); // 5001us early -> dropped
+ video_frame_tracker.OnFrameRendered(505001); // 5001us late -> dropped
+ video_frame_tracker.OnFrameRendered(600000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 2);
+}
+
+TEST(VideoFrameTrackerTest, MultipleFramesDroppedAreReportedCorrectly) {
+ VideoFrameTracker video_frame_tracker(100);
+
+ video_frame_tracker.OnInputBuffer(10000);
+ video_frame_tracker.OnInputBuffer(20000);
+ video_frame_tracker.OnInputBuffer(30000);
+ video_frame_tracker.OnInputBuffer(40000);
+ video_frame_tracker.OnInputBuffer(50000);
+ video_frame_tracker.OnInputBuffer(60000);
+ video_frame_tracker.OnInputBuffer(70000);
+ video_frame_tracker.OnInputBuffer(80000);
+ video_frame_tracker.OnInputBuffer(90000);
+
+ video_frame_tracker.OnFrameRendered(10000);
+ video_frame_tracker.OnFrameRendered(20000);
+ // Frame with timestamp 30000 was dropped.
+ video_frame_tracker.OnFrameRendered(40000);
+ // Frames with timestamp 50000, 60000, 70000, 80000 were dropped.
+ video_frame_tracker.OnFrameRendered(90000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 5);
+}
+
+TEST(VideoFrameTrackerTest, RenderQueueIsClearedOnSeek) {
+ VideoFrameTracker video_frame_tracker(100);
+
+ video_frame_tracker.OnInputBuffer(10000);
+ video_frame_tracker.OnInputBuffer(20000);
+ video_frame_tracker.OnInputBuffer(30000);
+
+ // These frames are not rendered but due to the seek below should not count as
+ // dropped.
+ video_frame_tracker.OnInputBuffer(40000);
+ video_frame_tracker.OnInputBuffer(50000);
+ video_frame_tracker.OnInputBuffer(60000);
+
+ video_frame_tracker.OnFrameRendered(10000);
+ // Frame with timestamp 20000 was dropped and should be registered.
+ video_frame_tracker.OnFrameRendered(30000);
+ // Frames 40000, 50000, 60000 were never rendered.
+
+ const SbTime kSeekToTime = 90000;
+ video_frame_tracker.Seek(kSeekToTime);
+ ASSERT_EQ(kSeekToTime, video_frame_tracker.seek_to_time());
+
+ video_frame_tracker.OnInputBuffer(90000);
+ video_frame_tracker.OnInputBuffer(100000);
+ video_frame_tracker.OnInputBuffer(110000);
+ video_frame_tracker.OnInputBuffer(120000);
+
+ video_frame_tracker.OnFrameRendered(90000);
+ video_frame_tracker.OnFrameRendered(100000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 1);
+}
+
+TEST(VideoFrameTrackerTest, UnorderedInputFramesAreHandled) {
+ VideoFrameTracker video_frame_tracker(100);
+
+ // Order of input frames should not matter as long as they are before the
+ // render timestamp.
+ video_frame_tracker.OnInputBuffer(20000);
+ video_frame_tracker.OnInputBuffer(30000);
+ video_frame_tracker.OnInputBuffer(10000);
+ video_frame_tracker.OnInputBuffer(40000);
+ video_frame_tracker.OnInputBuffer(60000);
+ video_frame_tracker.OnInputBuffer(80000);
+
+ video_frame_tracker.OnFrameRendered(10000);
+ video_frame_tracker.OnFrameRendered(20000);
+ video_frame_tracker.OnFrameRendered(30000);
+ video_frame_tracker.OnFrameRendered(40000);
+
+ // Add more input frames after the first frames are rendered.
+ video_frame_tracker.OnInputBuffer(50000);
+ video_frame_tracker.OnInputBuffer(70000);
+ video_frame_tracker.OnInputBuffer(90000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0);
+
+ video_frame_tracker.OnFrameRendered(50000);
+ video_frame_tracker.OnFrameRendered(60000);
+ video_frame_tracker.OnFrameRendered(70000);
+ video_frame_tracker.OnFrameRendered(80000);
+
+ EXPECT_EQ(video_frame_tracker.UpdateAndGetDroppedFrames(), 0);
+}
+
+} // namespace
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/evergreen/testing/linux/start_cobalt.sh b/src/starboard/evergreen/testing/linux/start_cobalt.sh
index e4d3823..b060bbc 100755
--- a/src/starboard/evergreen/testing/linux/start_cobalt.sh
+++ b/src/starboard/evergreen/testing/linux/start_cobalt.sh
@@ -37,7 +37,6 @@
log "info" " Starting Cobalt with:"
log "info" " --url=${URL}"
- log "info" " --content=${CONTENT}"
for arg in $ARGS; do
log "info" " ${arg}"
@@ -45,7 +44,7 @@
log "info" " Logs will be output to '${LOG_PATH}/${LOG}'"
- eval "${OUT}/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
+ eval "${OUT}/loader_app --url=\"\"${URL}\"\" ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
eval $__LOADER=$!
diff --git a/src/starboard/evergreen/testing/raspi/start_cobalt.sh b/src/starboard/evergreen/testing/raspi/start_cobalt.sh
index ae370d8..df4efb4 100755
--- a/src/starboard/evergreen/testing/raspi/start_cobalt.sh
+++ b/src/starboard/evergreen/testing/raspi/start_cobalt.sh
@@ -37,7 +37,6 @@
log "info" " Starting Cobalt with:"
log "info" " --url=${URL}"
- log "info" " --content=${CONTENT}"
for arg in $ARGS; do
log "info" " ${arg}"
@@ -45,7 +44,7 @@
log "info" " Logs will be output to '${LOG_PATH}/${LOG}'"
- eval "${SSH}\"/home/pi/coeg/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} \" 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
+ eval "${SSH}\"/home/pi/coeg/loader_app --url=\"\"${URL}\"\" ${ARGS} \" 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
eval $__LOADER=$!
diff --git a/src/starboard/evergreen/testing/tests/alternative_content_test.sh b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
index 84601d9..f3f6bb8 100755
--- a/src/starboard/evergreen/testing/tests/alternative_content_test.sh
+++ b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
@@ -32,7 +32,7 @@
return 1
fi
- cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "App is up to date" "--content=${CONTENT}"
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.1.log" "App is up to date" "--content=${STORAGE_DIR}/installation_1/content"
if [[ $? -ne 0 ]]; then
log "error" "Failed to find 'App is up to date' indicating failure"
diff --git a/src/starboard/loader_app/app_key_internal.cc b/src/starboard/loader_app/app_key_internal.cc
index 4fcd641..12e2f62 100644
--- a/src/starboard/loader_app/app_key_internal.cc
+++ b/src/starboard/loader_app/app_key_internal.cc
@@ -115,6 +115,11 @@
output.resize(output_size);
+ // Replace the '+' and '/' characters with '-' and '_' so that they are safe
+ // to use in a URL and with the filesystem.
+ std::replace(output.begin(), output.end(), '+', '-');
+ std::replace(output.begin(), output.end(), '/', '_');
+
return output;
}
diff --git a/src/starboard/loader_app/app_key_test.cc b/src/starboard/loader_app/app_key_test.cc
index 8207ee4..af3d5e1 100644
--- a/src/starboard/loader_app/app_key_test.cc
+++ b/src/starboard/loader_app/app_key_test.cc
@@ -673,5 +673,18 @@
}
}
+TEST(AppKeyTest, SunnyDayExtractAppKeySanitizesResult) {
+ // This magic is 11111011 11110000 in binary and thus begins with '+' and '/'
+ // when base64 encoded.
+ uint8_t magic[3] = {0xFB, 0xF0, 0x00};
+
+ const std::string actual =
+ ExtractAppKey(reinterpret_cast<const char*>(magic));
+
+ // Check that the '+' and '/' characters were replaced with '-' and '_'.
+ EXPECT_EQ("\xFB\xF0", actual);
+ EXPECT_EQ("-_A=", EncodeAppKey(actual));
+}
+
} // namespace loader_app
} // namespace starboard
diff --git a/src/starboard/sabi/README.md b/src/starboard/sabi/README.md
new file mode 100644
index 0000000..7becc8b
--- /dev/null
+++ b/src/starboard/sabi/README.md
@@ -0,0 +1,4 @@
+**IMPORTANT:** None of the files in this directory or any subdirectory should be
+modified. These files are used to define the Starboard ABI for a particular
+platform, and any changes can cause the binaries generated using those changes
+to no longer be compatible with any Evergreen binaries.
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
index c1cf847..fce1821 100644
--- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
+++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -18,6 +18,7 @@
#include "starboard/audio_sink.h"
#include <functional>
+#include <string>
#include "starboard/configuration.h"
#include "starboard/shared/internal_only.h"
@@ -27,8 +28,9 @@
// When |capability_changed| is true, it hints that the error is caused by a
// a transisent capability on the platform. The app should retry playback to
// recover from the error.
- // TODO: Allow to pass an error message.
- typedef void (*ErrorFunc)(bool capability_changed, void* context);
+ typedef void (*ErrorFunc)(bool capability_changed,
+ const std::string& error_message,
+ void* context);
#endif // SB_API_VERSION >= 12
typedef std::function<
diff --git a/src/starboard/shared/starboard/media/avc_util.cc b/src/starboard/shared/starboard/media/avc_util.cc
index d5b9620..15b5ba9 100644
--- a/src/starboard/shared/starboard/media/avc_util.cc
+++ b/src/starboard/shared/starboard/media/avc_util.cc
@@ -117,10 +117,12 @@
}
parameter_sets_.push_back(nalu);
combined_size_in_bytes_ += nalu.size();
- } else {
+ } else if (nalu[kAnnexBHeaderSizeInBytes] == kIdrStartCode) {
break;
}
}
+ SB_LOG_IF(ERROR, first_sps_index_ == -1 || first_pps_index_ == -1)
+ << "AVC parameter set NALUs not found.";
}
AvcParameterSets AvcParameterSets::ConvertTo(Format new_format) const {
diff --git a/src/starboard/shared/starboard/media/avc_util.h b/src/starboard/shared/starboard/media/avc_util.h
index d603aac..c9d7931 100644
--- a/src/starboard/shared/starboard/media/avc_util.h
+++ b/src/starboard/shared/starboard/media/avc_util.h
@@ -45,6 +45,7 @@
};
static const size_t kAnnexBHeaderSizeInBytes = 4;
+ static const uint8_t kIdrStartCode = 0x65;
static const uint8_t kSpsStartCode = 0x67;
static const uint8_t kPpsStartCode = 0x68;
diff --git a/src/starboard/shared/starboard/media/avc_util_test.cc b/src/starboard/shared/starboard/media/avc_util_test.cc
index 09345cb..a6b954e 100644
--- a/src/starboard/shared/starboard/media/avc_util_test.cc
+++ b/src/starboard/shared/starboard/media/avc_util_test.cc
@@ -33,7 +33,7 @@
const auto kAnnexBHeaderSizeInBytes =
AvcParameterSets::kAnnexBHeaderSizeInBytes;
const uint8_t kSliceStartCode = 0x61;
-const uint8_t kIdrStartCode = 0x65;
+const uint8_t kIdrStartCode = AvcParameterSets::kIdrStartCode;
const uint8_t kSpsStartCode = AvcParameterSets::kSpsStartCode;
const uint8_t kPpsStartCode = AvcParameterSets::kPpsStartCode;
@@ -323,7 +323,7 @@
}
}
-TEST(AvcParameterSetsTest, SpsAfterPayload) {
+TEST(AvcParameterSetsTest, SpsAfterIdr) {
auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB;
auto nalus_in_annex_b =
parameter_sets_in_annex_b + kIdrInAnnexB + kSpsInAnnexB;
@@ -334,7 +334,7 @@
HasEqualParameterSets(parameter_sets_in_annex_b, nalus_in_annex_b));
}
-TEST(AvcParameterSetsTest, PpsAfterPayload) {
+TEST(AvcParameterSetsTest, PpsAfterIdr) {
auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB;
auto nalus_in_annex_b =
parameter_sets_in_annex_b + kIdrInAnnexB + kPpsInAnnexB;
@@ -345,7 +345,7 @@
HasEqualParameterSets(parameter_sets_in_annex_b, nalus_in_annex_b));
}
-TEST(AvcParameterSetsTest, SpsAndPpsAfterPayloadWithoutSpsAndPps) {
+TEST(AvcParameterSetsTest, SpsAndPpsAfterIdrWithoutSpsAndPps) {
auto parameter_sets_in_annex_b = kSpsInAnnexB + kPpsInAnnexB;
auto nalus_in_annex_b =
kIdrInAnnexB + kPpsInAnnexB + parameter_sets_in_annex_b;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc
index 1e3bb83..12ce929 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.cc
@@ -15,6 +15,7 @@
#include "starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h"
#include <algorithm>
+#include <string>
#include "starboard/memory.h"
#include "starboard/shared/starboard/media/media_util.h"
@@ -449,15 +450,16 @@
}
}
-void AudioRendererImpl::OnError(bool capability_changed) {
+void AudioRendererImpl::OnError(bool capability_changed,
+ const std::string& error_message) {
SB_DCHECK(error_cb_);
if (capability_changed) {
- error_cb_(kSbPlayerErrorCapabilityChanged, "failed to start audio sink");
+ error_cb_(kSbPlayerErrorCapabilityChanged, error_message);
} else {
// Send |kSbPlayerErrorDecode| on fatal audio sink error. The error code
// will be mapped into MediaError eventually, and there is no corresponding
// error code in MediaError for audio sink error anyway.
- error_cb_(kSbPlayerErrorDecode, "failed to start audio sink");
+ error_cb_(kSbPlayerErrorDecode, error_message);
}
}
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h
index 90e9a1b..1129b9d 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal_impl.h
@@ -16,6 +16,7 @@
#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_IMPL_H_
#include <functional>
+#include <string>
#include <vector>
#include "starboard/atomic.h"
@@ -138,7 +139,8 @@
bool* is_playing,
bool* is_eos_reached) override;
void ConsumeFrames(int frames_consumed, SbTime frames_consumed_at) override;
- void OnError(bool capability_changed) override;
+ void OnError(bool capability_changed,
+ const std::string& error_message) override;
void UpdateVariablesOnSinkThread_Locked(SbTime system_time_on_consume_frames);
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h b/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h
index 7142d0a..1859e7c 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink.h
@@ -15,6 +15,8 @@
#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_H_
#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_H_
+#include <string>
+
#include "starboard/audio_sink.h"
#include "starboard/shared/internal_only.h"
#include "starboard/time.h"
@@ -40,7 +42,8 @@
// When |capability_changed| is true, it hints that the error is caused by a
// a transisent capability on the platform. The app should retry playback
// to recover from the error.
- virtual void OnError(bool capability_changed) = 0;
+ virtual void OnError(bool capability_changed,
+ const std::string& error_message) = 0;
protected:
~RenderCallback() {}
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc
index 61a48db..4970a5b 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.cc
@@ -14,6 +14,8 @@
#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h"
+#include <string>
+
#include "starboard/common/log.h"
#include "starboard/configuration_constants.h"
#include "starboard/shared/starboard/thread_checker.h"
@@ -187,13 +189,16 @@
}
// static
-void AudioRendererSinkImpl::ErrorFunc(bool capability_changed, void* context) {
+void AudioRendererSinkImpl::ErrorFunc(bool capability_changed,
+ const std::string& error_message,
+ void* context) {
AudioRendererSinkImpl* audio_renderer_sink =
static_cast<AudioRendererSinkImpl*>(context);
SB_DCHECK(audio_renderer_sink);
SB_DCHECK(audio_renderer_sink->render_callback_);
- audio_renderer_sink->render_callback_->OnError(capability_changed);
+ audio_renderer_sink->render_callback_->OnError(capability_changed,
+ error_message);
}
} // namespace filter
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h
index 239180f..319cb32 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h
@@ -16,6 +16,7 @@
#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_SINK_IMPL_H_
#include <functional>
+#include <string>
#include "starboard/audio_sink.h"
#include "starboard/shared/internal_only.h"
@@ -57,8 +58,8 @@
SbMediaAudioSampleType audio_sample_type) const override;
bool IsAudioFrameStorageTypeSupported(
SbMediaAudioFrameStorageType audio_frame_storage_type) const override;
- int GetNearestSupportedSampleFrequency(int sampling_frequency_hz) const
- override;
+ int GetNearestSupportedSampleFrequency(
+ int sampling_frequency_hz) const override;
bool HasStarted() const override;
void Start(SbTime media_start_time,
@@ -83,7 +84,9 @@
static void ConsumeFramesFunc(int frames_consumed,
SbTime frames_consumed_at,
void* context);
- static void ErrorFunc(bool capability_changed, void* context);
+ static void ErrorFunc(bool capability_changed,
+ const std::string& error_message,
+ void* context);
ThreadChecker thread_checker_;
const CreateAudioSinkFunc create_audio_sink_func_;
diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc
index 5e7bb68..9a55ac8 100644
--- a/src/starboard/shared/starboard/player/player_create.cc
+++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -88,7 +88,7 @@
&creation_param->audio_sample_info;
const auto output_mode = creation_param->output_mode;
-#else // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+#else // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
SbPlayer SbPlayerCreate(SbWindow window,
SbMediaVideoCodec video_codec,
@@ -169,8 +169,11 @@
if (audio_sample_info &&
audio_sample_info->number_of_channels > SbAudioSinkGetMaxChannels()) {
- SB_LOG(ERROR) << "audio_sample_info->number_of_channels exceeds the maximum"
- << " number of audio channels supported by this platform.";
+ SB_LOG(ERROR) << "audio_sample_info->number_of_channels ("
+ << audio_sample_info->number_of_channels
+ << ") exceeds the maximum"
+ << " number of audio channels supported by this platform ("
+ << SbAudioSinkGetMaxChannels() << ").";
return kSbPlayerInvalid;
}
diff --git a/src/starboard/stub/javascript_cache.cc b/src/starboard/stub/javascript_cache.cc
new file mode 100644
index 0000000..82e8bdd
--- /dev/null
+++ b/src/starboard/stub/javascript_cache.cc
@@ -0,0 +1,60 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/stub/javascript_cache.h"
+
+#include <functional>
+#include <string>
+
+#include "cobalt/extension/javascript_cache.h"
+#include "starboard/common/log.h"
+#include "starboard/file.h"
+
+namespace starboard {
+namespace stub {
+
+namespace {
+
+bool GetCachedScript(uint32_t key,
+ int source_length,
+ const uint8_t** cache_data_out,
+ int* cache_data_length) {
+ return false;
+}
+
+void ReleaseCachedScriptData(const uint8_t* cache_data) {}
+
+bool StoreCachedScript(uint32_t key,
+ int source_length,
+ const uint8_t* cache_data,
+ int cache_data_length) {
+ return false;
+}
+
+const CobaltExtensionJavaScriptCacheApi kJavaScriptCacheApi = {
+ kCobaltExtensionJavaScriptCacheName,
+ 1, // API version that's implemented.
+ &GetCachedScript,
+ &ReleaseCachedScriptData,
+ &StoreCachedScript,
+};
+
+} // namespace
+
+const void* GetJavaScriptCacheApi() {
+ return &kJavaScriptCacheApi;
+}
+
+} // namespace stub
+} // namespace starboard
diff --git a/src/starboard/stub/javascript_cache.h b/src/starboard/stub/javascript_cache.h
new file mode 100644
index 0000000..e0e04d6
--- /dev/null
+++ b/src/starboard/stub/javascript_cache.h
@@ -0,0 +1,26 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_STUB_JAVASCRIPT_CACHE_H_
+#define STARBOARD_STUB_JAVASCRIPT_CACHE_H_
+
+namespace starboard {
+namespace stub {
+
+const void* GetJavaScriptCacheApi();
+
+} // namespace stub
+} // namespace starboard
+
+#endif // STARBOARD_STUB_JAVASCRIPT_CACHE_H_
diff --git a/src/starboard/stub/starboard_platform.gyp b/src/starboard/stub/starboard_platform.gyp
index 717849d..62cceef 100644
--- a/src/starboard/stub/starboard_platform.gyp
+++ b/src/starboard/stub/starboard_platform.gyp
@@ -29,6 +29,8 @@
'configuration_constants.cc',
'font.cc',
'font.h',
+ 'javascript_cache.h',
+ 'javascript_cache.cc',
'main.cc',
'system_get_extensions.cc',
'thread_types_public.h',
diff --git a/src/starboard/stub/system_get_extensions.cc b/src/starboard/stub/system_get_extensions.cc
index 22e8777..f3f9783 100644
--- a/src/starboard/stub/system_get_extensions.cc
+++ b/src/starboard/stub/system_get_extensions.cc
@@ -16,9 +16,11 @@
#include "cobalt/extension/configuration.h"
#include "cobalt/extension/font.h"
+#include "cobalt/extension/javascript_cache.h"
#include "starboard/common/string.h"
#include "starboard/stub/configuration.h"
#include "starboard/stub/font.h"
+#include "starboard/stub/javascript_cache.h"
const void* SbSystemGetExtension(const char* name) {
if (SbStringCompareAll(name, kCobaltExtensionConfigurationName) == 0) {
@@ -27,5 +29,8 @@
if (SbStringCompareAll(name, kCobaltExtensionFontName) == 0) {
return starboard::stub::GetFontApi();
}
+ if (SbStringCompareAll(name, kCobaltExtensionJavaScriptCacheName) == 0) {
+ return starboard::stub::GetJavaScriptCacheApi();
+ }
return NULL;
}
diff --git a/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h b/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h
index c343d6b..8ff3a80 100644
--- a/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h
+++ b/src/third_party/mozjs-45/js/src/gc/StoreBuffer.h
@@ -296,7 +296,7 @@
explicit operator bool() const { return objectAndKind_ != 0; }
- typedef struct {
+ typedef struct Hasher {
typedef SlotsEdge Lookup;
static HashNumber hash(const Lookup& l) { return l.objectAndKind_ ^ l.start_ ^ l.count_; }
static bool match(const SlotsEdge& k, const Lookup& l) { return k == l; }
diff --git a/src/tools/gyp/pylib/gyp/win_tool.py b/src/tools/gyp/pylib/gyp/win_tool.py
index 6b950b7..c79ffd7 100755
--- a/src/tools/gyp/pylib/gyp/win_tool.py
+++ b/src/tools/gyp/pylib/gyp/win_tool.py
@@ -69,6 +69,14 @@
shutil.copytree(source, dest, ignore=shutil.ignore_patterns(r'.git'))
else:
shutil.copy2(source, dest)
+ # Hack to make target look 2 seconds newer, to prevent ninja erroneously re-trigger
+ # copy rules on nanosecond differences. Risk of getting stale content files due to this
+ # is almost non-existent
+ OFFSET_SECONDS = 2
+ stat = os.stat(source)
+ mtime = stat.st_mtime + OFFSET_SECONDS
+ atime = stat.st_atime + OFFSET_SECONDS
+ os.utime(dest, (atime, mtime))
if platform.system() == 'Windows':
def ExecLinkWrapper(self, arch, *args):