summaryrefslogtreecommitdiff
path: root/chromecast
diff options
context:
space:
mode:
authorPrimiano Tucci <primiano@google.com>2014-09-30 14:45:55 +0100
committerPrimiano Tucci <primiano@google.com>2014-09-30 14:45:55 +0100
commit1320f92c476a1ad9d19dba2a48c72b75566198e9 (patch)
treeea7f149ccad687b22c18a72b729646568b2d54fb /chromecast
parent39b78c562f50ad7d5551ee861121f899239525a2 (diff)
downloadchromium_org-1320f92c476a1ad9d19dba2a48c72b75566198e9.tar.gz
Merge from Chromium at DEPS revision 267aeeb8d85c
This commit was generated by merge_to_master.py. Change-Id: Id3aac9713b301fae64408cdaee0888724eeb7c0e
Diffstat (limited to 'chromecast')
-rw-r--r--chromecast/DEPS1
-rw-r--r--chromecast/android/cast_jni_registrar.cc29
-rw-r--r--chromecast/android/cast_jni_registrar.h19
-rw-r--r--chromecast/android/chromecast_config_android.cc33
-rw-r--r--chromecast/android/chromecast_config_android.h43
-rw-r--r--chromecast/android/platform_jni_loader.h18
-rw-r--r--chromecast/android/platform_jni_loader_stub.cc16
-rw-r--r--chromecast/android/src/dummy2
-rw-r--r--chromecast/chromecast.gyp289
-rw-r--r--chromecast/common/cast_paths.cc13
-rw-r--r--chromecast/common/cast_paths.h5
-rw-r--r--chromecast/common/chromecast_config.cc14
-rw-r--r--chromecast/common/chromecast_config.h11
-rw-r--r--chromecast/common/chromecast_switches.cc12
-rw-r--r--chromecast/common/chromecast_switches.h17
-rw-r--r--chromecast/common/global_descriptors.h21
-rw-r--r--chromecast/common/version.h.in2
-rw-r--r--chromecast/media/DEPS4
-rw-r--r--chromecast/media/base/key_systems_common.cc39
-rw-r--r--chromecast/media/base/key_systems_common.h33
-rw-r--r--chromecast/media/base/key_systems_common_simple.cc15
-rw-r--r--chromecast/media/cma/base/balanced_media_task_runner_factory.cc252
-rw-r--r--chromecast/media/cma/base/balanced_media_task_runner_factory.h68
-rw-r--r--chromecast/media/cma/base/balanced_media_task_runner_unittest.cc263
-rw-r--r--chromecast/media/cma/base/buffering_controller.cc194
-rw-r--r--chromecast/media/cma/base/buffering_controller.h108
-rw-r--r--chromecast/media/cma/base/buffering_controller_unittest.cc133
-rw-r--r--chromecast/media/cma/base/buffering_frame_provider.cc140
-rw-r--r--chromecast/media/cma/base/buffering_frame_provider.h117
-rw-r--r--chromecast/media/cma/base/buffering_frame_provider_unittest.cc187
-rw-r--r--chromecast/media/cma/base/buffering_state.cc129
-rw-r--r--chromecast/media/cma/base/buffering_state.h138
-rw-r--r--chromecast/media/cma/base/cma_logging.h23
-rw-r--r--chromecast/media/cma/base/coded_frame_provider.cc17
-rw-r--r--chromecast/media/cma/base/coded_frame_provider.h51
-rw-r--r--chromecast/media/cma/base/decoder_buffer_adapter.cc45
-rw-r--r--chromecast/media/cma/base/decoder_buffer_adapter.h45
-rw-r--r--chromecast/media/cma/base/decoder_buffer_base.cc17
-rw-r--r--chromecast/media/cma/base/decoder_buffer_base.h57
-rw-r--r--chromecast/media/cma/base/frame_generator_for_test.cc113
-rw-r--r--chromecast/media/cma/base/frame_generator_for_test.h59
-rw-r--r--chromecast/media/cma/base/media_task_runner.cc17
-rw-r--r--chromecast/media/cma/base/media_task_runner.h44
-rw-r--r--chromecast/media/cma/base/mock_frame_consumer.cc116
-rw-r--r--chromecast/media/cma/base/mock_frame_consumer.h69
-rw-r--r--chromecast/media/cma/base/mock_frame_provider.cc95
-rw-r--r--chromecast/media/cma/base/mock_frame_provider.h49
-rw-r--r--chromecast/media/cma/base/run_all_unittests.cc40
-rw-r--r--chromecast/media/cma/filters/demuxer_stream_adapter.cc207
-rw-r--r--chromecast/media/cma/filters/demuxer_stream_adapter.h93
-rw-r--r--chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc345
-rw-r--r--chromecast/media/cma/ipc/media_memory_chunk.cc14
-rw-r--r--chromecast/media/cma/ipc/media_memory_chunk.h39
-rw-r--r--chromecast/media/cma/ipc/media_message.cc198
-rw-r--r--chromecast/media/cma/ipc/media_message.h165
-rw-r--r--chromecast/media/cma/ipc/media_message_fifo.cc401
-rw-r--r--chromecast/media/cma/ipc/media_message_fifo.h208
-rw-r--r--chromecast/media/cma/ipc/media_message_fifo_unittest.cc195
-rw-r--r--chromecast/media/cma/ipc/media_message_type.h15
-rw-r--r--chromecast/media/cma/ipc/media_message_unittest.cc146
-rw-r--r--chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc70
-rw-r--r--chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h27
-rw-r--r--chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc200
-rw-r--r--chromecast/media/cma/ipc_streamer/av_streamer_proxy.h88
-rw-r--r--chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc253
-rw-r--r--chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc96
-rw-r--r--chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h61
-rw-r--r--chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc161
-rw-r--r--chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h29
-rw-r--r--chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc75
-rw-r--r--chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h31
-rw-r--r--chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc113
-rw-r--r--chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h27
-rw-r--r--chromecast/media/media.gyp154
-rw-r--r--chromecast/metrics/DEPS3
-rw-r--r--chromecast/metrics/OWNERS5
-rw-r--r--chromecast/metrics/cast_metrics_prefs.cc17
-rw-r--r--chromecast/metrics/cast_metrics_prefs.h18
-rw-r--r--chromecast/metrics/cast_metrics_service_client.cc164
-rw-r--r--chromecast/metrics/cast_metrics_service_client.h80
-rw-r--r--chromecast/metrics/cast_metrics_service_client_unittest.cc42
-rw-r--r--chromecast/metrics/platform_metrics_providers.h39
-rw-r--r--chromecast/metrics/platform_metrics_providers_simple.cc33
-rw-r--r--chromecast/net/network_change_notifier_factory_cast.cc16
-rw-r--r--chromecast/service/cast_service.h16
-rw-r--r--chromecast/service/cast_service_android.cc36
-rw-r--r--chromecast/service/cast_service_android.h30
-rw-r--r--chromecast/service/cast_service_simple.cc7
-rw-r--r--chromecast/service/cast_service_simple.h2
-rw-r--r--chromecast/shell/android/DEPS3
-rw-r--r--chromecast/shell/android/apk/AndroidManifest.xml142
-rw-r--r--chromecast/shell/android/apk/res/layout/cast_shell_activity.xml13
-rw-r--r--chromecast/shell/android/apk/res/layout/cast_window_view.xml18
-rw-r--r--chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.pngbin0 -> 5522 bytes
-rw-r--r--chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.pngbin0 -> 3841 bytes
-rw-r--r--chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.pngbin0 -> 7377 bytes
-rw-r--r--chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.pngbin0 -> 7155 bytes
-rw-r--r--chromecast/shell/android/apk/res/values-v17/styles.xml12
-rw-r--r--chromecast/shell/android/apk/res/values/strings.xml11
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java35
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java100
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java291
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java161
-rw-r--r--chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java157
-rw-r--r--chromecast/shell/app/DEPS1
-rw-r--r--chromecast/shell/app/android/cast_jni_loader.cc36
-rw-r--r--chromecast/shell/app/cast_main_delegate.cc56
-rw-r--r--chromecast/shell/app/cast_main_delegate.h13
-rw-r--r--chromecast/shell/browser/android/cast_window_android.cc173
-rw-r--r--chromecast/shell/browser/android/cast_window_android.h97
-rw-r--r--chromecast/shell/browser/android/cast_window_manager.cc88
-rw-r--r--chromecast/shell/browser/android/cast_window_manager.h35
-rw-r--r--chromecast/shell/browser/cast_browser_context.cc31
-rw-r--r--chromecast/shell/browser/cast_browser_context.h2
-rw-r--r--chromecast/shell/browser/cast_browser_main_parts.cc52
-rw-r--r--chromecast/shell/browser/cast_browser_main_parts.h21
-rw-r--r--chromecast/shell/browser/cast_browser_process.cc60
-rw-r--r--chromecast/shell/browser/cast_browser_process.h63
-rw-r--r--chromecast/shell/browser/cast_content_browser_client.cc44
-rw-r--r--chromecast/shell/browser/cast_content_browser_client.h5
-rw-r--r--chromecast/shell/browser/cast_download_manager_delegate.cc58
-rw-r--r--chromecast/shell/browser/cast_download_manager_delegate.h41
-rw-r--r--chromecast/shell/browser/cast_http_user_agent_settings.cc11
-rw-r--r--chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc133
-rw-r--r--chromecast/shell/browser/devtools/cast_dev_tools_delegate.h33
-rw-r--r--chromecast/shell/browser/devtools/remote_debugging_server.cc67
-rw-r--r--chromecast/shell/browser/test/DEPS3
-rw-r--r--chromecast/shell/browser/test/OWNERS4
-rw-r--r--chromecast/shell/browser/test/chromecast_browser_test.cc79
-rw-r--r--chromecast/shell/browser/test/chromecast_browser_test.h56
-rw-r--r--chromecast/shell/browser/test/chromecast_browser_test_runner.cc68
-rw-r--r--chromecast/shell/browser/test/chromecast_shell_browser_test.cc36
-rw-r--r--chromecast/shell/browser/url_request_context_factory.cc41
-rw-r--r--chromecast/shell/browser/url_request_context_factory.h3
-rw-r--r--chromecast/shell/common/cast_content_client.cc3
-rw-r--r--chromecast/shell/renderer/DEPS1
-rw-r--r--chromecast/shell/renderer/cast_content_renderer_client.cc5
-rw-r--r--chromecast/shell/renderer/key_systems_cast.cc43
-rw-r--r--chromecast/shell/renderer/key_systems_cast.h30
-rw-r--r--chromecast/shell/renderer/key_systems_cast_simple.cc16
140 files changed, 9398 insertions, 194 deletions
diff --git a/chromecast/DEPS b/chromecast/DEPS
index 438dbc008d..a10b9578e9 100644
--- a/chromecast/DEPS
+++ b/chromecast/DEPS
@@ -5,6 +5,7 @@ include_rules = [
"+crypto",
"+grit/chromecast_settings.h",
"+grit/shell_resources.h",
+ "+jni",
"+net",
"+ui",
]
diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc
new file mode 100644
index 0000000000..e8bae63a75
--- /dev/null
+++ b/chromecast/android/cast_jni_registrar.cc
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/cast_jni_registrar.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "chromecast/shell/browser/android/cast_window_android.h"
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+
+namespace chromecast {
+namespace android {
+
+namespace {
+
+static base::android::RegistrationMethod kMethods[] = {
+ { "CastWindowAndroid", shell::CastWindowAndroid::RegisterJni },
+ { "CastWindowManager", shell::RegisterCastWindowManager },
+};
+
+} // namespace
+
+bool RegisterJni(JNIEnv* env) {
+ return RegisterNativeMethods(env, kMethods, arraysize(kMethods));
+}
+
+} // namespace android
+} // namespace chromecast
diff --git a/chromecast/android/cast_jni_registrar.h b/chromecast/android/cast_jni_registrar.h
new file mode 100644
index 0000000000..b07316bd98
--- /dev/null
+++ b/chromecast/android/cast_jni_registrar.h
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
+#define CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
+
+#include <jni.h>
+
+namespace chromecast {
+namespace android {
+
+// Register all JNI bindings necessary for the Android cast shell.
+bool RegisterJni(JNIEnv* env);
+
+} // namespace android
+} // namespace chromecast
+
+#endif // CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_
diff --git a/chromecast/android/chromecast_config_android.cc b/chromecast/android/chromecast_config_android.cc
new file mode 100644
index 0000000000..d698b70a18
--- /dev/null
+++ b/chromecast/android/chromecast_config_android.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/chromecast_config_android.h"
+
+namespace chromecast {
+namespace android {
+
+namespace {
+base::LazyInstance<ChromecastConfigAndroid> g_instance =
+ LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+// static
+ChromecastConfigAndroid* ChromecastConfigAndroid::GetInstance() {
+ return g_instance.Pointer();
+}
+
+ChromecastConfigAndroid::ChromecastConfigAndroid() {
+}
+
+ChromecastConfigAndroid::~ChromecastConfigAndroid() {
+}
+
+// Registers a handler to be notified when SendUsageStats is changed.
+void ChromecastConfigAndroid::SetSendUsageStatsChangedCallback(
+ const base::Callback<void(bool)>& callback) {
+ send_usage_stats_changed_callback_ = callback;
+}
+
+} // namespace android
+} // namespace chromecast
diff --git a/chromecast/android/chromecast_config_android.h b/chromecast/android/chromecast_config_android.h
new file mode 100644
index 0000000000..627259e994
--- /dev/null
+++ b/chromecast/android/chromecast_config_android.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
+#define CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+
+namespace chromecast {
+namespace android {
+
+class ChromecastConfigAndroid {
+ public:
+ static ChromecastConfigAndroid* GetInstance();
+
+ // Registers a handler to be notified when SendUsageStats is changed.
+ void SetSendUsageStatsChangedCallback(
+ const base::Callback<void(bool)>& callback);
+
+ const base::Callback<void(bool)>& send_usage_stats_changed_callback() const {
+ return send_usage_stats_changed_callback_;
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<ChromecastConfigAndroid>;
+
+ ChromecastConfigAndroid();
+ ~ChromecastConfigAndroid();
+
+ base::Callback<void(bool)> send_usage_stats_changed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromecastConfigAndroid);
+};
+
+} // namespace android
+} // namespace chromecast
+
+#endif // CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_
diff --git a/chromecast/android/platform_jni_loader.h b/chromecast/android/platform_jni_loader.h
new file mode 100644
index 0000000000..76307461a2
--- /dev/null
+++ b/chromecast/android/platform_jni_loader.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
+#define CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
+
+#include <jni.h>
+
+namespace chromecast {
+namespace android {
+
+bool PlatformRegisterJni(JNIEnv* env);
+
+} // namespace android
+} // namespace chromecast
+
+#endif // CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_
diff --git a/chromecast/android/platform_jni_loader_stub.cc b/chromecast/android/platform_jni_loader_stub.cc
new file mode 100644
index 0000000000..0f8d814b0f
--- /dev/null
+++ b/chromecast/android/platform_jni_loader_stub.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/android/platform_jni_loader.h"
+
+namespace chromecast {
+namespace android {
+
+bool PlatformRegisterJni(JNIEnv* env) {
+ // Intentional no-op for public build.
+ return true;
+}
+
+} // namespace android
+} // namespace chromecast
diff --git a/chromecast/android/src/dummy b/chromecast/android/src/dummy
new file mode 100644
index 0000000000..c1069e016a
--- /dev/null
+++ b/chromecast/android/src/dummy
@@ -0,0 +1,2 @@
+Note(gunsch): This file is for the cast_shell_apk target in chromecast.gyp.
+See the notes above that target's 'java_in_dir' variable in chromecast.gyp.
diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp
index 070ffc73ba..9ba706aa61 100644
--- a/chromecast/chromecast.gyp
+++ b/chromecast/chromecast.gyp
@@ -26,6 +26,8 @@
'common/cast_resource_delegate.h',
'common/chromecast_config.cc',
'common/chromecast_config.h',
+ 'common/chromecast_switches.cc',
+ 'common/chromecast_switches.h',
'common/pref_names.cc',
'common/pref_names.h',
],
@@ -42,6 +44,61 @@
],
},
{
+ 'target_name': 'cast_metrics',
+ 'type': '<(component)',
+ 'dependencies': [
+ 'cast_common',
+ '../components/components.gyp:component_metrics_proto',
+ '../components/components.gyp:metrics',
+ '../components/components.gyp:metrics_gpu',
+ '../components/components.gyp:metrics_net',
+ '../components/components.gyp:metrics_profiler',
+ ],
+ 'sources': [
+ 'metrics/cast_metrics_prefs.cc',
+ 'metrics/cast_metrics_prefs.h',
+ 'metrics/cast_metrics_service_client.cc',
+ 'metrics/cast_metrics_service_client.h',
+ 'metrics/platform_metrics_providers.h',
+ ],
+ 'conditions': [
+ ['chromecast_branding=="Chrome"', {
+ 'dependencies': [
+ '<(cast_internal_gyp):cast_metrics_internal',
+ ],
+ }, {
+ 'sources': [
+ 'metrics/platform_metrics_providers_simple.cc',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'cast_metrics_unittests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ 'cast_metrics',
+ '../base/base.gyp:base_prefs_test_support',
+ '../base/base.gyp:run_all_unittests',
+ '../base/base.gyp:test_support_base',
+ '../components/components.gyp:component_metrics_proto',
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ 'metrics/cast_metrics_service_client_unittest.cc',
+ ],
+ }, # end of target 'cast_metrics_unittests'
+ {
+ 'target_name': 'cast_net',
+ 'type': '<(component)',
+ 'sources': [
+ 'net/network_change_notifier_cast.cc',
+ 'net/network_change_notifier_cast.h',
+ 'net/network_change_notifier_factory_cast.cc',
+ 'net/network_change_notifier_factory_cast.h',
+ ],
+ },
+ {
'target_name': 'cast_service',
'type': '<(component)',
'dependencies': [
@@ -61,9 +118,18 @@
'../base/base.gyp:base',
'../content/content.gyp:content',
],
- 'sources': [
- 'service/cast_service_simple.cc',
- 'service/cast_service_simple.h',
+ 'conditions': [
+ ['OS=="android"', {
+ 'sources': [
+ 'service/cast_service_android.cc',
+ 'service/cast_service_android.h',
+ ],
+ }, {
+ 'sources': [
+ 'service/cast_service_simple.cc',
+ 'service/cast_service_simple.h',
+ ],
+ }],
],
}],
],
@@ -94,7 +160,6 @@
'../content/app/resources/content_resources.gyp:content_resources',
'../content/app/strings/content_strings.gyp:content_strings',
'../content/browser/devtools/devtools_resources.gyp:devtools_resources',
- '../content/content_resources.gyp:content_resources',
'../net/net.gyp:net_resources',
'../third_party/WebKit/public/blink_resources.gyp:blink_resources',
'../ui/resources/ui_resources.gyp:ui_resources',
@@ -123,36 +188,42 @@
},
],
},
+ # This target contains all content-embedder implementation that is
+ # non-platform-specific.
{
- 'target_name': 'cast_shell',
- 'type': 'executable',
+ 'target_name': 'cast_shell_common',
+ 'type': '<(component)',
'dependencies': [
'cast_common',
+ 'cast_metrics',
'cast_service',
'cast_shell_pak',
'cast_shell_resources',
'cast_version_header',
'chromecast_locales.gyp:chromecast_locales_pak',
'chromecast_locales.gyp:chromecast_settings',
- '../ui/aura/aura.gyp:aura_test_support',
+ 'media/media.gyp:media_base',
+ '../components/components.gyp:cdm_renderer',
+ '../components/components.gyp:component_metrics_proto',
'../content/content.gyp:content',
'../content/content.gyp:content_app_browser',
'../skia/skia.gyp:skia',
+ '../third_party/WebKit/public/blink.gyp:blink',
+ '../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h',
],
'sources': [
- 'net/network_change_notifier_cast.cc',
- 'net/network_change_notifier_cast.h',
- 'net/network_change_notifier_factory_cast.cc',
- 'net/network_change_notifier_factory_cast.h',
- 'shell/app/cast_main.cc',
'shell/app/cast_main_delegate.cc',
'shell/app/cast_main_delegate.h',
'shell/browser/cast_browser_context.cc',
'shell/browser/cast_browser_context.h',
'shell/browser/cast_browser_main_parts.cc',
'shell/browser/cast_browser_main_parts.h',
+ 'shell/browser/cast_browser_process.cc',
+ 'shell/browser/cast_browser_process.h',
'shell/browser/cast_content_browser_client.cc',
'shell/browser/cast_content_browser_client.h',
+ 'shell/browser/cast_download_manager_delegate.cc',
+ 'shell/browser/cast_download_manager_delegate.h',
'shell/browser/cast_http_user_agent_settings.cc',
'shell/browser/cast_http_user_agent_settings.h',
'shell/browser/devtools/cast_dev_tools_delegate.cc',
@@ -168,20 +239,19 @@
'shell/common/cast_content_client.h',
'shell/renderer/cast_content_renderer_client.cc',
'shell/renderer/cast_content_renderer_client.h',
+ 'shell/renderer/key_systems_cast.cc',
+ 'shell/renderer/key_systems_cast.h',
],
'conditions': [
['chromecast_branding=="Chrome"', {
'dependencies': [
- 'internal/chromecast_internal.gyp:cast_gfx_internal',
'internal/chromecast_internal.gyp:cast_shell_internal',
],
}, {
- 'dependencies': [
- '../ui/ozone/ozone.gyp:eglplatform_shim_x11',
- ],
'sources': [
'shell/browser/devtools/remote_debugging_server_simple.cc',
'shell/browser/webui/webui_cast_simple.cc',
+ 'shell/renderer/key_systems_cast_simple.cc',
],
}],
],
@@ -209,6 +279,9 @@
'python',
'<(version_py_path)',
'-e', 'VERSION_FULL="<(version_full)"',
+ # Revision is taken from buildbot if available; otherwise, a dev string is used.
+ '-e', 'CAST_BUILD_REVISION="<!(echo ${BUILD_NUMBER:="local.${USER}"})"',
+ '-e', 'CAST_IS_DEBUG_BUILD=1 if "<(CONFIGURATION_NAME)" == "Debug" else 0',
'common/version.h.in',
'<@(_outputs)',
],
@@ -218,5 +291,189 @@
},
],
},
+ {
+ 'target_name': 'cast_tests',
+ 'type': 'none',
+ 'dependencies': [
+ 'media/media.gyp:cast_media_unittests',
+ ],
+ },
], # end of targets
+
+ # Targets for Android receiver.
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'libcast_shell_android',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'cast_common',
+ 'cast_jni_headers',
+ 'cast_shell_common',
+ 'cast_shell_pak',
+ 'cast_version_header',
+ '../base/base.gyp:base',
+ '../content/content.gyp:content_app_browser',
+ '../content/content.gyp:content',
+ '../skia/skia.gyp:skia',
+ '../ui/gfx/gfx.gyp:gfx',
+ '../ui/gl/gl.gyp:gl',
+ ],
+ 'sources': [
+ 'android/cast_jni_registrar.cc',
+ 'android/cast_jni_registrar.h',
+ 'android/chromecast_config_android.cc',
+ 'android/chromecast_config_android.h',
+ 'android/platform_jni_loader.h',
+ 'shell/app/android/cast_jni_loader.cc',
+ 'shell/browser/android/cast_window_manager.cc',
+ 'shell/browser/android/cast_window_manager.h',
+ 'shell/browser/android/cast_window_android.cc',
+ 'shell/browser/android/cast_window_android.h',
+ ],
+ 'conditions': [
+ ['chromecast_branding=="Chrome"', {
+ 'dependencies': [
+ '<(cast_internal_gyp):cast_shell_android_internal'
+ ],
+ }, {
+ 'sources': [
+ 'android/platform_jni_loader_stub.cc',
+ ],
+ }]
+ ],
+ }, # end of target 'libcast_shell_android'
+ {
+ 'target_name': 'cast_shell_java',
+ 'type': 'none',
+ 'dependencies': [
+ '../base/base.gyp:base_java',
+ '../content/content.gyp:content_java',
+ '../media/media.gyp:media_java',
+ '../net/net.gyp:net_java',
+ '../third_party/android_tools/android_tools.gyp:android_support_v13_javalib',
+ '../ui/android/ui_android.gyp:ui_java',
+ ],
+ 'variables': {
+ 'has_java_resources': 1,
+ 'java_in_dir': 'shell/android/apk',
+ 'resource_dir': 'shell/android/apk/res',
+ 'R_package': 'org.chromium.chromecast.shell',
+ },
+ 'includes': ['../build/java.gypi'],
+ }, # end of target 'cast_shell_java'
+ {
+ 'target_name': 'cast_shell_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'cast_shell_java',
+ 'libcast_shell_android',
+ ],
+ 'variables': {
+ 'apk_name': 'CastShell',
+ 'manifest_package_name': 'org.chromium.chromecast.shell',
+ # Note(gunsch): there are no Java files in the android/ directory.
+ # Unfortunately, the java_apk.gypi target rigidly insists on having
+ # a java_in_dir directory, but complains about duplicate classes
+ # from the common cast_shell_java target (shared with internal APK)
+ # if the actual Java path is used.
+ # This will hopefully be removable after the great GN migration.
+ 'java_in_dir': 'android',
+ 'android_manifest_path': 'shell/android/apk/AndroidManifest.xml',
+ 'package_name': 'org.chromium.chromecast.shell',
+ 'native_lib_target': 'libcast_shell_android',
+ 'asset_location': '<(PRODUCT_DIR)/assets',
+ 'additional_input_paths': ['<(PRODUCT_DIR)/assets/cast_shell.pak'],
+ },
+ 'includes': [ '../build/java_apk.gypi' ],
+ },
+ {
+ 'target_name': 'cast_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java',
+ 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/chromecast',
+ ],
+ },
+ 'variables': {
+ 'jni_gen_package': 'chromecast',
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
+ ], # end of targets
+ }, { # OS != "android"
+ 'targets': [
+ # This target contains all of the primary code of |cast_shell|, except
+ # for |main|. This allows end-to-end tests using |cast_shell|.
+ # This also includes all targets that cannot be built on Android.
+ {
+ 'target_name': 'cast_shell_core',
+ 'type': '<(component)',
+ 'dependencies': [
+ 'cast_net',
+ 'cast_shell_common',
+ 'media/media.gyp:cast_media',
+ '../ui/aura/aura.gyp:aura_test_support',
+ ],
+ 'conditions': [
+ ['chromecast_branding=="Chrome"', {
+ 'dependencies': [
+ 'internal/chromecast_internal.gyp:cast_gfx_internal',
+ ],
+ }, {
+ 'dependencies': [
+ '../ui/ozone/ozone.gyp:eglplatform_shim_x11',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'cast_shell',
+ 'type': 'executable',
+ 'dependencies': [
+ 'cast_shell_core',
+ ],
+ 'sources': [
+ 'shell/app/cast_main.cc',
+ ],
+ },
+ {
+ 'target_name': 'cast_shell_browser_test',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ 'cast_shell_test_support',
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'defines': [
+ 'HAS_OUT_OF_PROC_TEST_RUNNER',
+ ],
+ 'sources': [
+ 'shell/browser/test/chromecast_shell_browser_test.cc',
+ ],
+ },
+ {
+ 'target_name': 'cast_shell_test_support',
+ 'type': '<(component)',
+ 'defines': [
+ 'HAS_OUT_OF_PROC_TEST_RUNNER',
+ ],
+ 'dependencies': [
+ 'cast_shell_core',
+ '../content/content_shell_and_tests.gyp:content_browser_test_support',
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'sources': [
+ 'shell/browser/test/chromecast_browser_test.cc',
+ 'shell/browser/test/chromecast_browser_test.h',
+ 'shell/browser/test/chromecast_browser_test_runner.cc',
+ ],
+ },
+ ], # end of targets
+ }],
+ ], # end of conditions
}
diff --git a/chromecast/common/cast_paths.cc b/chromecast/common/cast_paths.cc
index ec485c7e57..d5d471ce97 100644
--- a/chromecast/common/cast_paths.cc
+++ b/chromecast/common/cast_paths.cc
@@ -5,8 +5,9 @@
#include "chromecast/common/cast_paths.h"
#include "base/base_paths.h"
-#include "base/file_util.h"
#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
#include "base/path_service.h"
#include "build/build_config.h"
@@ -27,10 +28,18 @@ bool PathProvider(int key, base::FilePath* result) {
#endif
return true;
}
+#if defined(OS_ANDROID)
+ case FILE_CAST_ANDROID_LOG: {
+ base::FilePath base_dir;
+ CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &base_dir));
+ *result = base_dir.AppendASCII("cast_shell.log");
+ return true;
+ }
+#endif // defined(OS_ANDROID)
case FILE_CAST_CONFIG: {
base::FilePath data_dir;
#if defined(OS_ANDROID)
- CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir);
+ CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir));
*result = data_dir.Append("cast_shell.conf");
#else
CHECK(PathService::Get(DIR_CAST_HOME, &data_dir));
diff --git a/chromecast/common/cast_paths.h b/chromecast/common/cast_paths.h
index f228cd185c..5ab156c20f 100644
--- a/chromecast/common/cast_paths.h
+++ b/chromecast/common/cast_paths.h
@@ -5,6 +5,8 @@
#ifndef CHROMECAST_COMMON_CAST_PATHS_H_
#define CHROMECAST_COMMON_CAST_PATHS_H_
+#include "build/build_config.h"
+
// This file declares path keys for the chromecast module. These can be used
// with the PathService to access various special directories and files.
@@ -16,6 +18,9 @@ enum {
DIR_CAST_HOME, // Return a modified $HOME which works for both
// development use and the actual device.
+#if defined(OS_ANDROID)
+ FILE_CAST_ANDROID_LOG, // Log file location for Android.
+#endif // defined(OS_ANDROID)
FILE_CAST_CONFIG, // Config/preferences file path.
FILE_CAST_PAK, // cast_shell.pak file path.
PATH_END
diff --git a/chromecast/common/chromecast_config.cc b/chromecast/common/chromecast_config.cc
index dfb3866966..af5a591996 100644
--- a/chromecast/common/chromecast_config.cc
+++ b/chromecast/common/chromecast_config.cc
@@ -7,7 +7,7 @@
#include <string>
#include "base/command_line.h"
-#include "base/file_util.h"
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/prefs/json_pref_store.h"
@@ -17,6 +17,7 @@
#include "base/strings/string_number_conversions.h"
#include "chromecast/common/cast_paths.h"
#include "chromecast/common/pref_names.h"
+#include "chromecast/metrics/cast_metrics_prefs.h"
namespace chromecast {
@@ -74,13 +75,15 @@ bool ChromecastConfig::Load(PrefRegistrySimple* registry) {
VLOG(1) << "Loading config from " << config_path_.value();
registry->RegisterIntegerPref(prefs::kRemoteDebuggingPort, 0);
+ metrics::RegisterPrefs(registry);
RegisterPlatformPrefs(registry);
PersistentPrefStore::PrefReadError prefs_read_error =
PersistentPrefStore::PREF_READ_ERROR_NONE;
base::PrefServiceFactory prefServiceFactory;
- prefServiceFactory.SetUserPrefsFile(config_path_,
- JsonPrefStore::GetTaskRunnerForFile(config_path_, worker_pool_));
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ JsonPrefStore::GetTaskRunnerForFile(config_path_, worker_pool_.get());
+ prefServiceFactory.SetUserPrefsFile(config_path_, task_runner.get());
prefServiceFactory.set_async(false);
prefServiceFactory.set_read_error_callback(
base::Bind(&UserPrefsLoadError, &prefs_read_error));
@@ -135,4 +138,9 @@ void ChromecastConfig::SetIntValue(const std::string& key, int value) const {
}
}
+bool ChromecastConfig::HasValue(const std::string& key) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return pref_service_->HasPrefPath(key.c_str());
+}
+
} // namespace chromecast
diff --git a/chromecast/common/chromecast_config.h b/chromecast/common/chromecast_config.h
index 5cd6a1ae06..23482c6be4 100644
--- a/chromecast/common/chromecast_config.h
+++ b/chromecast/common/chromecast_config.h
@@ -37,18 +37,21 @@ class ChromecastConfig {
// Saves configs into configuration file.
void Save() const;
- // Returns string value for key, if present.
+ // Returns string value for |key|, if present.
const std::string GetValue(const std::string& key) const;
- // Returns integer value for key, if present.
+ // Returns integer value for |key|, if present.
const int GetIntValue(const std::string& key) const;
- // Sets new string value for key.
+ // Sets new string value for |key|.
void SetValue(const std::string& key, const std::string& value) const;
- // Sets new int value for key.
+ // Sets new int value for |key|.
void SetIntValue(const std::string& key, int value) const;
+ // Whether or not a value has been set for |key|.
+ bool HasValue(const std::string& key) const;
+
scoped_refptr<base::SequencedWorkerPool> worker_pool() const {
return worker_pool_;
}
diff --git a/chromecast/common/chromecast_switches.cc b/chromecast/common/chromecast_switches.cc
new file mode 100644
index 0000000000..90b524b4bb
--- /dev/null
+++ b/chromecast/common/chromecast_switches.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/common/chromecast_switches.h"
+
+namespace switches {
+
+// Override the URL to which metrics logs are sent for debugging.
+const char kOverrideMetricsUploadUrl[] = "override-metrics-upload-url";
+
+} // namespace switches
diff --git a/chromecast/common/chromecast_switches.h b/chromecast/common/chromecast_switches.h
new file mode 100644
index 0000000000..73b8a67eb6
--- /dev/null
+++ b/chromecast/common/chromecast_switches.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_
+#define CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_
+
+#include "build/build_config.h"
+
+namespace switches {
+
+// Metrics switches
+extern const char kOverrideMetricsUploadUrl[];
+
+} // namespace switches
+
+#endif // CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_
diff --git a/chromecast/common/global_descriptors.h b/chromecast/common/global_descriptors.h
new file mode 100644
index 0000000000..cbbbb2ce93
--- /dev/null
+++ b/chromecast/common/global_descriptors.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
+#define CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
+
+#include "content/public/common/content_descriptors.h"
+
+// This is a list of global descriptor keys to be used with the
+// base::GlobalDescriptors object (see base/posix/global_descriptors.h)
+enum {
+ // TODO(gunsch): Remove once there's a real value here. Otherwise, non-Android
+ // build compile fails due to empty enum.
+ kDummyValue = kContentIPCDescriptorMax + 1,
+#if defined(OS_ANDROID)
+ kAndroidPakDescriptor,
+#endif // defined(OS_ANDROID)
+};
+
+#endif // CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_
diff --git a/chromecast/common/version.h.in b/chromecast/common/version.h.in
index 38bc38b918..628cb4c217 100644
--- a/chromecast/common/version.h.in
+++ b/chromecast/common/version.h.in
@@ -8,5 +8,7 @@
#define CHROMECAST_COMMON_VERSION_INFO_H_
#define PRODUCT_VERSION "@VERSION_FULL@"
+#define CAST_BUILD_REVISION "@CAST_BUILD_REVISION@"
+#define CAST_IS_DEBUG_BUILD @CAST_IS_DEBUG_BUILD@
#endif // CHROMECAST_COMMON_VERSION_INFO_H_
diff --git a/chromecast/media/DEPS b/chromecast/media/DEPS
new file mode 100644
index 0000000000..1891d1afe5
--- /dev/null
+++ b/chromecast/media/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+media/base",
+ "+media/cdm",
+]
diff --git a/chromecast/media/base/key_systems_common.cc b/chromecast/media/base/key_systems_common.cc
new file mode 100644
index 0000000000..89257192db
--- /dev/null
+++ b/chromecast/media/base/key_systems_common.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/base/key_systems_common.h"
+
+#include <cstddef>
+
+#include "media/cdm/key_system_names.h"
+
+#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
+
+namespace chromecast {
+namespace media {
+
+const char kChromecastPlayreadyKeySystem[] = "com.chromecast.playready";
+
+CastKeySystem GetKeySystemByName(const std::string& key_system_name) {
+#if defined(WIDEVINE_CDM_AVAILABLE)
+ if (key_system_name.compare(kWidevineKeySystem) == 0) {
+ return KEY_SYSTEM_WIDEVINE;
+ }
+#endif // defined(WIDEVINE_CDM_AVAILABLE)
+
+#if defined(PLAYREADY_CDM_AVAILABLE)
+ if (key_system_name.compare(kChromecastPlayreadyKeySystem) == 0) {
+ return KEY_SYSTEM_PLAYREADY;
+ }
+#endif // defined(PLAYREADY_CDM_AVAILABLE)
+
+ if (key_system_name.compare(::media::kClearKey) == 0) {
+ return KEY_SYSTEM_CLEAR_KEY;
+ }
+
+ return GetPlatformKeySystemByName(key_system_name);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/base/key_systems_common.h b/chromecast/media/base/key_systems_common.h
new file mode 100644
index 0000000000..9c0906b9f0
--- /dev/null
+++ b/chromecast/media/base/key_systems_common.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_
+#define CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_
+
+#include <string>
+
+namespace chromecast {
+namespace media {
+
+extern const char kChromecastPlayreadyKeySystem[];
+
+enum CastKeySystem {
+ KEY_SYSTEM_NONE = 0,
+ KEY_SYSTEM_CLEAR_KEY,
+ KEY_SYSTEM_PLAYREADY,
+ KEY_SYSTEM_WIDEVINE
+};
+
+// Translates a key system string into a CastKeySystem, calling into the
+// platform for known key systems if needed.
+CastKeySystem GetKeySystemByName(const std::string& key_system_name);
+
+// Translates a platform-specific key system string into a CastKeySystem.
+// TODO(gunsch): Remove when prefixed EME is removed.
+CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name);
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_
diff --git a/chromecast/media/base/key_systems_common_simple.cc b/chromecast/media/base/key_systems_common_simple.cc
new file mode 100644
index 0000000000..e6dbd02890
--- /dev/null
+++ b/chromecast/media/base/key_systems_common_simple.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/base/key_systems_common.h"
+
+namespace chromecast {
+namespace media {
+
+CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name) {
+ return KEY_SYSTEM_NONE;
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/balanced_media_task_runner_factory.cc b/chromecast/media/cma/base/balanced_media_task_runner_factory.cc
new file mode 100644
index 0000000000..93132603cf
--- /dev/null
+++ b/chromecast/media/cma/base/balanced_media_task_runner_factory.cc
@@ -0,0 +1,252 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "chromecast/media/cma/base/media_task_runner.h"
+#include "media/base/buffers.h"
+
+namespace chromecast {
+namespace media {
+
+// MediaTaskRunnerWithNotification -
+// Media task runner which also behaves as a media task runner observer.
+class MediaTaskRunnerWithNotification : public MediaTaskRunner {
+ public:
+ // Wraps a MediaTaskRunner so that a third party can:
+ // - be notified when a PostMediaTask is performed on this media task runner.
+ // |new_task_cb| is invoked in that case.
+ // - monitor the lifetime of the media task runner, i.e. check when the media
+ // task runner is not needed anymore.
+ // |shutdown_cb| is invoked in that case.
+ MediaTaskRunnerWithNotification(
+ const scoped_refptr<MediaTaskRunner>& media_task_runner,
+ const base::Closure& new_task_cb,
+ const base::Closure& shutdown_cb);
+
+ // MediaTaskRunner implementation.
+ virtual bool PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) OVERRIDE;
+
+ private:
+ virtual ~MediaTaskRunnerWithNotification();
+
+ scoped_refptr<MediaTaskRunner> const media_task_runner_;
+
+ const base::Closure new_task_cb_;
+ const base::Closure shutdown_cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaTaskRunnerWithNotification);
+};
+
+MediaTaskRunnerWithNotification::MediaTaskRunnerWithNotification(
+ const scoped_refptr<MediaTaskRunner>& media_task_runner,
+ const base::Closure& new_task_cb,
+ const base::Closure& shutdown_cb)
+ : media_task_runner_(media_task_runner),
+ new_task_cb_(new_task_cb),
+ shutdown_cb_(shutdown_cb) {
+}
+
+MediaTaskRunnerWithNotification::~MediaTaskRunnerWithNotification() {
+ shutdown_cb_.Run();
+}
+
+bool MediaTaskRunnerWithNotification::PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) {
+ bool may_run_in_future =
+ media_task_runner_->PostMediaTask(from_here, task, timestamp);
+ if (may_run_in_future)
+ new_task_cb_.Run();
+ return may_run_in_future;
+}
+
+
+// BalancedMediaTaskRunner -
+// Run media tasks whose timestamp is less or equal to a max timestamp.
+//
+// Restrictions of BalancedMediaTaskRunner:
+// - Can have at most one task in the queue.
+// - Tasks should be given by increasing timestamps.
+class BalancedMediaTaskRunner
+ : public MediaTaskRunner {
+ public:
+ explicit BalancedMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ // Schedule tasks whose timestamp is less than or equal to |max_timestamp|.
+ void ScheduleWork(base::TimeDelta max_timestamp);
+
+ // Return the timestamp of the last media task.
+ // Return ::media::kNoTimestamp() if no media task has been posted.
+ base::TimeDelta GetMediaTimestamp() const;
+
+ // MediaTaskRunner implementation.
+ virtual bool PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) OVERRIDE;
+
+ private:
+ virtual ~BalancedMediaTaskRunner();
+
+ scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
+
+ // Protects the following variables.
+ mutable base::Lock lock_;
+
+ // Possible pending media task.
+ tracked_objects::Location from_here_;
+ base::Closure pending_task_;
+
+ // Timestamp of the last posted task.
+ // Is initialized to ::media::kNoTimestamp().
+ base::TimeDelta last_timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunner);
+};
+
+BalancedMediaTaskRunner::BalancedMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : task_runner_(task_runner),
+ last_timestamp_(::media::kNoTimestamp()) {
+}
+
+BalancedMediaTaskRunner::~BalancedMediaTaskRunner() {
+}
+
+void BalancedMediaTaskRunner::ScheduleWork(base::TimeDelta max_media_time) {
+ base::Closure task;
+ {
+ base::AutoLock auto_lock(lock_);
+ if (pending_task_.is_null())
+ return;
+
+ if (last_timestamp_ != ::media::kNoTimestamp() &&
+ last_timestamp_ >= max_media_time) {
+ return;
+ }
+
+ task = base::ResetAndReturn(&pending_task_);
+ }
+ task_runner_->PostTask(from_here_, task);
+}
+
+base::TimeDelta BalancedMediaTaskRunner::GetMediaTimestamp() const {
+ base::AutoLock auto_lock(lock_);
+ return last_timestamp_;
+}
+
+bool BalancedMediaTaskRunner::PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) {
+ DCHECK(!task.is_null());
+
+ // Pass through for a task with no timestamp.
+ if (timestamp == ::media::kNoTimestamp()) {
+ return task_runner_->PostTask(from_here, task);
+ }
+
+ base::AutoLock auto_lock(lock_);
+
+ // Timestamps must be in order.
+ // Any task that does not meet that condition is simply discarded.
+ if (last_timestamp_ != ::media::kNoTimestamp() &&
+ timestamp < last_timestamp_) {
+ return false;
+ }
+
+ // Only support one pending task at a time.
+ DCHECK(pending_task_.is_null());
+ from_here_ = from_here;
+ pending_task_ = task;
+ last_timestamp_ = timestamp;
+
+ return true;
+}
+
+
+BalancedMediaTaskRunnerFactory::BalancedMediaTaskRunnerFactory(
+ base::TimeDelta max_delta)
+ : max_delta_(max_delta) {
+}
+
+BalancedMediaTaskRunnerFactory::~BalancedMediaTaskRunnerFactory() {
+}
+
+scoped_refptr<MediaTaskRunner>
+BalancedMediaTaskRunnerFactory::CreateMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
+ scoped_refptr<BalancedMediaTaskRunner> media_task_runner(
+ new BalancedMediaTaskRunner(task_runner));
+ scoped_refptr<MediaTaskRunnerWithNotification> media_task_runner_wrapper(
+ new MediaTaskRunnerWithNotification(
+ media_task_runner,
+ base::Bind(&BalancedMediaTaskRunnerFactory::OnNewTask, this),
+ base::Bind(
+ &BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner,
+ this, media_task_runner)));
+ base::AutoLock auto_lock(lock_);
+ // Note that |media_task_runner| is inserted here and
+ // not |media_task_runner_wrapper|. Otherwise, we would always have one
+ // ref on |media_task_runner_wrapper| and would never get the release
+ // notification.
+ // When |media_task_runner_wrapper| is going away,
+ // BalancedMediaTaskRunnerFactory will receive a notification and will in
+ // turn remove |media_task_runner|.
+ task_runners_.insert(media_task_runner);
+ return media_task_runner_wrapper;
+}
+
+void BalancedMediaTaskRunnerFactory::OnNewTask() {
+ typedef
+ std::multimap<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> >
+ TaskRunnerMap;
+ TaskRunnerMap runnable_task_runner;
+
+ base::AutoLock auto_lock(lock_);
+
+ // Get the minimum timestamp among all streams.
+ for (MediaTaskRunnerSet::const_iterator it = task_runners_.begin();
+ it != task_runners_.end(); ++it) {
+ base::TimeDelta timestamp((*it)->GetMediaTimestamp());
+ if (timestamp == ::media::kNoTimestamp())
+ continue;
+ runnable_task_runner.insert(
+ std::pair<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> >(
+ timestamp, *it));
+ }
+
+ // If there is no media task, just returns.
+ if (runnable_task_runner.empty())
+ return;
+
+ // Run tasks which meet the balancing criteria.
+ base::TimeDelta min_timestamp(runnable_task_runner.begin()->first);
+ base::TimeDelta max_timestamp = min_timestamp + max_delta_;
+ for (TaskRunnerMap::iterator it = runnable_task_runner.begin();
+ it != runnable_task_runner.end(); ++it) {
+ (*it).second->ScheduleWork(max_timestamp);
+ }
+}
+
+void BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner(
+ const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner) {
+ base::AutoLock auto_lock(lock_);
+ task_runners_.erase(media_task_runner);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/balanced_media_task_runner_factory.h b/chromecast/media/cma/base/balanced_media_task_runner_factory.h
new file mode 100644
index 0000000000..488373d563
--- /dev/null
+++ b/chromecast/media/cma/base/balanced_media_task_runner_factory.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_
+#define CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace chromecast {
+namespace media {
+class BalancedMediaTaskRunner;
+class MediaTaskRunner;
+
+// BalancedMediaTaskRunnerFactory -
+// Create media tasks runners that are loosely synchronized between each other.
+// For two tasks T1 and T2 with timestamps ts1 and ts2, the scheduler ensures
+// T2 is not scheduled before T1 if ts2 > ts1 + |max_delta|.
+class BalancedMediaTaskRunnerFactory
+ : public base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory> {
+ public:
+ explicit BalancedMediaTaskRunnerFactory(base::TimeDelta max_delta);
+
+ // Creates a media task runner using |task_runner| as the underlying
+ // regular task runner.
+ // Restriction on the returned media task runner:
+ // - can only schedule only one media task at a time.
+ // - timestamps of tasks posted on that task runner must be increasing.
+ scoped_refptr<MediaTaskRunner> CreateMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ private:
+ typedef std::set<scoped_refptr<BalancedMediaTaskRunner> > MediaTaskRunnerSet;
+
+ friend class base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory>;
+ virtual ~BalancedMediaTaskRunnerFactory();
+
+ // Invoked when one of the registered media task runners received a new media
+ // task.
+ void OnNewTask();
+
+ // Unregister a media task runner.
+ void UnregisterMediaTaskRunner(
+ const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner);
+
+ // Maximum timestamp deviation between tasks from the registered task runners.
+ const base::TimeDelta max_delta_;
+
+ // Task runners created by the factory that have not been unregistered yet.
+ base::Lock lock_;
+ MediaTaskRunnerSet task_runners_;
+
+ DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerFactory);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_
diff --git a/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc b/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc
new file mode 100644
index 0000000000..e3448f67ca
--- /dev/null
+++ b/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
+#include "chromecast/media/cma/base/media_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+struct MediaTaskRunnerTestContext {
+ MediaTaskRunnerTestContext();
+ ~MediaTaskRunnerTestContext();
+
+ scoped_refptr<MediaTaskRunner> media_task_runner;
+
+ bool is_pending_task;
+
+ std::vector<base::TimeDelta> task_timestamp_list;
+
+ size_t task_index;
+ base::TimeDelta max_timestamp;
+};
+
+MediaTaskRunnerTestContext::MediaTaskRunnerTestContext() {
+}
+
+MediaTaskRunnerTestContext::~MediaTaskRunnerTestContext() {
+}
+
+} // namespace
+
+class BalancedMediaTaskRunnerTest : public testing::Test {
+ public:
+ BalancedMediaTaskRunnerTest();
+ virtual ~BalancedMediaTaskRunnerTest();
+
+ void SetupTest(base::TimeDelta max_delta,
+ const std::vector<std::vector<int> >& timestamps_in_ms,
+ const std::vector<size_t>& pattern,
+ const std::vector<int>& expected_task_timestamps_ms);
+ void ProcessAllTasks();
+
+ protected:
+ // Expected task order based on their timestamps.
+ std::list<base::TimeDelta> expected_task_timestamps_;
+
+ private:
+ void ScheduleTask();
+ void Task(size_t task_runner_id, base::TimeDelta timestamp);
+
+ void OnTestTimeout();
+
+ scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_;
+
+ // Schedule first a task on media task runner #scheduling_pattern[0]
+ // then a task on media task runner #scheduling_pattern[1] and so on.
+ // Wrap around when reaching the end of the pattern.
+ std::vector<size_t> scheduling_pattern_;
+ size_t pattern_index_;
+
+ // For each media task runner, keep a track of which task has already been
+ // scheduled.
+ std::vector<MediaTaskRunnerTestContext> contexts_;
+
+ DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerTest);
+};
+
+BalancedMediaTaskRunnerTest::BalancedMediaTaskRunnerTest() {
+}
+
+BalancedMediaTaskRunnerTest::~BalancedMediaTaskRunnerTest() {
+}
+
+void BalancedMediaTaskRunnerTest::SetupTest(
+ base::TimeDelta max_delta,
+ const std::vector<std::vector<int> >& timestamps_in_ms,
+ const std::vector<size_t>& pattern,
+ const std::vector<int>& expected_task_timestamps_ms) {
+ media_task_runner_factory_ = new BalancedMediaTaskRunnerFactory(max_delta);
+
+ scheduling_pattern_ = pattern;
+ pattern_index_ = 0;
+
+ // Setup each task runner.
+ size_t n = timestamps_in_ms.size();
+ contexts_.resize(n);
+ for (size_t k = 0; k < n; k++) {
+ contexts_[k].media_task_runner =
+ media_task_runner_factory_->CreateMediaTaskRunner(
+ base::MessageLoopProxy::current());
+ contexts_[k].is_pending_task = false;
+ contexts_[k].task_index = 0;
+ contexts_[k].task_timestamp_list.resize(
+ timestamps_in_ms[k].size());
+ for (size_t i = 0; i < timestamps_in_ms[k].size(); i++) {
+ contexts_[k].task_timestamp_list[i] =
+ base::TimeDelta::FromMilliseconds(timestamps_in_ms[k][i]);
+ }
+ }
+
+ // Expected task order (for tasks that are actually run).
+ for (size_t k = 0; k < expected_task_timestamps_ms.size(); k++) {
+ expected_task_timestamps_.push_back(
+ base::TimeDelta::FromMilliseconds(expected_task_timestamps_ms[k]));
+ }
+}
+
+void BalancedMediaTaskRunnerTest::ProcessAllTasks() {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&BalancedMediaTaskRunnerTest::OnTestTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(5));
+ ScheduleTask();
+}
+
+void BalancedMediaTaskRunnerTest::ScheduleTask() {
+ bool has_task = false;
+ for (size_t k = 0; k < contexts_.size(); k++) {
+ if (contexts_[k].task_index < contexts_[k].task_timestamp_list.size())
+ has_task = true;
+ }
+ if (!has_task) {
+ base::MessageLoop::current()->QuitWhenIdle();
+ return;
+ }
+
+ size_t next_pattern_index =
+ (pattern_index_ + 1) % scheduling_pattern_.size();
+
+ size_t task_runner_id = scheduling_pattern_[pattern_index_];
+ MediaTaskRunnerTestContext& context = contexts_[task_runner_id];
+
+ // Check whether all tasks have been scheduled for that task runner
+ // or if there is already one pending task.
+ if (context.task_index >= context.task_timestamp_list.size() ||
+ context.is_pending_task) {
+ pattern_index_ = next_pattern_index;
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask,
+ base::Unretained(this)));
+ return;
+ }
+
+ bool expected_may_run = false;
+ if (context.task_timestamp_list[context.task_index] >=
+ context.max_timestamp) {
+ expected_may_run = true;
+ context.max_timestamp = context.task_timestamp_list[context.task_index];
+ }
+
+ bool may_run = context.media_task_runner->PostMediaTask(
+ FROM_HERE,
+ base::Bind(&BalancedMediaTaskRunnerTest::Task,
+ base::Unretained(this),
+ task_runner_id,
+ context.task_timestamp_list[context.task_index]),
+ context.task_timestamp_list[context.task_index]);
+ EXPECT_EQ(may_run, expected_may_run);
+
+ if (may_run)
+ context.is_pending_task = true;
+
+ context.task_index++;
+ pattern_index_ = next_pattern_index;
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask,
+ base::Unretained(this)));
+}
+
+void BalancedMediaTaskRunnerTest::Task(
+ size_t task_runner_id, base::TimeDelta timestamp) {
+ ASSERT_FALSE(expected_task_timestamps_.empty());
+ EXPECT_EQ(timestamp, expected_task_timestamps_.front());
+ expected_task_timestamps_.pop_front();
+
+ contexts_[task_runner_id].is_pending_task = false;
+}
+
+void BalancedMediaTaskRunnerTest::OnTestTimeout() {
+ ADD_FAILURE() << "Test timed out";
+ if (base::MessageLoop::current())
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+TEST_F(BalancedMediaTaskRunnerTest, OneTaskRunner) {
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+
+ // Timestamps of tasks for the single task runner.
+ int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70};
+ std::vector<std::vector<int> > timestamps_ms(1);
+ timestamps_ms[0] = std::vector<int>(
+ timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms));
+
+ // Scheduling pattern.
+ std::vector<size_t> scheduling_pattern(1);
+ scheduling_pattern[0] = 0;
+
+ // Expected results.
+ int expected_timestamps[] = {0, 10, 20, 30, 40, 50, 60, 70};
+ std::vector<int> expected_timestamps_ms(std::vector<int>(
+ expected_timestamps,
+ expected_timestamps + arraysize(expected_timestamps)));
+
+ SetupTest(base::TimeDelta::FromMilliseconds(30),
+ timestamps_ms,
+ scheduling_pattern,
+ expected_timestamps_ms);
+ ProcessAllTasks();
+ message_loop->Run();
+ EXPECT_TRUE(expected_task_timestamps_.empty());
+}
+
+TEST_F(BalancedMediaTaskRunnerTest, TwoTaskRunnerUnbalanced) {
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+
+ // Timestamps of tasks for the 2 task runners.
+ int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70};
+ int timestamps1_ms[] = {5, 15, 25, 35, 45, 35, 55, 65, 25, 35, 75};
+ std::vector<std::vector<int> > timestamps_ms(2);
+ timestamps_ms[0] = std::vector<int>(
+ timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms));
+ timestamps_ms[1] = std::vector<int>(
+ timestamps1_ms, timestamps1_ms + arraysize(timestamps1_ms));
+
+ // Scheduling pattern.
+ size_t pattern[] = {1, 0, 0, 0, 0};
+ std::vector<size_t> scheduling_pattern = std::vector<size_t>(
+ pattern, pattern + arraysize(pattern));
+
+ // Expected results.
+ int expected_timestamps[] = {
+ 5, 0, 10, 20, 30, 15, 40, 25, 50, 35, 60, 45, 70, 55, 65, 75 };
+ std::vector<int> expected_timestamps_ms(std::vector<int>(
+ expected_timestamps,
+ expected_timestamps + arraysize(expected_timestamps)));
+
+ SetupTest(base::TimeDelta::FromMilliseconds(30),
+ timestamps_ms,
+ scheduling_pattern,
+ expected_timestamps_ms);
+ ProcessAllTasks();
+ message_loop->Run();
+ EXPECT_TRUE(expected_task_timestamps_.empty());
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_controller.cc b/chromecast/media/cma/base/buffering_controller.cc
new file mode 100644
index 0000000000..2adaf82053
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_controller.cc
@@ -0,0 +1,194 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/buffering_controller.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "chromecast/media/cma/base/buffering_state.h"
+#include "chromecast/media/cma/base/cma_logging.h"
+#include "media/base/buffers.h"
+
+namespace chromecast {
+namespace media {
+
+BufferingController::BufferingController(
+ const scoped_refptr<BufferingConfig>& config,
+ const BufferingNotificationCB& buffering_notification_cb)
+ : config_(config),
+ buffering_notification_cb_(buffering_notification_cb),
+ is_buffering_(false),
+ begin_buffering_time_(base::Time()),
+ weak_factory_(this) {
+ weak_this_ = weak_factory_.GetWeakPtr();
+ thread_checker_.DetachFromThread();
+}
+
+BufferingController::~BufferingController() {
+ // Some weak pointers might possibly be invalidated here.
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void BufferingController::UpdateHighLevelThreshold(
+ base::TimeDelta high_level_threshold) {
+ // Can only decrease the high level threshold.
+ if (high_level_threshold > config_->high_level())
+ return;
+ CMALOG(kLogControl) << "High buffer threshold: "
+ << high_level_threshold.InMilliseconds();
+ config_->set_high_level(high_level_threshold);
+
+ // Make sure the low level threshold is somewhat consistent.
+ // Currently, we set it to one third of the high level threshold:
+ // this value could be adjusted in the future.
+ base::TimeDelta low_level_threshold = high_level_threshold / 3;
+ if (low_level_threshold <= config_->low_level()) {
+ CMALOG(kLogControl) << "Low buffer threshold: "
+ << low_level_threshold.InMilliseconds();
+ config_->set_low_level(low_level_threshold);
+ }
+
+ // Signal all the streams the config has changed.
+ for (StreamList::iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ (*it)->OnConfigChanged();
+ }
+
+ // Once all the streams have been notified, the buffering state must be
+ // updated (no notification is received from the streams).
+ OnBufferingStateChanged(false, false);
+}
+
+scoped_refptr<BufferingState> BufferingController::AddStream() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Add a new stream to the list of streams being monitored.
+ scoped_refptr<BufferingState> buffering_state(new BufferingState(
+ config_,
+ base::Bind(&BufferingController::OnBufferingStateChanged, weak_this_,
+ false, false),
+ base::Bind(&BufferingController::UpdateHighLevelThreshold, weak_this_)));
+ stream_list_.push_back(buffering_state);
+
+ // Update the state and force a notification to the streams.
+ // TODO(damienv): Should this be a PostTask ?
+ OnBufferingStateChanged(true, false);
+
+ return buffering_state;
+}
+
+void BufferingController::SetMediaTime(base::TimeDelta time) {
+ for (StreamList::iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ (*it)->SetMediaTime(time);
+ }
+}
+
+base::TimeDelta BufferingController::GetMaxRenderingTime() const {
+ base::TimeDelta max_rendering_time(::media::kNoTimestamp());
+ for (StreamList::const_iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ base::TimeDelta max_stream_rendering_time =
+ (*it)->GetMaxRenderingTime();
+ if (max_stream_rendering_time == ::media::kNoTimestamp())
+ return ::media::kNoTimestamp();
+ if (max_rendering_time == ::media::kNoTimestamp() ||
+ max_stream_rendering_time < max_rendering_time) {
+ max_rendering_time = max_stream_rendering_time;
+ }
+ }
+ return max_rendering_time;
+}
+
+void BufferingController::Reset() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ is_buffering_ = false;
+ stream_list_.clear();
+}
+
+void BufferingController::OnBufferingStateChanged(
+ bool force_notification, bool buffering_timeout) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Log the state of each stream.
+ DumpState();
+
+ bool is_low_buffering = IsLowBufferLevel();
+ bool is_high_buffering = !is_low_buffering;
+ if (!buffering_timeout) {
+ // Hysteresis:
+ // - to leave buffering, not only should we leave the low buffer level state
+ // but we should go to the high buffer level state (medium is not enough).
+ is_high_buffering = IsHighBufferLevel();
+ }
+
+ bool is_buffering_prv = is_buffering_;
+ if (is_buffering_) {
+ if (is_high_buffering)
+ is_buffering_ = false;
+ } else {
+ if (is_low_buffering)
+ is_buffering_ = true;
+ }
+
+ // Start buffering.
+ if (is_buffering_ && !is_buffering_prv) {
+ begin_buffering_time_ = base::Time::Now();
+ }
+
+ // End buffering.
+ if (is_buffering_prv && !is_buffering_) {
+ // TODO(damienv): |buffering_user_time| could be a UMA histogram.
+ base::Time current_time = base::Time::Now();
+ base::TimeDelta buffering_user_time = current_time - begin_buffering_time_;
+ CMALOG(kLogControl)
+ << "Buffering took: "
+ << buffering_user_time.InMilliseconds() << "ms";
+ }
+
+ if (is_buffering_prv != is_buffering_ || force_notification)
+ buffering_notification_cb_.Run(is_buffering_);
+}
+
+bool BufferingController::IsHighBufferLevel() {
+ if (stream_list_.empty())
+ return true;
+
+ bool is_high_buffering = true;
+ for (StreamList::iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ BufferingState::State stream_state = (*it)->GetState();
+ is_high_buffering = is_high_buffering &&
+ ((stream_state == BufferingState::kHighLevel) ||
+ (stream_state == BufferingState::kEosReached));
+ }
+ return is_high_buffering;
+}
+
+bool BufferingController::IsLowBufferLevel() {
+ if (stream_list_.empty())
+ return false;
+
+ for (StreamList::iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ BufferingState::State stream_state = (*it)->GetState();
+ if (stream_state == BufferingState::kLowLevel)
+ return true;
+ }
+
+ return false;
+}
+
+void BufferingController::DumpState() const {
+ CMALOG(kLogControl) << __FUNCTION__;
+ for (StreamList::const_iterator it = stream_list_.begin();
+ it != stream_list_.end(); ++it) {
+ CMALOG(kLogControl) << (*it)->ToString();
+ }
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_controller.h b/chromecast/media/cma/base/buffering_controller.h
new file mode 100644
index 0000000000..0581651d83
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_controller.h
@@ -0,0 +1,108 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H
+#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H
+
+#include <list>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+
+namespace chromecast {
+namespace media {
+class BufferingConfig;
+class BufferingState;
+
+class BufferingController {
+ public:
+ typedef base::Callback<void(bool)> BufferingNotificationCB;
+
+ // Creates a buffering controller where the conditions to trigger rebuffering
+ // are given by |config|. The whole point of the buffering controller is to
+ // derive a single buffering state from the buffering state of various
+ // streams.
+ // |buffering_notification_cb| is a callback invoked to inform about possible
+ // changes of the buffering state.
+ BufferingController(
+ const scoped_refptr<BufferingConfig>& config,
+ const BufferingNotificationCB& buffering_notification_cb);
+ ~BufferingController();
+
+ // Creates a buffering state for one stream. This state is added to the list
+ // of streams monitored by the buffering controller.
+ scoped_refptr<BufferingState> AddStream();
+
+ // Sets the playback time.
+ void SetMediaTime(base::TimeDelta time);
+
+ // Returns the maximum media time available for rendering.
+ // Return kNoTimestamp() if unknown.
+ base::TimeDelta GetMaxRenderingTime() const;
+
+ // Returns whether there is an active buffering phase.
+ bool IsBuffering() const { return is_buffering_; }
+
+ // Resets the buffering controller. This includes removing all the streams
+ // that were previously added.
+ void Reset();
+
+ private:
+ // Invoked each time the buffering state of one of the streams has changed.
+ // If |force_notification| is set, |buffering_notification_cb_| is invoked
+ // regardless whether the buffering state has changed or not.
+ // If |buffering_timeout| is set, then the condition to leave the buffering
+ // state is relaxed (we don't want to wait more).
+ void OnBufferingStateChanged(bool force_notification,
+ bool buffering_timeout);
+
+ // Updates the high buffer level threshold to |high_level_threshold|
+ // if needed.
+ // This condition is triggered when one of the stream reached its maximum
+ // capacity. In that case, to avoid possible race condition (the buffering
+ // controller waits for more data to come but the buffer is to small to
+ // accomodate additional data), the thresholds in |config_| are adjusted
+ // accordingly.
+ void UpdateHighLevelThreshold(base::TimeDelta high_level_threshold);
+
+ // Determines the overall buffer level based on the buffer level of each
+ // stream.
+ bool IsHighBufferLevel();
+ bool IsLowBufferLevel();
+
+ // Logs the state of the buffering controller.
+ void DumpState() const;
+
+ base::ThreadChecker thread_checker_;
+
+ // Settings used to determine when to start/stop buffering.
+ scoped_refptr<BufferingConfig> config_;
+
+ // Callback invoked each time there is a change of the buffering state.
+ BufferingNotificationCB buffering_notification_cb_;
+
+ // State of the buffering controller.
+ bool is_buffering_;
+
+ // Start time of a re-buffering phase.
+ base::Time begin_buffering_time_;
+
+ // Buffering level for each individual stream.
+ typedef std::list<scoped_refptr<BufferingState> > StreamList;
+ StreamList stream_list_;
+
+ base::WeakPtrFactory<BufferingController> weak_factory_;
+ base::WeakPtr<BufferingController> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferingController);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H
diff --git a/chromecast/media/cma/base/buffering_controller_unittest.cc b/chromecast/media/cma/base/buffering_controller_unittest.cc
new file mode 100644
index 0000000000..75eaed9ce1
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_controller_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/buffering_controller.h"
+#include "chromecast/media/cma/base/buffering_state.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class MockBufferingControllerClient {
+ public:
+ MockBufferingControllerClient();
+ ~MockBufferingControllerClient();
+
+ MOCK_METHOD1(OnBufferingNotification, void(bool is_buffering));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBufferingControllerClient);
+};
+
+MockBufferingControllerClient::MockBufferingControllerClient() {
+}
+
+MockBufferingControllerClient::~MockBufferingControllerClient() {
+}
+
+} // namespace
+
+class BufferingControllerTest : public testing::Test {
+ public:
+ BufferingControllerTest();
+ virtual ~BufferingControllerTest();
+
+ protected:
+ scoped_ptr<BufferingController> buffering_controller_;
+
+ MockBufferingControllerClient client_;
+
+ // Buffer level under the low level threshold.
+ base::TimeDelta d1_;
+
+ // Buffer level between the low and the high level.
+ base::TimeDelta d2_;
+
+ // Buffer level above the high level.
+ base::TimeDelta d3_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferingControllerTest);
+};
+
+BufferingControllerTest::BufferingControllerTest() {
+ base::TimeDelta low_level_threshold(
+ base::TimeDelta::FromMilliseconds(2000));
+ base::TimeDelta high_level_threshold(
+ base::TimeDelta::FromMilliseconds(6000));
+
+ d1_ = low_level_threshold - base::TimeDelta::FromMilliseconds(50);
+ d2_ = (low_level_threshold + high_level_threshold) / 2;
+ d3_ = high_level_threshold + base::TimeDelta::FromMilliseconds(50);
+
+ scoped_refptr<BufferingConfig> buffering_config(
+ new BufferingConfig(low_level_threshold, high_level_threshold));
+ buffering_controller_.reset(new BufferingController(
+ buffering_config,
+ base::Bind(&MockBufferingControllerClient::OnBufferingNotification,
+ base::Unretained(&client_))));
+}
+
+BufferingControllerTest::~BufferingControllerTest() {
+}
+
+TEST_F(BufferingControllerTest, OneStream_Typical) {
+ EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1);
+ scoped_refptr<BufferingState> buffering_state =
+ buffering_controller_->AddStream();
+ buffering_state->SetMediaTime(base::TimeDelta());
+
+ // Simulate pre-buffering.
+ buffering_state->SetBufferedTime(d2_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel);
+
+ EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1);
+ buffering_state->SetBufferedTime(d3_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel);
+
+ // Simulate some fluctuations of the buffering level.
+ buffering_state->SetBufferedTime(d2_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel);
+
+ // Simulate an underrun.
+ EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1);
+ buffering_state->SetBufferedTime(d1_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kLowLevel);
+
+ EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1);
+ buffering_state->SetBufferedTime(d3_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel);
+
+ // Simulate the end of stream.
+ buffering_state->NotifyEos();
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached);
+
+ buffering_state->SetBufferedTime(d2_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached);
+
+ buffering_state->SetBufferedTime(d1_);
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached);
+}
+
+TEST_F(BufferingControllerTest, OneStream_LeaveBufferingOnEos) {
+ EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1);
+ scoped_refptr<BufferingState> buffering_state =
+ buffering_controller_->AddStream();
+ buffering_state->SetMediaTime(base::TimeDelta());
+
+ EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1);
+ buffering_state->NotifyEos();
+ EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_frame_provider.cc b/chromecast/media/cma/base/buffering_frame_provider.cc
new file mode 100644
index 0000000000..1a8fec9a12
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_frame_provider.cc
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/buffering_frame_provider.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "chromecast/media/cma/base/buffering_state.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/buffers.h"
+
+namespace chromecast {
+namespace media {
+
+BufferingFrameProvider::BufferWithConfig::BufferWithConfig(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config)
+ : buffer_(buffer),
+ audio_config_(audio_config),
+ video_config_(video_config) {
+}
+
+BufferingFrameProvider::BufferWithConfig::~BufferWithConfig() {
+}
+
+BufferingFrameProvider::BufferingFrameProvider(
+ scoped_ptr<CodedFrameProvider> coded_frame_provider,
+ size_t max_buffer_size,
+ size_t max_frame_size,
+ const FrameBufferedCB& frame_buffered_cb)
+ : coded_frame_provider_(coded_frame_provider.Pass()),
+ is_pending_request_(false),
+ is_eos_(false),
+ total_buffer_size_(0),
+ max_buffer_size_(max_buffer_size),
+ max_frame_size_(max_frame_size),
+ frame_buffered_cb_(frame_buffered_cb),
+ weak_factory_(this),
+ weak_this_(weak_factory_.GetWeakPtr()) {
+ DCHECK_LE(max_frame_size, max_buffer_size);
+ thread_checker_.DetachFromThread();
+}
+
+BufferingFrameProvider::~BufferingFrameProvider() {
+ // Required since some weak pointers might be released in the destructor.
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void BufferingFrameProvider::Read(const ReadCB& read_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK(!read_cb.is_null());
+ read_cb_ = read_cb;
+
+ CompleteReadIfNeeded();
+
+ RequestBufferIfNeeded();
+}
+
+void BufferingFrameProvider::Flush(const base::Closure& flush_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Invalidate all the buffers that belong to this media timeline.
+ // This is needed since, even though |coded_frame_provider_| is flushed later
+ // in this function, there might be a pending task holding onto a buffer.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Create a new valid weak pointer that is used for the next media timeline.
+ weak_this_ = weak_factory_.GetWeakPtr();
+
+ is_pending_request_ = false;
+ is_eos_ = false;
+ buffer_list_.clear();
+ total_buffer_size_ = 0;
+ read_cb_.Reset();
+ coded_frame_provider_->Flush(flush_cb);
+}
+
+void BufferingFrameProvider::OnNewBuffer(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config) {
+ is_pending_request_ = false;
+ buffer_list_.push_back(
+ BufferWithConfig(buffer, audio_config, video_config));
+
+ if (buffer->end_of_stream()) {
+ is_eos_ = true;
+ } else {
+ total_buffer_size_ += buffer->data_size();
+ }
+
+ if (!frame_buffered_cb_.is_null()) {
+ // If the next upcoming frame is possibly filling the whole buffer,
+ // then the buffer is considered as having reached its max capacity.
+ bool max_capacity_flag =
+ (total_buffer_size_ + max_frame_size_ >= max_buffer_size_);
+ frame_buffered_cb_.Run(buffer, max_capacity_flag);
+ }
+
+ RequestBufferIfNeeded();
+
+ CompleteReadIfNeeded();
+}
+
+void BufferingFrameProvider::RequestBufferIfNeeded() {
+ if (is_pending_request_)
+ return;
+
+ if (is_eos_ || total_buffer_size_ >= max_buffer_size_)
+ return;
+
+ is_pending_request_ = true;
+ coded_frame_provider_->Read(BindToCurrentLoop(
+ base::Bind(&BufferingFrameProvider::OnNewBuffer, weak_this_)));
+}
+
+void BufferingFrameProvider::CompleteReadIfNeeded() {
+ if (read_cb_.is_null())
+ return;
+
+ if (buffer_list_.empty())
+ return;
+
+ BufferWithConfig buffer_with_config(buffer_list_.front());
+ buffer_list_.pop_front();
+ if (!buffer_with_config.buffer()->end_of_stream())
+ total_buffer_size_ -= buffer_with_config.buffer()->data_size();
+
+ base::ResetAndReturn(&read_cb_).Run(
+ buffer_with_config.buffer(),
+ buffer_with_config.audio_config(),
+ buffer_with_config.video_config());
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_frame_provider.h b/chromecast/media/cma/base/buffering_frame_provider.h
new file mode 100644
index 0000000000..99c863f068
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_frame_provider.h
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_
+
+#include <list>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/video_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+class DecoderBufferBase;
+
+// BufferingFrameProvider -
+// Fetch some data from another CodedFrameProvider up to a certain size limit.
+class BufferingFrameProvider : public CodedFrameProvider {
+ public:
+ typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&, bool)>
+ FrameBufferedCB;
+
+ // Creates a frame provider that buffers coded frames up to the
+ // |max_buffer_size| limit (given as a number of bytes).
+ // |max_frame_size| corresponds to an upper bound of the expected frame size.
+ // Each time a frame is buffered, |frame_buffered_cb| is invoked with the
+ // last frame buffered. The second parameter of the callback indicates
+ // whether the maximum capacity has been reached, i.e. whether the next frame
+ // size might overflow the buffer: |total_buffer_size_| + next_frame_size
+ // might be greater than |max_buffer_size|.
+ // Note: takes ownership of |coded_frame_provider|.
+ BufferingFrameProvider(
+ scoped_ptr<CodedFrameProvider> coded_frame_provider,
+ size_t max_buffer_size,
+ size_t max_frame_size,
+ const FrameBufferedCB& frame_buffered_cb);
+ virtual ~BufferingFrameProvider();
+
+ // CodedFrameProvider implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual void Flush(const base::Closure& flush_cb) OVERRIDE;
+
+ private:
+ class BufferWithConfig {
+ public:
+ BufferWithConfig(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config);
+ ~BufferWithConfig();
+
+ const scoped_refptr<DecoderBufferBase>& buffer() const { return buffer_; }
+ const ::media::AudioDecoderConfig& audio_config() const {
+ return audio_config_;
+ }
+ const ::media::VideoDecoderConfig& video_config() const {
+ return video_config_;
+ }
+
+ private:
+ scoped_refptr<DecoderBufferBase> buffer_;
+ ::media::AudioDecoderConfig audio_config_;
+ ::media::VideoDecoderConfig video_config_;
+ };
+
+ void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config);
+ void RequestBufferIfNeeded();
+ void CompleteReadIfNeeded();
+
+ base::ThreadChecker thread_checker_;
+
+ // Frame provider the buffering frame provider fetches data from.
+ scoped_ptr<CodedFrameProvider> coded_frame_provider_;
+
+ // Indicates whether there is a pending read request on
+ // |coded_frame_provider_|.
+ bool is_pending_request_;
+
+ // Indicates whether the end of stream has been reached.
+ bool is_eos_;
+
+ std::list<BufferWithConfig> buffer_list_;
+
+ // Size in bytes of audio/video buffers in |buffer_list_|.
+ size_t total_buffer_size_;
+
+ // Max amount of data to buffer.
+ // i.e. this is the maximum size of buffers in |buffer_list_|.
+ const size_t max_buffer_size_;
+
+ // Maximum expected frame size.
+ const size_t max_frame_size_;
+
+ // Callback invoked each time there is a new frame buffered.
+ FrameBufferedCB frame_buffered_cb_;
+
+ // Pending read callback.
+ ReadCB read_cb_;
+
+ base::WeakPtrFactory<BufferingFrameProvider> weak_factory_;
+ base::WeakPtr<BufferingFrameProvider> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferingFrameProvider);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_
diff --git a/chromecast/media/cma/base/buffering_frame_provider_unittest.cc b/chromecast/media/cma/base/buffering_frame_provider_unittest.cc
new file mode 100644
index 0000000000..6f7902a70b
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_frame_provider_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/buffering_frame_provider.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/base/frame_generator_for_test.h"
+#include "chromecast/media/cma/base/mock_frame_consumer.h"
+#include "chromecast/media/cma/base/mock_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/video_decoder_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+class BufferingFrameProviderTest : public testing::Test {
+ public:
+ BufferingFrameProviderTest();
+ virtual ~BufferingFrameProviderTest();
+
+ // Setup the test.
+ void Configure(
+ size_t frame_count,
+ const std::vector<bool>& provider_delayed_pattern,
+ const std::vector<bool>& consumer_delayed_pattern);
+
+ // Start the test.
+ void Start();
+
+ protected:
+ scoped_ptr<BufferingFrameProvider> buffering_frame_provider_;
+ scoped_ptr<MockFrameConsumer> frame_consumer_;
+
+ private:
+ void OnTestTimeout();
+ void OnTestCompleted();
+
+ DISALLOW_COPY_AND_ASSIGN(BufferingFrameProviderTest);
+};
+
+BufferingFrameProviderTest::BufferingFrameProviderTest() {
+}
+
+BufferingFrameProviderTest::~BufferingFrameProviderTest() {
+}
+
+void BufferingFrameProviderTest::Configure(
+ size_t frame_count,
+ const std::vector<bool>& provider_delayed_pattern,
+ const std::vector<bool>& consumer_delayed_pattern) {
+ DCHECK_GE(frame_count, 1u);
+
+ // Frame generation on the producer and consumer side.
+ std::vector<FrameGeneratorForTest::FrameSpec> frame_specs(frame_count);
+ for (size_t k = 0; k < frame_specs.size() - 1; k++) {
+ frame_specs[k].has_config = (k == 0);
+ frame_specs[k].timestamp = base::TimeDelta::FromMilliseconds(40) * k;
+ frame_specs[k].size = 512;
+ frame_specs[k].has_decrypt_config = ((k % 3) == 0);
+ }
+ frame_specs[frame_specs.size() - 1].is_eos = true;
+
+ scoped_ptr<FrameGeneratorForTest> frame_generator_provider(
+ new FrameGeneratorForTest(frame_specs));
+ scoped_ptr<FrameGeneratorForTest> frame_generator_consumer(
+ new FrameGeneratorForTest(frame_specs));
+
+ scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider());
+ frame_provider->Configure(provider_delayed_pattern,
+ frame_generator_provider.Pass());
+
+ size_t max_frame_size = 10 * 1024;
+ size_t buffer_size = 10 * max_frame_size;
+ buffering_frame_provider_.reset(
+ new BufferingFrameProvider(
+ scoped_ptr<CodedFrameProvider>(frame_provider.release()),
+ buffer_size,
+ max_frame_size,
+ BufferingFrameProvider::FrameBufferedCB()));
+
+ frame_consumer_.reset(
+ new MockFrameConsumer(buffering_frame_provider_.get()));
+ frame_consumer_->Configure(
+ consumer_delayed_pattern,
+ false,
+ frame_generator_consumer.Pass());
+}
+
+void BufferingFrameProviderTest::Start() {
+ frame_consumer_->Start(
+ base::Bind(&BufferingFrameProviderTest::OnTestCompleted,
+ base::Unretained(this)));
+}
+
+void BufferingFrameProviderTest::OnTestTimeout() {
+ ADD_FAILURE() << "Test timed out";
+ if (base::MessageLoop::current())
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+void BufferingFrameProviderTest::OnTestCompleted() {
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+TEST_F(BufferingFrameProviderTest, FastProviderSlowConsumer) {
+ bool provider_delayed_pattern[] = { false };
+ bool consumer_delayed_pattern[] = { true };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+TEST_F(BufferingFrameProviderTest, SlowProviderFastConsumer) {
+ bool provider_delayed_pattern[] = { true };
+ bool consumer_delayed_pattern[] = { false };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+TEST_F(BufferingFrameProviderTest, SlowFastProducerConsumer) {
+ // Lengths are prime between each other so we can test a lot of combinations.
+ bool provider_delayed_pattern[] = {
+ true, true, true, true, true,
+ false, false, false, false
+ };
+ bool consumer_delayed_pattern[] = {
+ true, true, true, true, true, true, true,
+ false, false, false, false, false, false, false
+ };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_state.cc b/chromecast/media/cma/base/buffering_state.cc
new file mode 100644
index 0000000000..e1fc49fe18
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_state.cc
@@ -0,0 +1,129 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/buffering_state.h"
+
+#include <sstream>
+
+#include "base/logging.h"
+#include "media/base/buffers.h"
+
+namespace chromecast {
+namespace media {
+
+BufferingConfig::BufferingConfig(
+ base::TimeDelta low_level_threshold,
+ base::TimeDelta high_level_threshold)
+ : low_level_threshold_(low_level_threshold),
+ high_level_threshold_(high_level_threshold) {
+}
+
+BufferingConfig::~BufferingConfig() {
+}
+
+
+BufferingState::BufferingState(
+ const scoped_refptr<BufferingConfig>& config,
+ const base::Closure& state_changed_cb,
+ const HighLevelBufferCB& high_level_buffer_cb)
+ : config_(config),
+ state_changed_cb_(state_changed_cb),
+ high_level_buffer_cb_(high_level_buffer_cb),
+ state_(kLowLevel),
+ media_time_(::media::kNoTimestamp()),
+ max_rendering_time_(::media::kNoTimestamp()),
+ buffered_time_(::media::kNoTimestamp()) {
+}
+
+BufferingState::~BufferingState() {
+}
+
+void BufferingState::OnConfigChanged() {
+ state_ = GetBufferLevelState();
+}
+
+void BufferingState::SetMediaTime(base::TimeDelta media_time) {
+ media_time_ = media_time;
+ switch (state_) {
+ case kLowLevel:
+ case kMediumLevel:
+ case kHighLevel:
+ UpdateState(GetBufferLevelState());
+ break;
+ case kEosReached:
+ break;
+ }
+}
+
+void BufferingState::SetMaxRenderingTime(base::TimeDelta max_rendering_time) {
+ max_rendering_time_ = max_rendering_time;
+}
+
+base::TimeDelta BufferingState::GetMaxRenderingTime() const {
+ return max_rendering_time_;
+}
+
+void BufferingState::SetBufferedTime(base::TimeDelta buffered_time) {
+ buffered_time_ = buffered_time;
+ switch (state_) {
+ case kLowLevel:
+ case kMediumLevel:
+ case kHighLevel:
+ UpdateState(GetBufferLevelState());
+ break;
+ case kEosReached:
+ break;
+ }
+}
+
+void BufferingState::NotifyEos() {
+ UpdateState(kEosReached);
+}
+
+void BufferingState::NotifyMaxCapacity(base::TimeDelta buffered_time) {
+ if (media_time_ == ::media::kNoTimestamp() ||
+ buffered_time == ::media::kNoTimestamp()) {
+ LOG(WARNING) << "Max capacity with no timestamp";
+ return;
+ }
+ base::TimeDelta buffer_duration = buffered_time - media_time_;
+ if (buffer_duration < config_->high_level())
+ high_level_buffer_cb_.Run(buffer_duration);
+}
+
+std::string BufferingState::ToString() const {
+ std::ostringstream s;
+ s << "state=" << state_
+ << " media_time_ms=" << media_time_.InMilliseconds()
+ << " buffered_time_ms=" << buffered_time_.InMilliseconds()
+ << " low_level_ms=" << config_->low_level().InMilliseconds()
+ << " high_level_ms=" << config_->high_level().InMilliseconds();
+ return s.str();
+}
+
+BufferingState::State BufferingState::GetBufferLevelState() const {
+ if (media_time_ == ::media::kNoTimestamp() ||
+ buffered_time_ == ::media::kNoTimestamp()) {
+ return kLowLevel;
+ }
+
+ base::TimeDelta buffer_duration = buffered_time_ - media_time_;
+ if (buffer_duration < config_->low_level())
+ return kLowLevel;
+ if (buffer_duration >= config_->high_level())
+ return kHighLevel;
+ return kMediumLevel;
+}
+
+void BufferingState::UpdateState(State new_state) {
+ if (new_state == state_)
+ return;
+
+ state_ = new_state;
+ if (!state_changed_cb_.is_null())
+ state_changed_cb_.Run();
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/buffering_state.h b/chromecast/media/cma/base/buffering_state.h
new file mode 100644
index 0000000000..ced8206d8a
--- /dev/null
+++ b/chromecast/media/cma/base/buffering_state.h
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_
+#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace chromecast {
+namespace media {
+
+class BufferingConfig : public base::RefCountedThreadSafe<BufferingConfig> {
+ public:
+ BufferingConfig(base::TimeDelta low_level_threshold,
+ base::TimeDelta high_level_threshold);
+
+ base::TimeDelta low_level() const { return low_level_threshold_; }
+ base::TimeDelta high_level() const { return high_level_threshold_; }
+
+ void set_low_level(base::TimeDelta low_level) {
+ low_level_threshold_ = low_level;
+ }
+ void set_high_level(base::TimeDelta high_level) {
+ high_level_threshold_ = high_level;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<BufferingConfig>;
+ virtual ~BufferingConfig();
+
+ base::TimeDelta low_level_threshold_;
+ base::TimeDelta high_level_threshold_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferingConfig);
+};
+
+class BufferingState
+ : public base::RefCountedThreadSafe<BufferingState> {
+ public:
+ typedef base::Callback<void(base::TimeDelta)> HighLevelBufferCB;
+
+ enum State {
+ kLowLevel,
+ kMediumLevel,
+ kHighLevel,
+ kEosReached,
+ };
+
+ // Creates a new buffering state. The initial state is |kLowLevel|.
+ // |state_changed_cb| is used to notify about possible state changes.
+ // |high_level_buffer_cb| is used to adjust the high buffer threshold
+ // when the underlying buffer is not large enough to accomodate
+ // the current high buffer level.
+ BufferingState(const scoped_refptr<BufferingConfig>& config,
+ const base::Closure& state_changed_cb,
+ const HighLevelBufferCB& high_level_buffer_cb);
+
+ // Returns the buffering state.
+ State GetState() const { return state_; }
+
+ // Invoked when the buffering configuration has changed.
+ // Based on the new configuration, the buffering state might change.
+ // However, |state_changed_cb_| is not triggered in that case.
+ void OnConfigChanged();
+
+ // Sets the current rendering time for this stream.
+ void SetMediaTime(base::TimeDelta media_time);
+
+ // Sets/gets the maximum rendering media time for this stream.
+ // The maximum rendering time is always lower than the buffered time.
+ void SetMaxRenderingTime(base::TimeDelta max_rendering_time);
+ base::TimeDelta GetMaxRenderingTime() const;
+
+ // Sets the buffered time.
+ void SetBufferedTime(base::TimeDelta buffered_time);
+
+ // Notifies the buffering state that all the frames for this stream have been
+ // buffered, i.e. the end of stream has been reached.
+ void NotifyEos();
+
+ // Notifies the buffering state the underlying buffer has reached
+ // its maximum capacity.
+ // The maximum frame timestamp in the buffer is given by |buffered_time|.
+ // Note: this timestamp can be different from the one provided through
+ // SetBufferedTime since SetBufferedTime takes the timestamp of a playable
+ // frame which is not necessarily the case here (e.g. missing key id).
+ void NotifyMaxCapacity(base::TimeDelta buffered_time);
+
+ // Buffering state as a human readable string, for debugging.
+ std::string ToString() const;
+
+ private:
+ friend class base::RefCountedThreadSafe<BufferingState>;
+ virtual ~BufferingState();
+
+ // Returns the state solely based on the buffered time.
+ State GetBufferLevelState() const;
+
+ // Updates the state to |new_state|.
+ void UpdateState(State new_state);
+
+ scoped_refptr<BufferingConfig> const config_;
+
+ // Callback invoked each time there is a change of state.
+ base::Closure state_changed_cb_;
+
+ // Callback invoked to adjust the high buffer level.
+ HighLevelBufferCB high_level_buffer_cb_;
+
+ // State.
+ State state_;
+
+ // Playback media time.
+ // Equal to kNoTimestamp() when not known.
+ base::TimeDelta media_time_;
+
+ // Maximum rendering media time.
+ // This corresponds to the timestamp of the last frame sent to the hardware
+ // decoder/renderer.
+ base::TimeDelta max_rendering_time_;
+
+ // Buffered media time.
+ // Equal to kNoTimestamp() when not known.
+ base::TimeDelta buffered_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferingState);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_
diff --git a/chromecast/media/cma/base/cma_logging.h b/chromecast/media/cma/base/cma_logging.h
new file mode 100644
index 0000000000..1743cea310
--- /dev/null
+++ b/chromecast/media/cma/base/cma_logging.h
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_
+#define CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_
+
+#include "base/logging.h"
+
+namespace chromecast {
+namespace media {
+
+#define CMALOG(loglevel) VLOG(loglevel)
+
+enum {
+ kLogControl = 2,
+ kLogFrame = 3
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_
diff --git a/chromecast/media/cma/base/coded_frame_provider.cc b/chromecast/media/cma/base/coded_frame_provider.cc
new file mode 100644
index 0000000000..56e461cfd8
--- /dev/null
+++ b/chromecast/media/cma/base/coded_frame_provider.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+
+namespace chromecast {
+namespace media {
+
+CodedFrameProvider::CodedFrameProvider() {
+}
+
+CodedFrameProvider::~CodedFrameProvider() {
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/coded_frame_provider.h b/chromecast/media/cma/base/coded_frame_provider.h
new file mode 100644
index 0000000000..b13231d8e5
--- /dev/null
+++ b/chromecast/media/cma/base/coded_frame_provider.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace media {
+class AudioDecoderConfig;
+class VideoDecoderConfig;
+}
+
+namespace chromecast {
+namespace media {
+class DecoderBufferBase;
+
+class CodedFrameProvider {
+ public:
+ typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&,
+ const ::media::AudioDecoderConfig&,
+ const ::media::VideoDecoderConfig&)> ReadCB;
+
+ CodedFrameProvider();
+ virtual ~CodedFrameProvider();
+
+ // Request a coded frame which is provided asynchronously through callback
+ // |read_cb|.
+ // If the frame is associated with a new video/audio configuration,
+ // these configurations are returned as part of the |read_cb| callback.
+ // Invoking the |read_cb| callback with invalid audio/video configurations
+ // means the configurations have not changed.
+ virtual void Read(const ReadCB& read_cb) = 0;
+
+ // Flush the coded frames held by the frame provider.
+ // Invoke callback |flush_cb| when completed.
+ // Note: any pending read is cancelled, meaning that any pending |read_cb|
+ // callback will not be invoked.
+ virtual void Flush(const base::Closure& flush_cb) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CodedFrameProvider);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_
diff --git a/chromecast/media/cma/base/decoder_buffer_adapter.cc b/chromecast/media/cma/base/decoder_buffer_adapter.cc
new file mode 100644
index 0000000000..236505b995
--- /dev/null
+++ b/chromecast/media/cma/base/decoder_buffer_adapter.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
+
+#include "media/base/decoder_buffer.h"
+
+namespace chromecast {
+namespace media {
+
+DecoderBufferAdapter::DecoderBufferAdapter(
+ const scoped_refptr< ::media::DecoderBuffer>& buffer)
+ : buffer_(buffer) {
+}
+
+DecoderBufferAdapter::~DecoderBufferAdapter() {
+}
+
+base::TimeDelta DecoderBufferAdapter::timestamp() const {
+ return buffer_->timestamp();
+}
+
+const uint8* DecoderBufferAdapter::data() const {
+ return buffer_->data();
+}
+
+uint8* DecoderBufferAdapter::writable_data() const {
+ return buffer_->writable_data();
+}
+
+int DecoderBufferAdapter::data_size() const {
+ return buffer_->data_size();
+}
+
+const ::media::DecryptConfig* DecoderBufferAdapter::decrypt_config() const {
+ return buffer_->decrypt_config();
+}
+
+bool DecoderBufferAdapter::end_of_stream() const {
+ return buffer_->end_of_stream();
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/decoder_buffer_adapter.h b/chromecast/media/cma/base/decoder_buffer_adapter.h
new file mode 100644
index 0000000000..9fe6da95ab
--- /dev/null
+++ b/chromecast/media/cma/base/decoder_buffer_adapter.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+
+namespace media {
+class DecoderBuffer;
+}
+
+namespace chromecast {
+namespace media {
+
+// DecoderBufferAdapter wraps a ::media::DecoderBuffer
+// into a DecoderBufferBase.
+class DecoderBufferAdapter : public DecoderBufferBase {
+ public:
+ explicit DecoderBufferAdapter(
+ const scoped_refptr< ::media::DecoderBuffer>& buffer);
+
+ // DecoderBufferBase implementation.
+ virtual base::TimeDelta timestamp() const OVERRIDE;
+ virtual const uint8* data() const OVERRIDE;
+ virtual uint8* writable_data() const OVERRIDE;
+ virtual int data_size() const OVERRIDE;
+ virtual const ::media::DecryptConfig* decrypt_config() const OVERRIDE;
+ virtual bool end_of_stream() const OVERRIDE;
+
+ private:
+ virtual ~DecoderBufferAdapter();
+
+ scoped_refptr< ::media::DecoderBuffer> const buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecoderBufferAdapter);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_
diff --git a/chromecast/media/cma/base/decoder_buffer_base.cc b/chromecast/media/cma/base/decoder_buffer_base.cc
new file mode 100644
index 0000000000..40c0a7d006
--- /dev/null
+++ b/chromecast/media/cma/base/decoder_buffer_base.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+
+namespace chromecast {
+namespace media {
+
+DecoderBufferBase::DecoderBufferBase() {
+}
+
+DecoderBufferBase::~DecoderBufferBase() {
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/decoder_buffer_base.h b/chromecast/media/cma/base/decoder_buffer_base.h
new file mode 100644
index 0000000000..9143104f14
--- /dev/null
+++ b/chromecast/media/cma/base/decoder_buffer_base.h
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_
+#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_
+
+#include "base/basictypes.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace media {
+class DecryptConfig;
+}
+
+namespace chromecast {
+namespace media {
+
+// DecoderBufferBase exposes only the properties of an audio/video buffer.
+// The way a DecoderBufferBase is created and organized in memory
+// is left as a detail of the implementation of derived classes.
+class DecoderBufferBase
+ : public base::RefCountedThreadSafe<DecoderBufferBase> {
+ public:
+ DecoderBufferBase();
+
+ // Returns the PTS of the frame.
+ virtual base::TimeDelta timestamp() const = 0;
+
+ // Gets the frame data.
+ virtual const uint8* data() const = 0;
+ virtual uint8* writable_data() const = 0;
+
+ // Returns the size of the frame in bytes.
+ virtual int data_size() const = 0;
+
+ // Returns the decrypt configuration.
+ // Returns NULL if the buffer has no decrypt info.
+ virtual const ::media::DecryptConfig* decrypt_config() const = 0;
+
+ // Indicate if this is a special frame that indicates the end of the stream.
+ // If true, functions to access the frame content cannot be called.
+ virtual bool end_of_stream() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<DecoderBufferBase>;
+ virtual ~DecoderBufferBase();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DecoderBufferBase);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_
diff --git a/chromecast/media/cma/base/frame_generator_for_test.cc b/chromecast/media/cma/base/frame_generator_for_test.cc
new file mode 100644
index 0000000000..c03fcf8847
--- /dev/null
+++ b/chromecast/media/cma/base/frame_generator_for_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/frame_generator_for_test.h"
+
+#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
+
+namespace chromecast {
+namespace media {
+
+FrameGeneratorForTest::FrameSpec::FrameSpec()
+ : has_config(false),
+ is_eos(false),
+ has_decrypt_config(false),
+ size(0) {
+}
+
+FrameGeneratorForTest::FrameSpec::~FrameSpec() {
+}
+
+FrameGeneratorForTest::FrameGeneratorForTest(
+ const std::vector<FrameSpec> frame_specs)
+ : frame_specs_(frame_specs),
+ frame_idx_(0),
+ total_buffer_size_(0) {
+}
+
+FrameGeneratorForTest::~FrameGeneratorForTest() {
+}
+
+bool FrameGeneratorForTest::HasDecoderConfig() const {
+ if (frame_idx_ >= frame_specs_.size())
+ return false;
+
+ return frame_specs_[frame_idx_].has_config;
+}
+
+scoped_refptr<DecoderBufferBase> FrameGeneratorForTest::Generate() {
+ if (frame_idx_ >= frame_specs_.size())
+ return scoped_refptr<DecoderBufferBase>();
+
+ const FrameSpec& frame_spec = frame_specs_[frame_idx_];
+ frame_idx_++;
+
+ if (frame_spec.is_eos) {
+ return scoped_refptr<DecoderBufferBase>(
+ new DecoderBufferAdapter(::media::DecoderBuffer::CreateEOSBuffer()));
+ }
+
+ scoped_refptr< ::media::DecoderBuffer> buffer(
+ new ::media::DecoderBuffer(frame_spec.size));
+
+ // Timestamp.
+ buffer->set_timestamp(frame_spec.timestamp);
+
+ // Generate the frame data.
+ for (size_t k = 0; k < frame_spec.size; k++) {
+ buffer->writable_data()[k] = total_buffer_size_ & 0xff;
+ total_buffer_size_++;
+ }
+
+ // Generate the decrypt configuration.
+ if (frame_spec.has_decrypt_config) {
+ uint32 frame_size = buffer->data_size();
+ uint32 chunk_size = 1;
+ std::vector< ::media::SubsampleEntry> subsamples;
+ while (frame_size > 0) {
+ ::media::SubsampleEntry subsample;
+ subsample.clear_bytes = chunk_size;
+ if (subsample.clear_bytes > frame_size)
+ subsample.clear_bytes = frame_size;
+ frame_size -= subsample.clear_bytes;
+ chunk_size <<= 1;
+
+ subsample.cypher_bytes = chunk_size;
+ if (subsample.cypher_bytes > frame_size)
+ subsample.cypher_bytes = frame_size;
+ frame_size -= subsample.cypher_bytes;
+ chunk_size <<= 1;
+
+ subsamples.push_back(subsample);
+ }
+
+ char key_id[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
+
+ char iv[] = {
+ 0x0, 0x2, 0x1, 0x3, 0x5, 0x4, 0x7, 0x6,
+ 0x9, 0x8, 0xb, 0xa, 0xd, 0xc, 0xf, 0xe };
+
+ scoped_ptr< ::media::DecryptConfig> decrypt_config(
+ new ::media::DecryptConfig(
+ std::string(key_id, arraysize(key_id)),
+ std::string(iv, arraysize(iv)),
+ subsamples));
+ buffer->set_decrypt_config(decrypt_config.Pass());
+ }
+
+ return scoped_refptr<DecoderBufferBase>(new DecoderBufferAdapter(buffer));
+}
+
+size_t FrameGeneratorForTest::RemainingFrameCount() const {
+ size_t count = frame_specs_.size() - frame_idx_;
+ return count;
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/frame_generator_for_test.h b/chromecast/media/cma/base/frame_generator_for_test.h
new file mode 100644
index 0000000000..1da3fa8518
--- /dev/null
+++ b/chromecast/media/cma/base/frame_generator_for_test.h
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_FRAME_GENERATOR_FOR_TEST_H_
+#define CHROMECAST_MEDIA_CMA_BASE_FRAME_GENERATOR_FOR_TEST_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace chromecast {
+namespace media {
+class DecoderBufferBase;
+
+class FrameGeneratorForTest {
+ public:
+ // Parameters used to generate frames.
+ struct FrameSpec {
+ FrameSpec();
+ ~FrameSpec();
+
+ // Indicates whether the frame comes with a new decoder configuration.
+ bool has_config;
+
+ bool is_eos;
+ base::TimeDelta timestamp;
+ bool has_decrypt_config;
+ size_t size;
+ };
+
+ explicit FrameGeneratorForTest(const std::vector<FrameSpec> frame_specs);
+ ~FrameGeneratorForTest();
+
+ // Indicates whether the next frame should come with a new decoder config.
+ bool HasDecoderConfig() const;
+
+ // Generates a frame.
+ // Returns NULL is there is no frame left to generate.
+ scoped_refptr<DecoderBufferBase> Generate();
+
+ // Number of frames not generated yet.
+ size_t RemainingFrameCount() const;
+
+ private:
+ std::vector<FrameSpec> frame_specs_;
+ size_t frame_idx_;
+
+ // Total size of A/V buffers generated so far.
+ size_t total_buffer_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameGeneratorForTest);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_TEST_FRAME_GENERATOR_H_
diff --git a/chromecast/media/cma/base/media_task_runner.cc b/chromecast/media/cma/base/media_task_runner.cc
new file mode 100644
index 0000000000..5b5423bbdf
--- /dev/null
+++ b/chromecast/media/cma/base/media_task_runner.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/media_task_runner.h"
+
+namespace chromecast {
+namespace media {
+
+MediaTaskRunner::MediaTaskRunner() {
+}
+
+MediaTaskRunner::~MediaTaskRunner() {
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/media_task_runner.h b/chromecast/media/cma/base/media_task_runner.h
new file mode 100644
index 0000000000..c4a3012c94
--- /dev/null
+++ b/chromecast/media/cma/base/media_task_runner.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_
+
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+
+namespace chromecast {
+namespace media {
+
+class MediaTaskRunner
+ : public base::RefCountedThreadSafe<MediaTaskRunner> {
+ public:
+ MediaTaskRunner();
+
+ // Post a task with the given media |timestamp|. If |timestamp| is equal to
+ // |kNoTimestamp()|, the task is scheduled right away.
+ // How the media timestamp is used to schedule the task is an implementation
+ // detail of derived classes.
+ // Returns true if the task may be run at some point in the future, and false
+ // if the task definitely will not be run.
+ virtual bool PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) = 0;
+
+ protected:
+ virtual ~MediaTaskRunner();
+ friend class base::RefCountedThreadSafe<MediaTaskRunner>;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MediaTaskRunner);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_
diff --git a/chromecast/media/cma/base/mock_frame_consumer.cc b/chromecast/media/cma/base/mock_frame_consumer.cc
new file mode 100644
index 0000000000..a32989dd39
--- /dev/null
+++ b/chromecast/media/cma/base/mock_frame_consumer.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/mock_frame_consumer.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/base/frame_generator_for_test.h"
+#include "media/base/video_decoder_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+MockFrameConsumer::MockFrameConsumer(
+ CodedFrameProvider* coded_frame_provider)
+ : coded_frame_provider_(coded_frame_provider),
+ pattern_idx_(0),
+ last_read_aborted_by_flush_(false) {
+}
+
+MockFrameConsumer::~MockFrameConsumer() {
+}
+
+void MockFrameConsumer::Configure(
+ const std::vector<bool>& delayed_task_pattern,
+ bool last_read_aborted_by_flush,
+ scoped_ptr<FrameGeneratorForTest> frame_generator) {
+ delayed_task_pattern_ = delayed_task_pattern;
+ last_read_aborted_by_flush_ = last_read_aborted_by_flush;
+ frame_generator_.reset(frame_generator.release());
+}
+
+void MockFrameConsumer::Start(const base::Closure& done_cb) {
+ done_cb_ = done_cb;
+
+ pattern_idx_ = 0;
+
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockFrameConsumer::ReadFrame, base::Unretained(this)));
+}
+
+void MockFrameConsumer::ReadFrame() {
+ // Once all the frames have been read, flush the frame provider.
+ if (frame_generator_->RemainingFrameCount() == 0 &&
+ !last_read_aborted_by_flush_) {
+ coded_frame_provider_->Flush(
+ base::Bind(&MockFrameConsumer::OnFlushCompleted,
+ base::Unretained(this)));
+ return;
+ }
+
+ coded_frame_provider_->Read(
+ base::Bind(&MockFrameConsumer::OnNewFrame,
+ base::Unretained(this)));
+
+ // The last read is right away aborted by a Flush.
+ if (frame_generator_->RemainingFrameCount() == 0 &&
+ last_read_aborted_by_flush_) {
+ coded_frame_provider_->Flush(
+ base::Bind(&MockFrameConsumer::OnFlushCompleted,
+ base::Unretained(this)));
+ return;
+ }
+}
+
+void MockFrameConsumer::OnNewFrame(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config) {
+ bool ref_has_config = frame_generator_->HasDecoderConfig();
+ scoped_refptr<DecoderBufferBase> ref_buffer = frame_generator_->Generate();
+
+ ASSERT_TRUE(buffer.get());
+ ASSERT_TRUE(ref_buffer.get());
+
+ EXPECT_EQ(video_config.IsValidConfig(), ref_has_config);
+
+ EXPECT_EQ(buffer->end_of_stream(), ref_buffer->end_of_stream());
+ if (!ref_buffer->end_of_stream()) {
+ EXPECT_EQ(buffer->timestamp(), ref_buffer->timestamp());
+ ASSERT_EQ(buffer->data_size(), ref_buffer->data_size());
+ for (size_t k = 0; k < ref_buffer->data_size(); k++)
+ EXPECT_EQ(buffer->data()[k], ref_buffer->data()[k]);
+ }
+
+ bool delayed = delayed_task_pattern_[pattern_idx_];
+ pattern_idx_ = (pattern_idx_ + 1) % delayed_task_pattern_.size();
+
+ if (delayed) {
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockFrameConsumer::ReadFrame,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(1));
+ } else {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockFrameConsumer::ReadFrame,
+ base::Unretained(this)));
+ }
+}
+
+void MockFrameConsumer::OnFlushCompleted() {
+ EXPECT_EQ(frame_generator_->RemainingFrameCount(), 0);
+ done_cb_.Run();
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/mock_frame_consumer.h b/chromecast/media/cma/base/mock_frame_consumer.h
new file mode 100644
index 0000000000..6c84119d15
--- /dev/null
+++ b/chromecast/media/cma/base/mock_frame_consumer.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace media {
+class AudioDecoderConfig;
+class VideoDecoderConfig;
+}
+
+namespace chromecast {
+namespace media {
+class CodedFrameProvider;
+class DecoderBufferBase;
+class FrameGeneratorForTest;
+
+class MockFrameConsumer {
+ public:
+ explicit MockFrameConsumer(CodedFrameProvider* coded_frame_provider);
+ ~MockFrameConsumer();
+
+ void Configure(const std::vector<bool>& delayed_task_pattern,
+ bool last_read_aborted_by_flush,
+ scoped_ptr<FrameGeneratorForTest> frame_generator);
+
+ // Starts consuming frames. Invoke |done_cb| when all the expected frames
+ // have been received.
+ void Start(const base::Closure& done_cb);
+
+ private:
+ void ReadFrame();
+ void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config);
+
+ void OnFlushCompleted();
+
+ CodedFrameProvider* const coded_frame_provider_;
+
+ base::Closure done_cb_;
+
+ // Parameterization of the frame consumer:
+ // |delayed_task_pattern_| indicates the pattern for fetching frames,
+ // i.e. after receiving a frame, either fetch a frame right away
+ // or wait some time before fetching another frame.
+ // |pattern_idx_| is the current index in the pattern.
+ // |last_read_aborted_by_flush_| indicates whether the last buffer request
+ // should be aborted by a Flush.
+ std::vector<bool> delayed_task_pattern_;
+ size_t pattern_idx_;
+ bool last_read_aborted_by_flush_;
+
+ // Expected results.
+ scoped_ptr<FrameGeneratorForTest> frame_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFrameConsumer);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_
diff --git a/chromecast/media/cma/base/mock_frame_provider.cc b/chromecast/media/cma/base/mock_frame_provider.cc
new file mode 100644
index 0000000000..286c50ae2b
--- /dev/null
+++ b/chromecast/media/cma/base/mock_frame_provider.cc
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/base/mock_frame_provider.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/base/frame_generator_for_test.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/video_decoder_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace chromecast {
+namespace media {
+
+MockFrameProvider::MockFrameProvider() {
+}
+
+MockFrameProvider::~MockFrameProvider() {
+}
+
+void MockFrameProvider::Configure(
+ const std::vector<bool>& delayed_task_pattern,
+ scoped_ptr<FrameGeneratorForTest> frame_generator) {
+ delayed_task_pattern_ = delayed_task_pattern;
+ pattern_idx_ = 0;
+
+ frame_generator_.reset(frame_generator.release());
+}
+
+void MockFrameProvider::Read(const ReadCB& read_cb) {
+ bool delayed = delayed_task_pattern_[pattern_idx_];
+ pattern_idx_ = (pattern_idx_ + 1) % delayed_task_pattern_.size();
+
+ if (delayed) {
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockFrameProvider::DoRead,
+ base::Unretained(this), read_cb),
+ base::TimeDelta::FromMilliseconds(1));
+ } else {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockFrameProvider::DoRead,
+ base::Unretained(this), read_cb));
+ }
+}
+
+void MockFrameProvider::Flush(const base::Closure& flush_cb) {
+ flush_cb.Run();
+}
+
+void MockFrameProvider::DoRead(const ReadCB& read_cb) {
+ bool has_config = frame_generator_->HasDecoderConfig();
+
+ scoped_refptr<DecoderBufferBase> buffer(frame_generator_->Generate());
+ ASSERT_TRUE(buffer.get());
+
+ ::media::AudioDecoderConfig audio_config;
+ ::media::VideoDecoderConfig video_config;
+ if (has_config) {
+ gfx::Size coded_size(640, 480);
+ gfx::Rect visible_rect(640, 480);
+ gfx::Size natural_size(640, 480);
+ video_config = ::media::VideoDecoderConfig(
+ ::media::kCodecH264,
+ ::media::VIDEO_CODEC_PROFILE_UNKNOWN,
+ ::media::VideoFrame::YV12,
+ coded_size,
+ visible_rect,
+ natural_size,
+ NULL, 0,
+ false);
+
+ audio_config = ::media::AudioDecoderConfig(
+ ::media::kCodecAAC,
+ ::media::kSampleFormatS16,
+ ::media::CHANNEL_LAYOUT_STEREO,
+ 44100,
+ NULL, 0,
+ false);
+ }
+
+ read_cb.Run(buffer, audio_config, video_config);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/base/mock_frame_provider.h b/chromecast/media/cma/base/mock_frame_provider.h
new file mode 100644
index 0000000000..fbea1cf61e
--- /dev/null
+++ b/chromecast/media/cma/base/mock_frame_provider.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_
+#define CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+
+namespace chromecast {
+namespace media {
+class FrameGeneratorForTest;
+
+class MockFrameProvider : public CodedFrameProvider {
+ public:
+ MockFrameProvider();
+ virtual ~MockFrameProvider();
+
+ void Configure(
+ const std::vector<bool>& delayed_task_pattern,
+ scoped_ptr<FrameGeneratorForTest> frame_generator);
+
+ // CodedFrameProvider implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual void Flush(const base::Closure& flush_cb) OVERRIDE;
+
+ private:
+ void DoRead(const ReadCB& read_cb);
+
+ // Parameterization of the frame provider.
+ // |delayed_task_pattern_| indicates the pattern for delivering frames,
+ // i.e. after receiving a Read request, either delivers a frame right away
+ // or wait some time before delivering the frame.
+ // |pattern_idx_| is the current index in the pattern.
+ std::vector<bool> delayed_task_pattern_;
+ size_t pattern_idx_;
+
+ scoped_ptr<FrameGeneratorForTest> frame_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFrameProvider);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_
diff --git a/chromecast/media/cma/base/run_all_unittests.cc b/chromecast/media/cma/base/run_all_unittests.cc
new file mode 100644
index 0000000000..07b9e2bfb8
--- /dev/null
+++ b/chromecast/media/cma/base/run_all_unittests.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+#include "media/base/media.h"
+
+#if defined(OS_ANDROID)
+#error "CMA not supported on Android"
+#endif
+
+class CmaTestSuite : public base::TestSuite {
+ public:
+ // Note: the base class constructor creates an AtExitManager.
+ CmaTestSuite(int argc, char** argv) : TestSuite(argc, argv) {}
+ virtual ~CmaTestSuite() {}
+
+ protected:
+ virtual void Initialize() OVERRIDE;
+};
+
+void CmaTestSuite::Initialize() {
+ // Run TestSuite::Initialize first so that logging is initialized.
+ base::TestSuite::Initialize();
+
+ // Initialize the FFMpeg library.
+ // Note: at this time, AtExitManager is already present.
+ media::InitializeMediaLibraryForTesting();
+}
+
+int main(int argc, char** argv) {
+ CmaTestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&CmaTestSuite::Run,
+ base::Unretained(&test_suite)));
+}
diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter.cc b/chromecast/media/cma/filters/demuxer_stream_adapter.cc
new file mode 100644
index 0000000000..2f27e78fe4
--- /dev/null
+++ b/chromecast/media/cma/filters/demuxer_stream_adapter.cc
@@ -0,0 +1,207 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/filters/demuxer_stream_adapter.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/single_thread_task_runner.h"
+#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
+#include "chromecast/media/cma/base/cma_logging.h"
+#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
+#include "chromecast/media/cma/base/media_task_runner.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/buffers.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/demuxer_stream.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class DummyMediaTaskRunner : public MediaTaskRunner {
+ public:
+ DummyMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ // MediaTaskRunner implementation.
+ virtual bool PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) OVERRIDE;
+
+ private:
+ virtual ~DummyMediaTaskRunner();
+
+ scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyMediaTaskRunner);
+};
+
+DummyMediaTaskRunner::DummyMediaTaskRunner(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : task_runner_(task_runner) {
+}
+
+DummyMediaTaskRunner::~DummyMediaTaskRunner() {
+}
+
+bool DummyMediaTaskRunner::PostMediaTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta timestamp) {
+ return task_runner_->PostTask(from_here, task);
+}
+
+} // namespace
+
+DemuxerStreamAdapter::DemuxerStreamAdapter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const scoped_refptr<BalancedMediaTaskRunnerFactory>&
+ media_task_runner_factory,
+ ::media::DemuxerStream* demuxer_stream)
+ : task_runner_(task_runner),
+ media_task_runner_factory_(media_task_runner_factory),
+ media_task_runner_(new DummyMediaTaskRunner(task_runner)),
+ demuxer_stream_(demuxer_stream),
+ is_pending_read_(false),
+ is_pending_demuxer_read_(false),
+ weak_factory_(this),
+ weak_this_(weak_factory_.GetWeakPtr()) {
+ ResetMediaTaskRunner();
+ thread_checker_.DetachFromThread();
+}
+
+DemuxerStreamAdapter::~DemuxerStreamAdapter() {
+ // Needed since we use weak pointers:
+ // weak pointers must be invalidated on the same thread.
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void DemuxerStreamAdapter::Read(const ReadCB& read_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK(flush_cb_.is_null());
+
+ // Support only one read at a time.
+ DCHECK(!is_pending_read_);
+ is_pending_read_ = true;
+ ReadInternal(read_cb);
+}
+
+void DemuxerStreamAdapter::ReadInternal(const ReadCB& read_cb) {
+ bool may_run_in_future = media_task_runner_->PostMediaTask(
+ FROM_HERE,
+ base::Bind(&DemuxerStreamAdapter::RequestBuffer, weak_this_, read_cb),
+ max_pts_);
+ DCHECK(may_run_in_future);
+}
+
+void DemuxerStreamAdapter::Flush(const base::Closure& flush_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CMALOG(kLogControl) << __FUNCTION__;
+
+ // Flush cancels any pending read.
+ is_pending_read_ = false;
+
+ // Reset the decoder configurations.
+ audio_config_ = ::media::AudioDecoderConfig();
+ video_config_ = ::media::VideoDecoderConfig();
+
+ // Create a new media task runner for the upcoming media timeline.
+ ResetMediaTaskRunner();
+
+ DCHECK(flush_cb_.is_null());
+ if (is_pending_demuxer_read_) {
+ // If there is a pending demuxer read, the implicit contract
+ // is that the pending read must be completed before invoking the
+ // flush callback.
+ flush_cb_ = flush_cb;
+ return;
+ }
+
+ // At this point, there is no more pending demuxer read,
+ // so all the previous tasks associated with the current timeline
+ // can be cancelled.
+ weak_factory_.InvalidateWeakPtrs();
+ weak_this_ = weak_factory_.GetWeakPtr();
+
+ CMALOG(kLogControl) << "Flush done";
+ flush_cb.Run();
+}
+
+void DemuxerStreamAdapter::ResetMediaTaskRunner() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ max_pts_ = ::media::kNoTimestamp();
+ if (media_task_runner_factory_.get()) {
+ media_task_runner_ =
+ media_task_runner_factory_->CreateMediaTaskRunner(task_runner_);
+ }
+}
+
+void DemuxerStreamAdapter::RequestBuffer(const ReadCB& read_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ is_pending_demuxer_read_ = true;
+ demuxer_stream_->Read(::media::BindToCurrentLoop(
+ base::Bind(&DemuxerStreamAdapter::OnNewBuffer, weak_this_, read_cb)));
+}
+
+void DemuxerStreamAdapter::OnNewBuffer(
+ const ReadCB& read_cb,
+ ::media::DemuxerStream::Status status,
+ const scoped_refptr< ::media::DecoderBuffer>& input) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ is_pending_demuxer_read_ = false;
+
+ // Just discard the buffer in the flush stage.
+ if (!flush_cb_.is_null()) {
+ CMALOG(kLogControl) << "Flush done";
+ base::ResetAndReturn(&flush_cb_).Run();
+ return;
+ }
+
+ if (status == ::media::DemuxerStream::kAborted) {
+ DCHECK(input.get() == NULL);
+ return;
+ }
+
+ if (status == ::media::DemuxerStream::kConfigChanged) {
+ DCHECK(input.get() == NULL);
+ if (demuxer_stream_->type() == ::media::DemuxerStream::VIDEO)
+ video_config_ = demuxer_stream_->video_decoder_config();
+ if (demuxer_stream_->type() == ::media::DemuxerStream::AUDIO)
+ audio_config_ = demuxer_stream_->audio_decoder_config();
+
+ // Got a new config, but we still need to get a frame.
+ ReadInternal(read_cb);
+ return;
+ }
+
+ DCHECK_EQ(status, ::media::DemuxerStream::kOk);
+
+ // Updates the timestamp used for task scheduling.
+ if (!input->end_of_stream() &&
+ input->timestamp() != ::media::kNoTimestamp() &&
+ (max_pts_ == ::media::kNoTimestamp() || input->timestamp() > max_pts_)) {
+ max_pts_ = input->timestamp();
+ }
+
+ // Provides the buffer as well as possibly valid audio and video configs.
+ is_pending_read_ = false;
+ scoped_refptr<DecoderBufferBase> buffer(new DecoderBufferAdapter(input));
+ read_cb.Run(buffer, audio_config_, video_config_);
+
+ // Back to the default audio/video config:
+ // an invalid audio/video config means there is no config update.
+ if (audio_config_.IsValidConfig())
+ audio_config_ = ::media::AudioDecoderConfig();
+ if (video_config_.IsValidConfig())
+ video_config_ = ::media::VideoDecoderConfig();
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter.h b/chromecast/media/cma/filters/demuxer_stream_adapter.h
new file mode 100644
index 0000000000..4729628816
--- /dev/null
+++ b/chromecast/media/cma/filters/demuxer_stream_adapter.h
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_
+#define CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/demuxer_stream.h"
+#include "media/base/video_decoder_config.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+class DemuxerStream;
+}
+
+namespace chromecast {
+namespace media {
+class BalancedMediaTaskRunnerFactory;
+class MediaTaskRunner;
+
+// DemuxerStreamAdapter wraps a DemuxerStream into a CodedFrameProvider.
+class DemuxerStreamAdapter : public CodedFrameProvider {
+ public:
+ DemuxerStreamAdapter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const scoped_refptr<BalancedMediaTaskRunnerFactory>&
+ media_task_runner_factory,
+ ::media::DemuxerStream* demuxer_stream);
+ virtual ~DemuxerStreamAdapter();
+
+ // CodedFrameProvider implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual void Flush(const base::Closure& flush_cb) OVERRIDE;
+
+ private:
+ void ResetMediaTaskRunner();
+
+ void ReadInternal(const ReadCB& read_cb);
+ void RequestBuffer(const ReadCB& read_cb);
+
+ // Callback invoked from the demuxer stream to signal a buffer is ready.
+ void OnNewBuffer(const ReadCB& read_cb,
+ ::media::DemuxerStream::Status status,
+ const scoped_refptr< ::media::DecoderBuffer>& input);
+
+ base::ThreadChecker thread_checker_;
+
+ // Task runner DemuxerStreamAdapter is running on.
+ scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
+
+ // Media task runner to pace requests to the DemuxerStream.
+ scoped_refptr<BalancedMediaTaskRunnerFactory> const
+ media_task_runner_factory_;
+ scoped_refptr<MediaTaskRunner> media_task_runner_;
+ base::TimeDelta max_pts_;
+
+ // Frames are provided by |demuxer_stream_|.
+ ::media::DemuxerStream* const demuxer_stream_;
+
+ // Indicate if there is a pending read.
+ bool is_pending_read_;
+
+ // Indicate if |demuxer_stream_| has a pending read.
+ bool is_pending_demuxer_read_;
+
+ // In case of a pending flush operation, this is the callback
+ // that is invoked when flush is completed.
+ base::Closure flush_cb_;
+
+ // Audio/video configuration that applies to the next frame.
+ ::media::AudioDecoderConfig audio_config_;
+ ::media::VideoDecoderConfig video_config_;
+
+ base::WeakPtrFactory<DemuxerStreamAdapter> weak_factory_;
+ base::WeakPtr<DemuxerStreamAdapter> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapter);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_
diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc
new file mode 100644
index 0000000000..cc1a6b0e9c
--- /dev/null
+++ b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc
@@ -0,0 +1,345 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/filters/demuxer_stream_adapter.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/demuxer_stream.h"
+#include "media/base/video_decoder_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class DummyDemuxerStream : public ::media::DemuxerStream {
+ public:
+ // Creates a demuxer stream which provides frames either with a delay
+ // or instantly. The scheduling pattern is the following:
+ // - provides |delayed_frame_count| frames with a delay,
+ // - then provides the following |cycle_count| - |delayed_frame_count|
+ // instantly,
+ // - then provides |delayed_frame_count| frames with a delay,
+ // - ... and so on.
+ // Special cases:
+ // - all frames are delayed: |delayed_frame_count| = |cycle_count|
+ // - all frames are provided instantly: |delayed_frame_count| = 0
+ // |config_idx| is a list of frame index before which there is
+ // a change of decoder configuration.
+ DummyDemuxerStream(int cycle_count,
+ int delayed_frame_count,
+ const std::list<int>& config_idx);
+ virtual ~DummyDemuxerStream();
+
+ // ::media::DemuxerStream implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual ::media::AudioDecoderConfig audio_decoder_config() OVERRIDE;
+ virtual ::media::VideoDecoderConfig video_decoder_config() OVERRIDE;
+ virtual Type type() OVERRIDE;
+ virtual bool SupportsConfigChanges() OVERRIDE;
+ virtual ::media::VideoRotation video_rotation() OVERRIDE;
+
+ bool has_pending_read() const {
+ return has_pending_read_;
+ }
+
+ private:
+ void DoRead(const ReadCB& read_cb);
+
+ // Demuxer configuration.
+ const int cycle_count_;
+ const int delayed_frame_count_;
+ std::list<int> config_idx_;
+
+ // Number of frames sent so far.
+ int frame_count_;
+
+ bool has_pending_read_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyDemuxerStream);
+};
+
+DummyDemuxerStream::DummyDemuxerStream(
+ int cycle_count,
+ int delayed_frame_count,
+ const std::list<int>& config_idx)
+ : cycle_count_(cycle_count),
+ delayed_frame_count_(delayed_frame_count),
+ config_idx_(config_idx),
+ frame_count_(0),
+ has_pending_read_(false) {
+ DCHECK_LE(delayed_frame_count, cycle_count);
+}
+
+DummyDemuxerStream::~DummyDemuxerStream() {
+}
+
+void DummyDemuxerStream::Read(const ReadCB& read_cb) {
+ has_pending_read_ = true;
+ if (!config_idx_.empty() && config_idx_.front() == frame_count_) {
+ config_idx_.pop_front();
+ has_pending_read_ = false;
+ read_cb.Run(kConfigChanged,
+ scoped_refptr< ::media::DecoderBuffer>());
+ return;
+ }
+
+ if ((frame_count_ % cycle_count_) < delayed_frame_count_) {
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DummyDemuxerStream::DoRead, base::Unretained(this),
+ read_cb),
+ base::TimeDelta::FromMilliseconds(20));
+ return;
+ }
+ DoRead(read_cb);
+}
+
+::media::AudioDecoderConfig DummyDemuxerStream::audio_decoder_config() {
+ LOG(FATAL) << "DummyDemuxerStream is a video DemuxerStream";
+ return ::media::AudioDecoderConfig();
+}
+
+::media::VideoDecoderConfig DummyDemuxerStream::video_decoder_config() {
+ gfx::Size coded_size(640, 480);
+ gfx::Rect visible_rect(640, 480);
+ gfx::Size natural_size(640, 480);
+ return ::media::VideoDecoderConfig(
+ ::media::kCodecH264,
+ ::media::VIDEO_CODEC_PROFILE_UNKNOWN,
+ ::media::VideoFrame::YV12,
+ coded_size,
+ visible_rect,
+ natural_size,
+ NULL, 0,
+ false);
+}
+
+::media::DemuxerStream::Type DummyDemuxerStream::type() {
+ return VIDEO;
+}
+
+bool DummyDemuxerStream::SupportsConfigChanges() {
+ return true;
+}
+
+::media::VideoRotation DummyDemuxerStream::video_rotation() {
+ return ::media::VIDEO_ROTATION_0;
+}
+
+void DummyDemuxerStream::DoRead(const ReadCB& read_cb) {
+ has_pending_read_ = false;
+ scoped_refptr< ::media::DecoderBuffer> buffer(
+ new ::media::DecoderBuffer(16));
+ buffer->set_timestamp(frame_count_ * base::TimeDelta::FromMilliseconds(40));
+ frame_count_++;
+ read_cb.Run(kOk, buffer);
+}
+
+} // namespace
+
+class DemuxerStreamAdapterTest : public testing::Test {
+ public:
+ DemuxerStreamAdapterTest();
+ virtual ~DemuxerStreamAdapterTest();
+
+ void Initialize(::media::DemuxerStream* demuxer_stream);
+ void Start();
+
+ protected:
+ void OnTestTimeout();
+ void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config);
+ void OnFlushCompleted();
+
+ // Total number of frames to request.
+ int total_frames_;
+
+ // Number of demuxer read before issuing an early flush.
+ int early_flush_idx_;
+ bool use_post_task_for_flush_;
+
+ // Number of expected read frames.
+ int total_expected_frames_;
+
+ // Number of frames actually read so far.
+ int frame_received_count_;
+
+ // List of expected frame indices with decoder config changes.
+ std::list<int> config_idx_;
+
+ scoped_ptr<DummyDemuxerStream> demuxer_stream_;
+
+ scoped_ptr<CodedFrameProvider> coded_frame_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapterTest);
+};
+
+DemuxerStreamAdapterTest::DemuxerStreamAdapterTest()
+ : use_post_task_for_flush_(false) {
+}
+
+DemuxerStreamAdapterTest::~DemuxerStreamAdapterTest() {
+}
+
+void DemuxerStreamAdapterTest::Initialize(
+ ::media::DemuxerStream* demuxer_stream) {
+ coded_frame_provider_.reset(
+ new DemuxerStreamAdapter(
+ base::MessageLoopProxy::current(),
+ scoped_refptr<BalancedMediaTaskRunnerFactory>(),
+ demuxer_stream));
+}
+
+void DemuxerStreamAdapterTest::Start() {
+ frame_received_count_ = 0;
+
+ // TODO(damienv): currently, test assertions which fail do not trigger the
+ // exit of the unit test, the message loop is still running. Find a different
+ // way to exit the unit test.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DemuxerStreamAdapterTest::OnTestTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(5));
+
+ coded_frame_provider_->Read(
+ base::Bind(&DemuxerStreamAdapterTest::OnNewFrame,
+ base::Unretained(this)));
+}
+
+void DemuxerStreamAdapterTest::OnTestTimeout() {
+ ADD_FAILURE() << "Test timed out";
+ if (base::MessageLoop::current())
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+void DemuxerStreamAdapterTest::OnNewFrame(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config) {
+ if (video_config.IsValidConfig()) {
+ ASSERT_GT(config_idx_.size(), 0);
+ ASSERT_EQ(frame_received_count_, config_idx_.front());
+ config_idx_.pop_front();
+ }
+
+ ASSERT_TRUE(buffer.get() != NULL);
+ ASSERT_EQ(buffer->timestamp(),
+ frame_received_count_ * base::TimeDelta::FromMilliseconds(40));
+ frame_received_count_++;
+
+ if (frame_received_count_ >= total_frames_) {
+ coded_frame_provider_->Flush(
+ base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted,
+ base::Unretained(this)));
+ return;
+ }
+
+ coded_frame_provider_->Read(
+ base::Bind(&DemuxerStreamAdapterTest::OnNewFrame,
+ base::Unretained(this)));
+
+ ASSERT_LE(frame_received_count_, early_flush_idx_);
+ if (frame_received_count_ == early_flush_idx_) {
+ base::Closure flush_cb =
+ base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted,
+ base::Unretained(this));
+ if (use_post_task_for_flush_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CodedFrameProvider::Flush,
+ base::Unretained(coded_frame_provider_.get()),
+ flush_cb));
+ } else {
+ coded_frame_provider_->Flush(flush_cb);
+ }
+ return;
+ }
+}
+
+void DemuxerStreamAdapterTest::OnFlushCompleted() {
+ ASSERT_EQ(frame_received_count_, total_expected_frames_);
+ ASSERT_FALSE(demuxer_stream_->has_pending_read());
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+TEST_F(DemuxerStreamAdapterTest, NoDelay) {
+ total_frames_ = 10;
+ early_flush_idx_ = total_frames_; // No early flush.
+ total_expected_frames_ = 10;
+ config_idx_.push_back(0);
+ config_idx_.push_back(5);
+
+ int cycle_count = 1;
+ int delayed_frame_count = 0;
+ demuxer_stream_.reset(
+ new DummyDemuxerStream(
+ cycle_count, delayed_frame_count, config_idx_));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ Initialize(demuxer_stream_.get());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
+ message_loop->Run();
+}
+
+TEST_F(DemuxerStreamAdapterTest, AllDelayed) {
+ total_frames_ = 10;
+ early_flush_idx_ = total_frames_; // No early flush.
+ total_expected_frames_ = 10;
+ config_idx_.push_back(0);
+ config_idx_.push_back(5);
+
+ int cycle_count = 1;
+ int delayed_frame_count = 1;
+ demuxer_stream_.reset(
+ new DummyDemuxerStream(
+ cycle_count, delayed_frame_count, config_idx_));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ Initialize(demuxer_stream_.get());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
+ message_loop->Run();
+}
+
+TEST_F(DemuxerStreamAdapterTest, AllDelayedEarlyFlush) {
+ total_frames_ = 10;
+ early_flush_idx_ = 5;
+ use_post_task_for_flush_ = true;
+ total_expected_frames_ = 5;
+ config_idx_.push_back(0);
+ config_idx_.push_back(3);
+
+ int cycle_count = 1;
+ int delayed_frame_count = 1;
+ demuxer_stream_.reset(
+ new DummyDemuxerStream(
+ cycle_count, delayed_frame_count, config_idx_));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ Initialize(demuxer_stream_.get());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
+ message_loop->Run();
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc/media_memory_chunk.cc b/chromecast/media/cma/ipc/media_memory_chunk.cc
new file mode 100644
index 0000000000..0d6896958b
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_memory_chunk.cc
@@ -0,0 +1,14 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+
+namespace chromecast {
+namespace media {
+
+MediaMemoryChunk::~MediaMemoryChunk() {
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc/media_memory_chunk.h b/chromecast/media/cma/ipc/media_memory_chunk.h
new file mode 100644
index 0000000000..1b91c840a9
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_memory_chunk.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_
+#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_
+
+#include "base/basictypes.h"
+
+namespace chromecast {
+namespace media {
+
+// MediaMemoryChunk represents a block of memory without doing any assumption
+// about the type of memory (e.g. shared memory) nor about the underlying
+// memory ownership.
+// The block of memory can be invalidated under the cover (e.g. if the derived
+// class does not own the underlying memory),
+// in that case, MediaMemoryChunk::valid() will return false.
+class MediaMemoryChunk {
+ public:
+ virtual ~MediaMemoryChunk();
+
+ // Returns the start of the block of memory.
+ virtual void* data() const = 0;
+
+ // Returns the size of the block of memory.
+ virtual size_t size() const = 0;
+
+ // Returns whether the underlying block of memory is valid.
+ // Since MediaMemoryChunk does not specify a memory ownership model,
+ // the underlying block of memory might be invalidated by a third party.
+ // In that case, valid() will return false.
+ virtual bool valid() const = 0;
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_
diff --git a/chromecast/media/cma/ipc/media_message.cc b/chromecast/media/cma/ipc/media_message.cc
new file mode 100644
index 0000000000..8dfcd7cb88
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message.cc
@@ -0,0 +1,198 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc/media_message.h"
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+
+namespace chromecast {
+namespace media {
+
+// static
+scoped_ptr<MediaMessage> MediaMessage::CreateDummyMessage(
+ uint32 type) {
+ return scoped_ptr<MediaMessage>(
+ new MediaMessage(type, std::numeric_limits<size_t>::max()));
+}
+
+// static
+scoped_ptr<MediaMessage> MediaMessage::CreateMessage(
+ uint32 type,
+ const MemoryAllocatorCB& memory_allocator,
+ size_t msg_content_capacity) {
+ size_t msg_size = minimum_msg_size() + msg_content_capacity;
+
+ // Make the message size a multiple of the alignment
+ // so that if we have proper alignment for array of messages.
+ size_t end_alignment = msg_size % ALIGNOF(SerializedMsg);
+ if (end_alignment != 0)
+ msg_size += ALIGNOF(SerializedMsg) - end_alignment;
+
+ scoped_ptr<MediaMemoryChunk> memory(memory_allocator.Run(msg_size));
+ if (!memory)
+ return scoped_ptr<MediaMessage>();
+
+ return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass()));
+}
+
+// static
+scoped_ptr<MediaMessage> MediaMessage::CreateMessage(
+ uint32 type,
+ scoped_ptr<MediaMemoryChunk> memory) {
+ return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass()));
+}
+
+// static
+scoped_ptr<MediaMessage> MediaMessage::MapMessage(
+ scoped_ptr<MediaMemoryChunk> memory) {
+ return scoped_ptr<MediaMessage>(new MediaMessage(memory.Pass()));
+}
+
+MediaMessage::MediaMessage(uint32 type, size_t msg_size)
+ : is_dummy_msg_(true),
+ cached_header_(&cached_msg_.header),
+ msg_(&cached_msg_),
+ msg_read_only_(&cached_msg_),
+ rd_offset_(0) {
+ cached_header_->size = msg_size;
+ cached_header_->type = type;
+ cached_header_->content_size = 0;
+}
+
+MediaMessage::MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory)
+ : is_dummy_msg_(false),
+ cached_header_(&cached_msg_.header),
+ msg_(static_cast<SerializedMsg*>(memory->data())),
+ msg_read_only_(msg_),
+ mem_(memory.Pass()),
+ rd_offset_(0) {
+ CHECK(mem_->valid());
+ CHECK_GE(mem_->size(), minimum_msg_size());
+
+ // Check memory alignment:
+ // needed to cast properly |msg_dst| to a SerializedMsg.
+ CHECK_EQ(
+ reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u);
+
+ // Make sure that |mem_->data()| + |mem_->size()| is also aligned correctly.
+ // This is needed if we append a second serialized message next to this one.
+ // The second serialized message must be aligned correctly.
+ // It is similar to what a compiler is doing for arrays of structures.
+ CHECK_EQ(mem_->size() % ALIGNOF(SerializedMsg), 0u);
+
+ cached_header_->size = mem_->size();
+ cached_header_->type = type;
+ cached_header_->content_size = 0;
+ msg_->header = *cached_header_;
+}
+
+MediaMessage::MediaMessage(scoped_ptr<MediaMemoryChunk> memory)
+ : is_dummy_msg_(false),
+ cached_header_(&cached_msg_.header),
+ msg_(NULL),
+ msg_read_only_(static_cast<SerializedMsg*>(memory->data())),
+ mem_(memory.Pass()),
+ rd_offset_(0) {
+ CHECK(mem_->valid());
+
+ // Check memory alignment.
+ CHECK_EQ(
+ reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u);
+
+ // Cache the message header which cannot be modified while reading.
+ CHECK_GE(mem_->size(), minimum_msg_size());
+ *cached_header_ = msg_read_only_->header;
+ CHECK_GE(cached_header_->size, minimum_msg_size());
+
+ // Make sure if we have 2 consecutive serialized messages in memory,
+ // the 2nd message is also aligned correctly.
+ CHECK_EQ(cached_header_->size % ALIGNOF(SerializedMsg), 0u);
+
+ size_t max_content_size = cached_header_->size - minimum_msg_size();
+ CHECK_LE(cached_header_->content_size, max_content_size);
+}
+
+MediaMessage::~MediaMessage() {
+}
+
+bool MediaMessage::IsSerializedMsgAvailable() const {
+ return !is_dummy_msg_ && mem_->valid();
+}
+
+bool MediaMessage::WriteBuffer(const void* src, size_t size) {
+ // No message to write into.
+ if (!msg_)
+ return false;
+
+ // The underlying memory was invalidated.
+ if (!is_dummy_msg_ && !mem_->valid())
+ return false;
+
+ size_t max_content_size = cached_header_->size - minimum_msg_size();
+ if (cached_header_->content_size + size > max_content_size) {
+ cached_header_->content_size = max_content_size;
+ msg_->header.content_size = cached_header_->content_size;
+ return false;
+ }
+
+ // Write the message only for non-dummy messages.
+ if (!is_dummy_msg_) {
+ uint8* wr_ptr = &msg_->content + cached_header_->content_size;
+ memcpy(wr_ptr, src, size);
+ }
+
+ cached_header_->content_size += size;
+ msg_->header.content_size = cached_header_->content_size;
+ return true;
+}
+
+bool MediaMessage::ReadBuffer(void* dst, size_t size) {
+ // No read possible for a dummy message.
+ if (is_dummy_msg_)
+ return false;
+
+ // The underlying memory was invalidated.
+ if (!mem_->valid())
+ return false;
+
+ if (rd_offset_ + size > cached_header_->content_size) {
+ rd_offset_ = cached_header_->content_size;
+ return false;
+ }
+
+ const uint8* rd_ptr = &msg_read_only_->content + rd_offset_;
+ memcpy(dst, rd_ptr, size);
+ rd_offset_ += size;
+ return true;
+}
+
+void* MediaMessage::GetWritableBuffer(size_t size) {
+ // No read possible for a dummy message.
+ if (is_dummy_msg_)
+ return NULL;
+
+ // The underlying memory was invalidated.
+ if (!mem_->valid())
+ return NULL;
+
+ if (rd_offset_ + size > cached_header_->content_size) {
+ rd_offset_ = cached_header_->content_size;
+ return NULL;
+ }
+
+ uint8* rd_ptr = &msg_read_only_->content + rd_offset_;
+ rd_offset_ += size;
+ return rd_ptr;
+}
+
+const void* MediaMessage::GetBuffer(size_t size) {
+ return GetWritableBuffer(size);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc/media_message.h b/chromecast/media/cma/ipc/media_message.h
new file mode 100644
index 0000000000..066409be3d
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message.h
@@ -0,0 +1,165 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_H_
+#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_H_
+
+#include <stddef.h>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace chromecast {
+namespace media {
+class MediaMemoryChunk;
+
+// MediaMessage -
+// Represents a media message, including:
+// - a message header that gives for example the message size or its type,
+// - the content of the message,
+// - and some possible padding if the content does not occupy the whole
+// reserved space.
+//
+class MediaMessage {
+ public:
+ // Memory allocator: given a number of bytes to allocate,
+ // return the pointer to the allocated block if successful
+ // or NULL if allocation failed.
+ typedef base::Callback<scoped_ptr<MediaMemoryChunk>(size_t)>
+ MemoryAllocatorCB;
+
+ // Creates a message with no associated memory for its content, i.e.
+ // each write on this message is a dummy operation.
+ // This type of message can be useful to calculate first the size of the
+ // message, before allocating the real message.
+ static scoped_ptr<MediaMessage> CreateDummyMessage(uint32 type);
+
+ // Creates a message with a capacity of at least |msg_content_capacity|
+ // bytes. The actual content size can be smaller than its capacity.
+ // The message can be populated with some Write functions.
+ static scoped_ptr<MediaMessage> CreateMessage(
+ uint32 type,
+ const MemoryAllocatorCB& memory_allocator,
+ size_t msg_content_capacity);
+
+ // Creates a message of type |type| whose serialized structure is stored
+ // in |mem|.
+ static scoped_ptr<MediaMessage> CreateMessage(
+ uint32 type,
+ scoped_ptr<MediaMemoryChunk> mem);
+
+ // Creates a message from a memory area which already contains
+ // the serialized structure of the message.
+ // Only Read functions can be invoked on this type of message.
+ static scoped_ptr<MediaMessage> MapMessage(
+ scoped_ptr<MediaMemoryChunk> mem);
+
+ // Return the minimum size of a message.
+ static size_t minimum_msg_size() {
+ return offsetof(SerializedMsg, content);
+ }
+
+ ~MediaMessage();
+
+ // Indicate whether the underlying serialized structure of the message is
+ // available.
+ // Note: the serialized structure might be unavailable in case of a dummy
+ // message or if the underlying memory has been invalidated.
+ bool IsSerializedMsgAvailable() const;
+
+ // Return the message and the total size of the message
+ // incuding the header, the content and the possible padding.
+ const void* msg() const { return msg_read_only_; }
+ size_t size() const { return cached_msg_.header.size; }
+
+ // Return the size of the message without padding.
+ size_t actual_size() const {
+ return minimum_msg_size() + cached_msg_.header.content_size;
+ }
+
+ // Return the size of the content of the message.
+ size_t content_size() const { return cached_msg_.header.content_size; }
+
+ // Return the type of the message.
+ uint32 type() const { return cached_msg_.header.type; }
+
+ // Append a POD to the message.
+ // Return true if the POD has been succesfully written.
+ template<typename T> bool WritePod(T* const& pod);
+ template<typename T> bool WritePod(const T& pod) {
+ return WriteBuffer(&pod, sizeof(T));
+ }
+
+ // Append a raw buffer to the message.
+ bool WriteBuffer(const void* src, size_t size);
+
+ // Read a POD from the message.
+ template<typename T> bool ReadPod(T* pod) {
+ return ReadBuffer(pod, sizeof(T));
+ }
+
+ // Read |size| bytes from the message from the last read position
+ // and write it to |dst|.
+ bool ReadBuffer(void* dst, size_t size);
+
+ // Return a pointer to a buffer of size |size|.
+ // Return NULL if not successful.
+ const void* GetBuffer(size_t size);
+ void* GetWritableBuffer(size_t size);
+
+ private:
+ MediaMessage(uint32 type, size_t msg_size);
+ MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory);
+ MediaMessage(scoped_ptr<MediaMemoryChunk> memory);
+
+ struct Header {
+ // Total size of the message (including both header & content).
+ uint32 size;
+ // Indicate the message type.
+ uint32 type;
+ // Actual size of the content in the message.
+ uint32 content_size;
+ };
+
+ struct SerializedMsg {
+ // Message header.
+ Header header;
+
+ // Start of the content of the message.
+ // Use uint8_t since no special alignment is needed.
+ uint8 content;
+ };
+
+ // Indicate whether the message is a dummy message, i.e. a message without
+ // a complete underlying serialized structure: only the message header is
+ // available.
+ bool is_dummy_msg_;
+
+ // |cached_msg_| is used for 2 purposes:
+ // - to create a dummy message
+ // - for security purpose: cache the msg header to avoid browser security
+ // issues.
+ SerializedMsg cached_msg_;
+ Header* const cached_header_;
+
+ SerializedMsg* msg_;
+ SerializedMsg* msg_read_only_;
+
+ // Memory allocated to store the underlying serialized structure into memory.
+ // Note: a dummy message has no underlying serialized structure:
+ // |mem_| is a null pointer in that case.
+ scoped_ptr<MediaMemoryChunk> mem_;
+
+ // Read iterator into the message.
+ size_t rd_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaMessage);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif
diff --git a/chromecast/media/cma/ipc/media_message_fifo.cc b/chromecast/media/cma/ipc/media_message_fifo.cc
new file mode 100644
index 0000000000..413afd267c
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message_fifo.cc
@@ -0,0 +1,401 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc/media_message_fifo.h"
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "chromecast/media/cma/base/cma_logging.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "chromecast/media/cma/ipc/media_message_type.h"
+
+namespace chromecast {
+namespace media {
+
+class MediaMessageFlag
+ : public base::RefCountedThreadSafe<MediaMessageFlag> {
+ public:
+ // |offset| is the offset in the fifo of the media message.
+ explicit MediaMessageFlag(size_t offset);
+
+ bool IsValid() const;
+
+ void Invalidate();
+
+ size_t offset() const { return offset_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<MediaMessageFlag>;
+ virtual ~MediaMessageFlag();
+
+ const size_t offset_;
+ bool flag_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaMessageFlag);
+};
+
+MediaMessageFlag::MediaMessageFlag(size_t offset)
+ : offset_(offset),
+ flag_(true) {
+}
+
+MediaMessageFlag::~MediaMessageFlag() {
+}
+
+bool MediaMessageFlag::IsValid() const {
+ return flag_;
+}
+
+void MediaMessageFlag::Invalidate() {
+ flag_ = false;
+}
+
+class FifoOwnedMemory : public MediaMemoryChunk {
+ public:
+ FifoOwnedMemory(void* data, size_t size,
+ const scoped_refptr<MediaMessageFlag>& flag,
+ const base::Closure& release_msg_cb);
+ virtual ~FifoOwnedMemory();
+
+ // MediaMemoryChunk implementation.
+ virtual void* data() const OVERRIDE { return data_; }
+ virtual size_t size() const OVERRIDE { return size_; }
+ virtual bool valid() const OVERRIDE { return flag_->IsValid(); }
+
+ private:
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ base::Closure release_msg_cb_;
+
+ void* const data_;
+ const size_t size_;
+ scoped_refptr<MediaMessageFlag> flag_;
+
+ DISALLOW_COPY_AND_ASSIGN(FifoOwnedMemory);
+};
+
+FifoOwnedMemory::FifoOwnedMemory(
+ void* data, size_t size,
+ const scoped_refptr<MediaMessageFlag>& flag,
+ const base::Closure& release_msg_cb)
+ : task_runner_(base::MessageLoopProxy::current()),
+ release_msg_cb_(release_msg_cb),
+ data_(data),
+ size_(size),
+ flag_(flag) {
+}
+
+FifoOwnedMemory::~FifoOwnedMemory() {
+ // Release the flag before notifying that the message has been released.
+ flag_ = scoped_refptr<MediaMessageFlag>();
+ if (!release_msg_cb_.is_null()) {
+ if (task_runner_->BelongsToCurrentThread()) {
+ release_msg_cb_.Run();
+ } else {
+ task_runner_->PostTask(FROM_HERE, release_msg_cb_);
+ }
+ }
+}
+
+MediaMessageFifo::MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk> mem, bool init)
+ : mem_(mem.Pass()),
+ weak_factory_(this) {
+ CHECK_EQ(reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(Descriptor),
+ 0u);
+ CHECK_GE(mem_->size(), sizeof(Descriptor));
+ Descriptor* desc = static_cast<Descriptor*>(mem_->data());
+ base_ = static_cast<void*>(&desc->first_item);
+
+ // TODO(damienv): remove cast when atomic size_t is defined in Chrome.
+ // Currently, the sign differs.
+ rd_offset_ = reinterpret_cast<AtomicSize*>(&(desc->rd_offset));
+ wr_offset_ = reinterpret_cast<AtomicSize*>(&(desc->wr_offset));
+
+ size_t max_size = mem_->size() -
+ (static_cast<char*>(base_) - static_cast<char*>(mem_->data()));
+ if (init) {
+ size_ = max_size;
+ desc->size = size_;
+ internal_rd_offset_ = 0;
+ internal_wr_offset_ = 0;
+ base::subtle::Acquire_Store(rd_offset_, 0);
+ base::subtle::Acquire_Store(wr_offset_, 0);
+ } else {
+ size_ = desc->size;
+ CHECK_LE(size_, max_size);
+ internal_rd_offset_ = current_rd_offset();
+ internal_wr_offset_ = current_wr_offset();
+ }
+ CMALOG(kLogControl)
+ << "MediaMessageFifo:" << " init=" << init << " size=" << size_;
+ CHECK_GT(size_, 0) << size_;
+
+ weak_this_ = weak_factory_.GetWeakPtr();
+ thread_checker_.DetachFromThread();
+}
+
+MediaMessageFifo::~MediaMessageFifo() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void MediaMessageFifo::ObserveReadActivity(
+ const base::Closure& read_event_cb) {
+ read_event_cb_ = read_event_cb;
+}
+
+void MediaMessageFifo::ObserveWriteActivity(
+ const base::Closure& write_event_cb) {
+ write_event_cb_ = write_event_cb;
+}
+
+scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemory(
+ size_t size_to_reserve) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Capture first both the read and write offsets.
+ // and exit right away if not enough free space.
+ size_t wr_offset = internal_wr_offset();
+ size_t rd_offset = current_rd_offset();
+ size_t allocated_size = (size_ + wr_offset - rd_offset) % size_;
+ size_t free_size = size_ - 1 - allocated_size;
+ if (free_size < size_to_reserve)
+ return scoped_ptr<MediaMemoryChunk>();
+ CHECK_LE(MediaMessage::minimum_msg_size(), size_to_reserve);
+
+ // Note: in the next 2 conditions, we have:
+ // trailing_byte_count < size_to_reserve
+ // and since at this stage: size_to_reserve <= free_size
+ // we also have trailing_byte_count <= free_size
+ // which means that all the trailing bytes are free space in the fifo.
+ size_t trailing_byte_count = size_ - wr_offset;
+ if (trailing_byte_count < MediaMessage::minimum_msg_size()) {
+ // If there is no space to even write the smallest message,
+ // skip the trailing bytes and come back to the beginning of the fifo.
+ // (no way to insert a padding message).
+ if (free_size < trailing_byte_count)
+ return scoped_ptr<MediaMemoryChunk>();
+ wr_offset = 0;
+ CommitInternalWrite(wr_offset);
+
+ } else if (trailing_byte_count < size_to_reserve) {
+ // At this point, we know we have at least the space to write a message.
+ // However, to avoid splitting a message, a padding message is needed.
+ scoped_ptr<MediaMemoryChunk> mem(
+ ReserveMemoryNoCheck(trailing_byte_count));
+ scoped_ptr<MediaMessage> padding_message(
+ MediaMessage::CreateMessage(PaddingMediaMsg, mem.Pass()));
+ }
+
+ // Recalculate the free size and exit if not enough free space.
+ wr_offset = internal_wr_offset();
+ allocated_size = (size_ + wr_offset - rd_offset) % size_;
+ free_size = size_ - 1 - allocated_size;
+ if (free_size < size_to_reserve)
+ return scoped_ptr<MediaMemoryChunk>();
+
+ return ReserveMemoryNoCheck(size_to_reserve);
+}
+
+scoped_ptr<MediaMessage> MediaMessageFifo::Pop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Capture the read and write offsets.
+ size_t rd_offset = internal_rd_offset();
+ size_t wr_offset = current_wr_offset();
+ size_t allocated_size = (size_ + wr_offset - rd_offset) % size_;
+
+ if (allocated_size < MediaMessage::minimum_msg_size())
+ return scoped_ptr<MediaMessage>();
+
+ size_t trailing_byte_count = size_ - rd_offset;
+ if (trailing_byte_count < MediaMessage::minimum_msg_size()) {
+ // If there is no space to even have the smallest message,
+ // skip the trailing bytes and come back to the beginning of the fifo.
+ // Note: all the trailing bytes correspond to allocated bytes since:
+ // trailing_byte_count < MediaMessage::minimum_msg_size() <= allocated_size
+ rd_offset = 0;
+ allocated_size -= trailing_byte_count;
+ trailing_byte_count = size_;
+ CommitInternalRead(rd_offset);
+ }
+
+ // The message should not be longer than the allocated size
+ // but since a message is a contiguous area of memory, it should also be
+ // smaller than |trailing_byte_count|.
+ size_t max_msg_size = std::min(allocated_size, trailing_byte_count);
+ if (max_msg_size < MediaMessage::minimum_msg_size())
+ return scoped_ptr<MediaMessage>();
+ void* msg_src = static_cast<uint8*>(base_) + rd_offset;
+
+ // Create a flag to protect the serialized structure of the message
+ // from being overwritten.
+ // The serialized structure starts at offset |rd_offset|.
+ scoped_refptr<MediaMessageFlag> rd_flag(new MediaMessageFlag(rd_offset));
+ rd_flags_.push_back(rd_flag);
+ scoped_ptr<MediaMemoryChunk> mem(
+ new FifoOwnedMemory(
+ msg_src, max_msg_size, rd_flag,
+ base::Bind(&MediaMessageFifo::OnRdMemoryReleased, weak_this_)));
+
+ // Create the message which wraps its the serialized structure.
+ scoped_ptr<MediaMessage> message(MediaMessage::MapMessage(mem.Pass()));
+ CHECK(message);
+
+ // Update the internal read pointer.
+ rd_offset = (rd_offset + message->size()) % size_;
+ CommitInternalRead(rd_offset);
+
+ return message.Pass();
+}
+
+void MediaMessageFifo::Flush() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ size_t wr_offset = current_wr_offset();
+
+ // Invalidate every memory region before flushing.
+ while (!rd_flags_.empty()) {
+ CMALOG(kLogControl) << "Invalidate flag";
+ rd_flags_.front()->Invalidate();
+ rd_flags_.pop_front();
+ }
+
+ // Flush by setting the read pointer to the value of the write pointer.
+ // Update first the internal read pointer then the public one.
+ CommitInternalRead(wr_offset);
+ CommitRead(wr_offset);
+}
+
+scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemoryNoCheck(
+ size_t size_to_reserve) {
+ size_t wr_offset = internal_wr_offset();
+
+ // Memory block corresponding to the serialized structure of the message.
+ void* msg_start = static_cast<uint8*>(base_) + wr_offset;
+ scoped_refptr<MediaMessageFlag> wr_flag(new MediaMessageFlag(wr_offset));
+ wr_flags_.push_back(wr_flag);
+ scoped_ptr<MediaMemoryChunk> mem(
+ new FifoOwnedMemory(
+ msg_start, size_to_reserve, wr_flag,
+ base::Bind(&MediaMessageFifo::OnWrMemoryReleased, weak_this_)));
+
+ // Update the internal write pointer.
+ wr_offset = (wr_offset + size_to_reserve) % size_;
+ CommitInternalWrite(wr_offset);
+
+ return mem.Pass();
+}
+
+void MediaMessageFifo::OnWrMemoryReleased() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (wr_flags_.empty()) {
+ // Sanity check: when there is no protected memory area,
+ // the external write offset has no reason to be different from
+ // the internal write offset.
+ DCHECK_EQ(current_wr_offset(), internal_wr_offset());
+ return;
+ }
+
+ // Update the external write offset.
+ while (!wr_flags_.empty() &&
+ (!wr_flags_.front()->IsValid() || wr_flags_.front()->HasOneRef())) {
+ // TODO(damienv): Could add a sanity check to make sure the offset is
+ // between the external write offset and the read offset (not included).
+ wr_flags_.pop_front();
+ }
+
+ // Update the read offset to the first locked memory area
+ // or to the internal read pointer if nothing prevents it.
+ size_t external_wr_offset = internal_wr_offset();
+ if (!wr_flags_.empty())
+ external_wr_offset = wr_flags_.front()->offset();
+ CommitWrite(external_wr_offset);
+}
+
+void MediaMessageFifo::OnRdMemoryReleased() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (rd_flags_.empty()) {
+ // Sanity check: when there is no protected memory area,
+ // the external read offset has no reason to be different from
+ // the internal read offset.
+ DCHECK_EQ(current_rd_offset(), internal_rd_offset());
+ return;
+ }
+
+ // Update the external read offset.
+ while (!rd_flags_.empty() &&
+ (!rd_flags_.front()->IsValid() || rd_flags_.front()->HasOneRef())) {
+ // TODO(damienv): Could add a sanity check to make sure the offset is
+ // between the external read offset and the write offset.
+ rd_flags_.pop_front();
+ }
+
+ // Update the read offset to the first locked memory area
+ // or to the internal read pointer if nothing prevents it.
+ size_t external_rd_offset = internal_rd_offset();
+ if (!rd_flags_.empty())
+ external_rd_offset = rd_flags_.front()->offset();
+ CommitRead(external_rd_offset);
+}
+
+size_t MediaMessageFifo::current_rd_offset() const {
+ DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize));
+ size_t rd_offset = base::subtle::Acquire_Load(rd_offset_);
+ CHECK_LT(rd_offset, size_);
+ return rd_offset;
+}
+
+size_t MediaMessageFifo::current_wr_offset() const {
+ DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize));
+
+ // When the fifo consumer acquires the write offset,
+ // we have to make sure that any possible following reads are actually
+ // returning results at least inline with the memory snapshot taken
+ // when the write offset was sampled.
+ // That's why an Acquire_Load is used here.
+ size_t wr_offset = base::subtle::Acquire_Load(wr_offset_);
+ CHECK_LT(wr_offset, size_);
+ return wr_offset;
+}
+
+void MediaMessageFifo::CommitRead(size_t new_rd_offset) {
+ // Add a memory fence to ensure the message content is completely read
+ // before updating the read offset.
+ base::subtle::Release_Store(rd_offset_, new_rd_offset);
+
+ // Make sure the read pointer has been updated before sending a notification.
+ if (!read_event_cb_.is_null()) {
+ base::subtle::MemoryBarrier();
+ read_event_cb_.Run();
+ }
+}
+
+void MediaMessageFifo::CommitWrite(size_t new_wr_offset) {
+ // Add a memory fence to ensure the message content is written
+ // before updating the write offset.
+ base::subtle::Release_Store(wr_offset_, new_wr_offset);
+
+ // Make sure the write pointer has been updated before sending a notification.
+ if (!write_event_cb_.is_null()) {
+ base::subtle::MemoryBarrier();
+ write_event_cb_.Run();
+ }
+}
+
+void MediaMessageFifo::CommitInternalRead(size_t new_rd_offset) {
+ internal_rd_offset_ = new_rd_offset;
+}
+
+void MediaMessageFifo::CommitInternalWrite(size_t new_wr_offset) {
+ internal_wr_offset_ = new_wr_offset;
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc/media_message_fifo.h b/chromecast/media/cma/ipc/media_message_fifo.h
new file mode 100644
index 0000000000..fe82721a9b
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message_fifo.h
@@ -0,0 +1,208 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_
+#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_
+
+#include <list>
+
+#include "base/atomicops.h"
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+
+namespace chromecast {
+namespace media {
+class MediaMemoryChunk;
+class MediaMessage;
+class MediaMessageFlag;
+
+// MediaMessageFifo is a lock free fifo implementation
+// to pass audio/video data from one thread to another or from one process
+// to another one (in that case using shared memory).
+//
+// Assuming the feeder and the consumer have a common block of shared memory
+// (representing the serialized structure of the fifo),
+// the feeder (which must be running on a single thread) instantiates its own
+// instance of MediaMessageFifo, same applies to the consumer.
+//
+// Example: assuming the block of shared memory is given by |mem|, a typical
+// feeder (using MediaMessageFifo instance fifo_feeder) will push messages
+// in the following way:
+// // Create a dummy message to calculate the size of the serialized message.
+// scoped_ptr<MediaMessage> dummy_msg(
+// MediaMessage::CreateDummyMessage(msg_type));
+// // ...
+// // Write all the fields to the dummy message.
+// // ...
+//
+// // Create the real message, once the size of the serialized message
+// // is known.
+// scoped_ptr<MediaMessage> msg(
+// MediaMessage::CreateMessage(
+// msg_type,
+// base::Bind(&MediaMessageFifo::ReserveMemory,
+// base::Unretained(fifo_feeder.get())),
+// dummy_msg->content_size()));
+// if (!msg) {
+// // Not enough space for the message:
+// // retry later (e.g. when receiving a read activity event, meaning
+// // some enough space might have been release).
+// return;
+// }
+// // ...
+// // Write all the fields to the real message
+// // in exactly the same way it was done for the dummy message.
+// // ...
+// // Once message |msg| is going out of scope, then MediaMessageFifo
+// // fifo_feeder is informed that the message is not needed anymore
+// // (i.e. it was fully written), and fifo_feeder can then update
+// // the external write pointer of the fifo so that the consumer
+// // can start consuming this message.
+//
+// A typical consumer (using MediaMessageFifo instance fifo_consumer)
+// will retrive messages in the following way:
+// scoped_ptr<MediaMessage> msg(fifo_consumer->Pop());
+// if (!msg) {
+// // The fifo is empty, i.e. no message left.
+// // Try reading again later (e.g. after receiving a write activity event.
+// return;
+// }
+// // Parse the message using Read functions of MediaMessage:
+// // ...
+// // Once the message is going out of scope, MediaMessageFifo will receive
+// // a notification that the underlying memory can be released
+// // (i.e. the external read pointer can be updated).
+//
+//
+class MediaMessageFifo {
+ public:
+ // Creates a media message fifo using |mem| as the underlying serialized
+ // structure.
+ // If |init| is true, the underlying fifo structure is initialized.
+ MediaMessageFifo(scoped_ptr<MediaMemoryChunk> mem, bool init);
+ ~MediaMessageFifo();
+
+ // When the consumer and the feeder are living in two different processes,
+ // we might want to convey some messages between these two processes to notify
+ // about some fifo activity.
+ void ObserveReadActivity(const base::Closure& read_event_cb);
+ void ObserveWriteActivity(const base::Closure& write_event_cb);
+
+ // Reserves a writeable block of memory at the back of the fifo,
+ // corresponding to the serialized structure of the message.
+ // Returns NULL if the required size cannot be allocated.
+ scoped_ptr<MediaMemoryChunk> ReserveMemory(size_t size);
+
+ // Pop a message from the queue.
+ // Returns a null pointer if there is no message left.
+ scoped_ptr<MediaMessage> Pop();
+
+ // Flush the fifo.
+ void Flush();
+
+ private:
+ struct Descriptor {
+ size_t size;
+ size_t rd_offset;
+ size_t wr_offset;
+
+ // Ensure the first item has the same alignment as an int64.
+ int64 first_item;
+ };
+
+ // Add some accessors to ensure security on the browser process side.
+ size_t current_rd_offset() const;
+ size_t current_wr_offset() const;
+ size_t internal_rd_offset() const {
+ DCHECK_LT(internal_rd_offset_, size_);
+ return internal_rd_offset_;
+ }
+ size_t internal_wr_offset() const {
+ DCHECK_LT(internal_wr_offset_, size_);
+ return internal_wr_offset_;
+ }
+
+ // Reserve a block of free memory without doing any check on the available
+ // space. Invoke this function only when all the checks have been done.
+ scoped_ptr<MediaMemoryChunk> ReserveMemoryNoCheck(size_t size);
+
+ // Invoked each time there is a memory region in the free space of the fifo
+ // that has possibly been written.
+ void OnWrMemoryReleased();
+
+ // Invoked each time there is a memory region in the allocated space
+ // of the fifo that has possibly been released.
+ void OnRdMemoryReleased();
+
+ // Functions to modify the internal/external read/write pointers.
+ void CommitRead(size_t new_rd_offset);
+ void CommitWrite(size_t new_wr_offset);
+ void CommitInternalRead(size_t new_rd_offset);
+ void CommitInternalWrite(size_t new_wr_offset);
+
+ // An instance of MediaMessageFifo must be running on a single thread.
+ // If the fifo feeder and consumer are living on 2 different threads
+ // or 2 different processes, they must create their own instance
+ // of MediaMessageFifo using the same underlying block of (shared) memory
+ // in the constructor.
+ base::ThreadChecker thread_checker_;
+
+ // Callbacks invoked to notify either of some read or write activity on the
+ // fifo. This is especially useful when the feeder and consumer are living in
+ // two different processes.
+ base::Closure read_event_cb_;
+ base::Closure write_event_cb_;
+
+ // The serialized structure of the fifo.
+ scoped_ptr<MediaMemoryChunk> mem_;
+
+ // The size in bytes of the fifo is cached locally for security purpose.
+ // (the renderer process cannot modify the size and make the browser process
+ // access out of range addresses).
+ size_t size_;
+
+ // TODO(damienv): This is a work-around since atomicops.h does not define
+ // an atomic size_t type.
+#if SIZE_MAX == UINT32_MAX
+ typedef base::subtle::Atomic32 AtomicSize;
+#elif SIZE_MAX == UINT64_MAX
+ typedef base::subtle::Atomic64 AtomicSize;
+#elif
+#error "Unsupported size_t"
+#endif
+ AtomicSize* rd_offset_;
+ AtomicSize* wr_offset_;
+
+ // Internal read offset: this is where data is actually read from.
+ // The external offset |rd_offset_| is only used to protect data from being
+ // overwritten by the feeder.
+ // At any time, the internal read pointer must be between the external read
+ // offset and the write offset (circular fifo definition of "between").
+ size_t internal_rd_offset_;
+ size_t internal_wr_offset_;
+
+ // Note: all the memory read/write are followed by a memory fence before
+ // updating the rd/wr pointer.
+ void* base_;
+
+ // Protects the messages that are either being read or written.
+ std::list<scoped_refptr<MediaMessageFlag> > rd_flags_;
+ std::list<scoped_refptr<MediaMessageFlag> > wr_flags_;
+
+ base::WeakPtrFactory<MediaMessageFifo> weak_factory_;
+ base::WeakPtr<MediaMessageFifo> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaMessageFifo);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_
diff --git a/chromecast/media/cma/ipc/media_message_fifo_unittest.cc b/chromecast/media/cma/ipc/media_message_fifo_unittest.cc
new file mode 100644
index 0000000000..bb1eef3848
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message_fifo_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "chromecast/media/cma/ipc/media_message_fifo.h"
+#include "chromecast/media/cma/ipc/media_message_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class FifoMemoryChunk : public MediaMemoryChunk {
+ public:
+ FifoMemoryChunk(void* mem, size_t size)
+ : mem_(mem), size_(size) {}
+ virtual ~FifoMemoryChunk() {}
+
+ virtual void* data() const OVERRIDE { return mem_; }
+ virtual size_t size() const OVERRIDE { return size_; }
+ virtual bool valid() const OVERRIDE { return true; }
+
+ private:
+ void* mem_;
+ size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk);
+};
+
+void MsgProducer(scoped_ptr<MediaMessageFifo> fifo,
+ int msg_count,
+ base::WaitableEvent* event) {
+
+ for (int k = 0; k < msg_count; k++) {
+ uint32 msg_type = 0x2 + (k % 5);
+ uint32 max_msg_content_size = k % 64;
+ do {
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateMessage(
+ msg_type,
+ base::Bind(&MediaMessageFifo::ReserveMemory,
+ base::Unretained(fifo.get())),
+ max_msg_content_size));
+ if (msg1)
+ break;
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ } while(true);
+ }
+
+ fifo.reset();
+
+ event->Signal();
+}
+
+void MsgConsumer(scoped_ptr<MediaMessageFifo> fifo,
+ int msg_count,
+ base::WaitableEvent* event) {
+
+ int k = 0;
+ while (k < msg_count) {
+ uint32 msg_type = 0x2 + (k % 5);
+ do {
+ scoped_ptr<MediaMessage> msg2(fifo->Pop());
+ if (msg2) {
+ if (msg2->type() != PaddingMediaMsg) {
+ EXPECT_EQ(msg2->type(), msg_type);
+ k++;
+ }
+ break;
+ }
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ } while(true);
+ }
+
+ fifo.reset();
+
+ event->Signal();
+}
+
+void MsgProducerConsumer(
+ scoped_ptr<MediaMessageFifo> producer_fifo,
+ scoped_ptr<MediaMessageFifo> consumer_fifo,
+ base::WaitableEvent* event) {
+ for (int k = 0; k < 2048; k++) {
+ // Should have enough space to create a message.
+ uint32 msg_type = 0x2 + (k % 5);
+ uint32 max_msg_content_size = k % 64;
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateMessage(
+ msg_type,
+ base::Bind(&MediaMessageFifo::ReserveMemory,
+ base::Unretained(producer_fifo.get())),
+ max_msg_content_size));
+ EXPECT_TRUE(msg1);
+
+ // Make sure the message is commited.
+ msg1.reset();
+
+ // At this point, we should have a message to read.
+ scoped_ptr<MediaMessage> msg2(consumer_fifo->Pop());
+ EXPECT_TRUE(msg2);
+ }
+
+ producer_fifo.reset();
+ consumer_fifo.reset();
+
+ event->Signal();
+}
+
+} // namespace
+
+TEST(MediaMessageFifoTest, AlternateWriteRead) {
+ size_t buffer_size = 64 * 1024;
+ scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]);
+
+ scoped_ptr<base::Thread> thread(
+ new base::Thread("FeederConsumerThread"));
+ thread->Start();
+
+ scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&buffer[0], buffer_size)),
+ true));
+ scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&buffer[0], buffer_size)),
+ false));
+
+ base::WaitableEvent event(false, false);
+ thread->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&MsgProducerConsumer,
+ base::Passed(&producer_fifo),
+ base::Passed(&consumer_fifo),
+ &event));
+ event.Wait();
+
+ thread.reset();
+}
+
+TEST(MediaMessageFifoTest, MultiThreaded) {
+ size_t buffer_size = 64 * 1024;
+ scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]);
+
+ scoped_ptr<base::Thread> producer_thread(
+ new base::Thread("FeederThread"));
+ scoped_ptr<base::Thread> consumer_thread(
+ new base::Thread("ConsumerThread"));
+ producer_thread->Start();
+ consumer_thread->Start();
+
+ scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&buffer[0], buffer_size)),
+ true));
+ scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&buffer[0], buffer_size)),
+ false));
+
+ base::WaitableEvent producer_event_done(false, false);
+ base::WaitableEvent consumer_event_done(false, false);
+
+ const int msg_count = 2048;
+ producer_thread->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&MsgProducer,
+ base::Passed(&producer_fifo),
+ msg_count,
+ &producer_event_done));
+ consumer_thread->message_loop_proxy()->PostTask(
+ FROM_HERE,
+ base::Bind(&MsgConsumer,
+ base::Passed(&consumer_fifo),
+ msg_count,
+ &consumer_event_done));
+
+ producer_event_done.Wait();
+ consumer_event_done.Wait();
+
+ producer_thread.reset();
+ consumer_thread.reset();
+}
+
+} // namespace media
+} // namespace chromecast
+
diff --git a/chromecast/media/cma/ipc/media_message_type.h b/chromecast/media/cma/ipc/media_message_type.h
new file mode 100644
index 0000000000..23ff8474d3
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message_type.h
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_TYPE_H_
+#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_TYPE_H_
+
+enum MediaMessageType {
+ PaddingMediaMsg = 1,
+ FrameMediaMsg,
+ AudioConfigMediaMsg,
+ VideoConfigMediaMsg,
+};
+
+#endif
diff --git a/chromecast/media/cma/ipc/media_message_unittest.cc b/chromecast/media/cma/ipc/media_message_unittest.cc
new file mode 100644
index 0000000000..07d9655bfb
--- /dev/null
+++ b/chromecast/media/cma/ipc/media_message_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class ExternalMemoryBlock
+ : public MediaMemoryChunk {
+ public:
+ ExternalMemoryBlock(void* data, size_t size)
+ : data_(data), size_(size) {}
+ virtual ~ExternalMemoryBlock() {}
+
+ // MediaMemoryChunk implementation.
+ virtual void* data() const OVERRIDE { return data_; }
+ virtual size_t size() const OVERRIDE { return size_; }
+ virtual bool valid() const OVERRIDE { return true; }
+
+ private:
+ void* const data_;
+ const size_t size_;
+};
+
+scoped_ptr<MediaMemoryChunk> DummyAllocator(
+ void* data, size_t size, size_t alloc_size) {
+ CHECK_LE(alloc_size, size);
+ return scoped_ptr<MediaMemoryChunk>(
+ new ExternalMemoryBlock(data, alloc_size));
+}
+
+}
+
+TEST(MediaMessageTest, WriteRead) {
+ int buffer_size = 1024;
+ scoped_ptr<uint8[]> buffer(new uint8[buffer_size]);
+ MediaMessage::MemoryAllocatorCB mem_alloc_cb(
+ base::Bind(&DummyAllocator, buffer.get(), buffer_size));
+ uint32 type = 0x1;
+ int msg_content_capacity = 512;
+
+ // Write a message.
+ int count = 64;
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity));
+ for (int k = 0; k < count; k++) {
+ int v1 = 2 * k + 1;
+ EXPECT_TRUE(msg1->WritePod(v1));
+ uint8 v2 = k;
+ EXPECT_TRUE(msg1->WritePod(v2));
+ }
+ EXPECT_EQ(msg1->content_size(), count * (sizeof(int) + sizeof(uint8)));
+
+ // Verify the integrity of the message.
+ scoped_ptr<MediaMessage> msg2(
+ MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>(
+ new ExternalMemoryBlock(&buffer[0], buffer_size))));
+ for (int k = 0; k < count; k++) {
+ int v1;
+ int expected_v1 = 2 * k + 1;
+ EXPECT_TRUE(msg2->ReadPod(&v1));
+ EXPECT_EQ(v1, expected_v1);
+ uint8 v2;
+ uint8 expected_v2 = k;
+ EXPECT_TRUE(msg2->ReadPod(&v2));
+ EXPECT_EQ(v2, expected_v2);
+ }
+}
+
+TEST(MediaMessageTest, WriteOverflow) {
+ int buffer_size = 1024;
+ scoped_ptr<uint8[]> buffer(new uint8[buffer_size]);
+ MediaMessage::MemoryAllocatorCB mem_alloc_cb(
+ base::Bind(&DummyAllocator, buffer.get(), buffer_size));
+ uint32 type = 0x1;
+ int msg_content_capacity = 8;
+
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity));
+ uint32 v1 = 0;
+ uint8 v2 = 0;
+ EXPECT_TRUE(msg1->WritePod(v1));
+ EXPECT_TRUE(msg1->WritePod(v1));
+
+ EXPECT_FALSE(msg1->WritePod(v1));
+ EXPECT_FALSE(msg1->WritePod(v2));
+}
+
+TEST(MediaMessageTest, ReadOverflow) {
+ int buffer_size = 1024;
+ scoped_ptr<uint8[]> buffer(new uint8[buffer_size]);
+ MediaMessage::MemoryAllocatorCB mem_alloc_cb(
+ base::Bind(&DummyAllocator, buffer.get(), buffer_size));
+ uint32 type = 0x1;
+ int msg_content_capacity = 8;
+
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity));
+ uint32 v1 = 0xcd;
+ EXPECT_TRUE(msg1->WritePod(v1));
+ EXPECT_TRUE(msg1->WritePod(v1));
+
+ scoped_ptr<MediaMessage> msg2(
+ MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>(
+ new ExternalMemoryBlock(&buffer[0], buffer_size))));
+ uint32 v2;
+ EXPECT_TRUE(msg2->ReadPod(&v2));
+ EXPECT_EQ(v2, v1);
+ EXPECT_TRUE(msg2->ReadPod(&v2));
+ EXPECT_EQ(v2, v1);
+ EXPECT_FALSE(msg2->ReadPod(&v2));
+}
+
+TEST(MediaMessageTest, DummyMessage) {
+ int buffer_size = 1024;
+ scoped_ptr<uint8[]> buffer(new uint8[buffer_size]);
+ MediaMessage::MemoryAllocatorCB mem_alloc_cb(
+ base::Bind(&DummyAllocator, buffer.get(), buffer_size));
+ uint32 type = 0x1;
+
+ // Create first a dummy message to estimate the content size.
+ scoped_ptr<MediaMessage> msg1(
+ MediaMessage::CreateDummyMessage(type));
+ uint32 v1 = 0xcd;
+ EXPECT_TRUE(msg1->WritePod(v1));
+ EXPECT_TRUE(msg1->WritePod(v1));
+
+ // Create the real message and write the actual content.
+ scoped_ptr<MediaMessage> msg2(
+ MediaMessage::CreateMessage(type, mem_alloc_cb, msg1->content_size()));
+ EXPECT_TRUE(msg2->WritePod(v1));
+ EXPECT_TRUE(msg2->WritePod(v1));
+ EXPECT_FALSE(msg2->WritePod(v1));
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc
new file mode 100644
index 0000000000..d595bdaac3
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "media/base/audio_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+const size_t kMaxExtraDataSize = 16 * 1024;
+}
+
+// static
+void AudioDecoderConfigMarshaller::Write(
+ const ::media::AudioDecoderConfig& config, MediaMessage* msg) {
+ CHECK(msg->WritePod(config.codec()));
+ CHECK(msg->WritePod(config.channel_layout()));
+ CHECK(msg->WritePod(config.samples_per_second()));
+ CHECK(msg->WritePod(config.sample_format()));
+ CHECK(msg->WritePod(config.is_encrypted()));
+ CHECK(msg->WritePod(config.extra_data_size()));
+ if (config.extra_data_size() > 0)
+ CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size()));
+}
+
+// static
+::media::AudioDecoderConfig AudioDecoderConfigMarshaller::Read(
+ MediaMessage* msg) {
+ ::media::AudioCodec codec;
+ ::media::SampleFormat sample_format;
+ ::media::ChannelLayout channel_layout;
+ int samples_per_second;
+ bool is_encrypted;
+ size_t extra_data_size;
+ scoped_ptr<uint8[]> extra_data;
+
+ CHECK(msg->ReadPod(&codec));
+ CHECK(msg->ReadPod(&channel_layout));
+ CHECK(msg->ReadPod(&samples_per_second));
+ CHECK(msg->ReadPod(&sample_format));
+ CHECK(msg->ReadPod(&is_encrypted));
+ CHECK(msg->ReadPod(&extra_data_size));
+
+ CHECK_GE(codec, ::media::kUnknownAudioCodec);
+ CHECK_LE(codec, ::media::kAudioCodecMax);
+ CHECK_GE(channel_layout, ::media::CHANNEL_LAYOUT_NONE);
+ CHECK_LE(channel_layout, ::media::CHANNEL_LAYOUT_MAX);
+ CHECK_GE(sample_format, ::media::kUnknownSampleFormat);
+ CHECK_LE(sample_format, ::media::kSampleFormatMax);
+ CHECK_LT(extra_data_size, kMaxExtraDataSize);
+ if (extra_data_size > 0) {
+ extra_data.reset(new uint8[extra_data_size]);
+ CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size));
+ }
+
+ return ::media::AudioDecoderConfig(
+ codec, sample_format,
+ channel_layout, samples_per_second,
+ extra_data.get(), extra_data_size,
+ is_encrypted);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h
new file mode 100644
index 0000000000..33f960e0c8
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_
+
+#include "media/base/audio_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+class MediaMessage;
+
+class AudioDecoderConfigMarshaller {
+ public:
+ // Writes the serialized structure of |config| into |msg|.
+ static void Write(
+ const ::media::AudioDecoderConfig& config, MediaMessage* msg);
+
+ // Returns an AudioDecoderConfig from its serialized structure.
+ static ::media::AudioDecoderConfig Read(MediaMessage* msg);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_
diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc
new file mode 100644
index 0000000000..de8dd54f6b
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc
@@ -0,0 +1,200 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/av_streamer_proxy.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "chromecast/media/cma/ipc/media_message_fifo.h"
+#include "chromecast/media/cma/ipc/media_message_type.h"
+#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h"
+#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h"
+#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h"
+
+namespace chromecast {
+namespace media {
+
+AvStreamerProxy::AvStreamerProxy()
+ : is_running_(false),
+ pending_read_(false),
+ pending_av_data_(false),
+ weak_factory_(this),
+ weak_this_(weak_factory_.GetWeakPtr()) {
+ thread_checker_.DetachFromThread();
+}
+
+AvStreamerProxy::~AvStreamerProxy() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void AvStreamerProxy::SetCodedFrameProvider(
+ scoped_ptr<CodedFrameProvider> frame_provider) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!frame_provider_);
+ frame_provider_.reset(frame_provider.release());
+}
+
+void AvStreamerProxy::SetMediaMessageFifo(
+ scoped_ptr<MediaMessageFifo> fifo) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!fifo_);
+ fifo_.reset(fifo.release());
+}
+
+void AvStreamerProxy::Start() {
+ DCHECK(!is_running_);
+
+ is_running_ = true;
+ RequestBufferIfNeeded();
+}
+
+void AvStreamerProxy::StopAndFlush(const base::Closure& done_cb) {
+ is_running_ = false;
+
+ pending_av_data_ = false;
+ pending_audio_config_ = ::media::AudioDecoderConfig();
+ pending_video_config_ = ::media::VideoDecoderConfig();
+ pending_buffer_ = scoped_refptr<DecoderBufferBase>();
+
+ pending_read_ = false;
+ frame_provider_->Flush(done_cb);
+}
+
+void AvStreamerProxy::OnFifoReadEvent() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Some enough space might have been released
+ // to accommodate the pending data.
+ if (pending_av_data_)
+ ProcessPendingData();
+}
+
+void AvStreamerProxy::RequestBufferIfNeeded() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!is_running_ || pending_read_ || pending_av_data_)
+ return;
+
+ // |frame_provider_| is assumed to run on the same message loop.
+ // Add a BindToCurrentLoop if that's not the case in the future.
+ pending_read_ = true;
+ frame_provider_->Read(base::Bind(&AvStreamerProxy::OnNewBuffer, weak_this_));
+}
+
+void AvStreamerProxy::OnNewBuffer(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ pending_read_ = false;
+
+ if (buffer->end_of_stream())
+ is_running_ = false;
+
+ DCHECK(!pending_av_data_);
+ pending_av_data_ = true;
+
+ pending_buffer_ = buffer;
+ pending_audio_config_ = audio_config;
+ pending_video_config_ = video_config;
+
+ ProcessPendingData();
+}
+
+void AvStreamerProxy::ProcessPendingData() {
+ if (pending_audio_config_.IsValidConfig()) {
+ if (!SendAudioDecoderConfig(pending_audio_config_))
+ return;
+ pending_audio_config_ = ::media::AudioDecoderConfig();
+ }
+
+ if (pending_video_config_.IsValidConfig()) {
+ if (!SendVideoDecoderConfig(pending_video_config_))
+ return;
+ pending_video_config_ = ::media::VideoDecoderConfig();
+ }
+
+ if (pending_buffer_.get()) {
+ if (!SendBuffer(pending_buffer_))
+ return;
+ pending_buffer_ = scoped_refptr<DecoderBufferBase>();
+ }
+
+ pending_av_data_ = false;
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerProxy::RequestBufferIfNeeded, weak_this_));
+}
+
+bool AvStreamerProxy::SendAudioDecoderConfig(
+ const ::media::AudioDecoderConfig& config) {
+ // Create a dummy message to calculate first the message size.
+ scoped_ptr<MediaMessage> dummy_msg(
+ MediaMessage::CreateDummyMessage(AudioConfigMediaMsg));
+ AudioDecoderConfigMarshaller::Write(config, dummy_msg.get());
+
+ // Create the real message and write the actual content.
+ scoped_ptr<MediaMessage> msg(
+ MediaMessage::CreateMessage(
+ AudioConfigMediaMsg,
+ base::Bind(&MediaMessageFifo::ReserveMemory,
+ base::Unretained(fifo_.get())),
+ dummy_msg->content_size()));
+ if (!msg)
+ return false;
+
+ AudioDecoderConfigMarshaller::Write(config, msg.get());
+ return true;
+}
+
+bool AvStreamerProxy::SendVideoDecoderConfig(
+ const ::media::VideoDecoderConfig& config) {
+ // Create a dummy message to calculate first the message size.
+ scoped_ptr<MediaMessage> dummy_msg(
+ MediaMessage::CreateDummyMessage(VideoConfigMediaMsg));
+ VideoDecoderConfigMarshaller::Write(config, dummy_msg.get());
+
+ // Create the real message and write the actual content.
+ scoped_ptr<MediaMessage> msg(
+ MediaMessage::CreateMessage(
+ VideoConfigMediaMsg,
+ base::Bind(&MediaMessageFifo::ReserveMemory,
+ base::Unretained(fifo_.get())),
+ dummy_msg->content_size()));
+ if (!msg)
+ return false;
+
+ VideoDecoderConfigMarshaller::Write(config, msg.get());
+ return true;
+}
+
+bool AvStreamerProxy::SendBuffer(
+ const scoped_refptr<DecoderBufferBase>& buffer) {
+ // Create a dummy message to calculate first the message size.
+ scoped_ptr<MediaMessage> dummy_msg(
+ MediaMessage::CreateDummyMessage(FrameMediaMsg));
+ DecoderBufferBaseMarshaller::Write(buffer, dummy_msg.get());
+
+ // Create the real message and write the actual content.
+ scoped_ptr<MediaMessage> msg(
+ MediaMessage::CreateMessage(
+ FrameMediaMsg,
+ base::Bind(&MediaMessageFifo::ReserveMemory,
+ base::Unretained(fifo_.get())),
+ dummy_msg->content_size()));
+ if (!msg)
+ return false;
+
+ DecoderBufferBaseMarshaller::Write(buffer, msg.get());
+ return true;
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h
new file mode 100644
index 0000000000..2e3dcc17d7
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/video_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+class CodedFrameProvider;
+class DecoderBufferBase;
+class MediaMessageFifo;
+
+// AvStreamerProxy streams audio/video data from a coded frame provider
+// to a media message fifo.
+class AvStreamerProxy {
+ public:
+ AvStreamerProxy();
+ ~AvStreamerProxy();
+
+ // AvStreamerProxy gets frames and audio/video config
+ // from the |frame_provider| and feed them into the |fifo|.
+ void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider);
+ void SetMediaMessageFifo(scoped_ptr<MediaMessageFifo> fifo);
+
+ // Starts fetching A/V buffers.
+ void Start();
+
+ // Stops fetching A/V buffers and flush the pending buffers,
+ // invoking |flush_cb| when done.
+ void StopAndFlush(const base::Closure& flush_cb);
+
+ // Event invoked when some data have been read from the fifo.
+ // This means some data can now possibly be written into the fifo.
+ void OnFifoReadEvent();
+
+ private:
+ void RequestBufferIfNeeded();
+
+ void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer,
+ const ::media::AudioDecoderConfig& audio_config,
+ const ::media::VideoDecoderConfig& video_config);
+
+ void ProcessPendingData();
+
+ bool SendAudioDecoderConfig(const ::media::AudioDecoderConfig& config);
+ bool SendVideoDecoderConfig(const ::media::VideoDecoderConfig& config);
+ bool SendBuffer(const scoped_refptr<DecoderBufferBase>& buffer);
+
+ base::ThreadChecker thread_checker_;
+
+ scoped_ptr<CodedFrameProvider> frame_provider_;
+
+ // Fifo used to convey A/V configs and buffers.
+ scoped_ptr<MediaMessageFifo> fifo_;
+
+ // State.
+ bool is_running_;
+
+ // Indicates if there is a pending request to the coded frame provider.
+ bool pending_read_;
+
+ // Pending config & buffer.
+ // |pending_av_data_| is set as long as one of the pending audio/video
+ // config or buffer is valid.
+ bool pending_av_data_;
+ ::media::AudioDecoderConfig pending_audio_config_;
+ ::media::VideoDecoderConfig pending_video_config_;
+ scoped_refptr<DecoderBufferBase> pending_buffer_;
+
+ base::WeakPtrFactory<AvStreamerProxy> weak_factory_;
+ base::WeakPtr<AvStreamerProxy> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(AvStreamerProxy);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_
diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc b/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc
new file mode 100644
index 0000000000..6480701573
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/base/frame_generator_for_test.h"
+#include "chromecast/media/cma/base/mock_frame_consumer.h"
+#include "chromecast/media/cma/base/mock_frame_provider.h"
+#include "chromecast/media/cma/ipc/media_memory_chunk.h"
+#include "chromecast/media/cma/ipc/media_message_fifo.h"
+#include "chromecast/media/cma/ipc_streamer/av_streamer_proxy.h"
+#include "chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/video_decoder_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+
+class FifoMemoryChunk : public MediaMemoryChunk {
+ public:
+ FifoMemoryChunk(void* mem, size_t size)
+ : mem_(mem), size_(size) {}
+ virtual ~FifoMemoryChunk() {}
+
+ virtual void* data() const OVERRIDE { return mem_; }
+ virtual size_t size() const OVERRIDE { return size_; }
+ virtual bool valid() const OVERRIDE { return true; }
+
+ private:
+ void* mem_;
+ size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk);
+};
+
+} // namespace
+
+class AvStreamerTest : public testing::Test {
+ public:
+ AvStreamerTest();
+ virtual ~AvStreamerTest();
+
+ // Setups the test.
+ void Configure(
+ size_t frame_count,
+ const std::vector<bool>& provider_delayed_pattern,
+ const std::vector<bool>& consumer_delayed_pattern);
+
+ // Starts the test.
+ void Start();
+
+ protected:
+ scoped_ptr<uint64[]> fifo_mem_;
+
+ scoped_ptr<AvStreamerProxy> av_buffer_proxy_;
+ scoped_ptr<CodedFrameProviderHost> coded_frame_provider_host_;
+ scoped_ptr<MockFrameConsumer> frame_consumer_;
+
+ private:
+ void OnTestTimeout();
+ void OnTestCompleted();
+
+ void OnFifoRead();
+ void OnFifoWrite();
+
+ DISALLOW_COPY_AND_ASSIGN(AvStreamerTest);
+};
+
+AvStreamerTest::AvStreamerTest() {
+}
+
+AvStreamerTest::~AvStreamerTest() {
+}
+
+void AvStreamerTest::Configure(
+ size_t frame_count,
+ const std::vector<bool>& provider_delayed_pattern,
+ const std::vector<bool>& consumer_delayed_pattern) {
+ // Frame generation on the producer and consumer side.
+ std::vector<FrameGeneratorForTest::FrameSpec> frame_specs;
+ frame_specs.resize(frame_count);
+ for (size_t k = 0; k < frame_specs.size() - 1; k++) {
+ frame_specs[k].has_config = (k == 0);
+ frame_specs[k].timestamp = base::TimeDelta::FromMilliseconds(40) * k;
+ frame_specs[k].size = 512;
+ frame_specs[k].has_decrypt_config = ((k % 3) == 0);
+ }
+ frame_specs[frame_specs.size() - 1].is_eos = true;
+
+ scoped_ptr<FrameGeneratorForTest> frame_generator_provider(
+ new FrameGeneratorForTest(frame_specs));
+ scoped_ptr<FrameGeneratorForTest> frame_generator_consumer(
+ new FrameGeneratorForTest(frame_specs));
+
+ scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider());
+ frame_provider->Configure(provider_delayed_pattern,
+ frame_generator_provider.Pass());
+
+ size_t fifo_size_div_8 = 512;
+ fifo_mem_.reset(new uint64[fifo_size_div_8]);
+ scoped_ptr<MediaMessageFifo> producer_fifo(
+ new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)),
+ true));
+ scoped_ptr<MediaMessageFifo> consumer_fifo(
+ new MediaMessageFifo(
+ scoped_ptr<MediaMemoryChunk>(
+ new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)),
+ false));
+ producer_fifo->ObserveWriteActivity(
+ base::Bind(&AvStreamerTest::OnFifoWrite, base::Unretained(this)));
+ consumer_fifo->ObserveReadActivity(
+ base::Bind(&AvStreamerTest::OnFifoRead, base::Unretained(this)));
+
+ av_buffer_proxy_.reset(
+ new AvStreamerProxy());
+ av_buffer_proxy_->SetCodedFrameProvider(
+ scoped_ptr<CodedFrameProvider>(frame_provider.release()));
+ av_buffer_proxy_->SetMediaMessageFifo(producer_fifo.Pass());
+
+ coded_frame_provider_host_.reset(
+ new CodedFrameProviderHost(consumer_fifo.Pass()));
+
+ frame_consumer_.reset(
+ new MockFrameConsumer(coded_frame_provider_host_.get()));
+ frame_consumer_->Configure(
+ consumer_delayed_pattern,
+ false,
+ frame_generator_consumer.Pass());
+}
+
+void AvStreamerTest::Start() {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerProxy::Start,
+ base::Unretained(av_buffer_proxy_.get())));
+
+ frame_consumer_->Start(
+ base::Bind(&AvStreamerTest::OnTestCompleted,
+ base::Unretained(this)));
+}
+
+void AvStreamerTest::OnTestTimeout() {
+ ADD_FAILURE() << "Test timed out";
+ if (base::MessageLoop::current())
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+void AvStreamerTest::OnTestCompleted() {
+ base::MessageLoop::current()->QuitWhenIdle();
+}
+
+void AvStreamerTest::OnFifoWrite() {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent,
+ base::Unretained(coded_frame_provider_host_.get())));
+}
+
+void AvStreamerTest::OnFifoRead() {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerProxy::OnFifoReadEvent,
+ base::Unretained(av_buffer_proxy_.get())));
+}
+
+TEST_F(AvStreamerTest, FastProviderSlowConsumer) {
+ bool provider_delayed_pattern[] = { false };
+ bool consumer_delayed_pattern[] = { true };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+TEST_F(AvStreamerTest, SlowProviderFastConsumer) {
+ bool provider_delayed_pattern[] = { true };
+ bool consumer_delayed_pattern[] = { false };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+TEST_F(AvStreamerTest, SlowFastProducerConsumer) {
+ // Pattern lengths are prime between each other
+ // so that a lot of combinations can be tested.
+ bool provider_delayed_pattern[] = {
+ true, true, true, true, true,
+ false, false, false, false
+ };
+ bool consumer_delayed_pattern[] = {
+ true, true, true, true, true, true, true,
+ false, false, false, false, false, false, false
+ };
+
+ const size_t frame_count = 100u;
+ Configure(
+ frame_count,
+ std::vector<bool>(
+ provider_delayed_pattern,
+ provider_delayed_pattern + arraysize(provider_delayed_pattern)),
+ std::vector<bool>(
+ consumer_delayed_pattern,
+ consumer_delayed_pattern + arraysize(consumer_delayed_pattern)));
+
+ scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
+ message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&AvStreamerTest::Start, base::Unretained(this)));
+ message_loop->Run();
+};
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc
new file mode 100644
index 0000000000..19c57bdb7a
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "chromecast/media/cma/ipc/media_message_fifo.h"
+#include "chromecast/media/cma/ipc/media_message_type.h"
+#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h"
+#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h"
+#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h"
+#include "media/base/buffers.h"
+#include "media/base/decrypt_config.h"
+
+namespace chromecast {
+namespace media {
+
+CodedFrameProviderHost::CodedFrameProviderHost(
+ scoped_ptr<MediaMessageFifo> media_message_fifo)
+ : fifo_(media_message_fifo.Pass()),
+ weak_factory_(this),
+ weak_this_(weak_factory_.GetWeakPtr()) {
+ thread_checker_.DetachFromThread();
+}
+
+CodedFrameProviderHost::~CodedFrameProviderHost() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void CodedFrameProviderHost::Read(const ReadCB& read_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Cannot be called if there is already a pending read.
+ DCHECK(read_cb_.is_null());
+ read_cb_ = read_cb;
+
+ ReadMessages();
+}
+
+void CodedFrameProviderHost::Flush(const base::Closure& flush_cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ audio_config_ = ::media::AudioDecoderConfig();
+ video_config_ = ::media::VideoDecoderConfig();
+ read_cb_.Reset();
+ fifo_->Flush();
+ flush_cb.Run();
+}
+
+void CodedFrameProviderHost::OnFifoWriteEvent() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ReadMessages();
+}
+
+base::Closure CodedFrameProviderHost::GetFifoWriteEventCb() {
+ return base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent, weak_this_);
+}
+
+void CodedFrameProviderHost::ReadMessages() {
+ // Read messages until a frame is provided (i.e. not just the audio/video
+ // configurations).
+ while (!read_cb_.is_null()) {
+ scoped_ptr<MediaMessage> msg(fifo_->Pop());
+ if (!msg)
+ break;
+
+ if (msg->type() == PaddingMediaMsg) {
+ // Ignore the message.
+ } else if (msg->type() == AudioConfigMediaMsg) {
+ audio_config_ = AudioDecoderConfigMarshaller::Read(msg.get());
+ } else if (msg->type() == VideoConfigMediaMsg) {
+ video_config_ = VideoDecoderConfigMarshaller::Read(msg.get());
+ } else if (msg->type() == FrameMediaMsg) {
+ scoped_refptr<DecoderBufferBase> buffer =
+ DecoderBufferBaseMarshaller::Read(msg.Pass());
+ base::ResetAndReturn(&read_cb_).Run(
+ buffer, audio_config_, video_config_);
+ audio_config_ = ::media::AudioDecoderConfig();
+ video_config_ = ::media::VideoDecoderConfig();
+ } else {
+ // Receiving an unexpected message.
+ // Possible use case (except software bugs): the renderer process has
+ // been compromised and an invalid message value has been written to
+ // the fifo. Crash the browser process in this case to avoid further
+ // security implications (so do not use NOTREACHED which crashes only
+ // in debug builds).
+ LOG(FATAL) << "Unknown media message";
+ }
+ }
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h
new file mode 100644
index 0000000000..3dcd329259
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "chromecast/media/cma/base/coded_frame_provider.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/video_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+class MediaMessageFifo;
+
+// CodedFrameProviderHost is a frame provider that gets the frames
+// from a media message fifo.
+class CodedFrameProviderHost : public CodedFrameProvider {
+ public:
+ // Note: if the media message fifo is located into shared memory,
+ // the caller must make sure the shared memory segment is valid
+ // during the whole lifetime of this object.
+ explicit CodedFrameProviderHost(
+ scoped_ptr<MediaMessageFifo> media_message_fifo);
+ virtual ~CodedFrameProviderHost();
+
+ // CodedFrameProvider implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual void Flush(const base::Closure& flush_cb) OVERRIDE;
+
+ // Invoked when some data has been written into the fifo.
+ void OnFifoWriteEvent();
+ base::Closure GetFifoWriteEventCb();
+
+ private:
+ void ReadMessages();
+
+ base::ThreadChecker thread_checker_;
+
+ // Fifo holding the frames.
+ scoped_ptr<MediaMessageFifo> fifo_;
+
+ ReadCB read_cb_;
+
+ // Audio/video configuration for the next A/V buffer.
+ ::media::AudioDecoderConfig audio_config_;
+ ::media::VideoDecoderConfig video_config_;
+
+ base::WeakPtrFactory<CodedFrameProviderHost> weak_factory_;
+ base::WeakPtr<CodedFrameProviderHost> weak_this_;
+
+ DISALLOW_COPY_AND_ASSIGN(CodedFrameProviderHost);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_
diff --git a/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc
new file mode 100644
index 0000000000..bfdb7c121c
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h"
+
+#include "base/logging.h"
+#include "chromecast/media/cma/base/decoder_buffer_base.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "chromecast/media/cma/ipc/media_message_type.h"
+#include "chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h"
+#include "media/base/decrypt_config.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+const size_t kMaxFrameSize = 4 * 1024 * 1024;
+
+class DecoderBufferFromMsg : public DecoderBufferBase {
+ public:
+ explicit DecoderBufferFromMsg(scoped_ptr<MediaMessage> msg);
+
+ void Initialize();
+
+ // DecoderBufferBase implementation.
+ virtual base::TimeDelta timestamp() const OVERRIDE;
+ virtual const uint8* data() const OVERRIDE;
+ virtual uint8* writable_data() const OVERRIDE;
+ virtual int data_size() const OVERRIDE;
+ virtual const ::media::DecryptConfig* decrypt_config() const OVERRIDE;
+ virtual bool end_of_stream() const OVERRIDE;
+
+ private:
+ virtual ~DecoderBufferFromMsg();
+
+ // Indicates whether this is an end of stream frame.
+ bool is_eos_;
+
+ // Frame timestamp.
+ base::TimeDelta pts_;
+
+ // CENC parameters.
+ scoped_ptr< ::media::DecryptConfig> decrypt_config_;
+
+ // Size of the frame.
+ int data_size_;
+
+ // Keeps the message since frame data is not copied.
+ scoped_ptr<MediaMessage> msg_;
+ uint8* data_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecoderBufferFromMsg);
+};
+
+DecoderBufferFromMsg::DecoderBufferFromMsg(
+ scoped_ptr<MediaMessage> msg)
+ : msg_(msg.Pass()),
+ is_eos_(true),
+ data_(NULL) {
+ CHECK(msg_);
+}
+
+DecoderBufferFromMsg::~DecoderBufferFromMsg() {
+}
+
+void DecoderBufferFromMsg::Initialize() {
+ CHECK_EQ(msg_->type(), FrameMediaMsg);
+
+ CHECK(msg_->ReadPod(&is_eos_));
+ if (is_eos_)
+ return;
+
+ int64 pts_internal = 0;
+ CHECK(msg_->ReadPod(&pts_internal));
+ pts_ = base::TimeDelta::FromInternalValue(pts_internal);
+
+ bool has_decrypt_config = false;
+ CHECK(msg_->ReadPod(&has_decrypt_config));
+ if (has_decrypt_config)
+ decrypt_config_.reset(DecryptConfigMarshaller::Read(msg_.get()).release());
+
+ CHECK(msg_->ReadPod(&data_size_));
+ CHECK_GT(data_size_, 0);
+ CHECK_LT(data_size_, kMaxFrameSize);
+
+ // Get a pointer to the frame data inside the message.
+ // Avoid copying the frame data here.
+ data_ = static_cast<uint8*>(msg_->GetWritableBuffer(data_size_));
+ CHECK(data_);
+
+ if (decrypt_config_) {
+ uint32 subsample_total_size = 0;
+ for (size_t k = 0; k < decrypt_config_->subsamples().size(); k++) {
+ subsample_total_size += decrypt_config_->subsamples()[k].clear_bytes;
+ subsample_total_size += decrypt_config_->subsamples()[k].cypher_bytes;
+ }
+ CHECK_EQ(subsample_total_size, data_size_);
+ }
+}
+
+base::TimeDelta DecoderBufferFromMsg::timestamp() const {
+ return pts_;
+}
+
+const uint8* DecoderBufferFromMsg::data() const {
+ CHECK(msg_->IsSerializedMsgAvailable());
+ return data_;
+}
+
+uint8* DecoderBufferFromMsg::writable_data() const {
+ CHECK(msg_->IsSerializedMsgAvailable());
+ return data_;
+}
+
+int DecoderBufferFromMsg::data_size() const {
+ return data_size_;
+}
+
+const ::media::DecryptConfig* DecoderBufferFromMsg::decrypt_config() const {
+ return decrypt_config_.get();
+}
+
+bool DecoderBufferFromMsg::end_of_stream() const {
+ return is_eos_;
+}
+
+} // namespace
+
+// static
+void DecoderBufferBaseMarshaller::Write(
+ const scoped_refptr<DecoderBufferBase>& buffer,
+ MediaMessage* msg) {
+ CHECK(msg->WritePod(buffer->end_of_stream()));
+ if (buffer->end_of_stream())
+ return;
+
+ CHECK(msg->WritePod(buffer->timestamp().ToInternalValue()));
+
+ bool has_decrypt_config =
+ (buffer->decrypt_config() != NULL &&
+ buffer->decrypt_config()->iv().size() > 0);
+ CHECK(msg->WritePod(has_decrypt_config));
+ if (has_decrypt_config)
+ DecryptConfigMarshaller::Write(*buffer->decrypt_config(), msg);
+
+ CHECK(msg->WritePod(buffer->data_size()));
+ CHECK(msg->WriteBuffer(buffer->data(), buffer->data_size()));
+}
+
+// static
+scoped_refptr<DecoderBufferBase> DecoderBufferBaseMarshaller::Read(
+ scoped_ptr<MediaMessage> msg) {
+ scoped_refptr<DecoderBufferFromMsg> buffer(
+ new DecoderBufferFromMsg(msg.Pass()));
+ buffer->Initialize();
+ return buffer;
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h
new file mode 100644
index 0000000000..bd5dbfe9b1
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace chromecast {
+namespace media {
+class DecoderBufferBase;
+class MediaMessage;
+
+class DecoderBufferBaseMarshaller {
+ public:
+ // Writes the serialized structure of |config| into |msg|.
+ static void Write(
+ const scoped_refptr<DecoderBufferBase>& buffer, MediaMessage* msg);
+
+ // Returns a decoder buffer from its serialized structure.
+ static scoped_refptr<DecoderBufferBase> Read(scoped_ptr<MediaMessage> msg);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_
diff --git a/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc
new file mode 100644
index 0000000000..9e3de954cc
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h"
+
+#include "base/logging.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "media/base/decrypt_config.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+const size_t kMaxKeyIdSize = 256;
+const size_t kMaxIvSize = 256;
+const size_t kMaxSubsampleCount = 1024;
+}
+
+// static
+void DecryptConfigMarshaller::Write(
+ const ::media::DecryptConfig& config, MediaMessage* msg) {
+ CHECK_GT(config.key_id().size(), 0);
+ CHECK_GT(config.iv().size(), 0);
+ CHECK_GT(config.subsamples().size(), 0);
+
+ CHECK(msg->WritePod(config.key_id().size()));
+ CHECK(msg->WriteBuffer(config.key_id().data(), config.key_id().size()));
+ CHECK(msg->WritePod(config.iv().size()));
+ CHECK(msg->WriteBuffer(config.iv().data(), config.iv().size()));
+ CHECK(msg->WritePod(config.subsamples().size()));
+ for (size_t k = 0; k < config.subsamples().size(); k++) {
+ CHECK(msg->WritePod(config.subsamples()[k].clear_bytes));
+ CHECK(msg->WritePod(config.subsamples()[k].cypher_bytes));
+ }
+}
+
+// static
+scoped_ptr< ::media::DecryptConfig> DecryptConfigMarshaller::Read(
+ MediaMessage* msg) {
+ size_t key_id_size = 0;
+ CHECK(msg->ReadPod(&key_id_size));
+ CHECK_GT(key_id_size, 0);
+ CHECK_LT(key_id_size, kMaxKeyIdSize);
+ scoped_ptr<char[]> key_id(new char[key_id_size]);
+ CHECK(msg->ReadBuffer(key_id.get(), key_id_size));
+
+ size_t iv_size = 0;
+ CHECK(msg->ReadPod(&iv_size));
+ CHECK_GT(iv_size, 0);
+ CHECK_LT(iv_size, kMaxIvSize);
+ scoped_ptr<char[]> iv(new char[iv_size]);
+ CHECK(msg->ReadBuffer(iv.get(), iv_size));
+
+ size_t subsample_count = 0;
+ CHECK(msg->ReadPod(&subsample_count));
+ CHECK_GT(subsample_count, 0);
+ CHECK_LT(subsample_count, kMaxSubsampleCount);
+ std::vector< ::media::SubsampleEntry> subsamples(subsample_count);
+ for (size_t k = 0; k < subsample_count; k++) {
+ subsamples[k].clear_bytes = 0;
+ subsamples[k].cypher_bytes = 0;
+ CHECK(msg->ReadPod(&subsamples[k].clear_bytes));
+ CHECK(msg->ReadPod(&subsamples[k].cypher_bytes));
+ }
+
+ return scoped_ptr< ::media::DecryptConfig>(
+ new ::media::DecryptConfig(
+ std::string(key_id.get(), key_id_size),
+ std::string(iv.get(), iv_size),
+ subsamples));
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h
new file mode 100644
index 0000000000..b600ba41c1
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_
+
+#include "base/memory/scoped_ptr.h"
+
+namespace media {
+class DecryptConfig;
+}
+
+namespace chromecast {
+namespace media {
+class MediaMessage;
+
+class DecryptConfigMarshaller {
+ public:
+ // Writes the serialized structure of |config| into |msg|.
+ static void Write(
+ const ::media::DecryptConfig& config, MediaMessage* msg);
+
+ // Returns a DecryptConfig from its serialized structure.
+ static scoped_ptr< ::media::DecryptConfig> Read(MediaMessage* msg);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_
diff --git a/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc
new file mode 100644
index 0000000000..645056ce22
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "chromecast/media/cma/ipc/media_message.h"
+#include "media/base/video_decoder_config.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+const size_t kMaxExtraDataSize = 16 * 1024;
+
+class SizeMarshaller {
+ public:
+ static void Write(const gfx::Size& size, MediaMessage* msg) {
+ CHECK(msg->WritePod(size.width()));
+ CHECK(msg->WritePod(size.height()));
+ }
+
+ static gfx::Size Read(MediaMessage* msg) {
+ int w, h;
+ CHECK(msg->ReadPod(&w));
+ CHECK(msg->ReadPod(&h));
+ return gfx::Size(w, h);
+ }
+};
+
+class RectMarshaller {
+ public:
+ static void Write(const gfx::Rect& rect, MediaMessage* msg) {
+ CHECK(msg->WritePod(rect.x()));
+ CHECK(msg->WritePod(rect.y()));
+ CHECK(msg->WritePod(rect.width()));
+ CHECK(msg->WritePod(rect.height()));
+ }
+
+ static gfx::Rect Read(MediaMessage* msg) {
+ int x, y, w, h;
+ CHECK(msg->ReadPod(&x));
+ CHECK(msg->ReadPod(&y));
+ CHECK(msg->ReadPod(&w));
+ CHECK(msg->ReadPod(&h));
+ return gfx::Rect(x, y, w, h);
+ }
+};
+
+} // namespace
+
+// static
+void VideoDecoderConfigMarshaller::Write(
+ const ::media::VideoDecoderConfig& config, MediaMessage* msg) {
+ CHECK(msg->WritePod(config.codec()));
+ CHECK(msg->WritePod(config.profile()));
+ CHECK(msg->WritePod(config.format()));
+ SizeMarshaller::Write(config.coded_size(), msg);
+ RectMarshaller::Write(config.visible_rect(), msg);
+ SizeMarshaller::Write(config.natural_size(), msg);
+ CHECK(msg->WritePod(config.is_encrypted()));
+ CHECK(msg->WritePod(config.extra_data_size()));
+ if (config.extra_data_size() > 0)
+ CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size()));
+}
+
+// static
+::media::VideoDecoderConfig VideoDecoderConfigMarshaller::Read(
+ MediaMessage* msg) {
+ ::media::VideoCodec codec;
+ ::media::VideoCodecProfile profile;
+ ::media::VideoFrame::Format format;
+ gfx::Size coded_size;
+ gfx::Rect visible_rect;
+ gfx::Size natural_size;
+ bool is_encrypted;
+ size_t extra_data_size;
+ scoped_ptr<uint8[]> extra_data;
+
+ CHECK(msg->ReadPod(&codec));
+ CHECK(msg->ReadPod(&profile));
+ CHECK(msg->ReadPod(&format));
+ coded_size = SizeMarshaller::Read(msg);
+ visible_rect = RectMarshaller::Read(msg);
+ natural_size = SizeMarshaller::Read(msg);
+ CHECK(msg->ReadPod(&is_encrypted));
+ CHECK(msg->ReadPod(&extra_data_size));
+
+ CHECK_GE(codec, ::media::kUnknownVideoCodec);
+ CHECK_LE(codec, ::media::kVideoCodecMax);
+ CHECK_GE(profile, ::media::VIDEO_CODEC_PROFILE_UNKNOWN);
+ CHECK_LE(profile, ::media::VIDEO_CODEC_PROFILE_MAX);
+ CHECK_GE(format, ::media::VideoFrame::UNKNOWN);
+ CHECK_LE(format, ::media::VideoFrame::FORMAT_MAX);
+ CHECK_LT(extra_data_size, kMaxExtraDataSize);
+ if (extra_data_size > 0) {
+ extra_data.reset(new uint8[extra_data_size]);
+ CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size));
+ }
+
+ return ::media::VideoDecoderConfig(
+ codec, profile, format,
+ coded_size, visible_rect, natural_size,
+ extra_data.get(), extra_data_size,
+ is_encrypted);
+}
+
+} // namespace media
+} // namespace chromecast
diff --git a/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h
new file mode 100644
index 0000000000..5ba7baec06
--- /dev/null
+++ b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_
+#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_
+
+#include "media/base/video_decoder_config.h"
+
+namespace chromecast {
+namespace media {
+class MediaMessage;
+
+class VideoDecoderConfigMarshaller {
+ public:
+ // Writes the serialized structure of |config| into |msg|.
+ static void Write(
+ const ::media::VideoDecoderConfig& config, MediaMessage* msg);
+
+ // Returns a VideoDecoderConfig from its serialized structure.
+ static ::media::VideoDecoderConfig Read(MediaMessage* msg);
+};
+
+} // namespace media
+} // namespace chromecast
+
+#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_
diff --git a/chromecast/media/media.gyp b/chromecast/media/media.gyp
new file mode 100644
index 0000000000..3cebc65631
--- /dev/null
+++ b/chromecast/media/media.gyp
@@ -0,0 +1,154 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'chromecast_branding%': 'Chromium',
+ },
+ 'targets': [
+ {
+ 'target_name': 'media_base',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h',
+ ],
+ 'sources': [
+ 'base/key_systems_common.cc',
+ 'base/key_systems_common.h',
+ ],
+ 'conditions': [
+ ['chromecast_branding=="Chrome"', {
+ 'dependencies': [
+ 'internal/chromecast_internal.gyp:media_base_internal',
+ ],
+ }, {
+ 'sources': [
+ 'base/key_systems_common_simple.cc',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'cma_base',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../media/media.gyp:media',
+ ],
+ 'include_dirs': [
+ '../..',
+ ],
+ 'sources': [
+ 'cma/base/balanced_media_task_runner_factory.cc',
+ 'cma/base/balanced_media_task_runner_factory.h',
+ 'cma/base/buffering_controller.cc',
+ 'cma/base/buffering_controller.h',
+ 'cma/base/buffering_frame_provider.cc',
+ 'cma/base/buffering_frame_provider.h',
+ 'cma/base/buffering_state.cc',
+ 'cma/base/buffering_state.h',
+ 'cma/base/cma_logging.h',
+ 'cma/base/coded_frame_provider.cc',
+ 'cma/base/coded_frame_provider.h',
+ 'cma/base/decoder_buffer_adapter.cc',
+ 'cma/base/decoder_buffer_adapter.h',
+ 'cma/base/decoder_buffer_base.cc',
+ 'cma/base/decoder_buffer_base.h',
+ 'cma/base/media_task_runner.cc',
+ 'cma/base/media_task_runner.h',
+ ],
+ },
+ {
+ 'target_name': 'cma_ipc',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ ],
+ 'sources': [
+ 'cma/ipc/media_memory_chunk.cc',
+ 'cma/ipc/media_memory_chunk.h',
+ 'cma/ipc/media_message.cc',
+ 'cma/ipc/media_message.h',
+ 'cma/ipc/media_message_fifo.cc',
+ 'cma/ipc/media_message_fifo.h',
+ ],
+ },
+ {
+ 'target_name': 'cma_ipc_streamer',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../media/media.gyp:media',
+ 'cma_base',
+ ],
+ 'sources': [
+ 'cma/ipc_streamer/audio_decoder_config_marshaller.cc',
+ 'cma/ipc_streamer/audio_decoder_config_marshaller.h',
+ 'cma/ipc_streamer/av_streamer_proxy.cc',
+ 'cma/ipc_streamer/av_streamer_proxy.h',
+ 'cma/ipc_streamer/coded_frame_provider_host.cc',
+ 'cma/ipc_streamer/coded_frame_provider_host.h',
+ 'cma/ipc_streamer/decoder_buffer_base_marshaller.cc',
+ 'cma/ipc_streamer/decoder_buffer_base_marshaller.h',
+ 'cma/ipc_streamer/decrypt_config_marshaller.cc',
+ 'cma/ipc_streamer/decrypt_config_marshaller.h',
+ 'cma/ipc_streamer/video_decoder_config_marshaller.cc',
+ 'cma/ipc_streamer/video_decoder_config_marshaller.h',
+ ],
+ },
+ {
+ 'target_name': 'cma_filters',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../media/media.gyp:media',
+ 'cma_base',
+ ],
+ 'sources': [
+ 'cma/filters/demuxer_stream_adapter.cc',
+ 'cma/filters/demuxer_stream_adapter.h',
+ ],
+ },
+ {
+ 'target_name': 'cast_media',
+ 'type': 'none',
+ 'dependencies': [
+ 'cma_base',
+ 'cma_filters',
+ 'cma_ipc',
+ 'cma_ipc_streamer',
+ ],
+ },
+ {
+ 'target_name': 'cast_media_unittests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ 'cast_media',
+ '../../base/base.gyp:base',
+ '../../base/base.gyp:base_i18n',
+ '../../base/base.gyp:test_support_base',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ '../../testing/gtest.gyp:gtest_main',
+ ],
+ 'sources': [
+ 'cma/base/balanced_media_task_runner_unittest.cc',
+ 'cma/base/buffering_controller_unittest.cc',
+ 'cma/base/buffering_frame_provider_unittest.cc',
+ 'cma/base/frame_generator_for_test.cc',
+ 'cma/base/frame_generator_for_test.h',
+ 'cma/base/mock_frame_consumer.cc',
+ 'cma/base/mock_frame_consumer.h',
+ 'cma/base/mock_frame_provider.cc',
+ 'cma/base/mock_frame_provider.h',
+ 'cma/base/run_all_unittests.cc',
+ 'cma/filters/demuxer_stream_adapter_unittest.cc',
+ 'cma/ipc/media_message_fifo_unittest.cc',
+ 'cma/ipc/media_message_unittest.cc',
+ 'cma/ipc_streamer/av_streamer_unittest.cc',
+ ],
+ },
+ ],
+}
diff --git a/chromecast/metrics/DEPS b/chromecast/metrics/DEPS
new file mode 100644
index 0000000000..d9ef433896
--- /dev/null
+++ b/chromecast/metrics/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/metrics",
+]
diff --git a/chromecast/metrics/OWNERS b/chromecast/metrics/OWNERS
new file mode 100644
index 0000000000..2518ed9062
--- /dev/null
+++ b/chromecast/metrics/OWNERS
@@ -0,0 +1,5 @@
+# components/metrics OWNERS for review of Chromecast metrics code
+asvitkine@chromium.org
+mpearson@chromium.org
+jar@chromium.org
+isherman@chromium.org
diff --git a/chromecast/metrics/cast_metrics_prefs.cc b/chromecast/metrics/cast_metrics_prefs.cc
new file mode 100644
index 0000000000..9138aa74dd
--- /dev/null
+++ b/chromecast/metrics/cast_metrics_prefs.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/metrics/cast_metrics_prefs.h"
+
+#include "components/metrics/metrics_service.h"
+
+namespace chromecast {
+namespace metrics {
+
+void RegisterPrefs(PrefRegistrySimple* registry) {
+ ::metrics::MetricsService::RegisterPrefs(registry);
+}
+
+} // namespace metrics
+} // namespace chromecast
diff --git a/chromecast/metrics/cast_metrics_prefs.h b/chromecast/metrics/cast_metrics_prefs.h
new file mode 100644
index 0000000000..ff7a973cc3
--- /dev/null
+++ b/chromecast/metrics/cast_metrics_prefs.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_METRICS_CAST_METRICS_PREFS_H_
+#define CHROMECAST_METRICS_CAST_METRICS_PREFS_H_
+
+class PrefRegistrySimple;
+
+namespace chromecast {
+namespace metrics {
+
+void RegisterPrefs(PrefRegistrySimple* registry);
+
+} // namespace metrics
+} // namespace chromecast
+
+#endif // CHROMECAST_METRICS_CAST_METRICS_PREFS_H_
diff --git a/chromecast/metrics/cast_metrics_service_client.cc b/chromecast/metrics/cast_metrics_service_client.cc
new file mode 100644
index 0000000000..637cd400b8
--- /dev/null
+++ b/chromecast/metrics/cast_metrics_service_client.cc
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/metrics/cast_metrics_service_client.h"
+
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "chromecast/common/chromecast_config.h"
+#include "chromecast/common/chromecast_switches.h"
+#include "chromecast/metrics/platform_metrics_providers.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/gpu/gpu_metrics_provider.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/net/net_metrics_log_uploader.h"
+#include "components/metrics/net/network_metrics_provider.h"
+#include "components/metrics/profiler/profiler_metrics_provider.h"
+
+namespace chromecast {
+namespace metrics {
+
+namespace {
+
+void StoreClientInfo(const ::metrics::ClientInfo& client_info) {
+}
+
+scoped_ptr<::metrics::ClientInfo> LoadClientInfo() {
+ return scoped_ptr<::metrics::ClientInfo>();
+}
+
+} // namespace
+
+// static
+CastMetricsServiceClient* CastMetricsServiceClient::Create(
+ base::TaskRunner* io_task_runner,
+ PrefService* pref_service,
+ net::URLRequestContextGetter* request_context) {
+ return new CastMetricsServiceClient(io_task_runner,
+ pref_service,
+ request_context);
+}
+
+void CastMetricsServiceClient::SetMetricsClientId(
+ const std::string& client_id) {
+ LOG(INFO) << "Metrics client ID set: " << client_id;
+ PlatformSetClientID(client_id);
+}
+
+bool CastMetricsServiceClient::IsOffTheRecordSessionActive() {
+ // Chromecast behaves as "off the record" w/r/t recording browsing state,
+ // but this value is about not disabling metrics because of it.
+ return false;
+}
+
+std::string CastMetricsServiceClient::GetApplicationLocale() {
+ return base::i18n::GetConfiguredLocale();
+}
+
+bool CastMetricsServiceClient::GetBrand(std::string* brand_code) {
+ return false;
+}
+
+::metrics::SystemProfileProto::Channel CastMetricsServiceClient::GetChannel() {
+ return GetPlatformReleaseChannel();
+}
+
+std::string CastMetricsServiceClient::GetVersionString() {
+ return GetPlatformVersionString();
+}
+
+void CastMetricsServiceClient::OnLogUploadComplete() {
+ PlatformOnLogUploadComplete();
+}
+
+void CastMetricsServiceClient::StartGatheringMetrics(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+void CastMetricsServiceClient::CollectFinalMetrics(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+scoped_ptr< ::metrics::MetricsLogUploader>
+CastMetricsServiceClient::CreateUploader(
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete) {
+ std::string uma_server_url(server_url);
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kOverrideMetricsUploadUrl)) {
+ uma_server_url.assign(
+ command_line->GetSwitchValueASCII(switches::kOverrideMetricsUploadUrl));
+ }
+ DCHECK(!uma_server_url.empty());
+ return scoped_ptr< ::metrics::MetricsLogUploader>(
+ new ::metrics::NetMetricsLogUploader(
+ request_context_,
+ uma_server_url,
+ mime_type,
+ on_upload_complete));
+}
+
+void CastMetricsServiceClient::EnableMetricsService(bool enabled) {
+ if (enabled) {
+ metrics_service_->Start();
+ } else {
+ metrics_service_->Stop();
+ }
+}
+
+CastMetricsServiceClient::CastMetricsServiceClient(
+ base::TaskRunner* io_task_runner,
+ PrefService* pref_service,
+ net::URLRequestContextGetter* request_context)
+ : metrics_state_manager_(::metrics::MetricsStateManager::Create(
+ pref_service,
+ base::Bind(&CastMetricsServiceClient::IsReportingEnabled,
+ base::Unretained(this)),
+ base::Bind(&StoreClientInfo),
+ base::Bind(&LoadClientInfo))),
+ metrics_service_(new ::metrics::MetricsService(
+ metrics_state_manager_.get(),
+ this,
+ pref_service)),
+ request_context_(request_context) {
+ // Always create a client id as it may also be used by crash reporting,
+ // (indirectly) included in feedback, and can be queried during setup.
+ // For UMA and crash reporting, associated opt-in settings will control
+ // sending reports as directed by the user.
+ // For Setup (which also communicates the user's opt-in preferences),
+ // report the client-id and expect that setup will handle the current opt-in
+ // value.
+ metrics_state_manager_->ForceClientIdCreation();
+
+ metrics_service_->RegisterMetricsProvider(
+ scoped_ptr< ::metrics::MetricsProvider>(
+ new ::metrics::GPUMetricsProvider));
+ metrics_service_->RegisterMetricsProvider(
+ scoped_ptr< ::metrics::MetricsProvider>(
+ new NetworkMetricsProvider(io_task_runner)));
+ metrics_service_->RegisterMetricsProvider(
+ scoped_ptr< ::metrics::MetricsProvider>(
+ new ::metrics::ProfilerMetricsProvider));
+ RegisterPlatformMetricsProviders(metrics_service_.get());
+
+ metrics_service_->InitializeMetricsRecordingState();
+
+ if (IsReportingEnabled())
+ metrics_service_->Start();
+}
+
+CastMetricsServiceClient::~CastMetricsServiceClient() {
+}
+
+bool CastMetricsServiceClient::IsReportingEnabled() {
+ return PlatformIsReportingEnabled();
+}
+
+} // namespace metrics
+} // namespace chromecast
diff --git a/chromecast/metrics/cast_metrics_service_client.h b/chromecast/metrics/cast_metrics_service_client.h
new file mode 100644
index 0000000000..fc55aea882
--- /dev/null
+++ b/chromecast/metrics/cast_metrics_service_client.h
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_
+#define CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/metrics_service_client.h"
+
+class PrefService;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace metrics {
+class MetricsService;
+class MetricsStateManager;
+} // namespace metrics
+
+namespace net {
+class URLRequestContextGetter;
+} // namespace net
+
+namespace chromecast {
+namespace metrics {
+
+class CastMetricsServiceClient : public ::metrics::MetricsServiceClient {
+ public:
+ virtual ~CastMetricsServiceClient();
+
+ static CastMetricsServiceClient* Create(
+ base::TaskRunner* io_task_runner,
+ PrefService* pref_service,
+ net::URLRequestContextGetter* request_context);
+
+ // metrics::MetricsServiceClient implementation:
+ virtual void SetMetricsClientId(const std::string& client_id) OVERRIDE;
+ virtual bool IsOffTheRecordSessionActive() OVERRIDE;
+ virtual std::string GetApplicationLocale() OVERRIDE;
+ virtual bool GetBrand(std::string* brand_code) OVERRIDE;
+ virtual ::metrics::SystemProfileProto::Channel GetChannel() OVERRIDE;
+ virtual std::string GetVersionString() OVERRIDE;
+ virtual void OnLogUploadComplete() OVERRIDE;
+ virtual void StartGatheringMetrics(
+ const base::Closure& done_callback) OVERRIDE;
+ virtual void CollectFinalMetrics(const base::Closure& done_callback) OVERRIDE;
+ virtual scoped_ptr< ::metrics::MetricsLogUploader> CreateUploader(
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete) OVERRIDE;
+
+ // Starts/stops the metrics service.
+ void EnableMetricsService(bool enabled);
+
+ private:
+ CastMetricsServiceClient(
+ base::TaskRunner* io_task_runner,
+ PrefService* pref_service,
+ net::URLRequestContextGetter* request_context);
+
+ // Returns whether or not metrics reporting is enabled.
+ bool IsReportingEnabled();
+
+ scoped_ptr< ::metrics::MetricsStateManager> metrics_state_manager_;
+ scoped_ptr< ::metrics::MetricsService> metrics_service_;
+ net::URLRequestContextGetter* request_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastMetricsServiceClient);
+};
+
+} // namespace metrics
+} // namespace chromecast
+
+#endif // CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_
diff --git a/chromecast/metrics/cast_metrics_service_client_unittest.cc b/chromecast/metrics/cast_metrics_service_client_unittest.cc
new file mode 100644
index 0000000000..55e59b001a
--- /dev/null
+++ b/chromecast/metrics/cast_metrics_service_client_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/metrics/cast_metrics_service_client.h"
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/prefs/testing_pref_service.h"
+#include "components/metrics/metrics_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+
+class CastMetricsTest : public testing::Test {
+ public:
+ CastMetricsTest() {}
+ virtual ~CastMetricsTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ message_loop_.reset(new base::MessageLoop());
+ prefs_.reset(new TestingPrefServiceSimple());
+ ::metrics::MetricsService::RegisterPrefs(prefs_->registry());
+ }
+
+ TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+
+ private:
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_ptr<TestingPrefServiceSimple> prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastMetricsTest);
+};
+
+TEST_F(CastMetricsTest, CreateMetricsServiceClient) {
+ // Create and expect this to not crash.
+ metrics::CastMetricsServiceClient::Create(prefs(), NULL);
+}
+
+} // namespace chromecast
diff --git a/chromecast/metrics/platform_metrics_providers.h b/chromecast/metrics/platform_metrics_providers.h
new file mode 100644
index 0000000000..9d847e5c39
--- /dev/null
+++ b/chromecast/metrics/platform_metrics_providers.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_
+#define CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_
+
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace metrics {
+class MetricsService;
+}
+
+namespace chromecast {
+namespace metrics {
+
+// Build-level hook for different platforms to provide data to MetricsService.
+void RegisterPlatformMetricsProviders(
+ ::metrics::MetricsService* metrics_service);
+
+// Returns the current release channel.
+::metrics::SystemProfileProto::Channel GetPlatformReleaseChannel();
+
+// Returns a string representing this build's version.
+std::string GetPlatformVersionString();
+
+// Returns whether or not metrics reporting should be on.
+bool PlatformIsReportingEnabled();
+
+// Called when the UMA client ID has been set.
+void PlatformSetClientID(const std::string& client_id);
+
+// Called when an upload has completed.
+void PlatformOnLogUploadComplete();
+
+} // namespace metrics
+} // namespace chromecast
+
+#endif // CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_
diff --git a/chromecast/metrics/platform_metrics_providers_simple.cc b/chromecast/metrics/platform_metrics_providers_simple.cc
new file mode 100644
index 0000000000..d312ec6017
--- /dev/null
+++ b/chromecast/metrics/platform_metrics_providers_simple.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/metrics/platform_metrics_providers.h"
+
+namespace chromecast {
+namespace metrics {
+
+void RegisterPlatformMetricsProviders(
+ ::metrics::MetricsService* metrics_service) {
+}
+
+::metrics::SystemProfileProto::Channel GetPlatformReleaseChannel() {
+ return ::metrics::SystemProfileProto::CHANNEL_STABLE;
+}
+
+std::string GetPlatformVersionString() {
+ return "";
+}
+
+bool PlatformIsReportingEnabled() {
+ return false;
+}
+
+void PlatformSetClientID(const std::string& client_id) {
+}
+
+void PlatformOnLogUploadComplete() {
+}
+
+} // namespace metrics
+} // namespace chromecast
diff --git a/chromecast/net/network_change_notifier_factory_cast.cc b/chromecast/net/network_change_notifier_factory_cast.cc
index 1c41171119..c446b1e576 100644
--- a/chromecast/net/network_change_notifier_factory_cast.cc
+++ b/chromecast/net/network_change_notifier_factory_cast.cc
@@ -4,28 +4,16 @@
#include "chromecast/net/network_change_notifier_factory_cast.h"
-#include "base/lazy_instance.h"
#include "chromecast/net/network_change_notifier_cast.h"
namespace chromecast {
-namespace {
-
-base::LazyInstance<NetworkChangeNotifierCast> g_network_change_notifier_cast =
- LAZY_INSTANCE_INITIALIZER;
-
-} // namespace
-
net::NetworkChangeNotifier* NetworkChangeNotifierFactoryCast::CreateInstance() {
- return g_network_change_notifier_cast.Pointer();
+ // Caller assumes ownership.
+ return new NetworkChangeNotifierCast();
}
NetworkChangeNotifierFactoryCast::~NetworkChangeNotifierFactoryCast() {
}
-// static
-NetworkChangeNotifierCast* NetworkChangeNotifierFactoryCast::GetInstance() {
- return g_network_change_notifier_cast.Pointer();
-}
-
} // namespace chromecast
diff --git a/chromecast/service/cast_service.h b/chromecast/service/cast_service.h
index 606f8f7c87..bfc5d7a67f 100644
--- a/chromecast/service/cast_service.h
+++ b/chromecast/service/cast_service.h
@@ -12,15 +12,27 @@ namespace base {
class ThreadChecker;
}
-namespace content{
+namespace content {
class BrowserContext;
}
+namespace net {
+class URLRequestContextGetter;
+}
+
namespace chromecast {
class CastService {
public:
- static CastService* Create(content::BrowserContext* browser_context);
+ // Create() takes a separate url request context getter because the request
+ // context getter obtained through the browser context might not be
+ // appropriate for the url requests made by the cast service/reciever.
+ // For example, on Chromecast, it is needed to pass in a system url request
+ // context getter that would set the request context for NSS, which the main
+ // getter doesn't do.
+ static CastService* Create(
+ content::BrowserContext* browser_context,
+ net::URLRequestContextGetter* request_context_getter);
virtual ~CastService();
diff --git a/chromecast/service/cast_service_android.cc b/chromecast/service/cast_service_android.cc
new file mode 100644
index 0000000000..4c8f30a44a
--- /dev/null
+++ b/chromecast/service/cast_service_android.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/service/cast_service_android.h"
+
+#include "chromecast/android/chromecast_config_android.h"
+
+namespace chromecast {
+
+// static
+CastService* CastService::Create(
+ content::BrowserContext* browser_context,
+ net::URLRequestContextGetter* request_context_getter) {
+ return new CastServiceAndroid(browser_context);
+}
+
+CastServiceAndroid::CastServiceAndroid(content::BrowserContext* browser_context)
+ : CastService(browser_context) {
+}
+
+CastServiceAndroid::~CastServiceAndroid() {
+}
+
+void CastServiceAndroid::Initialize() {
+ // TODO(gunsch): Wire this the SendUsageStatsChanged callback once
+ // CastService::Delegate is added.
+}
+
+void CastServiceAndroid::StartInternal() {
+}
+
+void CastServiceAndroid::StopInternal() {
+}
+
+} // namespace chromecast
diff --git a/chromecast/service/cast_service_android.h b/chromecast/service/cast_service_android.h
new file mode 100644
index 0000000000..234b012947
--- /dev/null
+++ b/chromecast/service/cast_service_android.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
+#define CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
+
+#include "base/macros.h"
+#include "chromecast/service/cast_service.h"
+
+namespace chromecast {
+
+class CastServiceAndroid : public CastService {
+ public:
+ explicit CastServiceAndroid(content::BrowserContext* browser_context);
+ virtual ~CastServiceAndroid();
+
+ protected:
+ // CastService implementation.
+ virtual void Initialize() OVERRIDE;
+ virtual void StartInternal() OVERRIDE;
+ virtual void StopInternal() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CastServiceAndroid);
+};
+
+} // namespace chromecast
+
+#endif // CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_
diff --git a/chromecast/service/cast_service_simple.cc b/chromecast/service/cast_service_simple.cc
index 16b14b6016..c278667595 100644
--- a/chromecast/service/cast_service_simple.cc
+++ b/chromecast/service/cast_service_simple.cc
@@ -10,6 +10,7 @@
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/filename_util.h"
+#include "net/url_request/url_request_context_getter.h"
#include "ui/aura/env.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/test/test_screen.h"
@@ -69,7 +70,9 @@ class FillLayout : public aura::LayoutManager {
} // namespace
// static
-CastService* CastService::Create(content::BrowserContext* browser_context) {
+CastService* CastService::Create(
+ content::BrowserContext* browser_context,
+ net::URLRequestContextGetter* request_context_getter) {
return new CastServiceSimple(browser_context);
}
@@ -115,7 +118,7 @@ void CastServiceSimple::StartInternal() {
web_contents_->GetController().LoadURL(GetStartupURL(),
content::Referrer(),
- content::PAGE_TRANSITION_TYPED,
+ ui::PAGE_TRANSITION_TYPED,
std::string());
}
diff --git a/chromecast/service/cast_service_simple.h b/chromecast/service/cast_service_simple.h
index 40c037c4bc..534b2c06d4 100644
--- a/chromecast/service/cast_service_simple.h
+++ b/chromecast/service/cast_service_simple.h
@@ -24,7 +24,7 @@ class CastServiceSimple : public CastService {
virtual ~CastServiceSimple();
protected:
- // CastService implementation.
+ // CastService implementation:
virtual void Initialize() OVERRIDE;
virtual void StartInternal() OVERRIDE;
virtual void StopInternal() OVERRIDE;
diff --git a/chromecast/shell/android/DEPS b/chromecast/shell/android/DEPS
new file mode 100644
index 0000000000..69269bce9e
--- /dev/null
+++ b/chromecast/shell/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content/public/android",
+]
diff --git a/chromecast/shell/android/apk/AndroidManifest.xml b/chromecast/shell/android/apk/AndroidManifest.xml
new file mode 100644
index 0000000000..63ad17c99e
--- /dev/null
+++ b/chromecast/shell/android/apk/AndroidManifest.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.chromecast.shell">
+
+ <permission android:name="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:protectionLevel="signature" />
+
+ <application android:name="org.chromium.chromecast.shell.CastApplication"
+ android:icon="@mipmap/app_icon"
+ android:label="@string/app_name">
+ <activity android:name="org.chromium.chromecast.shell.CastShellActivity"
+ android:theme="@style/CastShellTheme"
+ android:exported="true"
+ android:hardwareAccelerated="true"
+ android:taskAffinity=".CastShellActivity"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
+ android:excludeFromRecents="true"
+ android:noHistory="true"
+ android:permission="android.permission.INTERNET">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <!-- The following service entries exist in order to allow us to
+ start more than one sandboxed process. -->
+ <service android:name="org.chromium.content.app.SandboxedProcessService0"
+ android:process=":sandboxed_process0"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService1"
+ android:process=":sandboxed_process1"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService2"
+ android:process=":sandboxed_process2"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService3"
+ android:process=":sandboxed_process3"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService4"
+ android:process=":sandboxed_process4"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService5"
+ android:process=":sandboxed_process5"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService6"
+ android:process=":sandboxed_process6"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService7"
+ android:process=":sandboxed_process7"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService8"
+ android:process=":sandboxed_process8"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService9"
+ android:process=":sandboxed_process9"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService10"
+ android:process=":sandboxed_process10"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService11"
+ android:process=":sandboxed_process11"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService12"
+ android:process=":sandboxed_process12"
+ android:permission="org.chromium.chromecast.shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService13"
+ android:process=":sandboxed_process13"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService14"
+ android:process=":sandboxed_process14"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService15"
+ android:process=":sandboxed_process15"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService16"
+ android:process=":sandboxed_process16"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService17"
+ android:process=":sandboxed_process17"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService18"
+ android:process=":sandboxed_process18"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ <service android:name="org.chromium.content.app.SandboxedProcessService19"
+ android:process=":sandboxed_process19"
+ android:permission="org.chromium.content_shell.permission.SANDBOX"
+ android:isolatedProcess="true"
+ android:exported="false" />
+ </application>
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+</manifest>
diff --git a/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml
new file mode 100644
index 0000000000..428b1a512a
--- /dev/null
+++ b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <org.chromium.chromecast.shell.CastWindowManager
+ android:id="@+id/shell_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</merge>
diff --git a/chromecast/shell/android/apk/res/layout/cast_window_view.xml b/chromecast/shell/android/apk/res/layout/cast_window_view.xml
new file mode 100644
index 0000000000..ce57c9e556
--- /dev/null
+++ b/chromecast/shell/android/apk/res/layout/cast_window_view.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<org.chromium.chromecast.shell.CastWindowAndroid
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <FrameLayout android:id="@+id/contentview_holder"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</org.chromium.chromecast.shell.CastWindowAndroid>
diff --git a/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png
new file mode 100644
index 0000000000..72d8318a89
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png
new file mode 100644
index 0000000000..4076d5d276
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png
new file mode 100644
index 0000000000..c7f092c36b
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png
new file mode 100644
index 0000000000..9addc3b09f
--- /dev/null
+++ b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png
Binary files differ
diff --git a/chromecast/shell/android/apk/res/values-v17/styles.xml b/chromecast/shell/android/apk/res/values-v17/styles.xml
new file mode 100644
index 0000000000..80eaf9be38
--- /dev/null
+++ b/chromecast/shell/android/apk/res/values-v17/styles.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<resources>
+ <style name="CastShellTheme" parent="@android:style/Theme.Holo.Light.NoActionBar">
+ <item name="android:windowBackground">@android:color/black</item>
+ </style>
+</resources>
diff --git a/chromecast/shell/android/apk/res/values/strings.xml b/chromecast/shell/android/apk/res/values/strings.xml
new file mode 100644
index 0000000000..9ca510a6ef
--- /dev/null
+++ b/chromecast/shell/android/apk/res/values/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <string name="app_name">Chromecast Android Shell</string>
+ <string name="browser_process_initialization_failed">Initialization failed.</string>
+</resources>
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
new file mode 100644
index 0000000000..65ad319c7c
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import org.chromium.base.PathUtils;
+import org.chromium.base.ResourceExtractor;
+import org.chromium.content.app.ContentApplication;
+
+/**
+ * Entry point for the Android cast shell application. Handles initialization of information that
+ * needs to be shared across the main activity and the child services created.
+ *
+ * Note that this gets run for each process, including sandboxed child render processes. Child
+ * processes don't need most of the full "setup" performed in CastBrowserHelper.java, but they do
+ * require a few basic pieces (found here).
+ */
+public class CastApplication extends ContentApplication {
+
+ private static final String[] MANDATORY_PAK_FILES = new String[] {"cast_shell.pak"};
+ private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cast_shell";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ initializeApplicationParameters();
+ }
+
+ public static void initializeApplicationParameters() {
+ ResourceExtractor.setMandatoryPaksToExtract(MANDATORY_PAK_FILES);
+ PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+ }
+
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
new file mode 100644
index 0000000000..d3ea8eaebf
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Debug;
+import android.util.Log;
+
+import org.chromium.base.BaseSwitches;
+import org.chromium.base.CommandLine;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.content.browser.BrowserStartupController;
+import org.chromium.content.browser.DeviceUtils;
+import org.chromium.content.common.ContentSwitches;
+
+/**
+ * Static, one-time initialization for the browser process.
+ */
+public class CastBrowserHelper {
+ private static final String TAG = "CastBrowserHelper";
+
+ public static final String COMMAND_LINE_FILE = "/data/local/tmp/castshell-command-line";
+ public static final String COMMAND_LINE_ARGS_KEY = "commandLineArgs";
+
+ private static boolean sIsBrowserInitialized = false;
+
+ /**
+ * Starts the browser process synchronously, returning success or failure. If the browser has
+ * already started, immediately returns true without performing any more initialization.
+ * This may only be called on the UI thread.
+ *
+ * @return whether or not the process started successfully
+ */
+ public static boolean initializeBrowser(Context context) {
+ if (sIsBrowserInitialized) return true;
+
+ Log.d(TAG, "Performing one-time browser initialization");
+
+ // Initializing the command line must occur before loading the library.
+ if (!CommandLine.isInitialized()) {
+ if (allowCommandLineImport()) {
+ Log.d(TAG, "Initializing command line from " + COMMAND_LINE_FILE);
+ CommandLine.initFromFile(COMMAND_LINE_FILE);
+ } else {
+ CommandLine.init(null);
+ }
+
+ if (context instanceof Activity) {
+ Intent launchingIntent = ((Activity) context).getIntent();
+ String[] commandLineParams = getCommandLineParamsFromIntent(launchingIntent);
+ if (commandLineParams != null) {
+ CommandLine.getInstance().appendSwitchesAndArguments(commandLineParams);
+ }
+ }
+ }
+
+ CommandLine.getInstance().appendSwitchWithValue(
+ ContentSwitches.FORCE_DEVICE_SCALE_FACTOR, "1");
+
+ waitForDebuggerIfNeeded();
+
+ DeviceUtils.addDeviceSpecificUserAgentSwitch(context);
+
+ try {
+ LibraryLoader.ensureInitialized();
+
+ Log.d(TAG, "Loading BrowserStartupController...");
+ BrowserStartupController.get(context).startBrowserProcessesSync(false);
+
+ sIsBrowserInitialized = true;
+ return true;
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "Unable to launch browser process.", e);
+ return false;
+ }
+ }
+
+ private static boolean allowCommandLineImport() {
+ return !Build.TYPE.equals("user");
+ }
+
+ private static String[] getCommandLineParamsFromIntent(Intent intent) {
+ return intent != null ? intent.getStringArrayExtra(COMMAND_LINE_ARGS_KEY) : null;
+ }
+
+ private static void waitForDebuggerIfNeeded() {
+ if (!CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) {
+ return;
+ }
+ Log.e(TAG, "Waiting for Java debugger to connect...");
+ Debug.waitForDebugger();
+ Log.e(TAG, "Java debugger connected. Resuming execution.");
+ }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java
new file mode 100644
index 0000000000..46152e7b63
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java
@@ -0,0 +1,291 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import org.chromium.base.CommandLine;
+import org.chromium.content.browser.ActivityContentVideoViewClient;
+import org.chromium.content.browser.ContentVideoViewClient;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Activity for managing the Cast shell.
+ */
+public class CastShellActivity extends Activity {
+ private static final String TAG = "CastShellActivity";
+
+ private static final String ACTIVE_SHELL_URL_KEY = "activeUrl";
+ private static final int DEFAULT_HEIGHT_PIXELS = 720;
+ public static final String ACTION_EXTRA_RESOLUTION_HEIGHT =
+ "org.chromium.chromecast.shell.intent.extra.RESOLUTION_HEIGHT";
+
+ private CastWindowManager mCastWindowManager;
+ private AudioManager mAudioManager;
+ private BroadcastReceiver mBroadcastReceiver;
+
+ // Native window instance.
+ // TODO(byungchul, gunsch): CastShellActivity, CastWindowAndroid, and native CastWindowAndroid
+ // have a one-to-one relationship. Consider instantiating CastWindow here and CastWindow having
+ // this native shell instance.
+ private long mNativeCastWindow;
+
+ /**
+ * Returns whether or not CastShellActivity should launch the browser startup sequence.
+ * Intended to be overridden.
+ */
+ protected boolean shouldLaunchBrowser() {
+ return true;
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ exitIfUrlMissing();
+
+ if (shouldLaunchBrowser()) {
+ if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) {
+ Toast.makeText(this,
+ R.string.browser_process_initialization_failed,
+ Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+
+ // Whenever our app is visible, volume controls should modify the music stream.
+ // For more information read:
+ // http://developer.android.com/training/managing-audio/volume-playback.html
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Set flags to both exit sleep mode when this activity starts and
+ // avoid entering sleep mode while playing media. We cannot distinguish
+ // between video and audio so this applies to both.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ setContentView(R.layout.cast_shell_activity);
+ mCastWindowManager = (CastWindowManager) findViewById(R.id.shell_container);
+ mCastWindowManager.setDelegate(new CastWindowManager.Delegate() {
+ @Override
+ public void onCreated() {
+ }
+
+ @Override
+ public void onClosed() {
+ mNativeCastWindow = 0;
+ mCastWindowManager.setDelegate(null);
+ finish();
+ }
+ });
+ setResolution();
+ mCastWindowManager.setWindow(new WindowAndroid(this));
+
+ registerBroadcastReceiver();
+
+ String url = getIntent().getDataString();
+ Log.d(TAG, "onCreate startupUrl: " + url);
+ mNativeCastWindow = mCastWindowManager.launchCastWindow(url);
+
+ getActiveContentViewCore().setContentViewClient(new ContentViewClient() {
+ @Override
+ public ContentVideoViewClient getContentVideoViewClient() {
+ return new ActivityContentVideoViewClient(CastShellActivity.this);
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ unregisterBroadcastReceiver();
+
+ if (mNativeCastWindow != 0) {
+ mCastWindowManager.stopCastWindow(mNativeCastWindow, false /* gracefully */);
+ mNativeCastWindow = 0;
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ // Only handle direct intents (e.g. "fling") if this activity is also managing
+ // the browser process.
+ if (!shouldLaunchBrowser()) return;
+
+ String url = intent.getDataString();
+ Log.d(TAG, "onNewIntent: " + url);
+
+ // Reset broadcast intent uri and receiver.
+ setIntent(intent);
+ exitIfUrlMissing();
+ getActiveCastWindow().loadUrl(url);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Inform ContentView that this activity is being shown.
+ ContentViewCore view = getActiveContentViewCore();
+ if (view != null) view.onShow();
+
+ // Request audio focus so any other audio playback doesn't continue in the background.
+ if (mAudioManager.requestAudioFocus(
+ null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
+ != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Log.e(TAG, "Failed to obtain audio focus");
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ // As soon as the cast app is no longer in the foreground, we ought to immediately tear
+ // everything down. Apps should not continue running and playing sound in the background.
+ super.onPause();
+
+ // Release the audio focus. Note that releasing audio focus does not stop audio playback,
+ // it just notifies the framework that this activity has stopped playing audio.
+ if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Log.e(TAG, "Failed to abandon audio focus");
+ }
+
+ ContentViewCore view = getActiveContentViewCore();
+ if (view != null) view.onHide();
+
+ finishGracefully();
+ }
+
+ protected void finishGracefully() {
+ if (mNativeCastWindow != 0) {
+ mCastWindowManager.stopCastWindow(mNativeCastWindow, true /* gracefully */);
+ mNativeCastWindow = 0;
+ }
+ }
+
+ private void registerBroadcastReceiver() {
+ if (mBroadcastReceiver == null) {
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Received intent: action=" + intent.getAction());
+ if (CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS.equals(intent.getAction())) {
+ mCastWindowManager.nativeEnableDevTools(true);
+ } else if (CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS.equals(
+ intent.getAction())) {
+ mCastWindowManager.nativeEnableDevTools(false);
+ }
+ }
+ };
+ }
+
+ IntentFilter devtoolsBroadcastIntentFilter = new IntentFilter();
+ devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS);
+ devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS);
+ LocalBroadcastManager.getInstance(this)
+ .registerReceiver(mBroadcastReceiver, devtoolsBroadcastIntentFilter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
+ broadcastManager.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void setResolution() {
+ int requestedHeight = getIntent().getIntExtra(
+ ACTION_EXTRA_RESOLUTION_HEIGHT, DEFAULT_HEIGHT_PIXELS);
+ int displayHeight = getResources().getDisplayMetrics().heightPixels;
+ // Clamp within [DEFAULT_HEIGHT_PIXELS, displayHeight]
+ int desiredHeight =
+ Math.min(displayHeight, Math.max(DEFAULT_HEIGHT_PIXELS, requestedHeight));
+ double deviceScaleFactor = ((double) displayHeight) / desiredHeight;
+ Log.d(TAG, "Using scale factor " + deviceScaleFactor + " to set height " + desiredHeight);
+ CommandLine.getInstance().appendSwitchWithValue("force-device-scale-factor",
+ String.valueOf(deviceScaleFactor));
+ }
+
+ private void exitIfUrlMissing() {
+ Intent intent = getIntent();
+ if (intent != null && intent.getData() != null && !intent.getData().equals(Uri.EMPTY)) {
+ return;
+ }
+ // Log an exception so that the exit cause is obvious when reading the logs.
+ Log.e(TAG, "Activity will not start",
+ new IllegalArgumentException("Intent did not contain a valid url"));
+ System.exit(-1);
+ }
+
+ /**
+ * @return The currently visible {@link CastWindowAndroid} or null if one is not showing.
+ */
+ public CastWindowAndroid getActiveCastWindow() {
+ return mCastWindowManager.getActiveCastWindow();
+ }
+
+ /**
+ * @return The {@link ContentViewCore} owned by the currently visible {@link CastWindowAndroid},
+ * or null if one is not showing.
+ */
+ public ContentViewCore getActiveContentViewCore() {
+ CastWindowAndroid shell = getActiveCastWindow();
+ return shell != null ? shell.getContentViewCore() : null;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return super.onKeyUp(keyCode, event);
+ }
+
+ // Just finish this activity to go back to the previous activity or launcher.
+ finishGracefully();
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return super.dispatchKeyEvent(event);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ return false;
+ }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java
new file mode 100644
index 0000000000..c0851b8638
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.content.browser.ContentView;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewRenderView;
+import org.chromium.content.browser.WebContentsObserverAndroid;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Container for the various UI components that make up a shell window.
+ */
+@JNINamespace("chromecast::shell")
+public class CastWindowAndroid extends LinearLayout {
+ public static final String TAG = "CastWindowAndroid";
+
+ public static final String ACTION_PAGE_LOADED = "castPageLoaded";
+ public static final String ACTION_ENABLE_DEV_TOOLS = "castEnableDevTools";
+ public static final String ACTION_DISABLE_DEV_TOOLS = "castDisableDevTools";
+
+ private ContentViewCore mContentViewCore;
+ private ContentViewRenderView mContentViewRenderView;
+ private NavigationController mNavigationController;
+ private WebContents mWebContents;
+ private WebContentsObserverAndroid mWebContentsObserver;
+ private WindowAndroid mWindow;
+
+ /**
+ * Constructor for inflating via XML.
+ */
+ public CastWindowAndroid(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Set the SurfaceView being renderered to as soon as it is available.
+ */
+ public void setContentViewRenderView(ContentViewRenderView contentViewRenderView) {
+ FrameLayout contentViewHolder = (FrameLayout) findViewById(R.id.contentview_holder);
+ if (contentViewRenderView == null) {
+ if (mContentViewRenderView != null) {
+ contentViewHolder.removeView(mContentViewRenderView);
+ }
+ } else {
+ contentViewHolder.addView(contentViewRenderView,
+ new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+ }
+ mContentViewRenderView = contentViewRenderView;
+ }
+
+ /**
+ * @param window The owning window for this shell.
+ */
+ public void setWindow(WindowAndroid window) {
+ mWindow = window;
+ }
+
+ /**
+ * Loads an URL. This will perform minimal amounts of sanitizing of the URL to attempt to
+ * make it valid.
+ *
+ * @param url The URL to be loaded by the shell.
+ */
+ public void loadUrl(String url) {
+ if (url == null) return;
+
+ if (TextUtils.equals(url, mWebContents.getUrl())) {
+ mNavigationController.reload(true);
+ } else {
+ mNavigationController.loadUrl(new LoadUrlParams(normalizeUrl(url)));
+ }
+
+ // TODO(aurimas): Remove this when crbug.com/174541 is fixed.
+ mContentViewCore.getContainerView().clearFocus();
+ mContentViewCore.getContainerView().requestFocus();
+ }
+
+ /**
+ * Given a URI String, performs minimal normalization to attempt to build a usable URL from it.
+ * @param uriString The passed-in path to be normalized.
+ * @return The normalized URL, as a string.
+ */
+ private static String normalizeUrl(String uriString) {
+ if (uriString == null) return uriString;
+ Uri uri = Uri.parse(uriString);
+ if (uri.getScheme() == null) {
+ uri = Uri.parse("http://" + uriString);
+ }
+ return uri.toString();
+ }
+
+ /**
+ * Initializes the ContentView based on the native tab contents pointer passed in.
+ * @param nativeWebContents The pointer to the native tab contents object.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void initFromNativeWebContents(long nativeWebContents) {
+ Context context = getContext();
+ mContentViewCore = new ContentViewCore(context);
+ ContentView view = ContentView.newInstance(context, mContentViewCore);
+ mContentViewCore.initialize(view, view, nativeWebContents, mWindow);
+ mWebContents = mContentViewCore.getWebContents();
+ mNavigationController = mWebContents.getNavigationController();
+
+ if (getParent() != null) mContentViewCore.onShow();
+ ((FrameLayout) findViewById(R.id.contentview_holder)).addView(view,
+ new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT));
+ view.requestFocus();
+ mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
+
+ mWebContentsObserver = new WebContentsObserverAndroid(mWebContents) {
+ @Override
+ public void didStopLoading(String url) {
+ Uri intentUri = Uri.parse(mNavigationController
+ .getOriginalUrlForVisibleNavigationEntry());
+ Log.v(TAG, "Broadcast ACTION_PAGE_LOADED: scheme=" + intentUri.getScheme()
+ + ", host=" + intentUri.getHost());
+ LocalBroadcastManager.getInstance(getContext()).sendBroadcast(
+ new Intent(ACTION_PAGE_LOADED, intentUri));
+ }
+ };
+ }
+
+ /**
+ * @return The {@link ViewGroup} currently shown by this Shell.
+ */
+ public ViewGroup getContentView() {
+ return mContentViewCore.getContainerView();
+ }
+
+ /**
+ * @return The {@link ContentViewCore} currently managing the view shown by this Shell.
+ */
+ public ContentViewCore getContentViewCore() {
+ return mContentViewCore;
+ }
+}
diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java
new file mode 100644
index 0000000000..3d3a4e8e50
--- /dev/null
+++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java
@@ -0,0 +1,157 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromecast.shell;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewRenderView;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Container and generator of CastWindow instances.
+ */
+@JNINamespace("chromecast::shell")
+public class CastWindowManager extends FrameLayout {
+ private static final String TAG = "CastWindowManager";
+
+ private WindowAndroid mWindow;
+ private CastWindowAndroid mActiveCastWindow;
+
+ // The target for all content rendering.
+ private ContentViewRenderView mContentViewRenderView;
+
+ /**
+ * Delegate to deliver events from the native window.
+ */
+ public interface Delegate {
+ public void onCreated();
+ public void onClosed();
+ }
+ private Delegate mDelegate;
+
+ /**
+ * Constructor for inflating via XML.
+ */
+ public CastWindowManager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ nativeInit(this);
+ }
+
+ /**
+ * @param delegate Delegate to handle events.
+ */
+ public void setDelegate(Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * @param window Represents the activity window.
+ */
+ public void setWindow(WindowAndroid window) {
+ assert window != null;
+ mWindow = window;
+ mContentViewRenderView = new ContentViewRenderView(getContext()) {
+ @Override
+ protected void onReadyToRender() {
+ setOverlayVideoMode(true);
+ }
+ };
+ mContentViewRenderView.onNativeLibraryLoaded(window);
+ // Setting the background color to black avoids rendering a white splash screen
+ // before the players are loaded. See crbug/307113 for details.
+ mContentViewRenderView.setSurfaceViewBackgroundColor(Color.BLACK);
+ }
+
+ /**
+ * @return The window used to generate all shells.
+ */
+ public WindowAndroid getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * @return The currently visible shell view or null if one is not showing.
+ */
+ public CastWindowAndroid getActiveCastWindow() {
+ return mActiveCastWindow;
+ }
+
+ /**
+ * Creates a new shell pointing to the specified URL.
+ * @param url The URL the shell should load upon creation.
+ * @return Pointer of native cast shell instance.
+ */
+ public long launchCastWindow(String url) {
+ return nativeLaunchCastWindow(url);
+ }
+
+ /**
+ * Stops a native cast shell instance created by {@link #launchCastWindow(String)}.
+ * @param nativeCastWindow Pointer of native cast shell instance returned
+ * by {@link #launchCastWindow(String)}.
+ * @param gracefully Whether or not to call RVH::ClosePage to deliver unload event.
+ * @see #launchCastWindow(String)
+ */
+ public void stopCastWindow(long nativeCastWindow, boolean gracefully) {
+ nativeStopCastWindow(nativeCastWindow, gracefully);
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private Object createCastWindow() {
+ assert mContentViewRenderView != null;
+ LayoutInflater inflater =
+ (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ CastWindowAndroid shellView =
+ (CastWindowAndroid) inflater.inflate(R.layout.cast_window_view, null);
+ shellView.setWindow(mWindow);
+
+ if (mActiveCastWindow != null) closeCastWindow(mActiveCastWindow);
+
+ shellView.setContentViewRenderView(mContentViewRenderView);
+ addView(shellView, new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+ mActiveCastWindow = shellView;
+ ContentViewCore contentViewCore = mActiveCastWindow.getContentViewCore();
+ if (contentViewCore != null) {
+ mContentViewRenderView.setCurrentContentViewCore(contentViewCore);
+ contentViewCore.onShow();
+ }
+
+ if (mDelegate != null) {
+ mDelegate.onCreated();
+ }
+
+ return shellView;
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void closeCastWindow(CastWindowAndroid shellView) {
+ if (shellView == mActiveCastWindow) mActiveCastWindow = null;
+ ContentViewCore contentViewCore = shellView.getContentViewCore();
+ if (contentViewCore != null) contentViewCore.onHide();
+ shellView.setContentViewRenderView(null);
+ shellView.setWindow(null);
+ removeView(shellView);
+
+ if (mDelegate != null) {
+ mDelegate.onClosed();
+ }
+ }
+
+ private static native void nativeInit(Object shellManagerInstance);
+ private static native long nativeLaunchCastWindow(String url);
+ private static native void nativeStopCastWindow(long pointerOfNativeCastWindow,
+ boolean gracefully);
+ public static native void nativeEnableDevTools(boolean enable);
+}
diff --git a/chromecast/shell/app/DEPS b/chromecast/shell/app/DEPS
index 7c1793ed98..efc610d592 100644
--- a/chromecast/shell/app/DEPS
+++ b/chromecast/shell/app/DEPS
@@ -1,3 +1,4 @@
include_rules = [
"+content/public/app",
+ "+content/public/browser",
]
diff --git a/chromecast/shell/app/android/cast_jni_loader.cc b/chromecast/shell/app/android/cast_jni_loader.cc
new file mode 100644
index 0000000000..4cbf8f8e15
--- /dev/null
+++ b/chromecast/shell/app/android/cast_jni_loader.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "base/basictypes.h"
+#include "base/debug/debugger.h"
+#include "base/logging.h"
+#include "chromecast/android/cast_jni_registrar.h"
+#include "chromecast/android/platform_jni_loader.h"
+#include "chromecast/shell/app/cast_main_delegate.h"
+#include "content/public/app/android_library_loader_hooks.h"
+#include "content/public/app/content_main.h"
+#include "content/public/browser/android/compositor.h"
+
+// This is called by the VM when the shared library is first loaded.
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ base::android::SetLibraryLoadedHook(&content::LibraryLoaded);
+ base::android::InitVM(vm);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ if (!base::android::RegisterLibraryLoaderEntryHook(env)) return -1;
+
+ // To be called only from the UI thread. If loading the library is done on
+ // a separate thread, this should be moved elsewhere.
+ if (!chromecast::android::RegisterJni(env)) return -1;
+ // Allow platform-specific implementations to perform more JNI registration.
+ if (!chromecast::android::PlatformRegisterJni(env)) return -1;
+
+ content::Compositor::Initialize();
+ content::SetContentMainDelegate(new chromecast::shell::CastMainDelegate);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/chromecast/shell/app/cast_main_delegate.cc b/chromecast/shell/app/cast_main_delegate.cc
index 1fb2ad773a..719cc2b719 100644
--- a/chromecast/shell/app/cast_main_delegate.cc
+++ b/chromecast/shell/app/cast_main_delegate.cc
@@ -4,12 +4,16 @@
#include "chromecast/shell/app/cast_main_delegate.h"
+#include "base/cpu.h"
#include "base/logging.h"
#include "base/path_service.h"
+#include "base/posix/global_descriptors.h"
#include "chromecast/common/cast_paths.h"
#include "chromecast/common/cast_resource_delegate.h"
+#include "chromecast/common/global_descriptors.h"
#include "chromecast/shell/browser/cast_content_browser_client.h"
#include "chromecast/shell/renderer/cast_content_renderer_client.h"
+#include "content/public/browser/browser_main_runner.h"
#include "content/public/common/content_switches.h"
#include "ui/base/resource/resource_bundle.h"
@@ -23,26 +27,74 @@ CastMainDelegate::~CastMainDelegate() {
}
bool CastMainDelegate::BasicStartupComplete(int* exit_code) {
+ RegisterPathProvider();
+
logging::LoggingSettings settings;
+#if defined(OS_ANDROID)
+ base::FilePath log_file;
+ PathService::Get(FILE_CAST_ANDROID_LOG, &log_file);
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = log_file.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+#else
settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+#endif // defined(OS_ANDROID)
logging::InitLogging(settings);
// Time, process, and thread ID are available through logcat.
logging::SetLogItems(true, true, false, false);
- RegisterPathProvider();
-
content::SetContentClient(&content_client_);
return false;
}
void CastMainDelegate::PreSandboxStartup() {
+#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
+ // Create an instance of the CPU class to parse /proc/cpuinfo and cache the
+ // results. This data needs to be cached when file-reading is still allowed,
+ // since base::CPU expects to be callable later, when file-reading is no
+ // longer allowed.
+ base::CPU cpu_info;
+#endif
+
InitializeResourceBundle();
}
+int CastMainDelegate::RunProcess(
+ const std::string& process_type,
+ const content::MainFunctionParams& main_function_params) {
+#if defined(OS_ANDROID)
+ if (!process_type.empty())
+ return -1;
+
+ // Note: Android must handle running its own browser process.
+ // See ChromeMainDelegateAndroid::RunProcess.
+ browser_runner_.reset(content::BrowserMainRunner::Create());
+ return browser_runner_->Initialize(main_function_params);
+#else
+ return -1;
+#endif // defined(OS_ANDROID)
+}
+
+#if !defined(OS_ANDROID)
void CastMainDelegate::ZygoteForked() {
}
+#endif // !defined(OS_ANDROID)
void CastMainDelegate::InitializeResourceBundle() {
+#if defined(OS_ANDROID)
+ // On Android, the renderer runs with a different UID and can never access
+ // the file system. Use the file descriptor passed in at launch time.
+ int pak_fd =
+ base::GlobalDescriptors::GetInstance()->MaybeGet(kAndroidPakDescriptor);
+ if (pak_fd >= 0) {
+ ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
+ base::File(pak_fd), base::MemoryMappedFile::Region::kWholeFile);
+ ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile(
+ base::File(pak_fd), ui::SCALE_FACTOR_100P);
+ return;
+ }
+#endif // defined(OS_ANDROID)
+
resource_delegate_.reset(new CastResourceDelegate());
// TODO(gunsch): Use LOAD_COMMON_RESOURCES once ResourceBundle no longer
// hardcodes resource file names.
diff --git a/chromecast/shell/app/cast_main_delegate.h b/chromecast/shell/app/cast_main_delegate.h
index 8c9ed620dd..424095302c 100644
--- a/chromecast/shell/app/cast_main_delegate.h
+++ b/chromecast/shell/app/cast_main_delegate.h
@@ -10,6 +10,10 @@
#include "chromecast/shell/common/cast_content_client.h"
#include "content/public/app/content_main_delegate.h"
+namespace content {
+class BrowserMainRunner;
+} // namespace content
+
namespace chromecast {
class CastResourceDelegate;
@@ -27,7 +31,12 @@ class CastMainDelegate : public content::ContentMainDelegate {
// content::ContentMainDelegate implementation:
virtual bool BasicStartupComplete(int* exit_code) OVERRIDE;
virtual void PreSandboxStartup() OVERRIDE;
+ virtual int RunProcess(
+ const std::string& process_type,
+ const content::MainFunctionParams& main_function_params) OVERRIDE;
+#if !defined(OS_ANDROID)
virtual void ZygoteForked() OVERRIDE;
+#endif // !defined(OS_ANDROID)
virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE;
virtual content::ContentRendererClient*
CreateContentRendererClient() OVERRIDE;
@@ -40,6 +49,10 @@ class CastMainDelegate : public content::ContentMainDelegate {
scoped_ptr<CastResourceDelegate> resource_delegate_;
CastContentClient content_client_;
+#if defined(OS_ANDROID)
+ scoped_ptr<content::BrowserMainRunner> browser_runner_;
+#endif // defined(OS_ANDROID)
+
DISALLOW_COPY_AND_ASSIGN(CastMainDelegate);
};
diff --git a/chromecast/shell/browser/android/cast_window_android.cc b/chromecast/shell/browser/android/cast_window_android.cc
new file mode 100644
index 0000000000..4b68cb95d3
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_android.cc
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/android/cast_window_android.h"
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+#include "content/public/browser/devtools_agent_host.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/common/renderer_preferences.h"
+#include "jni/CastWindowAndroid_jni.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+// The time (in milliseconds) we wait for after a page is closed (i.e.
+// after an app is stopped) before we delete the corresponding WebContents.
+const int kWebContentsDestructionDelayInMs = 50;
+
+} // namespace
+
+// static
+bool CastWindowAndroid::RegisterJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+CastWindowAndroid::CastWindowAndroid(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents),
+ weak_factory_(this) {
+}
+
+CastWindowAndroid::~CastWindowAndroid() {
+}
+
+// static
+CastWindowAndroid* CastWindowAndroid::CreateNewWindow(
+ content::BrowserContext* browser_context,
+ const GURL& url) {
+ content::WebContents::CreateParams create_params(browser_context);
+ create_params.routing_id = MSG_ROUTING_NONE;
+ content::WebContents* web_contents =
+ content::WebContents::Create(create_params);
+ CastWindowAndroid* shell = CreateCastWindowAndroid(
+ web_contents,
+ create_params.initial_size);
+ if (!url.is_empty())
+ shell->LoadURL(url);
+ return shell;
+}
+
+// static
+CastWindowAndroid* CastWindowAndroid::CreateCastWindowAndroid(
+ content::WebContents* web_contents,
+ const gfx::Size& initial_size) {
+ CastWindowAndroid* shell = new CastWindowAndroid(web_contents);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ base::android::ScopedJavaLocalRef<jobject> shell_android(
+ CreateCastWindowView(shell));
+
+ shell->java_object_.Reset(env, shell_android.Release());
+ shell->web_contents_.reset(web_contents);
+ web_contents->SetDelegate(shell);
+
+ Java_CastWindowAndroid_initFromNativeWebContents(
+ env, shell->java_object_.obj(), reinterpret_cast<jint>(web_contents));
+
+ // Enabling hole-punching also requires runtime renderer preference
+ web_contents->GetMutableRendererPrefs()->
+ use_video_overlay_for_embedded_encrypted_video = true;
+ web_contents->GetRenderViewHost()->SyncRendererPrefs();
+
+ return shell;
+}
+
+void CastWindowAndroid::Close() {
+ // Close page first, which fires the window.unload event. The WebContents
+ // itself will be destroyed after browser-process has received renderer
+ // notification that the page is closed.
+ web_contents_->GetRenderViewHost()->ClosePage();
+}
+
+void CastWindowAndroid::Destroy() {
+ // Note: if multiple windows becomes supported, this may close other devtools
+ // sessions.
+ content::DevToolsAgentHost::DetachAllClients();
+ CloseCastWindowView(java_object_.obj());
+ delete this;
+}
+
+void CastWindowAndroid::LoadURL(const GURL& url) {
+ content::NavigationController::LoadURLParams params(url);
+ params.transition_type = ui::PageTransitionFromInt(
+ ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+ web_contents_->GetController().LoadURLWithParams(params);
+ web_contents_->Focus();
+}
+
+void CastWindowAndroid::AddNewContents(content::WebContents* source,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture,
+ bool* was_blocked) {
+ NOTIMPLEMENTED();
+ if (was_blocked) {
+ *was_blocked = true;
+ }
+}
+
+void CastWindowAndroid::CloseContents(content::WebContents* source) {
+ DCHECK_EQ(source, web_contents_.get());
+
+ // We need to delay the deletion of web_contents_ (currently for 50ms) to
+ // give (and guarantee) the renderer enough time to finish 'onunload'
+ // handler (but we don't want to wait any longer than that to delay the
+ // starting of next app).
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
+ // When shutting down in a test context, the last remaining WebContents
+ // is torn down at browser-thread shutdown time. Call Destroy directly to
+ // avoid losing the last posted task to delete this object.
+ // TODO(gunsch): This could probably be avoided by using a
+ // CompletionCallback in StopCurrentApp to wait until the app is completely
+ // stopped. This might require a separate message loop and might only be
+ // appropriate for test contexts or during shutdown, since it triggers a
+ // wait on the main thread.
+ Destroy();
+ return;
+ }
+
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&CastWindowAndroid::Destroy, weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kWebContentsDestructionDelayInMs));
+}
+
+bool CastWindowAndroid::CanOverscrollContent() const {
+ return false;
+}
+
+bool CastWindowAndroid::AddMessageToConsole(content::WebContents* source,
+ int32 level,
+ const base::string16& message,
+ int32 line_no,
+ const base::string16& source_id) {
+ return false;
+}
+
+void CastWindowAndroid::ActivateContents(content::WebContents* contents) {
+ DCHECK_EQ(contents, web_contents_.get());
+ contents->GetRenderViewHost()->Focus();
+}
+
+void CastWindowAndroid::DeactivateContents(content::WebContents* contents) {
+ DCHECK_EQ(contents, web_contents_.get());
+ contents->GetRenderViewHost()->Blur();
+}
+
+void CastWindowAndroid::RenderProcessGone(base::TerminationStatus status) {
+ LOG(ERROR) << "Render process gone: status=" << status;
+ Destroy();
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/browser/android/cast_window_android.h b/chromecast/shell/browser/android/cast_window_android.h
new file mode 100644
index 0000000000..bcb5c1f7ef
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_android.h
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
+#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
+
+#include <jni.h>
+#include <vector>
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "build/build_config.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/size.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+class SiteInstance;
+class WebContents;
+} // namespace content
+
+namespace chromecast {
+namespace shell {
+
+class CastWindowAndroid : public content::WebContentsDelegate,
+ public content::WebContentsObserver {
+ public:
+ // Creates a new window and immediately loads the given URL.
+ static CastWindowAndroid* CreateNewWindow(
+ content::BrowserContext* browser_context,
+ const GURL& url);
+
+ virtual ~CastWindowAndroid();
+
+ void LoadURL(const GURL& url);
+ // Calls RVH::ClosePage() and waits for acknowledgement before closing/
+ // deleting the window.
+ void Close();
+ // Destroys this window immediately.
+ void Destroy();
+
+ // Registers the JNI methods for CastWindowAndroid.
+ static bool RegisterJni(JNIEnv* env);
+
+ // content::WebContentsDelegate implementation:
+ virtual void AddNewContents(content::WebContents* source,
+ content::WebContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture,
+ bool* was_blocked) OVERRIDE;
+ virtual void CloseContents(content::WebContents* source) OVERRIDE;
+ virtual bool CanOverscrollContent() const OVERRIDE;
+ virtual bool AddMessageToConsole(content::WebContents* source,
+ int32 level,
+ const base::string16& message,
+ int32 line_no,
+ const base::string16& source_id) OVERRIDE;
+ virtual void ActivateContents(content::WebContents* contents) OVERRIDE;
+ virtual void DeactivateContents(content::WebContents* contents) OVERRIDE;
+
+ // content::WebContentsObserver implementation:
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+
+ private:
+ explicit CastWindowAndroid(content::WebContents* web_contents);
+
+ // Helper to create a new CastWindowAndroid given a newly created WebContents.
+ static CastWindowAndroid* CreateCastWindowAndroid(
+ content::WebContents* web_contents,
+ const gfx::Size& initial_size);
+
+ base::android::ScopedJavaGlobalRef<jobject> java_object_;
+ scoped_ptr<content::WebContents> web_contents_;
+
+ base::WeakPtrFactory<CastWindowAndroid> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastWindowAndroid);
+};
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_
diff --git a/chromecast/shell/browser/android/cast_window_manager.cc b/chromecast/shell/browser/android/cast_window_manager.cc
new file mode 100644
index 0000000000..481eb4bbff
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_manager.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/android/cast_window_manager.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "chromecast/common/chromecast_config.h"
+#include "chromecast/common/pref_names.h"
+#include "chromecast/shell/browser/android/cast_window_android.h"
+#include "chromecast/shell/browser/cast_browser_context.h"
+#include "chromecast/shell/browser/cast_browser_main_parts.h"
+#include "chromecast/shell/browser/cast_browser_process.h"
+#include "chromecast/shell/browser/cast_content_browser_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "ipc/ipc_channel.h"
+#include "jni/CastWindowManager_jni.h"
+#include "url/gurl.h"
+
+namespace {
+
+base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >
+ g_window_manager = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace chromecast {
+namespace shell {
+
+base::android::ScopedJavaLocalRef<jobject>
+CreateCastWindowView(CastWindowAndroid* shell) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ jobject j_window_manager = g_window_manager.Get().obj();
+ return Java_CastWindowManager_createCastWindow(env, j_window_manager);
+}
+
+void CloseCastWindowView(jobject shell_wrapper) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ jobject j_window_manager = g_window_manager.Get().obj();
+ Java_CastWindowManager_closeCastWindow(env, j_window_manager, shell_wrapper);
+}
+
+// Register native methods
+bool RegisterCastWindowManager(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void Init(JNIEnv* env, jclass clazz, jobject obj) {
+ g_window_manager.Get().Reset(
+ base::android::ScopedJavaLocalRef<jobject>(env, obj));
+}
+
+jlong LaunchCastWindow(JNIEnv* env, jclass clazz, jstring jurl) {
+ GURL url(base::android::ConvertJavaStringToUTF8(env, jurl));
+ return reinterpret_cast<jlong>(
+ CastWindowAndroid::CreateNewWindow(
+ CastBrowserProcess::GetInstance()->browser_context(),
+ url));
+}
+
+void StopCastWindow(JNIEnv* env, jclass clazz,
+ jlong nativeCastWindow, jboolean gracefully) {
+ CastWindowAndroid* window =
+ reinterpret_cast<CastWindowAndroid*>(nativeCastWindow);
+ DCHECK(window);
+ if (gracefully)
+ window->Close();
+ else
+ window->Destroy();
+}
+
+void EnableDevTools(JNIEnv* env, jclass clazz, jboolean enable) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ // The specific port value doesn't matter since Android uses Unix domain
+ // sockets, only whether or not it is zero.
+ chromecast::ChromecastConfig::GetInstance()->pref_service()->
+ SetInteger(prefs::kRemoteDebuggingPort, enable ? 1 : 0);
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/browser/android/cast_window_manager.h b/chromecast/shell/browser/android/cast_window_manager.h
new file mode 100644
index 0000000000..a7c2e0a918
--- /dev/null
+++ b/chromecast/shell/browser/android/cast_window_manager.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
+#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+
+class CastWindowAndroid;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace chromecast {
+namespace shell {
+
+// Given a CastWindowAndroid instance, creates and returns a Java wrapper.
+base::android::ScopedJavaLocalRef<jobject>
+CreateCastWindowView(CastWindowAndroid* shell);
+
+// Closes a previously created Java wrapper.
+void CloseCastWindowView(jobject shell_wrapper);
+
+// Registers the CastWindowManager native methods.
+bool RegisterCastWindowManager(JNIEnv* env);
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_
diff --git a/chromecast/shell/browser/cast_browser_context.cc b/chromecast/shell/browser/cast_browser_context.cc
index 295c34b8d4..b0280f67a3 100644
--- a/chromecast/shell/browser/cast_browser_context.cc
+++ b/chromecast/shell/browser/cast_browser_context.cc
@@ -5,9 +5,11 @@
#include "chromecast/shell/browser/cast_browser_context.h"
#include "base/command_line.h"
+#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "chromecast/common/cast_paths.h"
+#include "chromecast/shell/browser/cast_download_manager_delegate.h"
#include "chromecast/shell/browser/url_request_context_factory.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_context.h"
@@ -22,7 +24,8 @@ namespace shell {
class CastBrowserContext::CastResourceContext :
public content::ResourceContext {
public:
- CastResourceContext(URLRequestContextFactory* url_request_context_factory) :
+ explicit CastResourceContext(
+ URLRequestContextFactory* url_request_context_factory) :
url_request_context_factory_(url_request_context_factory) {}
virtual ~CastResourceContext() {}
@@ -37,14 +40,6 @@ class CastBrowserContext::CastResourceContext :
GetURLRequestContext();
}
- virtual bool AllowMicAccess(const GURL& origin) OVERRIDE {
- return false;
- }
-
- virtual bool AllowCameraAccess(const GURL& origin) OVERRIDE {
- return false;
- }
-
private:
URLRequestContextFactory* url_request_context_factory_;
@@ -54,19 +49,32 @@ class CastBrowserContext::CastResourceContext :
CastBrowserContext::CastBrowserContext(
URLRequestContextFactory* url_request_context_factory)
: url_request_context_factory_(url_request_context_factory),
- resource_context_(new CastResourceContext(url_request_context_factory)) {
+ resource_context_(new CastResourceContext(url_request_context_factory)),
+ download_manager_delegate_(new CastDownloadManagerDelegate()) {
InitWhileIOAllowed();
}
CastBrowserContext::~CastBrowserContext() {
+ content::BrowserThread::DeleteSoon(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ resource_context_.release());
}
void CastBrowserContext::InitWhileIOAllowed() {
+#if defined(OS_ANDROID)
+ CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &path_));
+ path_ = path_.Append(FILE_PATH_LITERAL("cast_shell"));
+
+ if (!base::PathExists(path_))
+ base::CreateDirectory(path_);
+#else
// Chromecast doesn't support user profiles nor does it have
// incognito mode. This means that all of the persistent
// data (currently only cookies and local storage) will be
// shared in a single location as defined here.
CHECK(PathService::Get(DIR_CAST_HOME, &path_));
+#endif // defined(OS_ANDROID)
}
base::FilePath CastBrowserContext::GetPath() const {
@@ -108,8 +116,7 @@ content::ResourceContext* CastBrowserContext::GetResourceContext() {
content::DownloadManagerDelegate*
CastBrowserContext::GetDownloadManagerDelegate() {
- NOTIMPLEMENTED();
- return NULL;
+ return download_manager_delegate_.get();
}
content::BrowserPluginGuestManager* CastBrowserContext::GetGuestManager() {
diff --git a/chromecast/shell/browser/cast_browser_context.h b/chromecast/shell/browser/cast_browser_context.h
index 1f124a5d48..527e7e2eac 100644
--- a/chromecast/shell/browser/cast_browser_context.h
+++ b/chromecast/shell/browser/cast_browser_context.h
@@ -13,6 +13,7 @@
namespace chromecast {
namespace shell {
+class CastDownloadManagerDelegate;
class URLRequestContextFactory;
// Chromecast does not currently support multiple profiles. So there is a
@@ -55,6 +56,7 @@ class CastBrowserContext : public content::BrowserContext {
URLRequestContextFactory* const url_request_context_factory_;
base::FilePath path_;
scoped_ptr<CastResourceContext> resource_context_;
+ scoped_ptr<CastDownloadManagerDelegate> download_manager_delegate_;
DISALLOW_COPY_AND_ASSIGN(CastBrowserContext);
};
diff --git a/chromecast/shell/browser/cast_browser_main_parts.cc b/chromecast/shell/browser/cast_browser_main_parts.cc
index 72c75440b4..0fe3a41290 100644
--- a/chromecast/shell/browser/cast_browser_main_parts.cc
+++ b/chromecast/shell/browser/cast_browser_main_parts.cc
@@ -5,17 +5,25 @@
#include "chromecast/shell/browser/cast_browser_main_parts.h"
#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_registry_simple.h"
#include "chromecast/common/chromecast_config.h"
+#include "chromecast/metrics/cast_metrics_service_client.h"
#include "chromecast/net/network_change_notifier_cast.h"
#include "chromecast/net/network_change_notifier_factory_cast.h"
#include "chromecast/service/cast_service.h"
#include "chromecast/shell/browser/cast_browser_context.h"
+#include "chromecast/shell/browser/cast_browser_process.h"
#include "chromecast/shell/browser/devtools/remote_debugging_server.h"
#include "chromecast/shell/browser/url_request_context_factory.h"
#include "chromecast/shell/browser/webui/webui_cast.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
+#if defined(OS_ANDROID)
+#include "net/android/network_change_notifier_factory_android.h"
+#endif // defined(OS_ANDROID)
+
namespace chromecast {
namespace shell {
@@ -29,6 +37,8 @@ struct DefaultCommandLineSwitch {
DefaultCommandLineSwitch g_default_switches[] = {
{ switches::kDisableApplicationCache, "" },
{ switches::kDisablePlugins, "" },
+ // Always enable HTMLMediaElement logs.
+ { switches::kBlinkPlatformLogChannels, "Media"},
{ NULL, NULL }, // Termination
};
@@ -48,6 +58,8 @@ CastBrowserMainParts::CastBrowserMainParts(
const content::MainFunctionParams& parameters,
URLRequestContextFactory* url_request_context_factory)
: BrowserMainParts(),
+ cast_browser_process_(new CastBrowserProcess()),
+ parameters_(parameters),
url_request_context_factory_(url_request_context_factory) {
CommandLine* command_line = CommandLine::ForCurrentProcess();
AddDefaultCommandLineSwitches(command_line);
@@ -57,12 +69,19 @@ CastBrowserMainParts::~CastBrowserMainParts() {
}
void CastBrowserMainParts::PreMainMessageLoopStart() {
+#if defined(OS_ANDROID)
+ net::NetworkChangeNotifier::SetFactory(
+ new net::NetworkChangeNotifierFactoryAndroid());
+#else
net::NetworkChangeNotifier::SetFactory(
new NetworkChangeNotifierFactoryCast());
+#endif // defined(OS_ANDROID)
}
void CastBrowserMainParts::PostMainMessageLoopStart() {
- NOTIMPLEMENTED();
+#if defined(OS_ANDROID)
+ base::MessageLoopForUI::current()->Start();
+#endif // defined(OS_ANDROID)
}
int CastBrowserMainParts::PreCreateThreads() {
@@ -73,26 +92,37 @@ int CastBrowserMainParts::PreCreateThreads() {
void CastBrowserMainParts::PreMainMessageLoopRun() {
url_request_context_factory_->InitializeOnUIThread();
- browser_context_.reset(new CastBrowserContext(url_request_context_factory_));
- dev_tools_.reset(new RemoteDebuggingServer());
+ cast_browser_process_->SetBrowserContext(
+ new CastBrowserContext(url_request_context_factory_));
+ cast_browser_process_->SetMetricsServiceClient(
+ metrics::CastMetricsServiceClient::Create(
+ content::BrowserThread::GetBlockingPool(),
+ ChromecastConfig::GetInstance()->pref_service(),
+ cast_browser_process_->browser_context()->GetRequestContext()));
+ cast_browser_process_->SetRemoteDebuggingServer(new RemoteDebuggingServer());
InitializeWebUI();
- cast_service_.reset(CastService::Create(browser_context_.get()));
- cast_service_->Start();
+ cast_browser_process_->SetCastService(
+ CastService::Create(cast_browser_process_->browser_context(),
+ url_request_context_factory_->GetSystemGetter()));
+ cast_browser_process_->cast_service()->Start();
}
bool CastBrowserMainParts::MainMessageLoopRun(int* result_code) {
- base::MessageLoopForUI::current()->Run();
+ // If parameters_.ui_task is not NULL, we are running browser tests. In this
+ // case, the browser's main message loop will not run.
+ if (parameters_.ui_task) {
+ parameters_.ui_task->Run();
+ } else {
+ base::MessageLoopForUI::current()->Run();
+ }
return true;
}
void CastBrowserMainParts::PostMainMessageLoopRun() {
- cast_service_->Stop();
-
- cast_service_.reset();
- dev_tools_.reset();
- browser_context_.reset();
+ cast_browser_process_->cast_service()->Stop();
+ cast_browser_process_.reset();
}
} // namespace shell
diff --git a/chromecast/shell/browser/cast_browser_main_parts.h b/chromecast/shell/browser/cast_browser_main_parts.h
index 988ceea92f..f1d695a066 100644
--- a/chromecast/shell/browser/cast_browser_main_parts.h
+++ b/chromecast/shell/browser/cast_browser_main_parts.h
@@ -9,19 +9,11 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "content/public/browser/browser_main_parts.h"
-
-namespace content {
-struct MainFunctionParams;
-}
+#include "content/public/common/main_function_params.h"
namespace chromecast {
-
-class CastService;
-
namespace shell {
-
-class CastBrowserContext;
-class RemoteDebuggingServer;
+class CastBrowserProcess;
class URLRequestContextFactory;
class CastBrowserMainParts : public content::BrowserMainParts {
@@ -39,14 +31,9 @@ class CastBrowserMainParts : public content::BrowserMainParts {
virtual bool MainMessageLoopRun(int* result_code) OVERRIDE;
virtual void PostMainMessageLoopRun() OVERRIDE;
- CastBrowserContext* browser_context() {
- return browser_context_.get();
- }
-
private:
- scoped_ptr<CastBrowserContext> browser_context_;
- scoped_ptr<CastService> cast_service_;
- scoped_ptr<RemoteDebuggingServer> dev_tools_;
+ scoped_ptr<CastBrowserProcess> cast_browser_process_;
+ const content::MainFunctionParams parameters_; // For running browser tests.
URLRequestContextFactory* const url_request_context_factory_;
DISALLOW_COPY_AND_ASSIGN(CastBrowserMainParts);
diff --git a/chromecast/shell/browser/cast_browser_process.cc b/chromecast/shell/browser/cast_browser_process.cc
new file mode 100644
index 0000000000..70802aac0b
--- /dev/null
+++ b/chromecast/shell/browser/cast_browser_process.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/cast_browser_process.h"
+
+#include "base/logging.h"
+#include "chromecast/metrics/cast_metrics_service_client.h"
+#include "chromecast/service/cast_service.h"
+#include "chromecast/shell/browser/cast_browser_context.h"
+#include "chromecast/shell/browser/devtools/remote_debugging_server.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+CastBrowserProcess* g_instance = NULL;
+} // namespace
+
+// static
+CastBrowserProcess* CastBrowserProcess::GetInstance() {
+ DCHECK(g_instance);
+ return g_instance;
+}
+
+CastBrowserProcess::CastBrowserProcess() {
+ DCHECK(!g_instance);
+ g_instance = this;
+}
+
+CastBrowserProcess::~CastBrowserProcess() {
+ DCHECK_EQ(g_instance, this);
+ g_instance = NULL;
+}
+
+void CastBrowserProcess::SetBrowserContext(
+ CastBrowserContext* browser_context) {
+ DCHECK(!browser_context_);
+ browser_context_.reset(browser_context);
+}
+
+void CastBrowserProcess::SetCastService(CastService* cast_service) {
+ DCHECK(!cast_service_);
+ cast_service_.reset(cast_service);
+}
+
+void CastBrowserProcess::SetRemoteDebuggingServer(
+ RemoteDebuggingServer* remote_debugging_server) {
+ DCHECK(!remote_debugging_server_);
+ remote_debugging_server_.reset(remote_debugging_server);
+}
+
+void CastBrowserProcess::SetMetricsServiceClient(
+ metrics::CastMetricsServiceClient* metrics_service_client) {
+ DCHECK(!metrics_service_client_);
+ metrics_service_client_.reset(metrics_service_client);
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/browser/cast_browser_process.h b/chromecast/shell/browser/cast_browser_process.h
new file mode 100644
index 0000000000..65830c2a3a
--- /dev/null
+++ b/chromecast/shell/browser/cast_browser_process.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_
+#define CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace breakpad {
+class CrashDumpManager;
+} // namespace breakpad
+
+namespace chromecast {
+class CastService;
+class WebCryptoServer;
+
+namespace metrics {
+class CastMetricsHelper;
+class CastMetricsServiceClient;
+} // namespace metrics
+
+namespace shell {
+class CastBrowserContext;
+class RemoteDebuggingServer;
+
+class CastBrowserProcess {
+ public:
+ // Gets the global instance of CastBrowserProcess. Does not create lazily and
+ // assumes the instance already exists.
+ static CastBrowserProcess* GetInstance();
+
+ CastBrowserProcess();
+ virtual ~CastBrowserProcess();
+
+ void SetBrowserContext(CastBrowserContext* browser_context);
+ void SetCastService(CastService* cast_service);
+ void SetRemoteDebuggingServer(RemoteDebuggingServer* remote_debugging_server);
+ void SetMetricsServiceClient(
+ metrics::CastMetricsServiceClient* metrics_service_client);
+
+ CastBrowserContext* browser_context() const { return browser_context_.get(); }
+ CastService* cast_service() const { return cast_service_.get(); }
+ metrics::CastMetricsServiceClient* metrics_service_client() const {
+ return metrics_service_client_.get();
+ }
+
+ private:
+ scoped_ptr<CastBrowserContext> browser_context_;
+ scoped_ptr<metrics::CastMetricsServiceClient> metrics_service_client_;
+ scoped_ptr<RemoteDebuggingServer> remote_debugging_server_;
+
+ // Note: CastService must be destroyed before others.
+ scoped_ptr<CastService> cast_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(CastBrowserProcess);
+};
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_
diff --git a/chromecast/shell/browser/cast_content_browser_client.cc b/chromecast/shell/browser/cast_content_browser_client.cc
index 720bbb82de..b2e6db72af 100644
--- a/chromecast/shell/browser/cast_content_browser_client.cc
+++ b/chromecast/shell/browser/cast_content_browser_client.cc
@@ -5,11 +5,19 @@
#include "chromecast/shell/browser/cast_content_browser_client.h"
#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "base/path_service.h"
+#include "chromecast/common/cast_paths.h"
+#include "chromecast/common/global_descriptors.h"
#include "chromecast/shell/browser/cast_browser_context.h"
#include "chromecast/shell/browser/cast_browser_main_parts.h"
+#include "chromecast/shell/browser/cast_browser_process.h"
+#include "chromecast/shell/browser/devtools/cast_dev_tools_delegate.h"
#include "chromecast/shell/browser/geolocation/cast_access_token_store.h"
#include "chromecast/shell/browser/url_request_context_factory.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/certificate_request_result_type.h"
+#include "content/public/browser/file_descriptor_info.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_descriptors.h"
#include "content/public/common/content_switches.h"
@@ -24,13 +32,16 @@ CastContentBrowserClient::CastContentBrowserClient()
}
CastContentBrowserClient::~CastContentBrowserClient() {
+ content::BrowserThread::DeleteSoon(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ url_request_context_factory_.release());
}
content::BrowserMainParts* CastContentBrowserClient::CreateBrowserMainParts(
const content::MainFunctionParams& parameters) {
- shell_browser_main_parts_ =
- new CastBrowserMainParts(parameters, url_request_context_factory_.get());
- return shell_browser_main_parts_;
+ return new CastBrowserMainParts(parameters,
+ url_request_context_factory_.get());
}
void CastContentBrowserClient::RenderProcessWillLaunch(
@@ -57,6 +68,9 @@ bool CastContentBrowserClient::IsHandledURL(const GURL& url) {
content::kChromeUIScheme,
content::kChromeDevToolsScheme,
url::kDataScheme,
+#if defined(OS_ANDROID)
+ url::kFileScheme,
+#endif // defined(OS_ANDROID)
};
const std::string& scheme = url.scheme();
@@ -81,7 +95,8 @@ void CastContentBrowserClient::AppendExtraCommandLineSwitches(
}
content::AccessTokenStore* CastContentBrowserClient::CreateAccessTokenStore() {
- return new CastAccessTokenStore(shell_browser_main_parts_->browser_context());
+ return new CastAccessTokenStore(
+ CastBrowserProcess::GetInstance()->browser_context());
}
void CastContentBrowserClient::OverrideWebkitPrefs(
@@ -97,7 +112,8 @@ void CastContentBrowserClient::OverrideWebkitPrefs(
}
std::string CastContentBrowserClient::GetApplicationLocale() {
- return "en-US";
+ const std::string locale(base::i18n::GetConfiguredLocale());
+ return locale.empty() ? "en-US" : locale;
}
void CastContentBrowserClient::AllowCertificateError(
@@ -137,10 +153,28 @@ bool CastContentBrowserClient::CanCreateWindow(
return false;
}
+content::DevToolsManagerDelegate*
+CastContentBrowserClient::GetDevToolsManagerDelegate() {
+ return new CastDevToolsManagerDelegate();
+}
+
void CastContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line,
int child_process_id,
std::vector<content::FileDescriptorInfo>* mappings) {
+#if defined(OS_ANDROID)
+ int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
+ base::FilePath pak_file;
+ CHECK(PathService::Get(FILE_CAST_PAK, &pak_file));
+ base::File pak_with_flags(pak_file, flags);
+ if (!pak_with_flags.IsValid()) {
+ NOTREACHED() << "Failed to open file when creating renderer process: "
+ << "cast_shell.pak";
+ }
+ mappings->push_back(content::FileDescriptorInfo(
+ kAndroidPakDescriptor,
+ base::FileDescriptor(base::File(pak_file, flags))));
+#endif // defined(OS_ANDROID)
}
} // namespace shell
diff --git a/chromecast/shell/browser/cast_content_browser_client.h b/chromecast/shell/browser/cast_content_browser_client.h
index 98504e3581..2bd8ef4d20 100644
--- a/chromecast/shell/browser/cast_content_browser_client.h
+++ b/chromecast/shell/browser/cast_content_browser_client.h
@@ -65,15 +65,14 @@ class CastContentBrowserClient: public content::ContentBrowserClient {
int render_process_id,
int opener_id,
bool* no_javascript_access) OVERRIDE;
+ virtual content::DevToolsManagerDelegate*
+ GetDevToolsManagerDelegate() OVERRIDE;
virtual void GetAdditionalMappedFilesForChildProcess(
const base::CommandLine& command_line,
int child_process_id,
std::vector<content::FileDescriptorInfo>* mappings) OVERRIDE;
private:
- // Note: BrowserMainLoop holds ownership of CastBrowserMainParts after it is
- // created.
- CastBrowserMainParts* shell_browser_main_parts_;
scoped_ptr<URLRequestContextFactory> url_request_context_factory_;
DISALLOW_COPY_AND_ASSIGN(CastContentBrowserClient);
diff --git a/chromecast/shell/browser/cast_download_manager_delegate.cc b/chromecast/shell/browser/cast_download_manager_delegate.cc
new file mode 100644
index 0000000000..edc35f0da5
--- /dev/null
+++ b/chromecast/shell/browser/cast_download_manager_delegate.cc
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/cast_download_manager_delegate.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "content/public/browser/download_danger_type.h"
+#include "content/public/browser/download_item.h"
+
+namespace chromecast {
+namespace shell {
+
+CastDownloadManagerDelegate::CastDownloadManagerDelegate() {}
+
+CastDownloadManagerDelegate::~CastDownloadManagerDelegate() {}
+
+void CastDownloadManagerDelegate::GetNextId(
+ const content::DownloadIdCallback& callback) {
+ // See default behavior of DownloadManagerImpl::GetNextId()
+ static uint32 next_id = content::DownloadItem::kInvalidId + 1;
+ callback.Run(next_id++);
+}
+
+bool CastDownloadManagerDelegate::DetermineDownloadTarget(
+ content::DownloadItem* item,
+ const content::DownloadTargetCallback& callback) {
+ // Running the DownloadTargetCallback with an empty FilePath signals
+ // that the download should be cancelled.
+ base::FilePath empty;
+ callback.Run(
+ empty,
+ content::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+ content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT,
+ empty);
+ return true;
+}
+
+bool CastDownloadManagerDelegate::ShouldOpenFileBasedOnExtension(
+ const base::FilePath& path) {
+ return false;
+}
+
+bool CastDownloadManagerDelegate::ShouldCompleteDownload(
+ content::DownloadItem* item,
+ const base::Closure& callback) {
+ return false;
+}
+
+bool CastDownloadManagerDelegate::ShouldOpenDownload(
+ content::DownloadItem* item,
+ const content::DownloadOpenDelayedCallback& callback) {
+ return false;
+}
+
+} // namespace shell
+} // namespace chromecast \ No newline at end of file
diff --git a/chromecast/shell/browser/cast_download_manager_delegate.h b/chromecast/shell/browser/cast_download_manager_delegate.h
new file mode 100644
index 0000000000..95ec81a9d5
--- /dev/null
+++ b/chromecast/shell/browser/cast_download_manager_delegate.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_
+#define CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "content/public/browser/download_manager_delegate.h"
+
+namespace chromecast {
+namespace shell {
+
+class CastDownloadManagerDelegate : public content::DownloadManagerDelegate {
+ public:
+ CastDownloadManagerDelegate();
+ virtual ~CastDownloadManagerDelegate();
+
+ // content::DownloadManagerDelegate implementation:
+ virtual void GetNextId(
+ const content::DownloadIdCallback& callback) OVERRIDE;
+ virtual bool DetermineDownloadTarget(
+ content::DownloadItem* item,
+ const content::DownloadTargetCallback& callback) OVERRIDE;
+ virtual bool ShouldOpenFileBasedOnExtension(
+ const base::FilePath& path) OVERRIDE;
+ virtual bool ShouldCompleteDownload(
+ content::DownloadItem* item,
+ const base::Closure& complete_callback) OVERRIDE;
+ virtual bool ShouldOpenDownload(
+ content::DownloadItem* item,
+ const content::DownloadOpenDelayedCallback& callback) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CastDownloadManagerDelegate);
+};
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_ \ No newline at end of file
diff --git a/chromecast/shell/browser/cast_http_user_agent_settings.cc b/chromecast/shell/browser/cast_http_user_agent_settings.cc
index fbc43b3916..e4a1537d77 100644
--- a/chromecast/shell/browser/cast_http_user_agent_settings.cc
+++ b/chromecast/shell/browser/cast_http_user_agent_settings.cc
@@ -11,6 +11,10 @@
#include "net/http/http_util.h"
#include "ui/base/l10n/l10n_util.h"
+#if defined(OS_ANDROID)
+#include "ui/base/l10n/l10n_util_android.h"
+#endif // defined(OS_ANDROID)
+
namespace chromecast {
namespace shell {
@@ -26,7 +30,12 @@ std::string CastHttpUserAgentSettings::GetAcceptLanguage() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (accept_language_.empty()) {
accept_language_ = net::HttpUtil::GenerateAcceptLanguageHeader(
- l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES));
+#if defined(OS_ANDROID)
+ l10n_util::GetDefaultLocale()
+#else
+ l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES)
+#endif // defined(OS_ANDROID)
+ );
}
return accept_language_;
}
diff --git a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc
index 77cfba1f3d..eaf526d194 100644
--- a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc
+++ b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc
@@ -23,17 +23,36 @@ namespace shell {
namespace {
const char kTargetTypePage[] = "page";
+const char kTargetTypeServiceWorker[] = "service_worker";
+const char kTargetTypeSharedWorker[] = "worker";
+const char kTargetTypeOther[] = "other";
class Target : public content::DevToolsTarget {
public:
- explicit Target(content::WebContents* web_contents);
+ explicit Target(scoped_refptr<content::DevToolsAgentHost> agent_host);
- virtual std::string GetId() const OVERRIDE { return id_; }
+ virtual std::string GetId() const OVERRIDE { return agent_host_->GetId(); }
virtual std::string GetParentId() const OVERRIDE { return std::string(); }
- virtual std::string GetType() const OVERRIDE { return kTargetTypePage; }
- virtual std::string GetTitle() const OVERRIDE { return title_; }
+ virtual std::string GetType() const OVERRIDE {
+ switch (agent_host_->GetType()) {
+ case content::DevToolsAgentHost::TYPE_WEB_CONTENTS:
+ return kTargetTypePage;
+ case content::DevToolsAgentHost::TYPE_SERVICE_WORKER:
+ return kTargetTypeServiceWorker;
+ case content::DevToolsAgentHost::TYPE_SHARED_WORKER:
+ return kTargetTypeSharedWorker;
+ default:
+ break;
+ }
+ return kTargetTypeOther;
+ }
+ virtual std::string GetTitle() const OVERRIDE {
+ return agent_host_->GetTitle();
+ }
virtual std::string GetDescription() const OVERRIDE { return std::string(); }
- virtual GURL GetURL() const OVERRIDE { return url_; }
+ virtual GURL GetURL() const OVERRIDE {
+ return agent_host_->GetURL();
+ }
virtual GURL GetFaviconURL() const OVERRIDE { return favicon_url_; }
virtual base::TimeTicks GetLastActivityTime() const OVERRIDE {
return last_activity_time_;
@@ -45,50 +64,36 @@ class Target : public content::DevToolsTarget {
const OVERRIDE {
return agent_host_;
}
- virtual bool Activate() const OVERRIDE;
- virtual bool Close() const OVERRIDE;
+ virtual bool Activate() const OVERRIDE {
+ return agent_host_->Activate();
+ }
+ virtual bool Close() const OVERRIDE {
+ return agent_host_->Close();
+ }
private:
scoped_refptr<content::DevToolsAgentHost> agent_host_;
- std::string id_;
- std::string title_;
- GURL url_;
GURL favicon_url_;
base::TimeTicks last_activity_time_;
DISALLOW_COPY_AND_ASSIGN(Target);
};
-Target::Target(content::WebContents* web_contents) {
- agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents);
- id_ = agent_host_->GetId();
- title_ = base::UTF16ToUTF8(web_contents->GetTitle());
- url_ = web_contents->GetURL();
- content::NavigationController& controller = web_contents->GetController();
- content::NavigationEntry* entry = controller.GetActiveEntry();
- if (entry != NULL && entry->GetURL().is_valid())
- favicon_url_ = entry->GetFavicon().url;
- last_activity_time_ = web_contents->GetLastActiveTime();
-}
-
-bool Target::Activate() const {
- content::WebContents* web_contents = agent_host_->GetWebContents();
- if (!web_contents)
- return false;
- web_contents->GetDelegate()->ActivateContents(web_contents);
- return true;
-}
-
-bool Target::Close() const {
- content::WebContents* web_contents = agent_host_->GetWebContents();
- if (!web_contents)
- return false;
- web_contents->GetRenderViewHost()->ClosePage();
- return true;
+Target::Target(scoped_refptr<content::DevToolsAgentHost> agent_host)
+ : agent_host_(agent_host) {
+ if (content::WebContents* web_contents = agent_host_->GetWebContents()) {
+ content::NavigationController& controller = web_contents->GetController();
+ content::NavigationEntry* entry = controller.GetActiveEntry();
+ if (entry != NULL && entry->GetURL().is_valid())
+ favicon_url_ = entry->GetFavicon().url;
+ last_activity_time_ = web_contents->GetLastActiveTime();
+ }
}
} // namespace
+// CastDevToolsDelegate -----------------------------------------------------
+
CastDevToolsDelegate::CastDevToolsDelegate() {
}
@@ -96,55 +101,59 @@ CastDevToolsDelegate::~CastDevToolsDelegate() {
}
std::string CastDevToolsDelegate::GetDiscoveryPageHTML() {
-#if defined(OS_ANDROID)
- return std::string();
-#else
return ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_CAST_SHELL_DEVTOOLS_DISCOVERY_PAGE).as_string();
-#endif // defined(OS_ANDROID)
}
bool CastDevToolsDelegate::BundlesFrontendResources() {
-#if defined(OS_ANDROID)
- // Since Android remote debugging connects over a Unix domain socket, Chrome
- // will not load the same homepage.
- return false;
-#else
return true;
-#endif // defined(OS_ANDROID)
}
base::FilePath CastDevToolsDelegate::GetDebugFrontendDir() {
return base::FilePath();
}
-std::string CastDevToolsDelegate::GetPageThumbnailData(const GURL& url) {
- return "";
+scoped_ptr<net::StreamListenSocket>
+CastDevToolsDelegate::CreateSocketForTethering(
+ net::StreamListenSocket::Delegate* delegate,
+ std::string* name) {
+ return scoped_ptr<net::StreamListenSocket>();
}
-scoped_ptr<content::DevToolsTarget> CastDevToolsDelegate::CreateNewTarget(
+// CastDevToolsManagerDelegate -----------------------------------------------
+
+CastDevToolsManagerDelegate::CastDevToolsManagerDelegate() {
+}
+
+CastDevToolsManagerDelegate::~CastDevToolsManagerDelegate() {
+}
+
+base::DictionaryValue* CastDevToolsManagerDelegate::HandleCommand(
+ content::DevToolsAgentHost* agent_host,
+ base::DictionaryValue* command) {
+ return NULL;
+}
+
+std::string CastDevToolsManagerDelegate::GetPageThumbnailData(
const GURL& url) {
+ return "";
+}
+
+scoped_ptr<content::DevToolsTarget>
+CastDevToolsManagerDelegate::CreateNewTarget(const GURL& url) {
return scoped_ptr<content::DevToolsTarget>();
}
-void CastDevToolsDelegate::EnumerateTargets(TargetCallback callback) {
+void CastDevToolsManagerDelegate::EnumerateTargets(TargetCallback callback) {
TargetList targets;
- std::vector<content::WebContents*> wc_list =
- content::DevToolsAgentHost::GetInspectableWebContents();
- for (std::vector<content::WebContents*>::iterator it = wc_list.begin();
- it != wc_list.end();
- ++it) {
+ content::DevToolsAgentHost::List agents =
+ content::DevToolsAgentHost::GetOrCreateAll();
+ for (content::DevToolsAgentHost::List::iterator it = agents.begin();
+ it != agents.end(); ++it) {
targets.push_back(new Target(*it));
}
callback.Run(targets);
}
-scoped_ptr<net::StreamListenSocket>
-CastDevToolsDelegate::CreateSocketForTethering(
- net::StreamListenSocket::Delegate* delegate,
- std::string* name) {
- return scoped_ptr<net::StreamListenSocket>();
-}
-
} // namespace shell
} // namespace chromecast
diff --git a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h
index e41e1a85bd..c180159105 100644
--- a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h
+++ b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h
@@ -6,12 +6,17 @@
#define CHROMECAST_SHELL_BROWSER_DEVTOOLS_CAST_DEV_TOOLS_DELEGATE_H_
#include "content/public/browser/devtools_http_handler_delegate.h"
+#include "content/public/browser/devtools_manager_delegate.h"
#include "net/socket/stream_listen_socket.h"
namespace base {
class FilePath;
}
+namespace content {
+class BrowserContext;
+}
+
namespace chromecast {
namespace shell {
@@ -24,10 +29,6 @@ class CastDevToolsDelegate : public content::DevToolsHttpHandlerDelegate {
virtual std::string GetDiscoveryPageHTML() OVERRIDE;
virtual bool BundlesFrontendResources() OVERRIDE;
virtual base::FilePath GetDebugFrontendDir() OVERRIDE;
- virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE;
- virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget(
- const GURL& url) OVERRIDE;
- virtual void EnumerateTargets(TargetCallback callback) OVERRIDE;
virtual scoped_ptr<net::StreamListenSocket> CreateSocketForTethering(
net::StreamListenSocket::Delegate* delegate,
std::string* name) OVERRIDE;
@@ -36,6 +37,30 @@ class CastDevToolsDelegate : public content::DevToolsHttpHandlerDelegate {
DISALLOW_COPY_AND_ASSIGN(CastDevToolsDelegate);
};
+class CastDevToolsManagerDelegate : public content::DevToolsManagerDelegate {
+ public:
+ CastDevToolsManagerDelegate();
+ virtual ~CastDevToolsManagerDelegate();
+
+ // DevToolsManagerDelegate implementation.
+ virtual void Inspect(
+ content::BrowserContext* browser_context,
+ content::DevToolsAgentHost* agent_host) OVERRIDE {}
+ virtual void DevToolsAgentStateChanged(
+ content::DevToolsAgentHost* agent_host,
+ bool attached) OVERRIDE {}
+ virtual base::DictionaryValue* HandleCommand(
+ content::DevToolsAgentHost* agent_host,
+ base::DictionaryValue* command) OVERRIDE;
+ virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget(
+ const GURL& url) OVERRIDE;
+ virtual void EnumerateTargets(TargetCallback callback) OVERRIDE;
+ virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CastDevToolsManagerDelegate);
+};
+
} // namespace shell
} // namespace chromecast
diff --git a/chromecast/shell/browser/devtools/remote_debugging_server.cc b/chromecast/shell/browser/devtools/remote_debugging_server.cc
index 076b06654e..f34af1e5a8 100644
--- a/chromecast/shell/browser/devtools/remote_debugging_server.cc
+++ b/chromecast/shell/browser/devtools/remote_debugging_server.cc
@@ -17,11 +17,11 @@
#include "content/public/browser/devtools_http_handler.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/user_agent.h"
-#include "net/socket/tcp_listen_socket.h"
+#include "net/socket/tcp_server_socket.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/devtools_auth.h"
-#include "net/socket/unix_domain_socket_posix.h"
+#include "net/socket/unix_domain_server_socket_posix.h"
#endif // defined(OS_ANDROID)
namespace chromecast {
@@ -29,13 +29,47 @@ namespace shell {
namespace {
-#if defined(OS_ANDROID)
-const char kFrontEndURL[] =
- "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html";
-#endif // defined(OS_ANDROID)
const int kDefaultRemoteDebuggingPort = 9222;
-net::StreamListenSocketFactory* CreateSocketFactory(int port) {
+#if defined(OS_ANDROID)
+class UnixDomainServerSocketFactory
+ : public content::DevToolsHttpHandler::ServerSocketFactory {
+ public:
+ explicit UnixDomainServerSocketFactory(const std::string& socket_name)
+ : content::DevToolsHttpHandler::ServerSocketFactory(socket_name, 0, 1) {}
+
+ private:
+ // content::DevToolsHttpHandler::ServerSocketFactory.
+ virtual scoped_ptr<net::ServerSocket> Create() const OVERRIDE {
+ return scoped_ptr<net::ServerSocket>(
+ new net::UnixDomainServerSocket(
+ base::Bind(&content::CanUserConnectToDevTools),
+ true /* use_abstract_namespace */));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory);
+};
+#else
+class TCPServerSocketFactory
+ : public content::DevToolsHttpHandler::ServerSocketFactory {
+ public:
+ TCPServerSocketFactory(const std::string& address, int port, int backlog)
+ : content::DevToolsHttpHandler::ServerSocketFactory(
+ address, port, backlog) {}
+
+ private:
+ // content::DevToolsHttpHandler::ServerSocketFactory.
+ virtual scoped_ptr<net::ServerSocket> Create() const OVERRIDE {
+ return scoped_ptr<net::ServerSocket>(
+ new net::TCPServerSocket(NULL, net::NetLog::Source()));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory);
+};
+#endif
+
+scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory>
+CreateSocketFactory(int port) {
#if defined(OS_ANDROID)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
std::string socket_name = "content_shell_devtools_remote";
@@ -43,19 +77,12 @@ net::StreamListenSocketFactory* CreateSocketFactory(int port) {
socket_name = command_line->GetSwitchValueASCII(
switches::kRemoteDebuggingSocketName);
}
- return new net::UnixDomainSocketWithAbstractNamespaceFactory(
- socket_name, "", base::Bind(&content::CanUserConnectToDevTools));
+ return scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory>(
+ new UnixDomainServerSocketFactory(socket_name));
#else
- return new net::TCPListenSocketFactory("0.0.0.0", port);
-#endif // defined(OS_ANDROID)
-}
-
-std::string GetFrontendUrl() {
-#if defined(OS_ANDROID)
- return base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str());
-#else
- return std::string();
-#endif // defined(OS_ANDROID)
+ return scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory>(
+ new TCPServerSocketFactory("0.0.0.0", port, 1));
+#endif
}
} // namespace
@@ -105,7 +132,7 @@ void RemoteDebuggingServer::OnPortChanged() {
if (port_ > 0) {
devtools_http_handler_ = content::DevToolsHttpHandler::Start(
CreateSocketFactory(port_),
- GetFrontendUrl(),
+ std::string(),
new CastDevToolsDelegate(),
base::FilePath());
LOG(INFO) << "Devtools started: port=" << port_;
diff --git a/chromecast/shell/browser/test/DEPS b/chromecast/shell/browser/test/DEPS
new file mode 100644
index 0000000000..b969a40c30
--- /dev/null
+++ b/chromecast/shell/browser/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content/public/test",
+]
diff --git a/chromecast/shell/browser/test/OWNERS b/chromecast/shell/browser/test/OWNERS
new file mode 100644
index 0000000000..23be33d4fc
--- /dev/null
+++ b/chromecast/shell/browser/test/OWNERS
@@ -0,0 +1,4 @@
+# content/public/test OWNERS for review of Chromecast browser test code
+jcivelli@chromium.org
+phajdan.jr@chromium.org
+sky@chromium.org
diff --git a/chromecast/shell/browser/test/chromecast_browser_test.cc b/chromecast/shell/browser/test/chromecast_browser_test.cc
new file mode 100644
index 0000000000..19250751e5
--- /dev/null
+++ b/chromecast/shell/browser/test/chromecast_browser_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/browser/test/chromecast_browser_test.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "chromecast/shell/browser/cast_browser_context.h"
+#include "chromecast/shell/browser/cast_browser_process.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+
+namespace chromecast {
+namespace shell {
+
+ChromecastBrowserTest::ChromecastBrowserTest()
+ : setup_called_(false) {
+}
+
+ChromecastBrowserTest::~ChromecastBrowserTest() {
+ CHECK(setup_called_) << "Overridden SetUp() did not call parent "
+ << "implementation, so test not run.";
+}
+
+void ChromecastBrowserTest::SetUp() {
+ SetUpCommandLine(CommandLine::ForCurrentProcess());
+ setup_called_ = true;
+ BrowserTestBase::SetUp();
+}
+
+void ChromecastBrowserTest::RunTestOnMainThreadLoop() {
+ // Pump startup related events.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ base::RunLoop().RunUntilIdle();
+
+ SetUpOnMainThread();
+
+ RunTestOnMainThread();
+
+ TearDownOnMainThread();
+
+ for (content::RenderProcessHost::iterator i(
+ content::RenderProcessHost::AllHostsIterator());
+ !i.IsAtEnd(); i.Advance()) {
+ i.GetCurrentValue()->FastShutdownIfPossible();
+ }
+
+ web_contents_.reset();
+}
+
+void ChromecastBrowserTest::NavigateToURL(content::WebContents* window,
+ const GURL& url) {
+ content::WaitForLoadStop(window);
+ content::TestNavigationObserver same_tab_observer(window, 1);
+ content::NavigationController::LoadURLParams params(url);
+ params.transition_type = ui::PageTransitionFromInt(
+ ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
+ window->GetController().LoadURLWithParams(params);
+ same_tab_observer.Wait();
+}
+
+content::WebContents* ChromecastBrowserTest::CreateBrowser() {
+ content::WebContents::CreateParams create_params(
+ CastBrowserProcess::GetInstance()->browser_context(),
+ NULL);
+ create_params.routing_id = MSG_ROUTING_NONE;
+ create_params.initial_size = gfx::Size(1280, 720);
+ web_contents_.reset(content::WebContents::Create(create_params));
+ return web_contents_.get();
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/browser/test/chromecast_browser_test.h b/chromecast/shell/browser/test/chromecast_browser_test.h
new file mode 100644
index 0000000000..da031e7901
--- /dev/null
+++ b/chromecast/shell/browser/test/chromecast_browser_test.h
@@ -0,0 +1,56 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_
+#define CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_base.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace chromecast {
+namespace shell {
+
+// This test allows for running an entire browser-process lifecycle per unit
+// test, using Chromecast's cast_shell. This starts up the shell, runs a test
+// case, then shuts down the entire shell.
+// Note that this process takes 7-10 seconds per test case on Chromecast, so
+// fewer test cases with more assertions are preferable.
+class ChromecastBrowserTest : public content::BrowserTestBase {
+ protected:
+ ChromecastBrowserTest();
+ virtual ~ChromecastBrowserTest();
+
+ // testing::Test implementation:
+ virtual void SetUp() OVERRIDE;
+
+ // BrowserTestBase implementation:
+ virtual void RunTestOnMainThreadLoop() OVERRIDE;
+
+ protected:
+ void NavigateToURL(content::WebContents* window, const GURL& gurl);
+
+ // Creates a new window and loads about:blank.
+ content::WebContents* CreateBrowser();
+
+ // Returns the window for the test.
+ content::WebContents* web_contents() const { return web_contents_.get(); }
+
+ private:
+ scoped_ptr<content::WebContents> web_contents_;
+
+ bool setup_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromecastBrowserTest);
+};
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_
diff --git a/chromecast/shell/browser/test/chromecast_browser_test_runner.cc b/chromecast/shell/browser/test/chromecast_browser_test_runner.cc
new file mode 100644
index 0000000000..baad234507
--- /dev/null
+++ b/chromecast/shell/browser/test/chromecast_browser_test_runner.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/sys_info.h"
+#include "chromecast/shell/app/cast_main_delegate.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/content_test_suite_base.h"
+#include "content/public/test/test_launcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromecast {
+namespace shell {
+
+namespace {
+
+const char kTestTypeBrowser[] = "browser";
+
+class BrowserTestSuite : public content::ContentTestSuiteBase {
+ public:
+ BrowserTestSuite(int argc, char** argv)
+ : content::ContentTestSuiteBase(argc, argv) {
+ }
+ virtual ~BrowserTestSuite() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BrowserTestSuite);
+};
+
+class ChromecastTestLauncherDelegate : public content::TestLauncherDelegate {
+ public:
+ ChromecastTestLauncherDelegate() {}
+ virtual ~ChromecastTestLauncherDelegate() {}
+
+ virtual int RunTestSuite(int argc, char** argv) OVERRIDE {
+ return BrowserTestSuite(argc, argv).Run();
+ }
+
+ virtual bool AdjustChildProcessCommandLine(
+ base::CommandLine* command_line,
+ const base::FilePath& temp_data_dir) OVERRIDE {
+ // TODO(gunsch): handle temp_data_dir
+ command_line->AppendSwitchASCII(switches::kTestType, kTestTypeBrowser);
+ return true;
+ }
+
+ protected:
+ virtual content::ContentMainDelegate* CreateContentMainDelegate() OVERRIDE {
+ return new CastMainDelegate();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromecastTestLauncherDelegate);
+};
+
+} // namespace
+
+} // namespace shell
+} // namespace chromecast
+
+int main(int argc, char** argv) {
+ int default_jobs = std::max(1, base::SysInfo::NumberOfProcessors() / 2);
+ chromecast::shell::ChromecastTestLauncherDelegate launcher_delegate;
+ return LaunchTests(&launcher_delegate, default_jobs, argc, argv);
+}
diff --git a/chromecast/shell/browser/test/chromecast_shell_browser_test.cc b/chromecast/shell/browser/test/chromecast_shell_browser_test.cc
new file mode 100644
index 0000000000..3016dcd0c2
--- /dev/null
+++ b/chromecast/shell/browser/test/chromecast_shell_browser_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "chromecast/shell/browser/test/chromecast_browser_test.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace chromecast {
+namespace shell {
+
+class ChromecastShellBrowserTest : public ChromecastBrowserTest {
+ public:
+ ChromecastShellBrowserTest() : url_(url::kAboutBlankURL) {}
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ CreateBrowser();
+ NavigateToURL(web_contents(), url_);
+ }
+
+ private:
+ const GURL url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromecastShellBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromecastShellBrowserTest, EmptyTest) {
+ // Run an entire browser lifecycle to ensure nothing breaks.
+ // TODO(gunsch): Remove this test case once there are actual assertions to
+ // test in a ChromecastBrowserTest instance.
+ EXPECT_TRUE(true);
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/browser/url_request_context_factory.cc b/chromecast/shell/browser/url_request_context_factory.cc
index 03b5d9ddf6..e61bc6a914 100644
--- a/chromecast/shell/browser/url_request_context_factory.cc
+++ b/chromecast/shell/browser/url_request_context_factory.cc
@@ -30,6 +30,7 @@
#include "net/ssl/default_channel_id_store.h"
#include "net/ssl/ssl_config_service_defaults.h"
#include "net/url_request/data_protocol_handler.h"
+#include "net/url_request/file_protocol_handler.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_intercepting_job_factory.h"
@@ -63,8 +64,10 @@ class URLRequestContextFactory::URLRequestContextGetter
request_context_.reset(factory_->CreateMediaRequestContext());
} else {
request_context_.reset(factory_->CreateSystemRequestContext());
+#if defined(USE_NSS)
// Set request context used by NSS for Crl requests.
net::SetURLRequestContextForNSSHttpIO(request_context_.get());
+#endif // defined(USE_NSS)
}
}
return request_context_.get();
@@ -144,13 +147,22 @@ void URLRequestContextFactory::InitializeOnUIThread() {
// because it registers itself to pref notification observer which is not
// thread safe.
http_user_agent_settings_.reset(new CastHttpUserAgentSettings());
+
+ // Proxy config service should be initialized in UI thread, since
+ // ProxyConfigServiceDelegate on Android expects UI thread.
+ proxy_config_service_.reset(net::ProxyService::CreateSystemProxyConfigService(
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::IO),
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE)));
}
net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter(
content::BrowserContext* browser_context,
content::ProtocolHandlerMap* protocol_handlers,
content::URLRequestInterceptorScopedVector request_interceptors) {
- DCHECK(!main_getter_) << "Main URLRequestContextGetter already initialized";
+ DCHECK(!main_getter_.get())
+ << "Main URLRequestContextGetter already initialized";
main_getter_ = new MainURLRequestContextGetter(this,
browser_context,
protocol_handlers,
@@ -159,19 +171,19 @@ net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter(
}
net::URLRequestContextGetter* URLRequestContextFactory::GetMainGetter() {
- CHECK(main_getter_);
+ CHECK(main_getter_.get());
return main_getter_.get();
}
net::URLRequestContextGetter* URLRequestContextFactory::GetSystemGetter() {
- if (!system_getter_) {
+ if (!system_getter_.get()) {
system_getter_ = new URLRequestContextGetter(this, false);
}
return system_getter_.get();
}
net::URLRequestContextGetter* URLRequestContextFactory::GetMediaGetter() {
- if (!media_getter_) {
+ if (!media_getter_.get()) {
media_getter_ = new URLRequestContextGetter(this, true);
}
return media_getter_.get();
@@ -201,13 +213,7 @@ void URLRequestContextFactory::InitializeSystemContextDependencies() {
http_server_properties_.reset(new net::HttpServerPropertiesImpl);
proxy_service_.reset(net::ProxyService::CreateUsingSystemProxyResolver(
- net::ProxyService::CreateSystemProxyConfigService(
- content::BrowserThread::GetMessageLoopProxyForThread(
- content::BrowserThread::IO).get(),
- content::BrowserThread::UnsafeGetMessageLoopForThread(
- content::BrowserThread::FILE)),
- 0,
- NULL));
+ proxy_config_service_.release(), 0, NULL));
system_dependencies_initialized_ = true;
}
@@ -235,6 +241,15 @@ void URLRequestContextFactory::InitializeMainContextDependencies(
url::kDataScheme,
new net::DataProtocolHandler);
DCHECK(set_protocol);
+#if defined(OS_ANDROID)
+ set_protocol = job_factory->SetProtocolHandler(
+ url::kFileScheme,
+ new net::FileProtocolHandler(
+ content::BrowserThread::GetBlockingPool()->
+ GetTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)));
+ DCHECK(set_protocol);
+#endif // defined(OS_ANDROID)
// Set up interceptors in the reverse order.
scoped_ptr<net::URLRequestJobFactory> top_job_factory =
@@ -288,6 +303,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() {
PopulateNetworkSessionParams(false, &system_params);
system_transaction_factory_.reset(new net::HttpNetworkLayer(
new net::HttpNetworkSession(system_params)));
+ system_job_factory_.reset(new net::URLRequestJobFactoryImpl());
net::URLRequestContext* system_context = new net::URLRequestContext();
system_context->set_host_resolver(host_resolver_.get());
@@ -305,6 +321,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() {
system_transaction_factory_.get());
system_context->set_http_user_agent_settings(
http_user_agent_settings_.get());
+ system_context->set_job_factory(system_job_factory_.get());
system_context->set_cookie_store(
content::CreateCookieStore(content::CookieStoreConfig()));
return system_context;
@@ -312,7 +329,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() {
net::URLRequestContext* URLRequestContextFactory::CreateMediaRequestContext() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
- DCHECK(main_getter_)
+ DCHECK(main_getter_.get())
<< "Getting MediaRequestContext before MainRequestContext";
net::URLRequestContext* main_context = main_getter_->GetURLRequestContext();
diff --git a/chromecast/shell/browser/url_request_context_factory.h b/chromecast/shell/browser/url_request_context_factory.h
index 03d062da42..8de7437b93 100644
--- a/chromecast/shell/browser/url_request_context_factory.h
+++ b/chromecast/shell/browser/url_request_context_factory.h
@@ -11,6 +11,7 @@
namespace net {
class HttpTransactionFactory;
class HttpUserAgentSettings;
+class ProxyConfigService;
class URLRequestJobFactory;
} // namespace net
@@ -83,11 +84,13 @@ class URLRequestContextFactory {
scoped_ptr<net::CertVerifier> cert_verifier_;
scoped_refptr<net::SSLConfigService> ssl_config_service_;
scoped_ptr<net::TransportSecurityState> transport_security_state_;
+ scoped_ptr<net::ProxyConfigService> proxy_config_service_;
scoped_ptr<net::ProxyService> proxy_service_;
scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory_;
scoped_ptr<net::HttpServerProperties> http_server_properties_;
scoped_ptr<net::HttpUserAgentSettings> http_user_agent_settings_;
scoped_ptr<net::HttpTransactionFactory> system_transaction_factory_;
+ scoped_ptr<net::URLRequestJobFactory> system_job_factory_;
bool main_dependencies_initialized_;
scoped_ptr<net::HttpTransactionFactory> main_transaction_factory_;
diff --git a/chromecast/shell/common/cast_content_client.cc b/chromecast/shell/common/cast_content_client.cc
index 65d50ec079..3b7eea27e6 100644
--- a/chromecast/shell/common/cast_content_client.cc
+++ b/chromecast/shell/common/cast_content_client.cc
@@ -14,7 +14,8 @@ namespace shell {
std::string GetUserAgent() {
std::string product = "Chrome/" PRODUCT_VERSION;
- return content::BuildUserAgentFromProduct(product) + " CrKey";
+ return content::BuildUserAgentFromProduct(product) +
+ " CrKey" CAST_BUILD_REVISION;
}
CastContentClient::~CastContentClient() {
diff --git a/chromecast/shell/renderer/DEPS b/chromecast/shell/renderer/DEPS
index ad0391cf78..161949b6be 100644
--- a/chromecast/shell/renderer/DEPS
+++ b/chromecast/shell/renderer/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+components/cdm/renderer",
"+content/public/renderer",
"+third_party/WebKit/public/platform",
"+third_party/WebKit/public/web",
diff --git a/chromecast/shell/renderer/cast_content_renderer_client.cc b/chromecast/shell/renderer/cast_content_renderer_client.cc
index e07e227929..59e6c1f469 100644
--- a/chromecast/shell/renderer/cast_content_renderer_client.cc
+++ b/chromecast/shell/renderer/cast_content_renderer_client.cc
@@ -8,10 +8,12 @@
#include "base/command_line.h"
#include "base/memory/memory_pressure_listener.h"
+#include "chromecast/shell/renderer/key_systems_cast.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_view.h"
#include "crypto/nss_util.h"
#include "third_party/WebKit/public/platform/WebColor.h"
+#include "third_party/WebKit/public/web/WebSettings.h"
#include "third_party/WebKit/public/web/WebView.h"
namespace chromecast {
@@ -41,11 +43,14 @@ void CastContentRendererClient::RenderViewCreated(
blink::WebView* webview = render_view->GetWebView();
if (webview) {
webview->setBaseBackgroundColor(kColorBlack);
+ webview->settings()->setShrinksViewportContentToFit(false);
}
}
void CastContentRendererClient::AddKeySystems(
std::vector<content::KeySystemInfo>* key_systems) {
+ AddChromecastKeySystems(key_systems);
+ AddChromecastPlatformKeySystems(key_systems);
}
} // namespace shell
diff --git a/chromecast/shell/renderer/key_systems_cast.cc b/chromecast/shell/renderer/key_systems_cast.cc
new file mode 100644
index 0000000000..ceffef5f0c
--- /dev/null
+++ b/chromecast/shell/renderer/key_systems_cast.cc
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/renderer/key_systems_cast.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "chromecast/media/base/key_systems_common.h"
+#include "components/cdm/renderer/widevine_key_systems.h"
+#include "content/public/common/eme_codec.h"
+
+#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
+
+namespace chromecast {
+namespace shell {
+
+void AddKeySystemWithCodecs(
+ const std::string& key_system_name,
+ std::vector<content::KeySystemInfo>* concrete_key_systems) {
+ content::KeySystemInfo info(key_system_name);
+ info.supported_codecs = content::EME_CODEC_MP4_ALL;
+ concrete_key_systems->push_back(info);
+}
+
+void AddChromecastKeySystems(
+ std::vector<content::KeySystemInfo>* key_systems_info) {
+#if defined(WIDEVINE_CDM_AVAILABLE)
+ AddWidevineWithCodecs(cdm::WIDEVINE,
+ content::EME_CODEC_MP4_ALL,
+ key_systems_info);
+#endif
+
+#if defined(PLAYREADY_CDM_AVAILABLE)
+ AddKeySystemWithCodecs(media::kChromecastPlayreadyKeySystem,
+ key_systems_info);
+#endif
+}
+
+} // namespace shell
+} // namespace chromecast
diff --git a/chromecast/shell/renderer/key_systems_cast.h b/chromecast/shell/renderer/key_systems_cast.h
new file mode 100644
index 0000000000..c291344a1b
--- /dev/null
+++ b/chromecast/shell/renderer/key_systems_cast.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_
+#define CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_
+
+#include <vector>
+
+#include "content/public/renderer/key_system_info.h"
+
+namespace chromecast {
+namespace shell {
+
+// Adds a single key system by name.
+void AddKeySystemWithCodecs(
+ const std::string& key_system_name,
+ std::vector<content::KeySystemInfo>* concrete_key_systems);
+
+void AddChromecastKeySystems(
+ std::vector<content::KeySystemInfo>* key_systems_info);
+
+// TODO(gunsch): Remove when prefixed EME is removed.
+void AddChromecastPlatformKeySystems(
+ std::vector<content::KeySystemInfo>* key_systems_info);
+
+} // namespace shell
+} // namespace chromecast
+
+#endif // CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_
diff --git a/chromecast/shell/renderer/key_systems_cast_simple.cc b/chromecast/shell/renderer/key_systems_cast_simple.cc
new file mode 100644
index 0000000000..752669f07a
--- /dev/null
+++ b/chromecast/shell/renderer/key_systems_cast_simple.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/shell/renderer/key_systems_cast.h"
+
+namespace chromecast {
+namespace shell {
+
+void AddChromecastPlatformKeySystems(
+ std::vector<content::KeySystemInfo>* key_systems_info) {
+ // Intentional no-op for public build.
+}
+
+} // namespace shell
+} // namespace chromecast