summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authoracolwell <acolwell@chromium.org>2014-09-06 12:01:32 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-06 19:06:46 +0000
commit9e0840d0672da96350a1f33d684f2c64d2574f46 (patch)
tree6d72e3d50fcfd66f70a58c5d3583358055de4d9a /media
parent87a3ebac6b26918b151151efed3311d0ddc20d73 (diff)
downloadchromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.zip
chromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.tar.gz
chromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.tar.bz2
Move WebMediaPlayerImpl and its dependencies to media/blink.
Moving WebMediaPlayerImpl and related classes in content/renderer/media to media/blink so that they can be reused by Mojo code. BUG=408338 Review URL: https://codereview.chromium.org/495353003 Cr-Commit-Position: refs/heads/master@{#293628}
Diffstat (limited to 'media')
-rw-r--r--media/DEPS1
-rw-r--r--media/base/data_source.cc3
-rw-r--r--media/base/data_source.h3
-rw-r--r--media/blink/BUILD.gn87
-rw-r--r--media/blink/DEPS10
-rw-r--r--media/blink/active_loader.cc26
-rw-r--r--media/blink/active_loader.h43
-rw-r--r--media/blink/buffered_data_source.cc535
-rw-r--r--media/blink/buffered_data_source.h240
-rw-r--r--media/blink/buffered_data_source_host_impl.cc58
-rw-r--r--media/blink/buffered_data_source_host_impl.h51
-rw-r--r--media/blink/buffered_data_source_host_impl_unittest.cc75
-rw-r--r--media/blink/buffered_data_source_unittest.cc779
-rw-r--r--media/blink/buffered_resource_loader.cc790
-rw-r--r--media/blink/buffered_resource_loader.h320
-rw-r--r--media/blink/buffered_resource_loader_unittest.cc1131
-rw-r--r--media/blink/cache_util.cc87
-rw-r--r--media/blink/cache_util.h41
-rw-r--r--media/blink/cache_util_unittest.cc97
-rw-r--r--media/blink/encrypted_media_player_support.cc15
-rw-r--r--media/blink/encrypted_media_player_support.h80
-rw-r--r--media/blink/media_blink.gyp104
-rw-r--r--media/blink/mock_webframeclient.h16
-rw-r--r--media/blink/mock_weburlloader.cc18
-rw-r--r--media/blink/mock_weburlloader.h33
-rw-r--r--media/blink/run_all_unittests.cc85
-rw-r--r--media/blink/test_response_generator.cc101
-rw-r--r--media/blink/test_response_generator.h66
-rw-r--r--media/blink/texttrack_impl.cc70
-rw-r--r--media/blink/texttrack_impl.h61
-rw-r--r--media/blink/video_frame_compositor.cc77
-rw-r--r--media/blink/video_frame_compositor.h68
-rw-r--r--media/blink/video_frame_compositor_unittest.cc160
-rw-r--r--media/blink/webaudiosourceprovider_impl.cc193
-rw-r--r--media/blink/webaudiosourceprovider_impl.h90
-rw-r--r--media/blink/webaudiosourceprovider_impl_unittest.cc240
-rw-r--r--media/blink/webinbandtexttrack_impl.cc58
-rw-r--r--media/blink/webinbandtexttrack_impl.h45
-rw-r--r--media/blink/webmediaplayer_delegate.h34
-rw-r--r--media/blink/webmediaplayer_impl.cc1034
-rw-r--r--media/blink/webmediaplayer_impl.h319
-rw-r--r--media/blink/webmediaplayer_params.cc44
-rw-r--r--media/blink/webmediaplayer_params.h102
-rw-r--r--media/blink/webmediaplayer_util.cc120
-rw-r--r--media/blink/webmediaplayer_util.h35
-rw-r--r--media/blink/webmediasource_impl.cc86
-rw-r--r--media/blink/webmediasource_impl.h43
-rw-r--r--media/blink/websourcebuffer_impl.cc134
-rw-r--r--media/blink/websourcebuffer_impl.h54
49 files changed, 7958 insertions, 4 deletions
diff --git a/media/DEPS b/media/DEPS
index f723644..f9c29be 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -11,4 +11,5 @@ include_rules = [
"+ui/gfx",
"+ui/gl",
"+ui/ozone",
+ "-media/blink",
]
diff --git a/media/base/data_source.cc b/media/base/data_source.cc
index c8ab446..b6999c2 100644
--- a/media/base/data_source.cc
+++ b/media/base/data_source.cc
@@ -8,9 +8,6 @@
namespace media {
-// static
-const int DataSource::kReadError = -1;
-
DataSource::DataSource() {}
DataSource::~DataSource() {}
diff --git a/media/base/data_source.h b/media/base/data_source.h
index e0b7373..42566a9 100644
--- a/media/base/data_source.h
+++ b/media/base/data_source.h
@@ -15,7 +15,8 @@ class MEDIA_EXPORT DataSource {
public:
typedef base::Callback<void(int64, int64)> StatusCallback;
typedef base::Callback<void(int)> ReadCB;
- static const int kReadError;
+
+ enum { kReadError = -1 };
DataSource();
virtual ~DataSource();
diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn
new file mode 100644
index 0000000..ad52f62
--- /dev/null
+++ b/media/blink/BUILD.gn
@@ -0,0 +1,87 @@
+# 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.
+component("blink") {
+ output_name = "media_blink"
+
+ deps = [
+ "//base",
+ "//cc",
+ "//cc/blink",
+ "//media",
+ "//net",
+ "//third_party/WebKit/public:blink",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ defines = [ "MEDIA_IMPLEMENTATION" ]
+
+ sources = [
+ "active_loader.cc",
+ "active_loader.h",
+ "buffered_data_source.cc",
+ "buffered_data_source.h",
+ "buffered_data_source_host_impl.cc",
+ "buffered_data_source_host_impl.h",
+ "buffered_resource_loader.cc",
+ "buffered_resource_loader.h",
+ "encrypted_media_player_support.cc",
+ "encrypted_media_player_support.h",
+ "cache_util.cc",
+ "cache_util.h",
+ "texttrack_impl.cc",
+ "texttrack_impl.h",
+ "video_frame_compositor.cc",
+ "video_frame_compositor.h",
+ "webaudiosourceprovider_impl.cc",
+ "webaudiosourceprovider_impl.h",
+ "webinbandtexttrack_impl.cc",
+ "webinbandtexttrack_impl.h",
+ "webmediaplayer_delegate.h",
+ "webmediaplayer_impl.cc",
+ "webmediaplayer_impl.h",
+ "webmediaplayer_params.cc",
+ "webmediaplayer_params.h",
+ "webmediaplayer_util.cc",
+ "webmediaplayer_util.h",
+ "webmediasource_impl.cc",
+ "webmediasource_impl.h",
+ "websourcebuffer_impl.cc",
+ "websourcebuffer_impl.h",
+ ]
+}
+
+test("media_blink_unittests") {
+ deps = [
+ ":blink",
+ "//base",
+ "//base/test:test_support",
+ "//cc",
+ "//cc/blink",
+ "//media",
+ "//media/base:test_support",
+ "//net",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/WebKit/public:blink",
+ "//ui/gfx/geometry",
+ "//ui/gfx:test_support",
+ "//url",
+ ]
+
+ sources = [
+ "buffered_data_source_host_impl_unittest.cc",
+ "buffered_data_source_unittest.cc",
+ "buffered_resource_loader_unittest.cc",
+ "cache_util_unittest.cc",
+ "mock_webframeclient.h",
+ "mock_weburlloader.cc",
+ "mock_weburlloader.h",
+ "run_all_unittests.cc",
+ "test_response_generator.cc",
+ "test_response_generator.h",
+ "video_frame_compositor_unittest.cc",
+ "webaudiosourceprovider_impl_unittest.cc",
+ ]
+}
diff --git a/media/blink/DEPS b/media/blink/DEPS
new file mode 100644
index 0000000..5c05816
--- /dev/null
+++ b/media/blink/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+cc/layers/video_frame_provider.h",
+ "+cc/layers/video_layer.h",
+ "+cc/blink/web_layer_impl.h",
+ "+media",
+ "+net/base",
+ "+net/http",
+ "+third_party/WebKit/public/platform",
+ "+third_party/WebKit/public/web",
+] \ No newline at end of file
diff --git a/media/blink/active_loader.cc b/media/blink/active_loader.cc
new file mode 100644
index 0000000..2f1e084
--- /dev/null
+++ b/media/blink/active_loader.cc
@@ -0,0 +1,26 @@
+// Copyright 2013 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 "media/blink/active_loader.h"
+
+#include "media/blink/buffered_resource_loader.h"
+#include "third_party/WebKit/public/platform/WebURLLoader.h"
+
+namespace media {
+
+ActiveLoader::ActiveLoader(scoped_ptr<blink::WebURLLoader> loader)
+ : loader_(loader.Pass()),
+ deferred_(false) {
+}
+
+ActiveLoader::~ActiveLoader() {
+ loader_->cancel();
+}
+
+void ActiveLoader::SetDeferred(bool deferred) {
+ deferred_ = deferred;
+ loader_->setDefersLoading(deferred);
+}
+
+} // namespace media
diff --git a/media/blink/active_loader.h b/media/blink/active_loader.h
new file mode 100644
index 0000000..d8c60df
--- /dev/null
+++ b/media/blink/active_loader.h
@@ -0,0 +1,43 @@
+// Copyright 2013 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 MEDIA_BLINK_ACTIVE_LOADER_H_
+#define MEDIA_BLINK_ACTIVE_LOADER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+
+namespace blink {
+class WebURLLoader;
+}
+
+namespace media {
+
+// Wraps an active WebURLLoader with some additional state.
+//
+// Handles deferring and deletion of loaders.
+class MEDIA_EXPORT ActiveLoader {
+ public:
+ // Creates an ActiveLoader with the given loader. It is assumed that the
+ // initial state of |loader| is loading and not deferred.
+ explicit ActiveLoader(scoped_ptr<blink::WebURLLoader> loader);
+ ~ActiveLoader();
+
+ // Starts or stops deferring the resource load.
+ void SetDeferred(bool deferred);
+ bool deferred() { return deferred_; }
+
+ private:
+ friend class BufferedDataSourceTest;
+
+ scoped_ptr<blink::WebURLLoader> loader_;
+ bool deferred_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ActiveLoader);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_ACTIVE_LOADER_H_
diff --git a/media/blink/buffered_data_source.cc b/media/blink/buffered_data_source.cc
new file mode 100644
index 0000000..91ed1a2
--- /dev/null
+++ b/media/blink/buffered_data_source.cc
@@ -0,0 +1,535 @@
+// Copyright 2013 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 "media/blink/buffered_data_source.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/single_thread_task_runner.h"
+#include "media/base/media_log.h"
+#include "net/base/net_errors.h"
+
+using blink::WebFrame;
+
+namespace {
+
+// BufferedDataSource has an intermediate buffer, this value governs the initial
+// size of that buffer. It is set to 32KB because this is a typical read size
+// of FFmpeg.
+const int kInitialReadBufferSize = 32768;
+
+// Number of cache misses we allow for a single Read() before signaling an
+// error.
+const int kNumCacheMissRetries = 3;
+
+} // namespace
+
+namespace media {
+
+class BufferedDataSource::ReadOperation {
+ public:
+ ReadOperation(int64 position, int size, uint8* data,
+ const DataSource::ReadCB& callback);
+ ~ReadOperation();
+
+ // Runs |callback_| with the given |result|, deleting the operation
+ // afterwards.
+ static void Run(scoped_ptr<ReadOperation> read_op, int result);
+
+ // State for the number of times this read operation has been retried.
+ int retries() { return retries_; }
+ void IncrementRetries() { ++retries_; }
+
+ int64 position() { return position_; }
+ int size() { return size_; }
+ uint8* data() { return data_; }
+
+ private:
+ int retries_;
+
+ const int64 position_;
+ const int size_;
+ uint8* data_;
+ DataSource::ReadCB callback_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation);
+};
+
+BufferedDataSource::ReadOperation::ReadOperation(
+ int64 position, int size, uint8* data,
+ const DataSource::ReadCB& callback)
+ : retries_(0),
+ position_(position),
+ size_(size),
+ data_(data),
+ callback_(callback) {
+ DCHECK(!callback_.is_null());
+}
+
+BufferedDataSource::ReadOperation::~ReadOperation() {
+ DCHECK(callback_.is_null());
+}
+
+// static
+void BufferedDataSource::ReadOperation::Run(
+ scoped_ptr<ReadOperation> read_op, int result) {
+ base::ResetAndReturn(&read_op->callback_).Run(result);
+}
+
+BufferedDataSource::BufferedDataSource(
+ const GURL& url,
+ BufferedResourceLoader::CORSMode cors_mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ WebFrame* frame,
+ MediaLog* media_log,
+ BufferedDataSourceHost* host,
+ const DownloadingCB& downloading_cb)
+ : url_(url),
+ cors_mode_(cors_mode),
+ total_bytes_(kPositionNotSpecified),
+ streaming_(false),
+ frame_(frame),
+ intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
+ intermediate_read_buffer_size_(kInitialReadBufferSize),
+ render_task_runner_(task_runner),
+ stop_signal_received_(false),
+ media_has_played_(false),
+ preload_(AUTO),
+ bitrate_(0),
+ playback_rate_(0.0),
+ media_log_(media_log),
+ host_(host),
+ downloading_cb_(downloading_cb),
+ weak_factory_(this) {
+ DCHECK(host_);
+ DCHECK(!downloading_cb_.is_null());
+}
+
+BufferedDataSource::~BufferedDataSource() {}
+
+// A factory method to create BufferedResourceLoader using the read parameters.
+// This method can be overridden to inject mock BufferedResourceLoader object
+// for testing purpose.
+BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
+ int64 first_byte_position, int64 last_byte_position) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+
+ BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
+ BufferedResourceLoader::kReadThenDefer :
+ BufferedResourceLoader::kCapacityDefer;
+
+ return new BufferedResourceLoader(url_,
+ cors_mode_,
+ first_byte_position,
+ last_byte_position,
+ strategy,
+ bitrate_,
+ playback_rate_,
+ media_log_.get());
+}
+
+void BufferedDataSource::Initialize(const InitializeCB& init_cb) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(!init_cb.is_null());
+ DCHECK(!loader_.get());
+
+ init_cb_ = init_cb;
+
+ if (url_.SchemeIsHTTPOrHTTPS()) {
+ // Do an unbounded range request starting at the beginning. If the server
+ // responds with 200 instead of 206 we'll fall back into a streaming mode.
+ loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
+ } else {
+ // For all other protocols, assume they support range request. We fetch
+ // the full range of the resource to obtain the instance size because
+ // we won't be served HTTP headers.
+ loader_.reset(CreateResourceLoader(kPositionNotSpecified,
+ kPositionNotSpecified));
+ }
+
+ base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
+ loader_->Start(
+ base::Bind(&BufferedDataSource::StartCallback, weak_this),
+ base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this),
+ base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
+ frame_);
+}
+
+void BufferedDataSource::SetPreload(Preload preload) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ preload_ = preload;
+}
+
+bool BufferedDataSource::HasSingleOrigin() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(init_cb_.is_null() && loader_.get())
+ << "Initialize() must complete before calling HasSingleOrigin()";
+ return loader_->HasSingleOrigin();
+}
+
+bool BufferedDataSource::DidPassCORSAccessCheck() const {
+ return loader_.get() && loader_->DidPassCORSAccessCheck();
+}
+
+void BufferedDataSource::Abort() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ {
+ base::AutoLock auto_lock(lock_);
+ StopInternal_Locked();
+ }
+ StopLoader();
+ frame_ = NULL;
+}
+
+void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ if (playback_rate < 0.0f)
+ return;
+
+ playback_rate_ = playback_rate;
+ loader_->SetPlaybackRate(playback_rate);
+}
+
+void BufferedDataSource::MediaIsPlaying() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ media_has_played_ = true;
+ UpdateDeferStrategy(false);
+}
+
+void BufferedDataSource::MediaIsPaused() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ UpdateDeferStrategy(true);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DataSource implementation.
+void BufferedDataSource::Stop() {
+ {
+ base::AutoLock auto_lock(lock_);
+ StopInternal_Locked();
+ }
+
+ render_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferedDataSource::StopLoader, weak_factory_.GetWeakPtr()));
+}
+
+void BufferedDataSource::SetBitrate(int bitrate) {
+ render_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&BufferedDataSource::SetBitrateTask,
+ weak_factory_.GetWeakPtr(),
+ bitrate));
+}
+
+void BufferedDataSource::Read(
+ int64 position, int size, uint8* data,
+ const DataSource::ReadCB& read_cb) {
+ DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
+ DCHECK(!read_cb.is_null());
+
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(!read_op_);
+
+ if (stop_signal_received_) {
+ read_cb.Run(kReadError);
+ return;
+ }
+
+ read_op_.reset(new ReadOperation(position, size, data, read_cb));
+ }
+
+ render_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferedDataSource::ReadTask, weak_factory_.GetWeakPtr()));
+}
+
+bool BufferedDataSource::GetSize(int64* size_out) {
+ if (total_bytes_ != kPositionNotSpecified) {
+ *size_out = total_bytes_;
+ return true;
+ }
+ *size_out = 0;
+ return false;
+}
+
+bool BufferedDataSource::IsStreaming() {
+ return streaming_;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Render thread tasks.
+void BufferedDataSource::ReadTask() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ ReadInternal();
+}
+
+void BufferedDataSource::StopInternal_Locked() {
+ lock_.AssertAcquired();
+ if (stop_signal_received_)
+ return;
+
+ stop_signal_received_ = true;
+
+ // Initialize() isn't part of the DataSource interface so don't call it in
+ // response to Stop().
+ init_cb_.Reset();
+
+ if (read_op_)
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+}
+
+void BufferedDataSource::StopLoader() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+
+ if (loader_)
+ loader_->Stop();
+}
+
+void BufferedDataSource::SetBitrateTask(int bitrate) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ bitrate_ = bitrate;
+ loader_->SetBitrate(bitrate);
+}
+
+// This method is the place where actual read happens, |loader_| must be valid
+// prior to make this method call.
+void BufferedDataSource::ReadInternal() {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ int64 position = 0;
+ int size = 0;
+ {
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ position = read_op_->position();
+ size = read_op_->size();
+ }
+
+ // First we prepare the intermediate read buffer for BufferedResourceLoader
+ // to write to.
+ if (size > intermediate_read_buffer_size_) {
+ intermediate_read_buffer_.reset(new uint8[size]);
+ }
+
+ // Perform the actual read with BufferedResourceLoader.
+ loader_->Read(position,
+ size,
+ intermediate_read_buffer_.get(),
+ base::Bind(&BufferedDataSource::ReadCallback,
+ weak_factory_.GetWeakPtr()));
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader callback methods.
+void BufferedDataSource::StartCallback(
+ BufferedResourceLoader::Status status) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ bool init_cb_is_null = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ init_cb_is_null = init_cb_.is_null();
+ }
+ if (init_cb_is_null) {
+ loader_->Stop();
+ return;
+ }
+
+ // All responses must be successful. Resources that are assumed to be fully
+ // buffered must have a known content length.
+ bool success = status == BufferedResourceLoader::kOk &&
+ (!assume_fully_buffered() ||
+ loader_->instance_size() != kPositionNotSpecified);
+
+ if (success) {
+ total_bytes_ = loader_->instance_size();
+ streaming_ =
+ !assume_fully_buffered() &&
+ (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
+
+ media_log_->SetDoubleProperty("total_bytes",
+ static_cast<double>(total_bytes_));
+ media_log_->SetBooleanProperty("streaming", streaming_);
+ } else {
+ loader_->Stop();
+ }
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (success) {
+ if (total_bytes_ != kPositionNotSpecified) {
+ host_->SetTotalBytes(total_bytes_);
+ if (assume_fully_buffered())
+ host_->AddBufferedByteRange(0, total_bytes_);
+ }
+
+ media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin());
+ media_log_->SetBooleanProperty("passed_cors_access_check",
+ loader_->DidPassCORSAccessCheck());
+ media_log_->SetBooleanProperty("range_header_supported",
+ loader_->range_supported());
+ }
+
+ base::ResetAndReturn(&init_cb_).Run(success);
+}
+
+void BufferedDataSource::PartialReadStartCallback(
+ BufferedResourceLoader::Status status) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ if (status == BufferedResourceLoader::kOk) {
+ // Once the request has started successfully, we can proceed with
+ // reading from it.
+ ReadInternal();
+ return;
+ }
+
+ // Stop the resource loader since we have received an error.
+ loader_->Stop();
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+}
+
+void BufferedDataSource::ReadCallback(
+ BufferedResourceLoader::Status status,
+ int bytes_read) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (status != BufferedResourceLoader::kOk) {
+ // Stop the resource load if it failed.
+ loader_->Stop();
+
+ if (status == BufferedResourceLoader::kCacheMiss &&
+ read_op_->retries() < kNumCacheMissRetries) {
+ read_op_->IncrementRetries();
+
+ // Recreate a loader starting from where we last left off until the
+ // end of the resource.
+ loader_.reset(CreateResourceLoader(
+ read_op_->position(), kPositionNotSpecified));
+
+ base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
+ loader_->Start(
+ base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this),
+ base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
+ weak_this),
+ base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
+ frame_);
+ return;
+ }
+
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+ return;
+ }
+
+ if (bytes_read > 0) {
+ memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
+ } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
+ // We've reached the end of the file and we didn't know the total size
+ // before. Update the total size so Read()s past the end of the file will
+ // fail like they would if we had known the file size at the beginning.
+ total_bytes_ = loader_->instance_size();
+
+ if (total_bytes_ != kPositionNotSpecified) {
+ host_->SetTotalBytes(total_bytes_);
+ host_->AddBufferedByteRange(loader_->first_byte_position(),
+ total_bytes_);
+ }
+ }
+ ReadOperation::Run(read_op_.Pass(), bytes_read);
+}
+
+void BufferedDataSource::LoadingStateChangedCallback(
+ BufferedResourceLoader::LoadingState state) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+
+ if (assume_fully_buffered())
+ return;
+
+ bool is_downloading_data;
+ switch (state) {
+ case BufferedResourceLoader::kLoading:
+ is_downloading_data = true;
+ break;
+ case BufferedResourceLoader::kLoadingDeferred:
+ case BufferedResourceLoader::kLoadingFinished:
+ is_downloading_data = false;
+ break;
+
+ // TODO(scherkus): we don't signal network activity changes when loads
+ // fail to preserve existing behaviour when deferring is toggled, however
+ // we should consider changing DownloadingCB to also propagate loading
+ // state. For example there isn't any signal today to notify the client that
+ // loading has failed (we only get errors on subsequent reads).
+ case BufferedResourceLoader::kLoadingFailed:
+ return;
+ }
+
+ downloading_cb_.Run(is_downloading_data);
+}
+
+void BufferedDataSource::ProgressCallback(int64 position) {
+ DCHECK(render_task_runner_->BelongsToCurrentThread());
+
+ if (assume_fully_buffered())
+ return;
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ host_->AddBufferedByteRange(loader_->first_byte_position(), position);
+}
+
+void BufferedDataSource::UpdateDeferStrategy(bool paused) {
+ // No need to aggressively buffer when we are assuming the resource is fully
+ // buffered.
+ if (assume_fully_buffered()) {
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
+ return;
+ }
+
+ // If the playback has started (at which point the preload value is ignored)
+ // and we're paused, then try to load as much as possible (the loader will
+ // fall back to kCapacityDefer if it knows the current response won't be
+ // useful from the cache in the future).
+ if (media_has_played_ && paused && loader_->range_supported()) {
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
+ return;
+ }
+
+ // If media is currently playing or the page indicated preload=auto or the
+ // the server does not support the byte range request or we do not want to go
+ // too far ahead of the read head, use threshold strategy to enable/disable
+ // deferring when the buffer is full/depleted.
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
+}
+
+} // namespace media
diff --git a/media/blink/buffered_data_source.h b/media/blink/buffered_data_source.h
new file mode 100644
index 0000000..5ae7d93
--- /dev/null
+++ b/media/blink/buffered_data_source.h
@@ -0,0 +1,240 @@
+// Copyright 2013 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 MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_
+#define MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/base/data_source.h"
+#include "media/base/media_export.h"
+#include "media/base/ranges.h"
+#include "media/blink/buffered_resource_loader.h"
+#include "url/gurl.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+class MediaLog;
+
+class MEDIA_EXPORT BufferedDataSourceHost {
+ public:
+ // Notify the host of the total size of the media file.
+ virtual void SetTotalBytes(int64 total_bytes) = 0;
+
+ // Notify the host that byte range [start,end] has been buffered.
+ // TODO(fischman): remove this method when demuxing is push-based instead of
+ // pull-based. http://crbug.com/131444
+ virtual void AddBufferedByteRange(int64 start, int64 end) = 0;
+
+ protected:
+ virtual ~BufferedDataSourceHost() {};
+};
+
+// A data source capable of loading URLs and buffering the data using an
+// in-memory sliding window.
+//
+// BufferedDataSource must be created and initialized on the render thread
+// before being passed to other threads. It may be deleted on any thread.
+class MEDIA_EXPORT BufferedDataSource : public DataSource {
+ public:
+ // Used to specify video preload states. They are "hints" to the browser about
+ // how aggressively the browser should load and buffer data.
+ // Please see the HTML5 spec for the descriptions of these values:
+ // http://www.w3.org/TR/html5/video.html#attr-media-preload
+ //
+ // Enum values must match the values in blink::WebMediaPlayer::Preload and
+ // there will be assertions at compile time if they do not match.
+ enum Preload {
+ NONE,
+ METADATA,
+ AUTO,
+ };
+ typedef base::Callback<void(bool)> DownloadingCB;
+
+ // |url| and |cors_mode| are passed to the object. Buffered byte range changes
+ // will be reported to |host|. |downloading_cb| will be called whenever the
+ // downloading/paused state of the source changes.
+ BufferedDataSource(
+ const GURL& url,
+ BufferedResourceLoader::CORSMode cors_mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ blink::WebFrame* frame,
+ MediaLog* media_log,
+ BufferedDataSourceHost* host,
+ const DownloadingCB& downloading_cb);
+ virtual ~BufferedDataSource();
+
+ // Executes |init_cb| with the result of initialization when it has completed.
+ //
+ // Method called on the render thread.
+ typedef base::Callback<void(bool)> InitializeCB;
+ void Initialize(const InitializeCB& init_cb);
+
+ // Adjusts the buffering algorithm based on the given preload value.
+ void SetPreload(Preload preload);
+
+ // Returns true if the media resource has a single origin, false otherwise.
+ // Only valid to call after Initialize() has completed.
+ //
+ // Method called on the render thread.
+ bool HasSingleOrigin();
+
+ // Returns true if the media resource passed a CORS access control check.
+ bool DidPassCORSAccessCheck() const;
+
+ // Cancels initialization, any pending loaders, and any pending read calls
+ // from the demuxer. The caller is expected to release its reference to this
+ // object and never call it again.
+ //
+ // Method called on the render thread.
+ void Abort();
+
+ // Notifies changes in playback state for controlling media buffering
+ // behavior.
+ void MediaPlaybackRateChanged(float playback_rate);
+ void MediaIsPlaying();
+ void MediaIsPaused();
+
+ // Returns true if the resource is local.
+ bool assume_fully_buffered() { return !url_.SchemeIsHTTPOrHTTPS(); }
+
+ // DataSource implementation.
+ // Called from demuxer thread.
+ virtual void Stop() OVERRIDE;
+
+ virtual void Read(int64 position, int size, uint8* data,
+ const DataSource::ReadCB& read_cb) OVERRIDE;
+ virtual bool GetSize(int64* size_out) OVERRIDE;
+ virtual bool IsStreaming() OVERRIDE;
+ virtual void SetBitrate(int bitrate) OVERRIDE;
+
+ protected:
+ // A factory method to create a BufferedResourceLoader based on the read
+ // parameters. We can override this file to object a mock
+ // BufferedResourceLoader for testing.
+ virtual BufferedResourceLoader* CreateResourceLoader(
+ int64 first_byte_position, int64 last_byte_position);
+
+ private:
+ friend class BufferedDataSourceTest;
+
+ // Task posted to perform actual reading on the render thread.
+ void ReadTask();
+
+ // Cancels oustanding callbacks and sets |stop_signal_received_|. Safe to call
+ // from any thread.
+ void StopInternal_Locked();
+
+ // Stops |loader_| if present. Used by Abort() and Stop().
+ void StopLoader();
+
+ // Tells |loader_| the bitrate of the media.
+ void SetBitrateTask(int bitrate);
+
+ // The method that performs actual read. This method can only be executed on
+ // the render thread.
+ void ReadInternal();
+
+ // BufferedResourceLoader::Start() callback for initial load.
+ void StartCallback(BufferedResourceLoader::Status status);
+
+ // BufferedResourceLoader::Start() callback for subsequent loads (i.e.,
+ // when accessing ranges that are outside initial buffered region).
+ void PartialReadStartCallback(BufferedResourceLoader::Status status);
+
+ // BufferedResourceLoader callbacks.
+ void ReadCallback(BufferedResourceLoader::Status status, int bytes_read);
+ void LoadingStateChangedCallback(BufferedResourceLoader::LoadingState state);
+ void ProgressCallback(int64 position);
+
+ // Update |loader_|'s deferring strategy in response to a play/pause, or
+ // change in playback rate.
+ void UpdateDeferStrategy(bool paused);
+
+ // URL of the resource requested.
+ GURL url_;
+ // crossorigin attribute on the corresponding HTML media element, if any.
+ BufferedResourceLoader::CORSMode cors_mode_;
+
+ // The total size of the resource. Set during StartCallback() if the size is
+ // known, otherwise it will remain kPositionNotSpecified until the size is
+ // determined by reaching EOF.
+ int64 total_bytes_;
+
+ // This value will be true if this data source can only support streaming.
+ // i.e. range request is not supported.
+ bool streaming_;
+
+ // A webframe for loading.
+ blink::WebFrame* frame_;
+
+ // A resource loader for the media resource.
+ scoped_ptr<BufferedResourceLoader> loader_;
+
+ // Callback method from the pipeline for initialization.
+ InitializeCB init_cb_;
+
+ // Read parameters received from the Read() method call. Must be accessed
+ // under |lock_|.
+ class ReadOperation;
+ scoped_ptr<ReadOperation> read_op_;
+
+ // This buffer is intermediate, we use it for BufferedResourceLoader to write
+ // to. And when read in BufferedResourceLoader is done, we copy data from
+ // this buffer to |read_buffer_|. The reason for an additional copy is that
+ // we don't own |read_buffer_|. But since the read operation is asynchronous,
+ // |read_buffer| can be destroyed at any time, so we only copy into
+ // |read_buffer| in the final step when it is safe.
+ // Memory is allocated for this member during initialization of this object
+ // because we want buffer to be passed into BufferedResourceLoader to be
+ // always non-null. And by initializing this member with a default size we can
+ // avoid creating zero-sized buffered if the first read has zero size.
+ scoped_ptr<uint8[]> intermediate_read_buffer_;
+ int intermediate_read_buffer_size_;
+
+ // The task runner of the render thread.
+ const scoped_refptr<base::SingleThreadTaskRunner> render_task_runner_;
+
+ // Protects |stop_signal_received_| and |read_op_|.
+ base::Lock lock_;
+
+ // Whether we've been told to stop via Abort() or Stop().
+ bool stop_signal_received_;
+
+ // This variable is true when the user has requested the video to play at
+ // least once.
+ bool media_has_played_;
+
+ // This variable holds the value of the preload attribute for the video
+ // element.
+ Preload preload_;
+
+ // Bitrate of the content, 0 if unknown.
+ int bitrate_;
+
+ // Current playback rate.
+ float playback_rate_;
+
+ scoped_refptr<MediaLog> media_log_;
+
+ // Host object to report buffered byte range changes to.
+ BufferedDataSourceHost* host_;
+
+ DownloadingCB downloading_cb_;
+
+ // NOTE: Weak pointers must be invalidated before all other member variables.
+ base::WeakPtrFactory<BufferedDataSource> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_
diff --git a/media/blink/buffered_data_source_host_impl.cc b/media/blink/buffered_data_source_host_impl.cc
new file mode 100644
index 0000000..42f9822
--- /dev/null
+++ b/media/blink/buffered_data_source_host_impl.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 "media/blink/buffered_data_source_host_impl.h"
+
+namespace media {
+
+BufferedDataSourceHostImpl::BufferedDataSourceHostImpl()
+ : total_bytes_(0),
+ did_loading_progress_(false) { }
+
+BufferedDataSourceHostImpl::~BufferedDataSourceHostImpl() { }
+
+void BufferedDataSourceHostImpl::SetTotalBytes(int64 total_bytes) {
+ total_bytes_ = total_bytes;
+}
+
+void BufferedDataSourceHostImpl::AddBufferedByteRange(int64 start, int64 end) {
+ buffered_byte_ranges_.Add(start, end);
+ did_loading_progress_ = true;
+}
+
+static base::TimeDelta TimeForByteOffset(
+ int64 byte_offset, int64 total_bytes, base::TimeDelta duration) {
+ double position = static_cast<double>(byte_offset) / total_bytes;
+ // Snap to the beginning/end where the approximation can look especially bad.
+ if (position < 0.01)
+ return base::TimeDelta();
+ if (position > 0.99)
+ return duration;
+ return base::TimeDelta::FromMilliseconds(
+ static_cast<int64>(position * duration.InMilliseconds()));
+}
+
+void BufferedDataSourceHostImpl::AddBufferedTimeRanges(
+ Ranges<base::TimeDelta>* buffered_time_ranges,
+ base::TimeDelta media_duration) const {
+ DCHECK(media_duration != kNoTimestamp());
+ DCHECK(media_duration != kInfiniteDuration());
+ if (total_bytes_ && buffered_byte_ranges_.size()) {
+ for (size_t i = 0; i < buffered_byte_ranges_.size(); ++i) {
+ int64 start = buffered_byte_ranges_.start(i);
+ int64 end = buffered_byte_ranges_.end(i);
+ buffered_time_ranges->Add(
+ TimeForByteOffset(start, total_bytes_, media_duration),
+ TimeForByteOffset(end, total_bytes_, media_duration));
+ }
+ }
+}
+
+bool BufferedDataSourceHostImpl::DidLoadingProgress() {
+ bool ret = did_loading_progress_;
+ did_loading_progress_ = false;
+ return ret;
+}
+
+} // namespace media
diff --git a/media/blink/buffered_data_source_host_impl.h b/media/blink/buffered_data_source_host_impl.h
new file mode 100644
index 0000000..fde5bb2
--- /dev/null
+++ b/media/blink/buffered_data_source_host_impl.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 MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_
+#define MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_
+
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/base/ranges.h"
+#include "media/blink/buffered_data_source.h"
+
+namespace media {
+
+// Provides an implementation of BufferedDataSourceHost that translates the
+// buffered byte ranges into estimated time ranges.
+class MEDIA_EXPORT BufferedDataSourceHostImpl
+ : public BufferedDataSourceHost {
+ public:
+ BufferedDataSourceHostImpl();
+ virtual ~BufferedDataSourceHostImpl();
+
+ // BufferedDataSourceHost implementation.
+ virtual void SetTotalBytes(int64 total_bytes) OVERRIDE;
+ virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE;
+
+ // Translate the byte ranges to time ranges and append them to the list.
+ // TODO(sandersd): This is a confusing name, find something better.
+ void AddBufferedTimeRanges(
+ Ranges<base::TimeDelta>* buffered_time_ranges,
+ base::TimeDelta media_duration) const;
+
+ bool DidLoadingProgress();
+
+ private:
+ // Total size of the data source.
+ int64 total_bytes_;
+
+ // List of buffered byte ranges for estimating buffered time.
+ Ranges<int64> buffered_byte_ranges_;
+
+ // True when AddBufferedByteRange() has been called more recently than
+ // DidLoadingProgress().
+ bool did_loading_progress_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceHostImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_
diff --git a/media/blink/buffered_data_source_host_impl_unittest.cc b/media/blink/buffered_data_source_host_impl_unittest.cc
new file mode 100644
index 0000000..ef0a461
--- /dev/null
+++ b/media/blink/buffered_data_source_host_impl_unittest.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 "media/blink/buffered_data_source_host_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class BufferedDataSourceHostImplTest : public testing::Test {
+ public:
+ BufferedDataSourceHostImplTest() {}
+
+ void Add() {
+ host_.AddBufferedTimeRanges(&ranges_, base::TimeDelta::FromSeconds(10));
+ }
+
+ protected:
+ BufferedDataSourceHostImpl host_;
+ Ranges<base::TimeDelta> ranges_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceHostImplTest);
+};
+
+TEST_F(BufferedDataSourceHostImplTest, Empty) {
+ EXPECT_FALSE(host_.DidLoadingProgress());
+ Add();
+ EXPECT_EQ(0u, ranges_.size());
+}
+
+TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges) {
+ host_.AddBufferedByteRange(10, 20);
+ host_.SetTotalBytes(100);
+ Add();
+ EXPECT_EQ(1u, ranges_.size());
+ EXPECT_EQ(base::TimeDelta::FromSeconds(1), ranges_.start(0));
+ EXPECT_EQ(base::TimeDelta::FromSeconds(2), ranges_.end(0));
+}
+
+TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges_Merges) {
+ ranges_.Add(base::TimeDelta::FromSeconds(0), base::TimeDelta::FromSeconds(1));
+ host_.AddBufferedByteRange(10, 20);
+ host_.SetTotalBytes(100);
+ Add();
+ EXPECT_EQ(1u, ranges_.size());
+ EXPECT_EQ(base::TimeDelta::FromSeconds(0), ranges_.start(0));
+ EXPECT_EQ(base::TimeDelta::FromSeconds(2), ranges_.end(0));
+}
+
+TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges_Snaps) {
+ host_.AddBufferedByteRange(5, 995);
+ host_.SetTotalBytes(1000);
+ Add();
+ EXPECT_EQ(1u, ranges_.size());
+ EXPECT_EQ(base::TimeDelta::FromSeconds(0), ranges_.start(0));
+ EXPECT_EQ(base::TimeDelta::FromSeconds(10), ranges_.end(0));
+}
+
+TEST_F(BufferedDataSourceHostImplTest, SetTotalBytes) {
+ host_.AddBufferedByteRange(10, 20);
+ Add();
+ EXPECT_EQ(0u, ranges_.size());
+
+ host_.SetTotalBytes(100);
+ Add();
+ EXPECT_EQ(1u, ranges_.size());
+}
+
+TEST_F(BufferedDataSourceHostImplTest, DidLoadingProgress) {
+ host_.AddBufferedByteRange(10, 20);
+ EXPECT_TRUE(host_.DidLoadingProgress());
+ EXPECT_FALSE(host_.DidLoadingProgress());
+}
+
+} // namespace media
diff --git a/media/blink/buffered_data_source_unittest.cc b/media/blink/buffered_data_source_unittest.cc
new file mode 100644
index 0000000..a76beb0
--- /dev/null
+++ b/media/blink/buffered_data_source_unittest.cc
@@ -0,0 +1,779 @@
+// Copyright 2013 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/message_loop/message_loop.h"
+#include "media/base/media_log.h"
+#include "media/base/mock_filters.h"
+#include "media/base/test_helpers.h"
+#include "media/blink/buffered_data_source.h"
+#include "media/blink/mock_webframeclient.h"
+#include "media/blink/mock_weburlloader.h"
+#include "media/blink/test_response_generator.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::Invoke;
+using ::testing::InSequence;
+using ::testing::NiceMock;
+using ::testing::StrictMock;
+
+using blink::WebLocalFrame;
+using blink::WebString;
+using blink::WebURLLoader;
+using blink::WebURLResponse;
+using blink::WebView;
+
+namespace media {
+
+class MockBufferedDataSourceHost : public BufferedDataSourceHost {
+ public:
+ MockBufferedDataSourceHost() {}
+ virtual ~MockBufferedDataSourceHost() {}
+
+ MOCK_METHOD1(SetTotalBytes, void(int64 total_bytes));
+ MOCK_METHOD2(AddBufferedByteRange, void(int64 start, int64 end));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSourceHost);
+};
+
+// Overrides CreateResourceLoader() to permit injecting a MockWebURLLoader.
+// Also keeps track of whether said MockWebURLLoader is actively loading.
+class MockBufferedDataSource : public BufferedDataSource {
+ public:
+ MockBufferedDataSource(
+ const GURL& url,
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ WebLocalFrame* frame,
+ BufferedDataSourceHost* host)
+ : BufferedDataSource(url,
+ BufferedResourceLoader::kUnspecified,
+ task_runner,
+ frame,
+ new media::MediaLog(),
+ host,
+ base::Bind(&MockBufferedDataSource::set_downloading,
+ base::Unretained(this))),
+ downloading_(false),
+ loading_(false) {}
+ virtual ~MockBufferedDataSource() {}
+
+ MOCK_METHOD2(CreateResourceLoader, BufferedResourceLoader*(int64, int64));
+ BufferedResourceLoader* CreateMockResourceLoader(int64 first_byte_position,
+ int64 last_byte_position) {
+ CHECK(!loading_) << "Previous resource load wasn't cancelled";
+
+ BufferedResourceLoader* loader =
+ BufferedDataSource::CreateResourceLoader(first_byte_position,
+ last_byte_position);
+
+ // Keep track of active loading state via loadAsynchronously() and cancel().
+ NiceMock<MockWebURLLoader>* url_loader = new NiceMock<MockWebURLLoader>();
+ ON_CALL(*url_loader, loadAsynchronously(_, _))
+ .WillByDefault(Assign(&loading_, true));
+ ON_CALL(*url_loader, cancel())
+ .WillByDefault(Assign(&loading_, false));
+
+ // |test_loader_| will be used when Start() is called.
+ loader->test_loader_ = scoped_ptr<WebURLLoader>(url_loader);
+ return loader;
+ }
+
+ bool loading() { return loading_; }
+ void set_loading(bool loading) { loading_ = loading; }
+ bool downloading() { return downloading_; }
+ void set_downloading(bool downloading) { downloading_ = downloading; }
+
+ private:
+ // Whether the resource is downloading or deferred.
+ bool downloading_;
+
+ // Whether the resource load has starting loading but yet to been cancelled.
+ bool loading_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource);
+};
+
+static const int64 kFileSize = 5000000;
+static const int64 kFarReadPosition = 4000000;
+static const int kDataSize = 1024;
+
+static const char kHttpUrl[] = "http://localhost/foo.webm";
+static const char kFileUrl[] = "file:///tmp/bar.webm";
+
+class BufferedDataSourceTest : public testing::Test {
+ public:
+ BufferedDataSourceTest()
+ : view_(WebView::create(NULL)),
+ frame_(WebLocalFrame::create(&client_)),
+ preload_(BufferedDataSource::AUTO) {
+ view_->setMainFrame(frame_);
+ }
+
+ virtual ~BufferedDataSourceTest() {
+ view_->close();
+ frame_->close();
+ }
+
+ MOCK_METHOD1(OnInitialize, void(bool));
+
+ void Initialize(const char* url, bool expected) {
+ GURL gurl(url);
+ data_source_.reset(
+ new MockBufferedDataSource(gurl,
+ message_loop_.message_loop_proxy(),
+ view_->mainFrame()->toWebLocalFrame(),
+ &host_));
+ data_source_->SetPreload(preload_);
+
+ response_generator_.reset(new TestResponseGenerator(gurl, kFileSize));
+ ExpectCreateResourceLoader();
+ EXPECT_CALL(*this, OnInitialize(expected));
+ data_source_->Initialize(base::Bind(&BufferedDataSourceTest::OnInitialize,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+
+ bool is_http = gurl.SchemeIsHTTPOrHTTPS();
+ EXPECT_EQ(data_source_->downloading(), is_http);
+ }
+
+ // Helper to initialize tests with a valid 200 response.
+ void InitializeWith200Response() {
+ Initialize(kHttpUrl, true);
+
+ EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length()));
+ Respond(response_generator_->Generate200());
+ }
+
+ // Helper to initialize tests with a valid 206 response.
+ void InitializeWith206Response() {
+ Initialize(kHttpUrl, true);
+
+ EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length()));
+ Respond(response_generator_->Generate206(0));
+ }
+
+ // Helper to initialize tests with a valid file:// response.
+ void InitializeWithFileResponse() {
+ Initialize(kFileUrl, true);
+
+ EXPECT_CALL(host_, SetTotalBytes(kFileSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kFileSize));
+ Respond(response_generator_->GenerateFileResponse(0));
+ }
+
+ // Stops any active loaders and shuts down the data source.
+ //
+ // This typically happens when the page is closed and for our purposes is
+ // appropriate to do when tearing down a test.
+ void Stop() {
+ if (data_source_->loading()) {
+ loader()->didFail(url_loader(), response_generator_->GenerateError());
+ message_loop_.RunUntilIdle();
+ }
+
+ data_source_->Stop();
+ message_loop_.RunUntilIdle();
+ }
+
+ void ExpectCreateResourceLoader() {
+ EXPECT_CALL(*data_source_, CreateResourceLoader(_, _))
+ .WillOnce(Invoke(data_source_.get(),
+ &MockBufferedDataSource::CreateMockResourceLoader));
+ message_loop_.RunUntilIdle();
+ }
+
+ void Respond(const WebURLResponse& response) {
+ loader()->didReceiveResponse(url_loader(), response);
+ message_loop_.RunUntilIdle();
+ }
+
+ void ReceiveData(int size) {
+ scoped_ptr<char[]> data(new char[size]);
+ memset(data.get(), 0xA5, size); // Arbitrary non-zero value.
+
+ loader()->didReceiveData(url_loader(), data.get(), size, size);
+ message_loop_.RunUntilIdle();
+ }
+
+ void FinishLoading() {
+ data_source_->set_loading(false);
+ loader()->didFinishLoading(url_loader(), 0, -1);
+ message_loop_.RunUntilIdle();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(int size));
+
+ void ReadAt(int64 position) {
+ data_source_->Read(position, kDataSize, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ }
+
+ // Accessors for private variables on |data_source_|.
+ BufferedResourceLoader* loader() {
+ return data_source_->loader_.get();
+ }
+ WebURLLoader* url_loader() {
+ return loader()->active_loader_->loader_.get();
+ }
+
+ BufferedDataSource::Preload preload() { return data_source_->preload_; }
+ void set_preload(BufferedDataSource::Preload preload) { preload_ = preload; }
+ BufferedResourceLoader::DeferStrategy defer_strategy() {
+ return loader()->defer_strategy_;
+ }
+ int data_source_bitrate() { return data_source_->bitrate_; }
+ int data_source_playback_rate() { return data_source_->playback_rate_; }
+ int loader_bitrate() { return loader()->bitrate_; }
+ int loader_playback_rate() { return loader()->playback_rate_; }
+ bool is_local_source() { return data_source_->assume_fully_buffered(); }
+ void set_might_be_reused_from_cache_in_future(bool value) {
+ loader()->might_be_reused_from_cache_in_future_ = value;
+ }
+
+ scoped_ptr<MockBufferedDataSource> data_source_;
+
+ scoped_ptr<TestResponseGenerator> response_generator_;
+ MockWebFrameClient client_;
+ WebView* view_;
+ WebLocalFrame* frame_;
+
+ StrictMock<MockBufferedDataSourceHost> host_;
+ base::MessageLoop message_loop_;
+
+ private:
+ // Used for calling BufferedDataSource::Read().
+ uint8 buffer_[kDataSize];
+
+ BufferedDataSource::Preload preload_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest);
+};
+
+TEST_F(BufferedDataSourceTest, Range_Supported) {
+ InitializeWith206Response();
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_FALSE(data_source_->IsStreaming());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_InstanceSizeUnknown) {
+ Initialize(kHttpUrl, true);
+
+ Respond(response_generator_->Generate206(
+ 0, TestResponseGenerator::kNoContentRangeInstanceSize));
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_TRUE(data_source_->IsStreaming());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_NotFound) {
+ Initialize(kHttpUrl, false);
+ Respond(response_generator_->Generate404());
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_NotSupported) {
+ InitializeWith200Response();
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_TRUE(data_source_->IsStreaming());
+ Stop();
+}
+
+// Special carve-out for Apache versions that choose to return a 200 for
+// Range:0- ("because it's more efficient" than a 206)
+TEST_F(BufferedDataSourceTest, Range_SupportedButReturned200) {
+ Initialize(kHttpUrl, true);
+ EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length()));
+ WebURLResponse response = response_generator_->Generate200();
+ response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
+ WebString::fromUTF8("bytes"));
+ Respond(response);
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_FALSE(data_source_->IsStreaming());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_MissingContentRange) {
+ Initialize(kHttpUrl, false);
+ Respond(response_generator_->Generate206(
+ 0, TestResponseGenerator::kNoContentRange));
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_MissingContentLength) {
+ Initialize(kHttpUrl, true);
+
+ // It'll manage without a Content-Length response.
+ EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length()));
+ Respond(response_generator_->Generate206(
+ 0, TestResponseGenerator::kNoContentLength));
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_FALSE(data_source_->IsStreaming());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Range_WrongContentRange) {
+ Initialize(kHttpUrl, false);
+
+ // Now it's done and will fail.
+ Respond(response_generator_->Generate206(1337));
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+// Test the case where the initial response from the server indicates that
+// Range requests are supported, but a later request prove otherwise.
+TEST_F(BufferedDataSourceTest, Range_ServerLied) {
+ InitializeWith206Response();
+
+ // Read causing a new request to be made -- we'll expect it to error.
+ ExpectCreateResourceLoader();
+ ReadAt(kFarReadPosition);
+
+ // Return a 200 in response to a range request.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ Respond(response_generator_->Generate200());
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_AbortWhileReading) {
+ InitializeWith206Response();
+
+ // Make sure there's a pending read -- we'll expect it to error.
+ ReadAt(0);
+
+ // Abort!!!
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ data_source_->Abort();
+ message_loop_.RunUntilIdle();
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_AbortWhileReading) {
+ InitializeWithFileResponse();
+
+ // Make sure there's a pending read -- we'll expect it to error.
+ ReadAt(0);
+
+ // Abort!!!
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ data_source_->Abort();
+ message_loop_.RunUntilIdle();
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_Retry) {
+ InitializeWith206Response();
+
+ // Read to advance our position.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1));
+ ReadAt(0);
+ ReceiveData(kDataSize);
+
+ // Issue a pending read but terminate the connection to force a retry.
+ ReadAt(kDataSize);
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->Generate206(kDataSize));
+
+ // Complete the read.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(kDataSize, (kDataSize * 2) - 1));
+ ReceiveData(kDataSize);
+
+ EXPECT_TRUE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_Retry) {
+ InitializeWithFileResponse();
+
+ // Read to advance our position.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ ReadAt(0);
+ ReceiveData(kDataSize);
+
+ // Issue a pending read but terminate the connection to force a retry.
+ ReadAt(kDataSize);
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->GenerateFileResponse(kDataSize));
+
+ // Complete the read.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ ReceiveData(kDataSize);
+
+ EXPECT_TRUE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_TooManyRetries) {
+ InitializeWith206Response();
+
+ // Make sure there's a pending read -- we'll expect it to error.
+ ReadAt(0);
+
+ // It'll try three times.
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->Generate206(0));
+
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->Generate206(0));
+
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->Generate206(0));
+
+ // It'll error after this.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ FinishLoading();
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_TooManyRetries) {
+ InitializeWithFileResponse();
+
+ // Make sure there's a pending read -- we'll expect it to error.
+ ReadAt(0);
+
+ // It'll try three times.
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->GenerateFileResponse(0));
+
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->GenerateFileResponse(0));
+
+ ExpectCreateResourceLoader();
+ FinishLoading();
+ Respond(response_generator_->GenerateFileResponse(0));
+
+ // It'll error after this.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ FinishLoading();
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_InstanceSizeUnknown) {
+ Initialize(kFileUrl, false);
+ EXPECT_FALSE(data_source_->downloading());
+
+ Respond(response_generator_->GenerateFileResponse(-1));
+
+ EXPECT_FALSE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_Successful) {
+ InitializeWithFileResponse();
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_FALSE(data_source_->IsStreaming());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, StopDuringRead) {
+ InitializeWith206Response();
+
+ uint8 buffer[256];
+ data_source_->Read(0, arraysize(buffer), buffer, base::Bind(
+ &BufferedDataSourceTest::ReadCallback, base::Unretained(this)));
+
+ // The outstanding read should fail before the stop callback runs.
+ {
+ InSequence s;
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ data_source_->Stop();
+ }
+ message_loop_.RunUntilIdle();
+}
+
+TEST_F(BufferedDataSourceTest, DefaultValues) {
+ InitializeWith206Response();
+
+ // Ensure we have sane values for default loading scenario.
+ EXPECT_EQ(BufferedDataSource::AUTO, preload());
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ EXPECT_EQ(0, data_source_bitrate());
+ EXPECT_EQ(0.0f, data_source_playback_rate());
+ EXPECT_EQ(0, loader_bitrate());
+ EXPECT_EQ(0.0f, loader_playback_rate());
+
+ EXPECT_TRUE(data_source_->loading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, SetBitrate) {
+ InitializeWith206Response();
+
+ data_source_->SetBitrate(1234);
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(1234, data_source_bitrate());
+ EXPECT_EQ(1234, loader_bitrate());
+
+ // Read so far ahead to cause the loader to get recreated.
+ BufferedResourceLoader* old_loader = loader();
+ ExpectCreateResourceLoader();
+ ReadAt(kFarReadPosition);
+ Respond(response_generator_->Generate206(kFarReadPosition));
+
+ // Verify loader changed but still has same bitrate.
+ EXPECT_NE(old_loader, loader());
+ EXPECT_EQ(1234, loader_bitrate());
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, MediaPlaybackRateChanged) {
+ InitializeWith206Response();
+
+ data_source_->MediaPlaybackRateChanged(2.0f);
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(2.0f, data_source_playback_rate());
+ EXPECT_EQ(2.0f, loader_playback_rate());
+
+ // Read so far ahead to cause the loader to get recreated.
+ BufferedResourceLoader* old_loader = loader();
+ ExpectCreateResourceLoader();
+ ReadAt(kFarReadPosition);
+ Respond(response_generator_->Generate206(kFarReadPosition));
+
+ // Verify loader changed but still has same playback rate.
+ EXPECT_NE(old_loader, loader());
+
+ EXPECT_TRUE(data_source_->loading());
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_Read) {
+ InitializeWith206Response();
+
+ ReadAt(0);
+
+ // Receive first half of the read.
+ EXPECT_CALL(host_, AddBufferedByteRange(0, (kDataSize / 2) - 1));
+ ReceiveData(kDataSize / 2);
+
+ // Receive last half of the read.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1));
+ ReceiveData(kDataSize / 2);
+
+ EXPECT_TRUE(data_source_->downloading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_Read_Seek) {
+ InitializeWith206Response();
+
+ // Read a bit from the beginning.
+ ReadAt(0);
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1));
+ ReceiveData(kDataSize);
+
+ // Simulate a seek by reading a bit beyond kDataSize.
+ ReadAt(kDataSize * 2);
+
+ // We receive data leading up to but not including our read.
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2 - 1));
+ ReceiveData(kDataSize);
+
+ // We now receive the rest of the data for our read.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3 - 1));
+ ReceiveData(kDataSize);
+
+ EXPECT_TRUE(data_source_->downloading());
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_Read) {
+ InitializeWithFileResponse();
+
+ ReadAt(0);
+
+ // Receive first half of the read but no buffering update.
+ ReceiveData(kDataSize / 2);
+
+ // Receive last half of the read but no buffering update.
+ EXPECT_CALL(*this, ReadCallback(kDataSize));
+ ReceiveData(kDataSize / 2);
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, Http_FinishLoading) {
+ InitializeWith206Response();
+
+ EXPECT_TRUE(data_source_->downloading());
+ FinishLoading();
+ EXPECT_FALSE(data_source_->downloading());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, File_FinishLoading) {
+ InitializeWithFileResponse();
+
+ EXPECT_FALSE(data_source_->downloading());
+ FinishLoading();
+ EXPECT_FALSE(data_source_->downloading());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, LocalResource_DeferStrategy) {
+ InitializeWithFileResponse();
+
+ EXPECT_EQ(BufferedDataSource::AUTO, preload());
+ EXPECT_TRUE(is_local_source());
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, LocalResource_PreloadMetadata_DeferStrategy) {
+ set_preload(BufferedDataSource::METADATA);
+ InitializeWithFileResponse();
+
+ EXPECT_EQ(BufferedDataSource::METADATA, preload());
+ EXPECT_TRUE(is_local_source());
+ EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, ExternalResource_Reponse200_DeferStrategy) {
+ InitializeWith200Response();
+
+ EXPECT_EQ(BufferedDataSource::AUTO, preload());
+ EXPECT_FALSE(is_local_source());
+ EXPECT_FALSE(loader()->range_supported());
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest,
+ ExternalResource_Response200_PreloadMetadata_DeferStrategy) {
+ set_preload(BufferedDataSource::METADATA);
+ InitializeWith200Response();
+
+ EXPECT_EQ(BufferedDataSource::METADATA, preload());
+ EXPECT_FALSE(is_local_source());
+ EXPECT_FALSE(loader()->range_supported());
+ EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest, ExternalResource_Reponse206_DeferStrategy) {
+ InitializeWith206Response();
+
+ EXPECT_EQ(BufferedDataSource::AUTO, preload());
+ EXPECT_FALSE(is_local_source());
+ EXPECT_TRUE(loader()->range_supported());
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+ set_might_be_reused_from_cache_in_future(true);
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kNeverDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+ set_might_be_reused_from_cache_in_future(false);
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+TEST_F(BufferedDataSourceTest,
+ ExternalResource_Response206_PreloadMetadata_DeferStrategy) {
+ set_preload(BufferedDataSource::METADATA);
+ InitializeWith206Response();
+
+ EXPECT_EQ(BufferedDataSource::METADATA, preload());
+ EXPECT_FALSE(is_local_source());
+ EXPECT_TRUE(loader()->range_supported());
+ EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+ set_might_be_reused_from_cache_in_future(true);
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kNeverDefer, defer_strategy());
+
+ data_source_->MediaIsPlaying();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+ set_might_be_reused_from_cache_in_future(false);
+ data_source_->MediaIsPaused();
+ EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy());
+
+ Stop();
+}
+
+} // namespace media
diff --git a/media/blink/buffered_resource_loader.cc b/media/blink/buffered_resource_loader.cc
new file mode 100644
index 0000000..d88266d
--- /dev/null
+++ b/media/blink/buffered_resource_loader.cc
@@ -0,0 +1,790 @@
+// Copyright 2013 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 "media/blink/buffered_resource_loader.h"
+
+#include "base/bits.h"
+#include "base/callback_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "media/base/media_log.h"
+#include "media/blink/cache_util.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+#include "third_party/WebKit/public/web/WebKit.h"
+#include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
+
+using blink::WebFrame;
+using blink::WebString;
+using blink::WebURLError;
+using blink::WebURLLoader;
+using blink::WebURLLoaderOptions;
+using blink::WebURLRequest;
+using blink::WebURLResponse;
+
+namespace media {
+
+static const int kHttpOK = 200;
+static const int kHttpPartialContent = 206;
+
+// Define the number of bytes in a megabyte.
+static const int kMegabyte = 1024 * 1024;
+
+// Minimum capacity of the buffer in forward or backward direction.
+//
+// 2MB is an arbitrary limit; it just seems to be "good enough" in practice.
+static const int kMinBufferCapacity = 2 * kMegabyte;
+
+// Maximum capacity of the buffer in forward or backward direction. This is
+// effectively the largest single read the code path can handle.
+// 20MB is an arbitrary limit; it just seems to be "good enough" in practice.
+static const int kMaxBufferCapacity = 20 * kMegabyte;
+
+// Maximum number of bytes outside the buffer we will wait for in order to
+// fulfill a read. If a read starts more than 2MB away from the data we
+// currently have in the buffer, we will not wait for buffer to reach the read's
+// location and will instead reset the request.
+static const int kForwardWaitThreshold = 2 * kMegabyte;
+
+// Computes the suggested backward and forward capacity for the buffer
+// if one wants to play at |playback_rate| * the natural playback speed.
+// Use a value of 0 for |bitrate| if it is unknown.
+static void ComputeTargetBufferWindow(float playback_rate, int bitrate,
+ int* out_backward_capacity,
+ int* out_forward_capacity) {
+ static const int kDefaultBitrate = 200 * 1024 * 8; // 200 Kbps.
+ static const int kMaxBitrate = 20 * kMegabyte * 8; // 20 Mbps.
+ static const float kMaxPlaybackRate = 25.0;
+ static const int kTargetSecondsBufferedAhead = 10;
+ static const int kTargetSecondsBufferedBehind = 2;
+
+ // Use a default bit rate if unknown and clamp to prevent overflow.
+ if (bitrate <= 0)
+ bitrate = kDefaultBitrate;
+ bitrate = std::min(bitrate, kMaxBitrate);
+
+ // Only scale the buffer window for playback rates greater than 1.0 in
+ // magnitude and clamp to prevent overflow.
+ bool backward_playback = false;
+ if (playback_rate < 0.0f) {
+ backward_playback = true;
+ playback_rate *= -1.0f;
+ }
+
+ playback_rate = std::max(playback_rate, 1.0f);
+ playback_rate = std::min(playback_rate, kMaxPlaybackRate);
+
+ int bytes_per_second = (bitrate / 8.0) * playback_rate;
+
+ // Clamp between kMinBufferCapacity and kMaxBufferCapacity.
+ *out_forward_capacity = std::max(
+ kTargetSecondsBufferedAhead * bytes_per_second, kMinBufferCapacity);
+ *out_backward_capacity = std::max(
+ kTargetSecondsBufferedBehind * bytes_per_second, kMinBufferCapacity);
+
+ *out_forward_capacity = std::min(*out_forward_capacity, kMaxBufferCapacity);
+ *out_backward_capacity = std::min(*out_backward_capacity, kMaxBufferCapacity);
+
+ if (backward_playback)
+ std::swap(*out_forward_capacity, *out_backward_capacity);
+}
+
+BufferedResourceLoader::BufferedResourceLoader(
+ const GURL& url,
+ CORSMode cors_mode,
+ int64 first_byte_position,
+ int64 last_byte_position,
+ DeferStrategy strategy,
+ int bitrate,
+ float playback_rate,
+ MediaLog* media_log)
+ : buffer_(kMinBufferCapacity, kMinBufferCapacity),
+ loader_failed_(false),
+ defer_strategy_(strategy),
+ might_be_reused_from_cache_in_future_(true),
+ range_supported_(false),
+ saved_forward_capacity_(0),
+ url_(url),
+ cors_mode_(cors_mode),
+ first_byte_position_(first_byte_position),
+ last_byte_position_(last_byte_position),
+ single_origin_(true),
+ offset_(0),
+ content_length_(kPositionNotSpecified),
+ instance_size_(kPositionNotSpecified),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ first_offset_(0),
+ last_offset_(0),
+ bitrate_(bitrate),
+ playback_rate_(playback_rate),
+ media_log_(media_log) {
+
+ // Set the initial capacity of |buffer_| based on |bitrate_| and
+ // |playback_rate_|.
+ UpdateBufferWindow();
+}
+
+BufferedResourceLoader::~BufferedResourceLoader() {}
+
+void BufferedResourceLoader::Start(
+ const StartCB& start_cb,
+ const LoadingStateChangedCB& loading_cb,
+ const ProgressCB& progress_cb,
+ WebFrame* frame) {
+ // Make sure we have not started.
+ DCHECK(start_cb_.is_null());
+ DCHECK(loading_cb_.is_null());
+ DCHECK(progress_cb_.is_null());
+ DCHECK(!start_cb.is_null());
+ DCHECK(!loading_cb.is_null());
+ DCHECK(!progress_cb.is_null());
+ CHECK(frame);
+
+ start_cb_ = start_cb;
+ loading_cb_ = loading_cb;
+ progress_cb_ = progress_cb;
+
+ if (first_byte_position_ != kPositionNotSpecified) {
+ // TODO(hclam): server may not support range request so |offset_| may not
+ // equal to |first_byte_position_|.
+ offset_ = first_byte_position_;
+ }
+
+ // Prepare the request.
+ WebURLRequest request(url_);
+ // TODO(mkwst): Split this into video/audio.
+ request.setRequestContext(WebURLRequest::RequestContextVideo);
+
+ if (IsRangeRequest()) {
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kRange),
+ WebString::fromUTF8(net::HttpByteRange::Bounded(
+ first_byte_position_, last_byte_position_).GetHeaderValue()));
+ }
+
+ frame->setReferrerForRequest(request, blink::WebURL());
+
+ // Disable compression, compression for audio/video doesn't make sense...
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
+ WebString::fromUTF8("identity;q=1, *;q=0"));
+
+ // Check for our test WebURLLoader.
+ scoped_ptr<WebURLLoader> loader;
+ if (test_loader_) {
+ loader = test_loader_.Pass();
+ } else {
+ WebURLLoaderOptions options;
+ if (cors_mode_ == kUnspecified) {
+ options.allowCredentials = true;
+ options.crossOriginRequestPolicy =
+ WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
+ } else {
+ options.exposeAllResponseHeaders = true;
+ // The author header set is empty, no preflight should go ahead.
+ options.preflightPolicy = WebURLLoaderOptions::PreventPreflight;
+ options.crossOriginRequestPolicy =
+ WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
+ if (cors_mode_ == kUseCredentials)
+ options.allowCredentials = true;
+ }
+ loader.reset(frame->createAssociatedURLLoader(options));
+ }
+
+ // Start the resource loading.
+ loader->loadAsynchronously(request, this);
+ active_loader_.reset(new ActiveLoader(loader.Pass()));
+ loading_cb_.Run(kLoading);
+}
+
+void BufferedResourceLoader::Stop() {
+ // Reset callbacks.
+ start_cb_.Reset();
+ loading_cb_.Reset();
+ progress_cb_.Reset();
+ read_cb_.Reset();
+
+ // Cancel and reset any active loaders.
+ active_loader_.reset();
+}
+
+void BufferedResourceLoader::Read(
+ int64 position,
+ int read_size,
+ uint8* buffer,
+ const ReadCB& read_cb) {
+ DCHECK(start_cb_.is_null());
+ DCHECK(read_cb_.is_null());
+ DCHECK(!read_cb.is_null());
+ DCHECK(buffer);
+ DCHECK_GT(read_size, 0);
+
+ // Save the parameter of reading.
+ read_cb_ = read_cb;
+ read_position_ = position;
+ read_size_ = read_size;
+ read_buffer_ = buffer;
+
+ // Reads should immediately fail if the loader also failed.
+ if (loader_failed_) {
+ DoneRead(kFailed, 0);
+ return;
+ }
+
+ // If we're attempting to read past the end of the file, return a zero
+ // indicating EOF.
+ //
+ // This can happen with callees that read in fixed-sized amounts for parsing
+ // or at the end of chunked 200 responses when we discover the actual length
+ // of the file.
+ if (instance_size_ != kPositionNotSpecified &&
+ instance_size_ <= read_position_) {
+ DVLOG(1) << "Appear to have seeked beyond EOS; returning 0.";
+ DoneRead(kOk, 0);
+ return;
+ }
+
+ // Make sure |offset_| and |read_position_| does not differ by a large
+ // amount.
+ if (read_position_ > offset_ + kint32max ||
+ read_position_ < offset_ + kint32min) {
+ DoneRead(kCacheMiss, 0);
+ return;
+ }
+
+ // Make sure |read_size_| is not too large for the buffer to ever be able to
+ // fulfill the read request.
+ if (read_size_ > kMaxBufferCapacity) {
+ DoneRead(kFailed, 0);
+ return;
+ }
+
+ // Prepare the parameters.
+ first_offset_ = read_position_ - offset_;
+ last_offset_ = first_offset_ + read_size_;
+
+ // If we can serve the request now, do the actual read.
+ if (CanFulfillRead()) {
+ ReadInternal();
+ UpdateDeferBehavior();
+ return;
+ }
+
+ // If we expect the read request to be fulfilled later, expand capacity as
+ // necessary and disable deferring.
+ if (WillFulfillRead()) {
+ // Advance offset as much as possible to create additional capacity.
+ int advance = std::min(first_offset_, buffer_.forward_bytes());
+ bool ret = buffer_.Seek(advance);
+ DCHECK(ret);
+
+ offset_ += advance;
+ first_offset_ -= advance;
+ last_offset_ -= advance;
+
+ // Expand capacity to accomodate a read that extends past the normal
+ // capacity.
+ //
+ // This can happen when reading in a large seek index or when the
+ // first byte of a read request falls within kForwardWaitThreshold.
+ if (last_offset_ > buffer_.forward_capacity()) {
+ saved_forward_capacity_ = buffer_.forward_capacity();
+ buffer_.set_forward_capacity(last_offset_);
+ }
+
+ // Make sure we stop deferring now that there's additional capacity.
+ DCHECK(!ShouldDefer())
+ << "Capacity was not adjusted properly to prevent deferring.";
+ UpdateDeferBehavior();
+
+ return;
+ }
+
+ // Make a callback to report failure.
+ DoneRead(kCacheMiss, 0);
+}
+
+int64 BufferedResourceLoader::content_length() {
+ return content_length_;
+}
+
+int64 BufferedResourceLoader::instance_size() {
+ return instance_size_;
+}
+
+bool BufferedResourceLoader::range_supported() {
+ return range_supported_;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// blink::WebURLLoaderClient implementation.
+void BufferedResourceLoader::willSendRequest(
+ WebURLLoader* loader,
+ WebURLRequest& newRequest,
+ const WebURLResponse& redirectResponse) {
+
+ // The load may have been stopped and |start_cb| is destroyed.
+ // In this case we shouldn't do anything.
+ if (start_cb_.is_null()) {
+ // Set the url in the request to an invalid value (empty url).
+ newRequest.setURL(blink::WebURL());
+ return;
+ }
+
+ // Only allow |single_origin_| if we haven't seen a different origin yet.
+ if (single_origin_)
+ single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
+
+ url_ = newRequest.url();
+}
+
+void BufferedResourceLoader::didSendData(
+ WebURLLoader* loader,
+ unsigned long long bytes_sent,
+ unsigned long long total_bytes_to_be_sent) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didReceiveResponse(
+ WebURLLoader* loader,
+ const WebURLResponse& response) {
+ DVLOG(1) << "didReceiveResponse: HTTP/"
+ << (response.httpVersion() == WebURLResponse::HTTP_0_9 ? "0.9" :
+ response.httpVersion() == WebURLResponse::HTTP_1_0 ? "1.0" :
+ response.httpVersion() == WebURLResponse::HTTP_1_1 ? "1.1" :
+ "Unknown")
+ << " " << response.httpStatusCode();
+ DCHECK(active_loader_.get());
+
+ // The loader may have been stopped and |start_cb| is destroyed.
+ // In this case we shouldn't do anything.
+ if (start_cb_.is_null())
+ return;
+
+ uint32 reasons = GetReasonsForUncacheability(response);
+ might_be_reused_from_cache_in_future_ = reasons == 0;
+ UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
+ int shift = 0;
+ int max_enum = base::bits::Log2Ceiling(kMaxReason);
+ while (reasons) {
+ DCHECK_LT(shift, max_enum); // Sanity check.
+ if (reasons & 0x1) {
+ UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason",
+ shift,
+ max_enum); // PRESUBMIT_IGNORE_UMA_MAX
+ }
+
+ reasons >>= 1;
+ ++shift;
+ }
+
+ // Expected content length can be |kPositionNotSpecified|, in that case
+ // |content_length_| is not specified and this is a streaming response.
+ content_length_ = response.expectedContentLength();
+
+ // We make a strong assumption that when we reach here we have either
+ // received a response from HTTP/HTTPS protocol or the request was
+ // successful (in particular range request). So we only verify the partial
+ // response for HTTP and HTTPS protocol.
+ if (url_.SchemeIsHTTPOrHTTPS()) {
+ bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
+ bool ok_response = (response.httpStatusCode() == kHttpOK);
+
+ if (IsRangeRequest()) {
+ // Check to see whether the server supports byte ranges.
+ std::string accept_ranges =
+ response.httpHeaderField("Accept-Ranges").utf8();
+ range_supported_ = (accept_ranges.find("bytes") != std::string::npos);
+
+ // If we have verified the partial response and it is correct, we will
+ // return kOk. It's also possible for a server to support range requests
+ // without advertising "Accept-Ranges: bytes".
+ if (partial_response && VerifyPartialResponse(response)) {
+ range_supported_ = true;
+ } else if (ok_response && first_byte_position_ == 0 &&
+ last_byte_position_ == kPositionNotSpecified) {
+ // We accept a 200 response for a Range:0- request, trusting the
+ // Accept-Ranges header, because Apache thinks that's a reasonable thing
+ // to return.
+ instance_size_ = content_length_;
+ } else {
+ DoneStart(kFailed);
+ return;
+ }
+ } else {
+ instance_size_ = content_length_;
+ if (response.httpStatusCode() != kHttpOK) {
+ // We didn't request a range but server didn't reply with "200 OK".
+ DoneStart(kFailed);
+ return;
+ }
+ }
+
+ } else {
+ CHECK_EQ(instance_size_, kPositionNotSpecified);
+ if (content_length_ != kPositionNotSpecified) {
+ if (first_byte_position_ == kPositionNotSpecified)
+ instance_size_ = content_length_;
+ else if (last_byte_position_ == kPositionNotSpecified)
+ instance_size_ = content_length_ + first_byte_position_;
+ }
+ }
+
+ // Calls with a successful response.
+ DoneStart(kOk);
+}
+
+void BufferedResourceLoader::didReceiveData(
+ WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) {
+ DVLOG(1) << "didReceiveData: " << data_length << " bytes";
+ DCHECK(active_loader_.get());
+ DCHECK_GT(data_length, 0);
+
+ buffer_.Append(reinterpret_cast<const uint8*>(data), data_length);
+
+ // If there is an active read request, try to fulfill the request.
+ if (HasPendingRead() && CanFulfillRead())
+ ReadInternal();
+
+ // At last see if the buffer is full and we need to defer the downloading.
+ UpdateDeferBehavior();
+
+ // Consume excess bytes from our in-memory buffer if necessary.
+ if (buffer_.forward_bytes() > buffer_.forward_capacity()) {
+ int excess = buffer_.forward_bytes() - buffer_.forward_capacity();
+ bool success = buffer_.Seek(excess);
+ DCHECK(success);
+ offset_ += first_offset_ + excess;
+ }
+
+ // Notify latest progress and buffered offset.
+ progress_cb_.Run(offset_ + buffer_.forward_bytes() - 1);
+ Log();
+}
+
+void BufferedResourceLoader::didDownloadData(
+ blink::WebURLLoader* loader,
+ int dataLength,
+ int encoded_data_length) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didReceiveCachedMetadata(
+ WebURLLoader* loader,
+ const char* data,
+ int data_length) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didFinishLoading(
+ WebURLLoader* loader,
+ double finishTime,
+ int64_t total_encoded_data_length) {
+ DVLOG(1) << "didFinishLoading";
+ DCHECK(active_loader_.get());
+
+ // We're done with the loader.
+ active_loader_.reset();
+ loading_cb_.Run(kLoadingFinished);
+
+ // If we didn't know the |instance_size_| we do now.
+ if (instance_size_ == kPositionNotSpecified) {
+ instance_size_ = offset_ + buffer_.forward_bytes();
+ }
+
+ // If there is a start callback, run it.
+ if (!start_cb_.is_null()) {
+ DCHECK(read_cb_.is_null())
+ << "Shouldn't have a read callback during start";
+ DoneStart(kOk);
+ return;
+ }
+
+ // Don't leave read callbacks hanging around.
+ if (HasPendingRead()) {
+ // Try to fulfill with what is in the buffer.
+ if (CanFulfillRead())
+ ReadInternal();
+ else
+ DoneRead(kCacheMiss, 0);
+ }
+}
+
+void BufferedResourceLoader::didFail(
+ WebURLLoader* loader,
+ const WebURLError& error) {
+ DVLOG(1) << "didFail: reason=" << error.reason
+ << ", isCancellation=" << error.isCancellation
+ << ", domain=" << error.domain.utf8().data()
+ << ", localizedDescription="
+ << error.localizedDescription.utf8().data();
+ DCHECK(active_loader_.get());
+
+ // We don't need to continue loading after failure.
+ //
+ // Keep it alive until we exit this method so that |error| remains valid.
+ scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
+ loader_failed_ = true;
+ loading_cb_.Run(kLoadingFailed);
+
+ // Don't leave start callbacks hanging around.
+ if (!start_cb_.is_null()) {
+ DCHECK(read_cb_.is_null())
+ << "Shouldn't have a read callback during start";
+ DoneStart(kFailed);
+ return;
+ }
+
+ // Don't leave read callbacks hanging around.
+ if (HasPendingRead()) {
+ DoneRead(kFailed, 0);
+ }
+}
+
+bool BufferedResourceLoader::HasSingleOrigin() const {
+ DCHECK(start_cb_.is_null())
+ << "Start() must complete before calling HasSingleOrigin()";
+ return single_origin_;
+}
+
+bool BufferedResourceLoader::DidPassCORSAccessCheck() const {
+ DCHECK(start_cb_.is_null())
+ << "Start() must complete before calling DidPassCORSAccessCheck()";
+ return !loader_failed_ && cors_mode_ != kUnspecified;
+}
+
+void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) {
+ if (!might_be_reused_from_cache_in_future_ && strategy == kNeverDefer)
+ strategy = kCapacityDefer;
+ defer_strategy_ = strategy;
+ UpdateDeferBehavior();
+}
+
+void BufferedResourceLoader::SetPlaybackRate(float playback_rate) {
+ playback_rate_ = playback_rate;
+
+ // This is a pause so don't bother updating the buffer window as we'll likely
+ // get unpaused in the future.
+ if (playback_rate_ == 0.0)
+ return;
+
+ UpdateBufferWindow();
+}
+
+void BufferedResourceLoader::SetBitrate(int bitrate) {
+ DCHECK(bitrate >= 0);
+ bitrate_ = bitrate;
+ UpdateBufferWindow();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Helper methods.
+
+void BufferedResourceLoader::UpdateBufferWindow() {
+ int backward_capacity;
+ int forward_capacity;
+ ComputeTargetBufferWindow(
+ playback_rate_, bitrate_, &backward_capacity, &forward_capacity);
+
+ // This does not evict data from the buffer if the new capacities are less
+ // than the current capacities; the new limits will be enforced after the
+ // existing excess buffered data is consumed.
+ buffer_.set_backward_capacity(backward_capacity);
+ buffer_.set_forward_capacity(forward_capacity);
+}
+
+void BufferedResourceLoader::UpdateDeferBehavior() {
+ if (!active_loader_)
+ return;
+
+ SetDeferred(ShouldDefer());
+}
+
+void BufferedResourceLoader::SetDeferred(bool deferred) {
+ if (active_loader_->deferred() == deferred)
+ return;
+
+ active_loader_->SetDeferred(deferred);
+ loading_cb_.Run(deferred ? kLoadingDeferred : kLoading);
+}
+
+bool BufferedResourceLoader::ShouldDefer() const {
+ switch(defer_strategy_) {
+ case kNeverDefer:
+ return false;
+
+ case kReadThenDefer:
+ DCHECK(read_cb_.is_null() || last_offset_ > buffer_.forward_bytes())
+ << "We shouldn't stop deferring if we can fulfill the read";
+ return read_cb_.is_null();
+
+ case kCapacityDefer:
+ return buffer_.forward_bytes() >= buffer_.forward_capacity();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool BufferedResourceLoader::CanFulfillRead() const {
+ // If we are reading too far in the backward direction.
+ if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
+ return false;
+
+ // If the start offset is too far ahead.
+ if (first_offset_ >= buffer_.forward_bytes())
+ return false;
+
+ // At the point, we verified that first byte requested is within the buffer.
+ // If the request has completed, then just returns with what we have now.
+ if (!active_loader_)
+ return true;
+
+ // If the resource request is still active, make sure the whole requested
+ // range is covered.
+ if (last_offset_ > buffer_.forward_bytes())
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::WillFulfillRead() const {
+ // Trying to read too far behind.
+ if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
+ return false;
+
+ // Trying to read too far ahead.
+ if ((first_offset_ - buffer_.forward_bytes()) >= kForwardWaitThreshold)
+ return false;
+
+ // The resource request has completed, there's no way we can fulfill the
+ // read request.
+ if (!active_loader_)
+ return false;
+
+ return true;
+}
+
+void BufferedResourceLoader::ReadInternal() {
+ // Seek to the first byte requested.
+ bool ret = buffer_.Seek(first_offset_);
+ DCHECK(ret);
+
+ // Then do the read.
+ int read = buffer_.Read(read_buffer_, read_size_);
+ offset_ += first_offset_ + read;
+
+ // And report with what we have read.
+ DoneRead(kOk, read);
+}
+
+int64 BufferedResourceLoader::first_byte_position() const {
+ return first_byte_position_;
+}
+
+// static
+bool BufferedResourceLoader::ParseContentRange(
+ const std::string& content_range_str, int64* first_byte_position,
+ int64* last_byte_position, int64* instance_size) {
+ const std::string kUpThroughBytesUnit = "bytes ";
+ if (content_range_str.find(kUpThroughBytesUnit) != 0)
+ return false;
+ std::string range_spec =
+ content_range_str.substr(kUpThroughBytesUnit.length());
+ size_t dash_offset = range_spec.find("-");
+ size_t slash_offset = range_spec.find("/");
+
+ if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
+ slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
+ return false;
+ }
+ if (!base::StringToInt64(range_spec.substr(0, dash_offset),
+ first_byte_position) ||
+ !base::StringToInt64(range_spec.substr(dash_offset + 1,
+ slash_offset - dash_offset - 1),
+ last_byte_position)) {
+ return false;
+ }
+ if (slash_offset == range_spec.length() - 2 &&
+ range_spec[slash_offset + 1] == '*') {
+ *instance_size = kPositionNotSpecified;
+ } else {
+ if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
+ instance_size)) {
+ return false;
+ }
+ }
+ if (*last_byte_position < *first_byte_position ||
+ (*instance_size != kPositionNotSpecified &&
+ *last_byte_position >= *instance_size)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BufferedResourceLoader::VerifyPartialResponse(
+ const WebURLResponse& response) {
+ int64 first_byte_position, last_byte_position, instance_size;
+ if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
+ &first_byte_position, &last_byte_position,
+ &instance_size)) {
+ return false;
+ }
+
+ if (instance_size != kPositionNotSpecified) {
+ instance_size_ = instance_size;
+ }
+
+ if (first_byte_position_ != kPositionNotSpecified &&
+ first_byte_position_ != first_byte_position) {
+ return false;
+ }
+
+ // TODO(hclam): I should also check |last_byte_position|, but since
+ // we will never make such a request that it is ok to leave it unimplemented.
+ return true;
+}
+
+void BufferedResourceLoader::DoneRead(Status status, int bytes_read) {
+ if (saved_forward_capacity_) {
+ buffer_.set_forward_capacity(saved_forward_capacity_);
+ saved_forward_capacity_ = 0;
+ }
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = NULL;
+ first_offset_ = 0;
+ last_offset_ = 0;
+ Log();
+
+ base::ResetAndReturn(&read_cb_).Run(status, bytes_read);
+}
+
+
+void BufferedResourceLoader::DoneStart(Status status) {
+ base::ResetAndReturn(&start_cb_).Run(status);
+}
+
+bool BufferedResourceLoader::IsRangeRequest() const {
+ return first_byte_position_ != kPositionNotSpecified;
+}
+
+void BufferedResourceLoader::Log() {
+ media_log_->AddEvent(
+ media_log_->CreateBufferedExtentsChangedEvent(
+ offset_ - buffer_.backward_bytes(),
+ offset_,
+ offset_ + buffer_.forward_bytes()));
+}
+
+} // namespace media
diff --git a/media/blink/buffered_resource_loader.h b/media/blink/buffered_resource_loader.h
new file mode 100644
index 0000000..fb633be
--- /dev/null
+++ b/media/blink/buffered_resource_loader.h
@@ -0,0 +1,320 @@
+// Copyright 2013 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 MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_
+#define MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "media/base/media_export.h"
+#include "media/base/seekable_buffer.h"
+#include "media/blink/active_loader.h"
+#include "third_party/WebKit/public/platform/WebURLLoader.h"
+#include "third_party/WebKit/public/platform/WebURLLoaderClient.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "url/gurl.h"
+
+namespace media {
+class MediaLog;
+class SeekableBuffer;
+
+const int64 kPositionNotSpecified = -1;
+
+// BufferedResourceLoader is single threaded and must be accessed on the
+// render thread. It wraps a WebURLLoader and does in-memory buffering,
+// pausing resource loading when the in-memory buffer is full and resuming
+// resource loading when there is available capacity.
+class MEDIA_EXPORT BufferedResourceLoader
+ : NON_EXPORTED_BASE(public blink::WebURLLoaderClient) {
+ public:
+ // kNeverDefer - Aggresively buffer; never defer loading while paused.
+ // kReadThenDefer - Request only enough data to fulfill read requests.
+ // kCapacityDefer - Try to keep amount of buffered data at capacity.
+ enum DeferStrategy {
+ kNeverDefer,
+ kReadThenDefer,
+ kCapacityDefer,
+ };
+
+ // Status codes for start/read operations on BufferedResourceLoader.
+ enum Status {
+ // Everything went as planned.
+ kOk,
+
+ // The operation failed, which may have been due to:
+ // - Page navigation
+ // - Server replied 4xx/5xx
+ // - The response was invalid
+ // - Connection was terminated
+ //
+ // At this point you should delete the loader.
+ kFailed,
+
+ // The loader will never be able to satisfy the read request. Please stop,
+ // delete, create a new loader, and try again.
+ kCacheMiss,
+ };
+
+ // Keep in sync with WebMediaPlayer::CORSMode.
+ enum CORSMode { kUnspecified, kAnonymous, kUseCredentials };
+
+ enum LoadingState {
+ kLoading, // Actively attempting to download data.
+ kLoadingDeferred, // Loading intentionally deferred.
+ kLoadingFinished, // Loading finished normally; no more data will arrive.
+ kLoadingFailed, // Loading finished abnormally; no more data will arrive.
+ };
+
+ // |url| - URL for the resource to be loaded.
+ // |cors_mode| - HTML media element's crossorigin attribute.
+ // |first_byte_position| - First byte to start loading from,
+ // |kPositionNotSpecified| for not specified.
+ // |last_byte_position| - Last byte to be loaded,
+ // |kPositionNotSpecified| for not specified.
+ // |strategy| is the initial loading strategy to use.
+ // |bitrate| is the bitrate of the media, 0 if unknown.
+ // |playback_rate| is the current playback rate of the media.
+ BufferedResourceLoader(
+ const GURL& url,
+ CORSMode cors_mode,
+ int64 first_byte_position,
+ int64 last_byte_position,
+ DeferStrategy strategy,
+ int bitrate,
+ float playback_rate,
+ MediaLog* media_log);
+ virtual ~BufferedResourceLoader();
+
+ // Start the resource loading with the specified URL and range.
+ //
+ // |loading_cb| is executed when the loading state has changed.
+ // |progress_cb| is executed when additional data has arrived.
+ typedef base::Callback<void(Status)> StartCB;
+ typedef base::Callback<void(LoadingState)> LoadingStateChangedCB;
+ typedef base::Callback<void(int64)> ProgressCB;
+ void Start(const StartCB& start_cb,
+ const LoadingStateChangedCB& loading_cb,
+ const ProgressCB& progress_cb,
+ blink::WebFrame* frame);
+
+ // Stops everything associated with this loader, including active URL loads
+ // and pending callbacks.
+ //
+ // It is safe to delete a BufferedResourceLoader after calling Stop().
+ void Stop();
+
+ // Copies |read_size| bytes from |position| into |buffer|, executing |read_cb|
+ // when the operation has completed.
+ //
+ // The callback will contain the number of bytes read iff the status is kOk,
+ // zero otherwise.
+ //
+ // If necessary will temporarily increase forward capacity of buffer to
+ // accomodate an unusually large read.
+ typedef base::Callback<void(Status, int)> ReadCB;
+ void Read(int64 position, int read_size,
+ uint8* buffer, const ReadCB& read_cb);
+
+ // Gets the content length in bytes of the instance after this loader has been
+ // started. If this value is |kPositionNotSpecified|, then content length is
+ // unknown.
+ int64 content_length();
+
+ // Gets the original size of the file requested. If this value is
+ // |kPositionNotSpecified|, then the size is unknown.
+ int64 instance_size();
+
+ // Returns true if the server supports byte range requests.
+ bool range_supported();
+
+ // blink::WebURLLoaderClient implementation.
+ virtual void willSendRequest(
+ blink::WebURLLoader* loader,
+ blink::WebURLRequest& newRequest,
+ const blink::WebURLResponse& redirectResponse);
+ virtual void didSendData(
+ blink::WebURLLoader* loader,
+ unsigned long long bytesSent,
+ unsigned long long totalBytesToBeSent);
+ virtual void didReceiveResponse(
+ blink::WebURLLoader* loader,
+ const blink::WebURLResponse& response);
+ virtual void didDownloadData(
+ blink::WebURLLoader* loader,
+ int data_length,
+ int encoded_data_length);
+ virtual void didReceiveData(
+ blink::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length);
+ virtual void didReceiveCachedMetadata(
+ blink::WebURLLoader* loader,
+ const char* data, int dataLength);
+ virtual void didFinishLoading(
+ blink::WebURLLoader* loader,
+ double finishTime,
+ int64_t total_encoded_data_length);
+ virtual void didFail(
+ blink::WebURLLoader* loader,
+ const blink::WebURLError&);
+
+ // Returns true if the media resource has a single origin, false otherwise.
+ // Only valid to call after Start() has completed.
+ bool HasSingleOrigin() const;
+
+ // Returns true if the media resource passed a CORS access control check.
+ // Only valid to call after Start() has completed.
+ bool DidPassCORSAccessCheck() const;
+
+ // Sets the defer strategy to the given value unless it seems unwise.
+ // Specifically downgrade kNeverDefer to kCapacityDefer if we know the
+ // current response will not be used to satisfy future requests (the cache
+ // won't help us).
+ void UpdateDeferStrategy(DeferStrategy strategy);
+
+ // Sets the playback rate to the given value and updates buffer window
+ // accordingly.
+ void SetPlaybackRate(float playback_rate);
+
+ // Sets the bitrate to the given value and updates buffer window
+ // accordingly.
+ void SetBitrate(int bitrate);
+
+ // Return the |first_byte_position| passed into the ctor.
+ int64 first_byte_position() const;
+
+ // Parse a Content-Range header into its component pieces and return true if
+ // each of the expected elements was found & parsed correctly.
+ // |*instance_size| may be set to kPositionNotSpecified if the range ends in
+ // "/*".
+ // NOTE: only public for testing! This is an implementation detail of
+ // VerifyPartialResponse (a private method).
+ static bool ParseContentRange(
+ const std::string& content_range_str, int64* first_byte_position,
+ int64* last_byte_position, int64* instance_size);
+
+ private:
+ friend class BufferedDataSourceTest;
+ friend class BufferedResourceLoaderTest;
+ friend class MockBufferedDataSource;
+
+ // Updates the |buffer_|'s forward and backward capacities.
+ void UpdateBufferWindow();
+
+ // Updates deferring behavior based on current buffering scheme.
+ void UpdateDeferBehavior();
+
+ // Sets |active_loader_|'s defer state and fires |loading_cb_| if the state
+ // changed.
+ void SetDeferred(bool deferred);
+
+ // Returns true if we should defer resource loading based on the current
+ // buffering scheme.
+ bool ShouldDefer() const;
+
+ // Returns true if the current read request can be fulfilled by what is in
+ // the buffer.
+ bool CanFulfillRead() const;
+
+ // Returns true if the current read request will be fulfilled in the future.
+ bool WillFulfillRead() const;
+
+ // Method that does the actual read and calls the |read_cb_|, assuming the
+ // request range is in |buffer_|.
+ void ReadInternal();
+
+ // If we have made a range request, verify the response from the server.
+ bool VerifyPartialResponse(const blink::WebURLResponse& response);
+
+ // Done with read. Invokes the read callback and reset parameters for the
+ // read request.
+ void DoneRead(Status status, int bytes_read);
+
+ // Done with start. Invokes the start callback and reset it.
+ void DoneStart(Status status);
+
+ bool HasPendingRead() { return !read_cb_.is_null(); }
+
+ // Helper function that returns true if a range request was specified.
+ bool IsRangeRequest() const;
+
+ // Log everything interesting to |media_log_|.
+ void Log();
+
+ // A sliding window of buffer.
+ SeekableBuffer buffer_;
+
+ // Keeps track of an active WebURLLoader and associated state.
+ scoped_ptr<ActiveLoader> active_loader_;
+
+ // Tracks if |active_loader_| failed. If so, then all calls to Read() will
+ // fail.
+ bool loader_failed_;
+
+ // Current buffering algorithm in place for resource loading.
+ DeferStrategy defer_strategy_;
+
+ // True if the currently-reading response might be used to satisfy a future
+ // request from the cache.
+ bool might_be_reused_from_cache_in_future_;
+
+ // True if Range header is supported.
+ bool range_supported_;
+
+ // Forward capacity to reset to after an extension.
+ int saved_forward_capacity_;
+
+ GURL url_;
+ CORSMode cors_mode_;
+ const int64 first_byte_position_;
+ const int64 last_byte_position_;
+ bool single_origin_;
+
+ // Executed whenever the state of resource loading has changed.
+ LoadingStateChangedCB loading_cb_;
+
+ // Executed whenever additional data has been downloaded and reports the
+ // zero-indexed file offset of the furthest buffered byte.
+ ProgressCB progress_cb_;
+
+ // Members used during request start.
+ StartCB start_cb_;
+ int64 offset_;
+ int64 content_length_;
+ int64 instance_size_;
+
+ // Members used during a read operation. They should be reset after each
+ // read has completed or failed.
+ ReadCB read_cb_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+
+ // Offsets of the requested first byte and last byte in |buffer_|. They are
+ // written by Read().
+ int first_offset_;
+ int last_offset_;
+
+ // Injected WebURLLoader instance for testing purposes.
+ scoped_ptr<blink::WebURLLoader> test_loader_;
+
+ // Bitrate of the media. Set to 0 if unknown.
+ int bitrate_;
+
+ // Playback rate of the media.
+ float playback_rate_;
+
+ scoped_refptr<MediaLog> media_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_
diff --git a/media/blink/buffered_resource_loader_unittest.cc b/media/blink/buffered_resource_loader_unittest.cc
new file mode 100644
index 0000000..3e7eaa8
--- /dev/null
+++ b/media/blink/buffered_resource_loader_unittest.cc
@@ -0,0 +1,1131 @@
+// Copyright 2013 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 <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "media/base/media_log.h"
+#include "media/base/seekable_buffer.h"
+#include "media/blink/buffered_resource_loader.h"
+#include "media/blink/mock_webframeclient.h"
+#include "media/blink/mock_weburlloader.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::Truly;
+using ::testing::NiceMock;
+
+using blink::WebLocalFrame;
+using blink::WebString;
+using blink::WebURLError;
+using blink::WebURLResponse;
+using blink::WebView;
+
+namespace media {
+
+static const char* kHttpUrl = "http://test";
+static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing";
+static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2";
+static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2";
+
+static const int kDataSize = 1024;
+static const int kHttpOK = 200;
+static const int kHttpPartialContent = 206;
+
+enum NetworkState {
+ NONE,
+ LOADED,
+ LOADING
+};
+
+// Predicate that tests that request disallows compressed data.
+static bool CorrectAcceptEncoding(const blink::WebURLRequest &request) {
+ std::string value = request.httpHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding)).utf8();
+ return (value.find("identity;q=1") != std::string::npos) &&
+ (value.find("*;q=0") != std::string::npos);
+}
+
+class BufferedResourceLoaderTest : public testing::Test {
+ public:
+ BufferedResourceLoaderTest()
+ : view_(WebView::create(NULL)), frame_(WebLocalFrame::create(&client_)) {
+ view_->setMainFrame(frame_);
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~BufferedResourceLoaderTest() {
+ view_->close();
+ frame_->close();
+ }
+
+ void Initialize(const char* url, int first_position, int last_position) {
+ gurl_ = GURL(url);
+ first_position_ = first_position;
+ last_position_ = last_position;
+
+ loader_.reset(new BufferedResourceLoader(
+ gurl_, BufferedResourceLoader::kUnspecified,
+ first_position_, last_position_,
+ BufferedResourceLoader::kCapacityDefer, 0, 0,
+ new MediaLog()));
+
+ // |test_loader_| will be used when Start() is called.
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+ loader_->test_loader_ = scoped_ptr<blink::WebURLLoader>(url_loader_);
+ }
+
+ void SetLoaderBuffer(int forward_capacity, int backward_capacity) {
+ loader_->buffer_.set_forward_capacity(forward_capacity);
+ loader_->buffer_.set_backward_capacity(backward_capacity);
+ loader_->buffer_.Clear();
+ }
+
+ void Start() {
+ InSequence s;
+ EXPECT_CALL(*url_loader_, loadAsynchronously(Truly(CorrectAcceptEncoding),
+ loader_.get()));
+
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ loader_->Start(
+ base::Bind(&BufferedResourceLoaderTest::StartCallback,
+ base::Unretained(this)),
+ base::Bind(&BufferedResourceLoaderTest::LoadingCallback,
+ base::Unretained(this)),
+ base::Bind(&BufferedResourceLoaderTest::ProgressCallback,
+ base::Unretained(this)),
+ view_->mainFrame());
+ }
+
+ void FullResponse(int64 instance_size) {
+ FullResponse(instance_size, BufferedResourceLoader::kOk);
+ }
+
+ void FullResponse(int64 instance_size,
+ BufferedResourceLoader::Status status) {
+ EXPECT_CALL(*this, StartCallback(status));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"),
+ WebString::fromUTF8(base::StringPrintf("%"
+ PRId64, instance_size)));
+ response.setExpectedContentLength(instance_size);
+ response.setHTTPStatusCode(kHttpOK);
+ loader_->didReceiveResponse(url_loader_, response);
+
+ if (status == BufferedResourceLoader::kOk) {
+ EXPECT_EQ(instance_size, loader_->content_length());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+ }
+
+ EXPECT_FALSE(loader_->range_supported());
+ }
+
+ void PartialResponse(int64 first_position, int64 last_position,
+ int64 instance_size) {
+ PartialResponse(first_position, last_position, instance_size, false, true);
+ }
+
+ void PartialResponse(int64 first_position, int64 last_position,
+ int64 instance_size, bool chunked, bool accept_ranges) {
+ EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kOk));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8(base::StringPrintf("bytes "
+ "%" PRId64 "-%" PRId64 "/%" PRId64,
+ first_position,
+ last_position,
+ instance_size)));
+
+ // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked.
+ int64 content_length = -1;
+ if (chunked) {
+ response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"),
+ WebString::fromUTF8("chunked"));
+ } else {
+ content_length = last_position - first_position + 1;
+ }
+ response.setExpectedContentLength(content_length);
+
+ // A server isn't required to return Accept-Ranges even though it might.
+ if (accept_ranges) {
+ response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
+ WebString::fromUTF8("bytes"));
+ }
+
+ response.setHTTPStatusCode(kHttpPartialContent);
+ loader_->didReceiveResponse(url_loader_, response);
+
+ // XXX: what's the difference between these two? For example in the chunked
+ // range request case, Content-Length is unspecified (because it's chunked)
+ // but Content-Range: a-b/c can be returned, where c == Content-Length
+ //
+ // Can we eliminate one?
+ EXPECT_EQ(content_length, loader_->content_length());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+
+ // A valid partial response should always result in this being true.
+ EXPECT_TRUE(loader_->range_supported());
+ }
+
+ void Redirect(const char* url) {
+ GURL redirectUrl(url);
+ blink::WebURLRequest newRequest(redirectUrl);
+ blink::WebURLResponse redirectResponse(gurl_);
+
+ loader_->willSendRequest(url_loader_, newRequest, redirectResponse);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*url_loader_, cancel());
+ loader_->Stop();
+ loader_.reset();
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ EXPECT_CALL(*this, ProgressCallback(position + size - 1));
+ loader_->didReceiveData(url_loader_,
+ reinterpret_cast<char*>(data_ + position),
+ size,
+ size);
+ }
+
+ void WriteData(int size) {
+ EXPECT_CALL(*this, ProgressCallback(_));
+
+ scoped_ptr<char[]> data(new char[size]);
+ loader_->didReceiveData(url_loader_, data.get(), size, size);
+ }
+
+ void WriteUntilThreshold() {
+ int buffered = loader_->buffer_.forward_bytes();
+ int capacity = loader_->buffer_.forward_capacity();
+ CHECK_LT(buffered, capacity);
+
+ EXPECT_CALL(*this, LoadingCallback(
+ BufferedResourceLoader::kLoadingDeferred));
+ WriteData(capacity - buffered);
+ }
+
+ // Helper method to read from |loader_|.
+ void ReadLoader(int64 position, int size, uint8* buffer) {
+ loader_->Read(position, size, buffer,
+ base::Bind(&BufferedResourceLoaderTest::ReadCallback,
+ base::Unretained(this)));
+ }
+
+ // Verifies that data in buffer[0...size] is equal to data_[pos...pos+size].
+ void VerifyBuffer(uint8* buffer, int pos, int size) {
+ EXPECT_EQ(0, memcmp(buffer, data_ + pos, size));
+ }
+
+ void ConfirmLoaderOffsets(int64 expected_offset,
+ int expected_first_offset,
+ int expected_last_offset) {
+ EXPECT_EQ(loader_->offset_, expected_offset);
+ EXPECT_EQ(loader_->first_offset_, expected_first_offset);
+ EXPECT_EQ(loader_->last_offset_, expected_last_offset);
+ }
+
+ void ConfirmBufferState(int backward_bytes,
+ int backward_capacity,
+ int forward_bytes,
+ int forward_capacity) {
+ EXPECT_EQ(backward_bytes, loader_->buffer_.backward_bytes());
+ EXPECT_EQ(backward_capacity, loader_->buffer_.backward_capacity());
+ EXPECT_EQ(forward_bytes, loader_->buffer_.forward_bytes());
+ EXPECT_EQ(forward_capacity, loader_->buffer_.forward_capacity());
+ }
+
+ void ConfirmLoaderBufferBackwardCapacity(int expected_backward_capacity) {
+ EXPECT_EQ(loader_->buffer_.backward_capacity(),
+ expected_backward_capacity);
+ }
+
+ void ConfirmLoaderBufferForwardCapacity(int expected_forward_capacity) {
+ EXPECT_EQ(loader_->buffer_.forward_capacity(), expected_forward_capacity);
+ }
+
+ // Makes sure the |loader_| buffer window is in a reasonable range.
+ void CheckBufferWindowBounds() {
+ // Corresponds to value defined in buffered_resource_loader.cc.
+ static const int kMinBufferCapacity = 2 * 1024 * 1024;
+ EXPECT_GE(loader_->buffer_.forward_capacity(), kMinBufferCapacity);
+ EXPECT_GE(loader_->buffer_.backward_capacity(), kMinBufferCapacity);
+
+ // Corresponds to value defined in buffered_resource_loader.cc.
+ static const int kMaxBufferCapacity = 20 * 1024 * 1024;
+ EXPECT_LE(loader_->buffer_.forward_capacity(), kMaxBufferCapacity);
+ EXPECT_LE(loader_->buffer_.backward_capacity(), kMaxBufferCapacity);
+ }
+
+ MOCK_METHOD1(StartCallback, void(BufferedResourceLoader::Status));
+ MOCK_METHOD2(ReadCallback, void(BufferedResourceLoader::Status, int));
+ MOCK_METHOD1(LoadingCallback, void(BufferedResourceLoader::LoadingState));
+ MOCK_METHOD1(ProgressCallback, void(int64));
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+ int64 last_position_;
+
+ scoped_ptr<BufferedResourceLoader> loader_;
+ NiceMock<MockWebURLLoader>* url_loader_;
+
+ MockWebFrameClient client_;
+ WebView* view_;
+ WebLocalFrame* frame_;
+
+ base::MessageLoop message_loop_;
+
+ uint8 data_[kDataSize];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
+};
+
+TEST_F(BufferedResourceLoaderTest, StartStop) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ StopWhenLoad();
+}
+
+// Tests that a bad HTTP response is recived, e.g. file not found.
+TEST_F(BufferedResourceLoaderTest, BadHttpResponse) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kFailed));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(404);
+ response.setHTTPStatusText("Not Found\n");
+ loader_->didReceiveResponse(url_loader_, response);
+ StopWhenLoad();
+}
+
+// Tests that partial content is requested but not fulfilled.
+TEST_F(BufferedResourceLoaderTest, NotPartialResponse) {
+ Initialize(kHttpUrl, 100, -1);
+ Start();
+ FullResponse(1024, BufferedResourceLoader::kFailed);
+ StopWhenLoad();
+}
+
+// Tests that a 200 response is received.
+TEST_F(BufferedResourceLoaderTest, FullResponse) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ FullResponse(1024);
+ StopWhenLoad();
+}
+
+// Tests that a partial content response is received.
+TEST_F(BufferedResourceLoaderTest, PartialResponse) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_Chunked) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, true, true);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_NoAcceptRanges) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, false, false);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_ChunkedNoAcceptRanges) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, true, false);
+ StopWhenLoad();
+}
+
+// Tests that an invalid partial response is received.
+TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) {
+ Initialize(kHttpUrl, 0, 10);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kFailed));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8(base::StringPrintf("bytes "
+ "%d-%d/%d", 1, 10, 1024)));
+ response.setExpectedContentLength(10);
+ response.setHTTPStatusCode(kHttpPartialContent);
+ loader_->didReceiveResponse(url_loader_, response);
+ StopWhenLoad();
+}
+
+// Tests the logic of sliding window for data buffering and reading.
+TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
+ Initialize(kHttpUrl, 10, 29);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
+ Start();
+ PartialResponse(10, 29, 30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Writes 10 bytes and read them back.
+ WriteLoader(10, 10);
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+
+ // Writes 10 bytes and read 2 times.
+ WriteLoader(20, 10);
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5));
+ ReadLoader(20, 5, buffer);
+ VerifyBuffer(buffer, 20, 5);
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5));
+ ReadLoader(25, 5, buffer);
+ VerifyBuffer(buffer, 25, 5);
+
+ // Read backward within buffer.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+
+ // Read backward outside buffer.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(9, 10, buffer);
+
+ // Response has completed.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFinished));
+ loader_->didFinishLoading(url_loader_, 0, -1);
+
+ // Try to read 10 from position 25 will just return with 5 bytes.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5));
+ ReadLoader(25, 10, buffer);
+ VerifyBuffer(buffer, 25, 5);
+
+ // Try to read outside buffered range after request has completed.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(5, 10, buffer);
+
+ // Try to read beyond the instance size.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 0));
+ ReadLoader(30, 10, buffer);
+}
+
+// Tests the logic of expanding the data buffer for large reads.
+TEST_F(BufferedResourceLoaderTest, ReadExtendBuffer) {
+ Initialize(kHttpUrl, 10, 0x014FFFFFF);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 0x014FFFFFF, 0x015000000);
+
+ uint8 buffer[20];
+ InSequence s;
+
+ // Write more than forward capacity and read it back. Ensure forward capacity
+ // gets reset after reading.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ WriteLoader(10, 20);
+
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 20));
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(10, 20, buffer);
+
+ VerifyBuffer(buffer, 10, 20);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Make and outstanding read request larger than forward capacity. Ensure
+ // forward capacity gets extended.
+ ReadLoader(30, 20, buffer);
+ ConfirmLoaderBufferForwardCapacity(20);
+
+ // Fulfill outstanding request. Ensure forward capacity gets reset.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 20));
+ WriteLoader(30, 20);
+
+ VerifyBuffer(buffer, 30, 20);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Try to read further ahead than kForwardWaitThreshold allows. Ensure
+ // forward capacity is not changed.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(0x00300000, 1, buffer);
+
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Try to read more than maximum forward capacity. Ensure forward capacity is
+ // not changed.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0));
+ ReadLoader(30, 0x01400001, buffer);
+
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
+ Initialize(kHttpUrl, 10, 0x00FFFFFF);
+ Start();
+ PartialResponse(10, 0x00FFFFFF, 0x01000000);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Read very far ahead will get a cache miss.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(0x00FFFFFF, 1, buffer);
+
+ // The following call will not call ReadCallback() because it is waiting for
+ // data to arrive.
+ ReadLoader(10, 10, buffer);
+
+ // Writing to loader will fulfill the read request.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ WriteLoader(10, 20);
+ VerifyBuffer(buffer, 10, 10);
+
+ // The following call cannot be fulfilled now.
+ ReadLoader(25, 10, buffer);
+
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFinished));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5));
+ loader_->didFinishLoading(url_loader_, 0, -1);
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(10, 29, 30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // We should convert any error we receive to BufferedResourceLoader::kFailed.
+ ReadLoader(10, 10, buffer);
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0));
+ WebURLError error;
+ error.reason = net::ERR_TIMED_OUT;
+ error.isCancellation = false;
+ loader_->didFail(url_loader_, error);
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestFailedWithNoPendingReads) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(10, 29, 30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Write enough data so that a read would technically complete had the request
+ // not failed.
+ WriteLoader(10, 20);
+
+ // Fail without a pending read.
+ WebURLError error;
+ error.reason = net::ERR_TIMED_OUT;
+ error.isCancellation = false;
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed));
+ loader_->didFail(url_loader_, error);
+
+ // Now we should immediately fail any read even if we have data buffered.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0));
+ ReadLoader(10, 10, buffer);
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestCancelledWhenRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(10, 29, 30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // We should convert any error we receive to BufferedResourceLoader::kFailed.
+ ReadLoader(10, 10, buffer);
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0));
+ WebURLError error;
+ error.reason = 0;
+ error.isCancellation = true;
+ loader_->didFail(url_loader_, error);
+}
+
+// Tests the data buffering logic of NeverDefer strategy.
+TEST_F(BufferedResourceLoaderTest, NeverDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Read past the buffer size; should not defer regardless.
+ WriteLoader(10, 10);
+ WriteLoader(20, 50);
+
+ // Should move past window.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(10, 10, buffer);
+
+ StopWhenLoad();
+}
+
+// Tests the data buffering logic of ReadThenDefer strategy.
+TEST_F(BufferedResourceLoaderTest, ReadThenDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kReadThenDefer);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Make an outstanding read request.
+ ReadLoader(10, 10, buffer);
+
+ // Receive almost enough data to cover, shouldn't defer.
+ WriteLoader(10, 9);
+
+ // As soon as we have received enough data to fulfill the read, defer.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ WriteLoader(19, 1);
+
+ VerifyBuffer(buffer, 10, 10);
+
+ // Read again which should disable deferring since there should be nothing
+ // left in our internal buffer.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(20, 10, buffer);
+
+ // Over-fulfill requested bytes, then deferring should be enabled again.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ WriteLoader(20, 40);
+
+ VerifyBuffer(buffer, 20, 10);
+
+ // Read far ahead, which should disable deferring. In this case we still have
+ // bytes in our internal buffer.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(80, 10, buffer);
+
+ // Fulfill requested bytes, then deferring should be enabled again.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ WriteLoader(60, 40);
+
+ VerifyBuffer(buffer, 80, 10);
+
+ StopWhenLoad();
+}
+
+// Tests the data buffering logic of kCapacityDefer strategy.
+TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Write half of capacity: keep not deferring.
+ WriteData(5);
+
+ // Write rest of space until capacity: start deferring.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ WriteData(5);
+
+ // Read a byte from the buffer: stop deferring.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 1));
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(10, 1, buffer);
+
+ // Write a byte to hit capacity: start deferring.
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred));
+ WriteData(6);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadForwardsPastBuffered) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 1));
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(10, 1, buffer);
+ ConfirmBufferState(1, 10, 9, 10);
+ ConfirmLoaderOffsets(11, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. I ***
+ // Read past buffered: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=11 [xxxxxxxxx_]
+ // ^ ^^^ requested 4 bytes @ offset 20
+ // AFTER
+ // offset=24 [__________]
+ //
+ ReadLoader(20, 4, buffer);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(2);
+
+ // Write the rest, read should complete.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 4));
+ WriteData(2);
+
+ // POSTCONDITION
+ ConfirmBufferState(4, 10, 0, 10);
+ ConfirmLoaderOffsets(24, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadBackwardsPastBuffered) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. II ***
+ // Read backwards a little too much: cache miss.
+ //
+ // BEFORE
+ // offset=10 [__________|xxxxxxxxxx]
+ // ^ ^^^ requested 10 bytes @ offset 9
+ // AFTER
+ // offset=10 [__________|xxxxxxxxxx] !!! cache miss !!!
+ //
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(9, 4, buffer);
+
+ // POSTCONDITION
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_SmallReadWithinThreshold) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. III ***
+ // Read past forward capacity but within capacity: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset forward to create capacity.
+ // 2) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx]
+ // ^^^^ requested 4 bytes @ offset 24
+ // ADJUSTED OFFSET
+ // offset=20 [__________]
+ // ^^^^ requested 4 bytes @ offset 24
+ // AFTER
+ // offset=28 [__________]
+ //
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(24, 4, buffer);
+ ConfirmLoaderOffsets(20, 4, 8);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(4);
+
+ // Write the rest, read should complete.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 4));
+ WriteData(4);
+
+ // POSTCONDITION
+ ConfirmBufferState(8, 10, 0, 10);
+ ConfirmLoaderOffsets(28, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadWithinThreshold) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. IV ***
+ // Read a large amount past forward capacity but within
+ // capacity: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset forward to create capacity.
+ // 2) Expand capacity to make sure we don't defer as data arrives.
+ // 3) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx]
+ // ^^^^^^^^^^^^ requested 12 bytes @ offset 24
+ // ADJUSTED OFFSET
+ // offset=20 [__________]
+ // ^^^^^^ ^^^^^^ requested 12 bytes @ offset 24
+ // ADJUSTED CAPACITY
+ // offset=20 [________________]
+ // ^^^^^^^^^^^^ requested 12 bytes @ offset 24
+ // AFTER
+ // offset=36 [__________]
+ //
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(24, 12, buffer);
+ ConfirmLoaderOffsets(20, 4, 16);
+ ConfirmBufferState(10, 10, 0, 16);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(10);
+
+ // Write the rest, read should complete and capacity should go back to normal.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 12));
+ WriteData(6);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // POSTCONDITION
+ ConfirmBufferState(6, 10, 0, 10);
+ ConfirmLoaderOffsets(36, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadBackwards) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10));
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(10, 10, buffer);
+ WriteUntilThreshold();
+ ConfirmBufferState(10, 10, 10, 10);
+ ConfirmLoaderOffsets(20, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. V ***
+ // Read a large amount that involves backwards data: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset *backwards* to create capacity.
+ // 2) Expand capacity to make sure we don't defer as data arrives.
+ // 3) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=20 [xxxxxxxxxx|xxxxxxxxxx]
+ // ^^^^ ^^^^^^^^^^ ^^^^ requested 18 bytes @ offset 16
+ // ADJUSTED OFFSET
+ // offset=16 [____xxxxxx|xxxxxxxxxx]xxxx
+ // ^^^^^^^^^^ ^^^^^^^^ requested 18 bytes @ offset 16
+ // ADJUSTED CAPACITY
+ // offset=16 [____xxxxxx|xxxxxxxxxxxxxx____]
+ // ^^^^^^^^^^^^^^^^^^ requested 18 bytes @ offset 16
+ // AFTER
+ // offset=34 [xxxxxxxxxx|__________]
+ //
+ EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading));
+ ReadLoader(16, 18, buffer);
+ ConfirmLoaderOffsets(16, 0, 18);
+ ConfirmBufferState(6, 10, 14, 18);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(2);
+
+ // Write the rest, read should complete and capacity should go back to normal.
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 18));
+ WriteData(2);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // POSTCONDITION
+ ConfirmBufferState(4, 10, 0, 10);
+ ConfirmLoaderOffsets(34, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadPastThreshold) {
+ const int kSize = 5 * 1024 * 1024;
+ const int kThreshold = 2 * 1024 * 1024;
+
+ Initialize(kHttpUrl, 10, kSize);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, kSize - 1, kSize);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ // *** TRICKY BUSINESS, PT. VI ***
+ // Read past the forward wait threshold: cache miss.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx] ...
+ // ^^^^ requested 10 bytes @ threshold
+ // AFTER
+ // offset=10 [xxxxxxxxxx] !!! cache miss !!!
+ //
+ EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0));
+ ReadLoader(kThreshold + 20, 10, buffer);
+
+ // POSTCONDITION
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, HasSingleOrigin) {
+ // Make sure no redirect case works as expected.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to the same domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect twice to the same domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToSameDomainUrl2);
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to a different domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ FullResponse(1024);
+ EXPECT_FALSE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to the same domain and then to a different domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ FullResponse(1024);
+ EXPECT_FALSE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Default) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+
+ // Test ensures that default construction of a BufferedResourceLoader has sane
+ // values.
+ //
+ // Please do not change these values in order to make a test pass! Instead,
+ // start a conversation on what the default buffer window capacities should
+ // be.
+ ConfirmLoaderBufferBackwardCapacity(2 * 1024 * 1024);
+ ConfirmLoaderBufferForwardCapacity(2 * 1024 * 1024);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_Unknown) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(0);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_BelowLowerBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(1024 * 8); // 1 Kbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_WithinBounds) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(2 * 1024 * 1024 * 8); // 2 Mbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_AboveUpperBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(100 * 1024 * 1024 * 8); // 100 Mbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Negative) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(-10);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Zero) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(0);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_BelowLowerBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(0.1f);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_WithinBounds) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(10);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_AboveUpperBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(100);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+static void ExpectContentRange(
+ const std::string& str, bool expect_success,
+ int64 expected_first, int64 expected_last, int64 expected_size) {
+ int64 first, last, size;
+ ASSERT_EQ(expect_success, BufferedResourceLoader::ParseContentRange(
+ str, &first, &last, &size)) << str;
+ if (!expect_success)
+ return;
+ EXPECT_EQ(first, expected_first);
+ EXPECT_EQ(last, expected_last);
+ EXPECT_EQ(size, expected_size);
+}
+
+static void ExpectContentRangeFailure(const std::string& str) {
+ ExpectContentRange(str, false, 0, 0, 0);
+}
+
+static void ExpectContentRangeSuccess(
+ const std::string& str,
+ int64 expected_first, int64 expected_last, int64 expected_size) {
+ ExpectContentRange(str, true, expected_first, expected_last, expected_size);
+}
+
+TEST(BufferedResourceLoaderStandaloneTest, ParseContentRange) {
+ ExpectContentRangeFailure("cytes 0-499/500");
+ ExpectContentRangeFailure("bytes 0499/500");
+ ExpectContentRangeFailure("bytes 0-499500");
+ ExpectContentRangeFailure("bytes 0-499/500-blorg");
+ ExpectContentRangeFailure("bytes 0-499/500-1");
+ ExpectContentRangeFailure("bytes 0-499/400");
+ ExpectContentRangeFailure("bytes 0-/400");
+ ExpectContentRangeFailure("bytes -300/400");
+ ExpectContentRangeFailure("bytes 20-10/400");
+
+ ExpectContentRangeSuccess("bytes 0-499/500", 0, 499, 500);
+ ExpectContentRangeSuccess("bytes 0-0/500", 0, 0, 500);
+ ExpectContentRangeSuccess("bytes 10-11/50", 10, 11, 50);
+ ExpectContentRangeSuccess("bytes 10-11/*", 10, 11,
+ kPositionNotSpecified);
+}
+
+} // namespace media
diff --git a/media/blink/cache_util.cc b/media/blink/cache_util.cc
new file mode 100644
index 0000000..65bed43
--- /dev/null
+++ b/media/blink/cache_util.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 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 "media/blink/cache_util.h"
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/http/http_util.h"
+#include "net/http/http_version.h"
+#include "third_party/WebKit/public/platform/WebCString.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+using base::Time;
+using base::TimeDelta;
+using net::HttpVersion;
+using blink::WebURLResponse;
+
+namespace media {
+
+enum { kHttpOK = 200, kHttpPartialContent = 206 };
+
+uint32 GetReasonsForUncacheability(const WebURLResponse& response) {
+ uint32 reasons = 0;
+ const int code = response.httpStatusCode();
+ const int version = response.httpVersion();
+ const HttpVersion http_version =
+ version == WebURLResponse::HTTP_1_1 ? HttpVersion(1, 1) :
+ version == WebURLResponse::HTTP_1_0 ? HttpVersion(1, 0) :
+ version == WebURLResponse::HTTP_0_9 ? HttpVersion(0, 9) :
+ HttpVersion();
+ if (code != kHttpOK && code != kHttpPartialContent)
+ reasons |= kNoData;
+ if (http_version < HttpVersion(1, 1) && code == kHttpPartialContent)
+ reasons |= kPre11PartialResponse;
+ if (code == kHttpPartialContent &&
+ !net::HttpUtil::HasStrongValidators(
+ http_version,
+ response.httpHeaderField("etag").utf8(),
+ response.httpHeaderField("Last-Modified").utf8(),
+ response.httpHeaderField("Date").utf8())) {
+ reasons |= kNoStrongValidatorOnPartialResponse;
+ }
+
+ std::string cache_control_header =
+ response.httpHeaderField("cache-control").utf8();
+ base::StringToLowerASCII(&cache_control_header);
+ if (cache_control_header.find("no-cache") != std::string::npos)
+ reasons |= kNoCache;
+ if (cache_control_header.find("no-store") != std::string::npos)
+ reasons |= kNoStore;
+ if (cache_control_header.find("must-revalidate") != std::string::npos)
+ reasons |= kHasMustRevalidate;
+
+ const TimeDelta kMinimumAgeForUsefulness =
+ TimeDelta::FromSeconds(3600); // Arbitrary value.
+
+ const char kMaxAgePrefix[] = "max-age=";
+ const size_t kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1;
+ if (cache_control_header.substr(0, kMaxAgePrefixLen) == kMaxAgePrefix) {
+ int64 max_age_seconds;
+ base::StringToInt64(
+ base::StringPiece(cache_control_header.begin() + kMaxAgePrefixLen,
+ cache_control_header.end()),
+ &max_age_seconds);
+ if (TimeDelta::FromSeconds(max_age_seconds) < kMinimumAgeForUsefulness)
+ reasons |= kShortMaxAge;
+ }
+
+ Time date;
+ Time expires;
+ if (Time::FromString(response.httpHeaderField("Date").utf8().data(), &date) &&
+ Time::FromString(response.httpHeaderField("Expires").utf8().data(),
+ &expires) &&
+ date > Time() && expires > Time() &&
+ (expires - date) < kMinimumAgeForUsefulness) {
+ reasons |= kExpiresTooSoon;
+ }
+
+ return reasons;
+}
+
+} // namespace media
diff --git a/media/blink/cache_util.h b/media/blink/cache_util.h
new file mode 100644
index 0000000..5c59f14
--- /dev/null
+++ b/media/blink/cache_util.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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 MEDIA_BLINK_CACHE_UTIL_H_
+#define MEDIA_BLINK_CACHE_UTIL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace blink {
+class WebURLResponse;
+}
+
+namespace media {
+
+// Reasons that a cached WebURLResponse will *not* prevent a future request to
+// the server. Reported via UMA, so don't change/reuse previously-existing
+// values.
+enum UncacheableReason {
+ kNoData = 1 << 0, // Not 200 or 206.
+ kPre11PartialResponse = 1 << 1, // 206 but HTTP version < 1.1.
+ kNoStrongValidatorOnPartialResponse = 1 << 2, // 206, no strong validator.
+ kShortMaxAge = 1 << 3, // Max age less than 1h (arbitrary value).
+ kExpiresTooSoon = 1 << 4, // Expires in less than 1h (arbitrary value).
+ kHasMustRevalidate = 1 << 5, // Response asks for revalidation.
+ kNoCache = 1 << 6, // Response included a no-cache header.
+ kNoStore = 1 << 7, // Response included a no-store header.
+ kMaxReason // Needs to be one more than max legitimate reason.
+};
+
+// Return the logical OR of the reasons "response" cannot be used for a future
+// request (using the disk cache), or 0 if it might be useful.
+uint32 MEDIA_EXPORT GetReasonsForUncacheability(
+ const blink::WebURLResponse& response);
+
+} // namespace media
+
+#endif // MEDIA_BLINK_CACHE_UTIL_H_
diff --git a/media/blink/cache_util_unittest.cc b/media/blink/cache_util_unittest.cc
new file mode 100644
index 0000000..7ea2f13
--- /dev/null
+++ b/media/blink/cache_util_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright 2013 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 "media/blink/cache_util.h"
+
+#include <string>
+
+#include "base/format_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+using blink::WebString;
+using blink::WebURLResponse;
+
+namespace media {
+
+// Inputs & expected output for GetReasonsForUncacheability.
+struct GRFUTestCase {
+ WebURLResponse::HTTPVersion version;
+ int status_code;
+ const char* headers;
+ uint32 expected_reasons;
+};
+
+// Create a new WebURLResponse object.
+static WebURLResponse CreateResponse(const GRFUTestCase& test) {
+ WebURLResponse response;
+ response.initialize();
+ response.setHTTPVersion(test.version);
+ response.setHTTPStatusCode(test.status_code);
+ std::vector<std::string> lines;
+ Tokenize(test.headers, "\n", &lines);
+ for (size_t i = 0; i < lines.size(); ++i) {
+ size_t colon = lines[i].find(": ");
+ response.addHTTPHeaderField(
+ WebString::fromUTF8(lines[i].substr(0, colon)),
+ WebString::fromUTF8(lines[i].substr(colon + 2)));
+ }
+ return response;
+}
+
+TEST(CacheUtilTest, GetReasonsForUncacheability) {
+ enum { kNoReasons = 0 };
+
+ const GRFUTestCase tests[] = {
+ {
+ WebURLResponse::HTTP_1_1, 206, "ETag: 'fooblort'", kNoReasons
+ },
+ {
+ WebURLResponse::HTTP_1_1, 206, "", kNoStrongValidatorOnPartialResponse
+ },
+ {
+ WebURLResponse::HTTP_1_0, 206, "",
+ kPre11PartialResponse | kNoStrongValidatorOnPartialResponse
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200, "cache-control: max-Age=42", kShortMaxAge
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200, "cache-control: max-Age=4200", kNoReasons
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200,
+ "Date: Tue, 22 May 2012 23:46:08 GMT\n"
+ "Expires: Tue, 22 May 2012 23:56:08 GMT", kExpiresTooSoon
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200, "cache-control: must-revalidate",
+ kHasMustRevalidate
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200, "cache-control: no-cache", kNoCache
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200, "cache-control: no-store", kNoStore
+ },
+ {
+ WebURLResponse::HTTP_1_1, 200,
+ "cache-control: no-cache\ncache-control: no-store", kNoCache | kNoStore
+ },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("case: %" PRIuS
+ ", version: %d, code: %d, headers: %s",
+ i, tests[i].version, tests[i].status_code,
+ tests[i].headers));
+ EXPECT_EQ(GetReasonsForUncacheability(CreateResponse(tests[i])),
+ tests[i].expected_reasons);
+ }
+}
+
+} // namespace media
diff --git a/media/blink/encrypted_media_player_support.cc b/media/blink/encrypted_media_player_support.cc
new file mode 100644
index 0000000..663141f
--- /dev/null
+++ b/media/blink/encrypted_media_player_support.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 "media/blink/encrypted_media_player_support.h"
+
+namespace media {
+
+EncryptedMediaPlayerSupport::EncryptedMediaPlayerSupport() {
+}
+
+EncryptedMediaPlayerSupport::~EncryptedMediaPlayerSupport() {
+}
+
+} // namespace media
diff --git a/media/blink/encrypted_media_player_support.h b/media/blink/encrypted_media_player_support.h
new file mode 100644
index 0000000..b387686
--- /dev/null
+++ b/media/blink/encrypted_media_player_support.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 MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_
+#define MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_
+
+#include "media/base/decryptor.h"
+#include "media/base/demuxer.h"
+#include "media/base/media_export.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayer.h"
+
+namespace blink {
+class WebContentDecryptionModule;
+class WebContentDecryptionModuleResult;
+class WebLocalFrame;
+class WebMediaPlayerClient;
+class WebString;
+}
+
+namespace media {
+
+class MEDIA_EXPORT EncryptedMediaPlayerSupport {
+ public:
+ EncryptedMediaPlayerSupport();
+ virtual ~EncryptedMediaPlayerSupport();
+
+ // Prefixed API methods.
+ virtual blink::WebMediaPlayer::MediaKeyException GenerateKeyRequest(
+ blink::WebLocalFrame* frame,
+ const blink::WebString& key_system,
+ const unsigned char* init_data,
+ unsigned init_data_length) = 0;
+
+ virtual blink::WebMediaPlayer::MediaKeyException AddKey(
+ const blink::WebString& key_system,
+ const unsigned char* key,
+ unsigned key_length,
+ const unsigned char* init_data,
+ unsigned init_data_length,
+ const blink::WebString& session_id) = 0;
+
+ virtual blink::WebMediaPlayer::MediaKeyException CancelKeyRequest(
+ const blink::WebString& key_system,
+ const blink::WebString& session_id) = 0;
+
+
+ // Unprefixed API methods.
+ virtual void SetContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm) = 0;
+ virtual void SetContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm,
+ blink::WebContentDecryptionModuleResult result) = 0;
+ virtual void SetContentDecryptionModuleSync(
+ blink::WebContentDecryptionModule* cdm) = 0;
+
+
+ // Callback factory and notification methods used by WebMediaPlayerImpl.
+
+ // Creates a callback that Demuxers can use to signal that the content
+ // requires a key. This method make sure the callback returned can be safely
+ // invoked from any thread.
+ virtual Demuxer::NeedKeyCB CreateNeedKeyCB() = 0;
+
+ // Creates a callback that renderers can use to set decryptor
+ // ready callback. This method make sure the callback returned can be safely
+ // invoked from any thread.
+ virtual SetDecryptorReadyCB CreateSetDecryptorReadyCB() = 0;
+
+ // Called to inform this object that the media pipeline encountered
+ // and handled a decryption error.
+ virtual void OnPipelineDecryptError() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EncryptedMediaPlayerSupport);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_
diff --git a/media/blink/media_blink.gyp b/media/blink/media_blink.gyp
new file mode 100644
index 0000000..975a701
--- /dev/null
+++ b/media/blink/media_blink.gyp
@@ -0,0 +1,104 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ # GN version: //media/blink
+ 'target_name': 'media_blink',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../../base/base.gyp:base',
+ '../../cc/cc.gyp:cc',
+ '../../cc/blink/cc_blink.gyp:cc_blink',
+ '../../ui/gfx/gfx.gyp:gfx_geometry',
+ '../../net/net.gyp:net',
+ '../../third_party/WebKit/public/blink.gyp:blink',
+ '../media.gyp:media',
+ '../media.gyp:shared_memory_support',
+ '../../url/url.gyp:url_lib',
+ ],
+ 'defines': [
+ 'MEDIA_IMPLEMENTATION',
+ ],
+ # This sources list is duplicated in //media/blink/BUILD.gn
+ 'sources': [
+ 'active_loader.cc',
+ 'active_loader.h',
+ 'buffered_data_source.cc',
+ 'buffered_data_source.h',
+ 'buffered_data_source_host_impl.cc',
+ 'buffered_data_source_host_impl.h',
+ 'buffered_resource_loader.cc',
+ 'buffered_resource_loader.h',
+ 'encrypted_media_player_support.cc',
+ 'encrypted_media_player_support.h',
+ 'cache_util.cc',
+ 'cache_util.h',
+ 'texttrack_impl.cc',
+ 'texttrack_impl.h',
+ 'video_frame_compositor.cc',
+ 'video_frame_compositor.h',
+ 'webaudiosourceprovider_impl.cc',
+ 'webaudiosourceprovider_impl.h',
+ 'webinbandtexttrack_impl.cc',
+ 'webinbandtexttrack_impl.h',
+ 'webmediaplayer_delegate.h',
+ 'webmediaplayer_impl.cc',
+ 'webmediaplayer_impl.h',
+ 'webmediaplayer_params.cc',
+ 'webmediaplayer_params.h',
+ 'webmediaplayer_util.cc',
+ 'webmediaplayer_util.h',
+ 'webmediasource_impl.cc',
+ 'webmediasource_impl.h',
+ 'websourcebuffer_impl.cc',
+ 'websourcebuffer_impl.h',
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'sources!': [
+ 'webmediaplayer_impl.cc',
+ ],
+ },
+ ],
+ ],
+ },
+ {
+ 'target_name': 'media_blink_unittests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ 'media_blink',
+ '../media.gyp:media',
+ '../media.gyp:media_test_support',
+ '../../base/base.gyp:base',
+ '../../base/base.gyp:test_support_base',
+ '../../cc/cc.gyp:cc',
+ '../../cc/blink/cc_blink.gyp:cc_blink',
+ '../../net/net.gyp:net',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ '../../third_party/WebKit/public/blink.gyp:blink',
+ '../../ui/gfx/gfx.gyp:gfx',
+ '../../ui/gfx/gfx.gyp:gfx_geometry',
+ '../../ui/gfx/gfx.gyp:gfx_test_support',
+ '../../url/url.gyp:url_lib',
+ ],
+ 'sources': [
+ 'buffered_data_source_host_impl_unittest.cc',
+ 'buffered_data_source_unittest.cc',
+ 'buffered_resource_loader_unittest.cc',
+ 'cache_util_unittest.cc',
+ 'mock_webframeclient.h',
+ 'mock_weburlloader.cc',
+ 'mock_weburlloader.h',
+ 'run_all_unittests.cc',
+ 'test_response_generator.cc',
+ 'test_response_generator.h',
+ 'video_frame_compositor_unittest.cc',
+ 'webaudiosourceprovider_impl_unittest.cc',
+ ],
+ },
+ ]
+}
diff --git a/media/blink/mock_webframeclient.h b/media/blink/mock_webframeclient.h
new file mode 100644
index 0000000..7a63358
--- /dev/null
+++ b/media/blink/mock_webframeclient.h
@@ -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.
+
+#ifndef MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_
+#define MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_
+
+#include "third_party/WebKit/public/web/WebFrameClient.h"
+
+namespace media {
+
+class MockWebFrameClient : public blink::WebFrameClient {};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_
diff --git a/media/blink/mock_weburlloader.cc b/media/blink/mock_weburlloader.cc
new file mode 100644
index 0000000..578476a
--- /dev/null
+++ b/media/blink/mock_weburlloader.cc
@@ -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.
+
+#include "media/blink/mock_weburlloader.h"
+
+#include "third_party/WebKit/public/platform/WebData.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+namespace media {
+
+MockWebURLLoader::MockWebURLLoader() {}
+
+MockWebURLLoader::~MockWebURLLoader() {}
+
+} // namespace media
diff --git a/media/blink/mock_weburlloader.h b/media/blink/mock_weburlloader.h
new file mode 100644
index 0000000..70dd5a6
--- /dev/null
+++ b/media/blink/mock_weburlloader.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 MEDIA_BLINK_MOCK_WEBURLLOADER_H_
+#define MEDIA_BLINK_MOCK_WEBURLLOADER_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/WebKit/public/platform/WebURLLoader.h"
+
+namespace media {
+
+class MockWebURLLoader : public blink::WebURLLoader {
+ public:
+ MockWebURLLoader();
+ virtual ~MockWebURLLoader();
+
+ MOCK_METHOD4(loadSynchronously, void(const blink::WebURLRequest& request,
+ blink::WebURLResponse& response,
+ blink::WebURLError& error,
+ blink::WebData& data));
+ MOCK_METHOD2(loadAsynchronously, void(const blink::WebURLRequest& request,
+ blink::WebURLLoaderClient* client));
+ MOCK_METHOD0(cancel, void());
+ MOCK_METHOD1(setDefersLoading, void(bool value));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockWebURLLoader);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_MOCK_WEBURLLOADER_H_
diff --git a/media/blink/run_all_unittests.cc b/media/blink/run_all_unittests.cc
new file mode 100644
index 0000000..081a121
--- /dev/null
+++ b/media/blink/run_all_unittests.cc
@@ -0,0 +1,85 @@
+// 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"
+#include "third_party/WebKit/public/web/WebKit.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "media/base/android/media_jni_registrar.h"
+#include "ui/gl/android/gl_jni_registrar.h"
+#endif
+
+class TestBlinkPlatformSupport : NON_EXPORTED_BASE(public blink::Platform) {
+ public:
+ virtual ~TestBlinkPlatformSupport();
+
+ virtual void cryptographicallyRandomValues(unsigned char* buffer,
+ size_t length) OVERRIDE;
+ virtual const unsigned char* getTraceCategoryEnabledFlag(
+ const char* categoryName) OVERRIDE;
+};
+
+TestBlinkPlatformSupport::~TestBlinkPlatformSupport() {}
+
+void TestBlinkPlatformSupport::cryptographicallyRandomValues(
+ unsigned char* buffer,
+ size_t length) {
+}
+
+const unsigned char* TestBlinkPlatformSupport::getTraceCategoryEnabledFlag(
+ const char* categoryName) {
+ static const unsigned char tracingIsDisabled = 0;
+ return &tracingIsDisabled;
+}
+
+class BlinkMediaTestSuite : public base::TestSuite {
+ public:
+ BlinkMediaTestSuite(int argc, char** argv);
+ virtual ~BlinkMediaTestSuite();
+
+ protected:
+ virtual void Initialize() OVERRIDE;
+
+ private:
+ scoped_ptr<TestBlinkPlatformSupport> blink_platform_support_;
+};
+
+BlinkMediaTestSuite::BlinkMediaTestSuite(int argc, char** argv)
+ : TestSuite(argc, argv),
+ blink_platform_support_(new TestBlinkPlatformSupport()) {
+}
+
+BlinkMediaTestSuite::~BlinkMediaTestSuite() {}
+
+void BlinkMediaTestSuite::Initialize() {
+ // Run TestSuite::Initialize first so that logging is initialized.
+ base::TestSuite::Initialize();
+
+#if defined(OS_ANDROID)
+ // Register JNI bindings for android.
+ JNIEnv* env = base::android::AttachCurrentThread();
+ // Needed for surface texture support.
+ ui::gl::android::RegisterJni(env);
+ media::RegisterJni(env);
+#endif
+
+ // Run this here instead of main() to ensure an AtExitManager is already
+ // present.
+ media::InitializeMediaLibraryForTesting();
+
+ blink::initialize(blink_platform_support_.get());
+}
+
+int main(int argc, char** argv) {
+ BlinkMediaTestSuite test_suite(argc, argv);
+
+ return base::LaunchUnitTests(
+ argc, argv, base::Bind(&BlinkMediaTestSuite::Run,
+ base::Unretained(&test_suite)));
+}
diff --git a/media/blink/test_response_generator.cc b/media/blink/test_response_generator.cc
new file mode 100644
index 0000000..aa3b748
--- /dev/null
+++ b/media/blink/test_response_generator.cc
@@ -0,0 +1,101 @@
+// Copyright 2013 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 "media/blink/test_response_generator.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+using blink::WebString;
+using blink::WebURLError;
+using blink::WebURLResponse;
+
+namespace media {
+
+TestResponseGenerator::TestResponseGenerator(const GURL& gurl,
+ int64 content_length)
+ : gurl_(gurl),
+ content_length_(content_length) {
+}
+
+WebURLError TestResponseGenerator::GenerateError() {
+ WebURLError error;
+ error.reason = net::ERR_ABORTED;
+ error.domain = WebString::fromUTF8(net::kErrorDomain);
+ return error;
+}
+
+WebURLResponse TestResponseGenerator::Generate200() {
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(200);
+
+ response.setHTTPHeaderField(
+ WebString::fromUTF8("Content-Length"),
+ WebString::fromUTF8(base::Int64ToString(content_length_)));
+ response.setExpectedContentLength(content_length_);
+ return response;
+}
+
+WebURLResponse TestResponseGenerator::Generate206(int64 first_byte_offset) {
+ return Generate206(first_byte_offset, kNormal);
+}
+
+WebURLResponse TestResponseGenerator::Generate206(int64 first_byte_offset,
+ Flags flags) {
+ int64 range_content_length = content_length_ - first_byte_offset;
+ int64 last_byte_offset = content_length_ - 1;
+
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(206);
+
+ if ((flags & kNoAcceptRanges) == 0) {
+ response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
+ WebString::fromUTF8("bytes"));
+ }
+
+ if ((flags & kNoContentRange) == 0) {
+ std::string content_range = base::StringPrintf(
+ "bytes %" PRId64 "-%" PRId64 "/",
+ first_byte_offset, last_byte_offset);
+ if (flags & kNoContentRangeInstanceSize)
+ content_range += "*";
+ else
+ content_range += base::StringPrintf("%" PRId64, content_length_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8(content_range));
+ }
+
+ if ((flags & kNoContentLength) == 0) {
+ response.setHTTPHeaderField(
+ WebString::fromUTF8("Content-Length"),
+ WebString::fromUTF8(base::Int64ToString(range_content_length)));
+ response.setExpectedContentLength(range_content_length);
+ }
+ return response;
+}
+
+WebURLResponse TestResponseGenerator::Generate404() {
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(404);
+ return response;
+}
+
+WebURLResponse TestResponseGenerator::GenerateFileResponse(
+ int64 first_byte_offset) {
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(0);
+
+ if (first_byte_offset >= 0) {
+ response.setExpectedContentLength(content_length_ - first_byte_offset);
+ } else {
+ response.setExpectedContentLength(-1);
+ }
+ return response;
+}
+
+} // namespace media
diff --git a/media/blink/test_response_generator.h b/media/blink/test_response_generator.h
new file mode 100644
index 0000000..7e3e98d
--- /dev/null
+++ b/media/blink/test_response_generator.h
@@ -0,0 +1,66 @@
+// Copyright 2013 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 MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_
+#define MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_
+
+#include "base/basictypes.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+#include "url/gurl.h"
+
+namespace media {
+
+// Generates WebURLErrors and WebURLResponses suitable for testing purposes.
+class TestResponseGenerator {
+ public:
+ enum Flags {
+ kNormal = 0,
+ kNoAcceptRanges = 1 << 0, // Don't include Accept-Ranges in 206 response.
+ kNoContentRange = 1 << 1, // Don't include Content-Range in 206 response.
+ kNoContentLength = 1 << 2, // Don't include Content-Length in 206 response.
+ kNoContentRangeInstanceSize = 1 << 3, // Content-Range: N-M/* in 206.
+ };
+
+ // Build an HTTP response generator for the given URL. |content_length| is
+ // used to generate Content-Length and Content-Range headers.
+ TestResponseGenerator(const GURL& gurl, int64 content_length);
+
+ // Generates a WebURLError object.
+ blink::WebURLError GenerateError();
+
+ // Generates a regular HTTP 200 response.
+ blink::WebURLResponse Generate200();
+
+ // Generates a regular HTTP 206 response starting from |first_byte_offset|
+ // until the end of the resource.
+ blink::WebURLResponse Generate206(int64 first_byte_offset);
+
+ // Generates a custom HTTP 206 response starting from |first_byte_offset|
+ // until the end of the resource. You can tweak what gets included in the
+ // headers via |flags|.
+ blink::WebURLResponse Generate206(int64 first_byte_offset, Flags flags);
+
+ // Generates a regular HTTP 404 response.
+ blink::WebURLResponse Generate404();
+
+ // Generates a file:// response starting from |first_byte_offset| until the
+ // end of the resource.
+ //
+ // If |first_byte_offset| is negative a response containing no content length
+ // will be returned.
+ blink::WebURLResponse GenerateFileResponse(int64 first_byte_offset);
+
+ int64 content_length() { return content_length_; }
+
+ private:
+ GURL gurl_;
+ int64 content_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestResponseGenerator);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_
diff --git a/media/blink/texttrack_impl.cc b/media/blink/texttrack_impl.cc
new file mode 100644
index 0000000..1d58af9
--- /dev/null
+++ b/media/blink/texttrack_impl.cc
@@ -0,0 +1,70 @@
+// Copyright 2013 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 "media/blink/texttrack_impl.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/blink/webinbandtexttrack_impl.h"
+#include "third_party/WebKit/public/platform/WebInbandTextTrackClient.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
+
+namespace media {
+
+TextTrackImpl::TextTrackImpl(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ blink::WebMediaPlayerClient* client,
+ scoped_ptr<WebInbandTextTrackImpl> text_track)
+ : task_runner_(task_runner),
+ client_(client),
+ text_track_(text_track.Pass()) {
+ client_->addTextTrack(text_track_.get());
+}
+
+TextTrackImpl::~TextTrackImpl() {
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&TextTrackImpl::OnRemoveTrack,
+ client_,
+ base::Passed(&text_track_)));
+}
+
+void TextTrackImpl::addWebVTTCue(const base::TimeDelta& start,
+ const base::TimeDelta& end,
+ const std::string& id,
+ const std::string& content,
+ const std::string& settings) {
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&TextTrackImpl::OnAddCue,
+ text_track_.get(),
+ start, end,
+ id, content, settings));
+}
+
+void TextTrackImpl::OnAddCue(WebInbandTextTrackImpl* text_track,
+ const base::TimeDelta& start,
+ const base::TimeDelta& end,
+ const std::string& id,
+ const std::string& content,
+ const std::string& settings) {
+ if (blink::WebInbandTextTrackClient* client = text_track->client()) {
+ client->addWebVTTCue(start.InSecondsF(),
+ end.InSecondsF(),
+ blink::WebString::fromUTF8(id),
+ blink::WebString::fromUTF8(content),
+ blink::WebString::fromUTF8(settings));
+ }
+}
+
+void TextTrackImpl::OnRemoveTrack(
+ blink::WebMediaPlayerClient* client,
+ scoped_ptr<WebInbandTextTrackImpl> text_track) {
+ if (text_track->client())
+ client->removeTextTrack(text_track.get());
+}
+
+} // namespace media
diff --git a/media/blink/texttrack_impl.h b/media/blink/texttrack_impl.h
new file mode 100644
index 0000000..1d85bde
--- /dev/null
+++ b/media/blink/texttrack_impl.h
@@ -0,0 +1,61 @@
+// Copyright 2013 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 MEDIA_BLINK_TEXTTRACK_IMPL_H_
+#define MEDIA_BLINK_TEXTTRACK_IMPL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/text_track.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace blink {
+class WebInbandTextTrackClient;
+class WebMediaPlayerClient;
+}
+
+namespace media {
+
+class WebInbandTextTrackImpl;
+
+class TextTrackImpl : public TextTrack {
+ public:
+ // Constructor assumes ownership of the |text_track| object.
+ TextTrackImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ blink::WebMediaPlayerClient* client,
+ scoped_ptr<WebInbandTextTrackImpl> text_track);
+
+ virtual ~TextTrackImpl();
+
+ virtual void addWebVTTCue(const base::TimeDelta& start,
+ const base::TimeDelta& end,
+ const std::string& id,
+ const std::string& content,
+ const std::string& settings) OVERRIDE;
+
+ private:
+ static void OnAddCue(WebInbandTextTrackImpl* text_track,
+ const base::TimeDelta& start,
+ const base::TimeDelta& end,
+ const std::string& id,
+ const std::string& content,
+ const std::string& settings);
+
+ static void OnRemoveTrack(blink::WebMediaPlayerClient* client,
+ scoped_ptr<WebInbandTextTrackImpl> text_track);
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ blink::WebMediaPlayerClient* client_;
+ scoped_ptr<WebInbandTextTrackImpl> text_track_;
+ DISALLOW_COPY_AND_ASSIGN(TextTrackImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_TEXTTRACK_IMPL_H_
diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc
new file mode 100644
index 0000000..7f254d4
--- /dev/null
+++ b/media/blink/video_frame_compositor.cc
@@ -0,0 +1,77 @@
+// 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 "media/blink/video_frame_compositor.h"
+
+#include "media/base/video_frame.h"
+
+namespace media {
+
+static bool IsOpaque(const scoped_refptr<VideoFrame>& frame) {
+ switch (frame->format()) {
+ case VideoFrame::UNKNOWN:
+ case VideoFrame::YV12:
+ case VideoFrame::YV12J:
+ case VideoFrame::YV16:
+ case VideoFrame::I420:
+ case VideoFrame::YV24:
+ case VideoFrame::NV12:
+ return true;
+
+ case VideoFrame::YV12A:
+#if defined(VIDEO_HOLE)
+ case VideoFrame::HOLE:
+#endif // defined(VIDEO_HOLE)
+ case VideoFrame::NATIVE_TEXTURE:
+ break;
+ }
+ return false;
+}
+
+VideoFrameCompositor::VideoFrameCompositor(
+ const base::Callback<void(gfx::Size)>& natural_size_changed_cb,
+ const base::Callback<void(bool)>& opacity_changed_cb)
+ : natural_size_changed_cb_(natural_size_changed_cb),
+ opacity_changed_cb_(opacity_changed_cb),
+ client_(NULL) {
+}
+
+VideoFrameCompositor::~VideoFrameCompositor() {
+ if (client_)
+ client_->StopUsingProvider();
+}
+
+void VideoFrameCompositor::SetVideoFrameProviderClient(
+ cc::VideoFrameProvider::Client* client) {
+ if (client_)
+ client_->StopUsingProvider();
+ client_ = client;
+}
+
+scoped_refptr<VideoFrame> VideoFrameCompositor::GetCurrentFrame() {
+ return current_frame_;
+}
+
+void VideoFrameCompositor::PutCurrentFrame(
+ const scoped_refptr<VideoFrame>& frame) {
+}
+
+void VideoFrameCompositor::UpdateCurrentFrame(
+ const scoped_refptr<VideoFrame>& frame) {
+ if (current_frame_.get() &&
+ current_frame_->natural_size() != frame->natural_size()) {
+ natural_size_changed_cb_.Run(frame->natural_size());
+ }
+
+ if (!current_frame_.get() || IsOpaque(current_frame_) != IsOpaque(frame)) {
+ opacity_changed_cb_.Run(IsOpaque(frame));
+ }
+
+ current_frame_ = frame;
+
+ if (client_)
+ client_->DidReceiveFrame();
+}
+
+} // namespace media
diff --git a/media/blink/video_frame_compositor.h b/media/blink/video_frame_compositor.h
new file mode 100644
index 0000000..9fc8b6a
--- /dev/null
+++ b/media/blink/video_frame_compositor.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 MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_
+#define MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "cc/layers/video_frame_provider.h"
+#include "media/base/media_export.h"
+#include "ui/gfx/size.h"
+
+namespace media {
+class VideoFrame;
+
+// VideoFrameCompositor handles incoming frames by notifying the compositor and
+// dispatching callbacks when detecting changes in video frames.
+//
+// Typical usage is to deliver ready-to-be-displayed video frames to
+// UpdateCurrentFrame() so that VideoFrameCompositor can take care of tracking
+// changes in video frames and firing callbacks as needed.
+//
+// VideoFrameCompositor must live on the same thread as the compositor.
+class MEDIA_EXPORT VideoFrameCompositor
+ : NON_EXPORTED_BASE(public cc::VideoFrameProvider) {
+ public:
+ // |natural_size_changed_cb| is run with the new natural size of the video
+ // frame whenever a change in natural size is detected. It is not called the
+ // first time UpdateCurrentFrame() is called. Run on the same thread as the
+ // caller of UpdateCurrentFrame().
+ //
+ // |opacity_changed_cb| is run when a change in opacity is detected. It *is*
+ // called the first time UpdateCurrentFrame() is called. Run on the same
+ // thread as the caller of UpdateCurrentFrame().
+ //
+ // TODO(scherkus): Investigate the inconsistency between the callbacks with
+ // respect to why we don't call |natural_size_changed_cb| on the first frame.
+ // I suspect it was for historical reasons that no longer make sense.
+ VideoFrameCompositor(
+ const base::Callback<void(gfx::Size)>& natural_size_changed_cb,
+ const base::Callback<void(bool)>& opacity_changed_cb);
+ virtual ~VideoFrameCompositor();
+
+ // cc::VideoFrameProvider implementation.
+ virtual void SetVideoFrameProviderClient(
+ cc::VideoFrameProvider::Client* client) OVERRIDE;
+ virtual scoped_refptr<VideoFrame> GetCurrentFrame() OVERRIDE;
+ virtual void PutCurrentFrame(
+ const scoped_refptr<VideoFrame>& frame) OVERRIDE;
+
+ // Updates the current frame and notifies the compositor.
+ void UpdateCurrentFrame(const scoped_refptr<VideoFrame>& frame);
+
+ private:
+ base::Callback<void(gfx::Size)> natural_size_changed_cb_;
+ base::Callback<void(bool)> opacity_changed_cb_;
+
+ cc::VideoFrameProvider::Client* client_;
+
+ scoped_refptr<VideoFrame> current_frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositor);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_
diff --git a/media/blink/video_frame_compositor_unittest.cc b/media/blink/video_frame_compositor_unittest.cc
new file mode 100644
index 0000000..a7c46ca
--- /dev/null
+++ b/media/blink/video_frame_compositor_unittest.cc
@@ -0,0 +1,160 @@
+// 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 "cc/layers/video_frame_provider.h"
+#include "media/base/video_frame.h"
+#include "media/blink/video_frame_compositor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class VideoFrameCompositorTest : public testing::Test,
+ public cc::VideoFrameProvider::Client {
+ public:
+ VideoFrameCompositorTest()
+ : compositor_(new VideoFrameCompositor(
+ base::Bind(&VideoFrameCompositorTest::NaturalSizeChanged,
+ base::Unretained(this)),
+ base::Bind(&VideoFrameCompositorTest::OpacityChanged,
+ base::Unretained(this)))),
+ did_receive_frame_count_(0),
+ natural_size_changed_count_(0),
+ opacity_changed_count_(0),
+ opaque_(false) {
+ compositor_->SetVideoFrameProviderClient(this);
+ }
+
+ virtual ~VideoFrameCompositorTest() {
+ compositor_->SetVideoFrameProviderClient(NULL);
+ }
+
+ VideoFrameCompositor* compositor() { return compositor_.get(); }
+ int did_receive_frame_count() { return did_receive_frame_count_; }
+ int natural_size_changed_count() { return natural_size_changed_count_; }
+ gfx::Size natural_size() { return natural_size_; }
+
+ int opacity_changed_count() { return opacity_changed_count_; }
+ bool opaque() { return opaque_; }
+
+ private:
+ // cc::VideoFrameProvider::Client implementation.
+ virtual void StopUsingProvider() OVERRIDE {}
+ virtual void DidReceiveFrame() OVERRIDE {
+ ++did_receive_frame_count_;
+ }
+ virtual void DidUpdateMatrix(const float* matrix) OVERRIDE {}
+
+ void NaturalSizeChanged(gfx::Size natural_size) {
+ ++natural_size_changed_count_;
+ natural_size_ = natural_size;
+ }
+
+ void OpacityChanged(bool opaque) {
+ ++opacity_changed_count_;
+ opaque_ = opaque;
+ }
+
+ scoped_ptr<VideoFrameCompositor> compositor_;
+ int did_receive_frame_count_;
+ int natural_size_changed_count_;
+ gfx::Size natural_size_;
+ int opacity_changed_count_;
+ bool opaque_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositorTest);
+};
+
+TEST_F(VideoFrameCompositorTest, InitialValues) {
+ EXPECT_FALSE(compositor()->GetCurrentFrame().get());
+}
+
+TEST_F(VideoFrameCompositorTest, UpdateCurrentFrame) {
+ scoped_refptr<VideoFrame> expected = VideoFrame::CreateEOSFrame();
+
+ // Should notify compositor synchronously.
+ EXPECT_EQ(0, did_receive_frame_count());
+ compositor()->UpdateCurrentFrame(expected);
+ scoped_refptr<VideoFrame> actual = compositor()->GetCurrentFrame();
+ EXPECT_EQ(expected, actual);
+ EXPECT_EQ(1, did_receive_frame_count());
+}
+
+TEST_F(VideoFrameCompositorTest, NaturalSizeChanged) {
+ gfx::Size initial_size(8, 8);
+ scoped_refptr<VideoFrame> initial_frame =
+ VideoFrame::CreateBlackFrame(initial_size);
+
+ gfx::Size larger_size(16, 16);
+ scoped_refptr<VideoFrame> larger_frame =
+ VideoFrame::CreateBlackFrame(larger_size);
+
+ // Initial expectations.
+ EXPECT_EQ(0, natural_size().width());
+ EXPECT_EQ(0, natural_size().height());
+ EXPECT_EQ(0, natural_size_changed_count());
+
+ // Callback isn't fired for the first frame.
+ compositor()->UpdateCurrentFrame(initial_frame);
+ EXPECT_EQ(0, natural_size().width());
+ EXPECT_EQ(0, natural_size().height());
+ EXPECT_EQ(0, natural_size_changed_count());
+
+ // Callback should be fired once.
+ compositor()->UpdateCurrentFrame(larger_frame);
+ EXPECT_EQ(larger_size.width(), natural_size().width());
+ EXPECT_EQ(larger_size.height(), natural_size().height());
+ EXPECT_EQ(1, natural_size_changed_count());
+
+ compositor()->UpdateCurrentFrame(larger_frame);
+ EXPECT_EQ(larger_size.width(), natural_size().width());
+ EXPECT_EQ(larger_size.height(), natural_size().height());
+ EXPECT_EQ(1, natural_size_changed_count());
+
+ // Callback is fired once more when switching back to initial size.
+ compositor()->UpdateCurrentFrame(initial_frame);
+ EXPECT_EQ(initial_size.width(), natural_size().width());
+ EXPECT_EQ(initial_size.height(), natural_size().height());
+ EXPECT_EQ(2, natural_size_changed_count());
+
+ compositor()->UpdateCurrentFrame(initial_frame);
+ EXPECT_EQ(initial_size.width(), natural_size().width());
+ EXPECT_EQ(initial_size, natural_size());
+ EXPECT_EQ(2, natural_size_changed_count());
+}
+
+TEST_F(VideoFrameCompositorTest, OpacityChanged) {
+ gfx::Size size(8, 8);
+ gfx::Rect rect(gfx::Point(0, 0), size);
+ scoped_refptr<VideoFrame> opaque_frame = VideoFrame::CreateFrame(
+ VideoFrame::YV12, size, rect, size, base::TimeDelta());
+ scoped_refptr<VideoFrame> not_opaque_frame = VideoFrame::CreateFrame(
+ VideoFrame::YV12A, size, rect, size, base::TimeDelta());
+
+ // Initial expectations.
+ EXPECT_FALSE(opaque());
+ EXPECT_EQ(0, opacity_changed_count());
+
+ // Callback is fired for the first frame.
+ compositor()->UpdateCurrentFrame(not_opaque_frame);
+ EXPECT_FALSE(opaque());
+ EXPECT_EQ(1, opacity_changed_count());
+
+ // Callback shouldn't be first subsequent times with same opaqueness.
+ compositor()->UpdateCurrentFrame(not_opaque_frame);
+ EXPECT_FALSE(opaque());
+ EXPECT_EQ(1, opacity_changed_count());
+
+ // Callback is fired when using opacity changes.
+ compositor()->UpdateCurrentFrame(opaque_frame);
+ EXPECT_TRUE(opaque());
+ EXPECT_EQ(2, opacity_changed_count());
+
+ // Callback shouldn't be first subsequent times with same opaqueness.
+ compositor()->UpdateCurrentFrame(opaque_frame);
+ EXPECT_TRUE(opaque());
+ EXPECT_EQ(2, opacity_changed_count());
+}
+
+} // namespace media
diff --git a/media/blink/webaudiosourceprovider_impl.cc b/media/blink/webaudiosourceprovider_impl.cc
new file mode 100644
index 0000000..6637719
--- /dev/null
+++ b/media/blink/webaudiosourceprovider_impl.cc
@@ -0,0 +1,193 @@
+// Copyright 2013 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 "media/blink/webaudiosourceprovider_impl.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "media/base/bind_to_current_loop.h"
+#include "third_party/WebKit/public/platform/WebAudioSourceProviderClient.h"
+
+using blink::WebVector;
+
+namespace media {
+
+namespace {
+
+// Simple helper class for Try() locks. Lock is Try()'d on construction and
+// must be checked via the locked() attribute. If acquisition was successful
+// the lock will be released upon destruction.
+// TODO(dalecurtis): This should probably move to base/ if others start using
+// this pattern.
+class AutoTryLock {
+ public:
+ explicit AutoTryLock(base::Lock& lock)
+ : lock_(lock),
+ acquired_(lock_.Try()) {}
+
+ bool locked() const { return acquired_; }
+
+ ~AutoTryLock() {
+ if (acquired_) {
+ lock_.AssertAcquired();
+ lock_.Release();
+ }
+ }
+
+ private:
+ base::Lock& lock_;
+ const bool acquired_;
+ DISALLOW_COPY_AND_ASSIGN(AutoTryLock);
+};
+
+} // namespace
+
+WebAudioSourceProviderImpl::WebAudioSourceProviderImpl(
+ const scoped_refptr<AudioRendererSink>& sink)
+ : channels_(0),
+ sample_rate_(0),
+ volume_(1.0),
+ state_(kStopped),
+ renderer_(NULL),
+ client_(NULL),
+ sink_(sink),
+ weak_factory_(this) {}
+
+WebAudioSourceProviderImpl::~WebAudioSourceProviderImpl() {
+}
+
+void WebAudioSourceProviderImpl::setClient(
+ blink::WebAudioSourceProviderClient* client) {
+ base::AutoLock auto_lock(sink_lock_);
+ if (client && client != client_) {
+ // Detach the audio renderer from normal playback.
+ sink_->Stop();
+
+ // The client will now take control by calling provideInput() periodically.
+ client_ = client;
+
+ set_format_cb_ = BindToCurrentLoop(base::Bind(
+ &WebAudioSourceProviderImpl::OnSetFormat, weak_factory_.GetWeakPtr()));
+
+ // If |renderer_| is set, then run |set_format_cb_| to send |client_|
+ // the current format info. If |renderer_| is not set, then |set_format_cb_|
+ // will get called when Initialize() is called.
+ // Note: Always using |set_format_cb_| ensures we have the same
+ // locking order when calling into |client_|.
+ if (renderer_)
+ base::ResetAndReturn(&set_format_cb_).Run();
+ } else if (!client && client_) {
+ // Restore normal playback.
+ client_ = NULL;
+ sink_->SetVolume(volume_);
+ if (state_ >= kStarted)
+ sink_->Start();
+ if (state_ >= kPlaying)
+ sink_->Play();
+ }
+}
+
+void WebAudioSourceProviderImpl::provideInput(
+ const WebVector<float*>& audio_data, size_t number_of_frames) {
+ if (!bus_wrapper_ ||
+ static_cast<size_t>(bus_wrapper_->channels()) != audio_data.size()) {
+ bus_wrapper_ = AudioBus::CreateWrapper(static_cast<int>(audio_data.size()));
+ }
+
+ bus_wrapper_->set_frames(static_cast<int>(number_of_frames));
+ for (size_t i = 0; i < audio_data.size(); ++i)
+ bus_wrapper_->SetChannelData(static_cast<int>(i), audio_data[i]);
+
+ // Use a try lock to avoid contention in the real-time audio thread.
+ AutoTryLock auto_try_lock(sink_lock_);
+ if (!auto_try_lock.locked() || state_ != kPlaying) {
+ // Provide silence if we failed to acquire the lock or the source is not
+ // running.
+ bus_wrapper_->Zero();
+ return;
+ }
+
+ DCHECK(renderer_);
+ DCHECK(client_);
+ DCHECK_EQ(channels_, bus_wrapper_->channels());
+ const int frames = renderer_->Render(bus_wrapper_.get(), 0);
+ if (frames < static_cast<int>(number_of_frames)) {
+ bus_wrapper_->ZeroFramesPartial(
+ frames,
+ static_cast<int>(number_of_frames - frames));
+ }
+
+ bus_wrapper_->Scale(volume_);
+}
+
+void WebAudioSourceProviderImpl::Start() {
+ base::AutoLock auto_lock(sink_lock_);
+ DCHECK_EQ(state_, kStopped);
+ state_ = kStarted;
+ if (!client_)
+ sink_->Start();
+}
+
+void WebAudioSourceProviderImpl::Stop() {
+ base::AutoLock auto_lock(sink_lock_);
+ state_ = kStopped;
+ if (!client_)
+ sink_->Stop();
+}
+
+void WebAudioSourceProviderImpl::Play() {
+ base::AutoLock auto_lock(sink_lock_);
+ DCHECK_EQ(state_, kStarted);
+ state_ = kPlaying;
+ if (!client_)
+ sink_->Play();
+}
+
+void WebAudioSourceProviderImpl::Pause() {
+ base::AutoLock auto_lock(sink_lock_);
+ DCHECK(state_ == kPlaying || state_ == kStarted);
+ state_ = kStarted;
+ if (!client_)
+ sink_->Pause();
+}
+
+bool WebAudioSourceProviderImpl::SetVolume(double volume) {
+ base::AutoLock auto_lock(sink_lock_);
+ volume_ = volume;
+ if (!client_)
+ sink_->SetVolume(volume);
+ return true;
+}
+
+void WebAudioSourceProviderImpl::Initialize(
+ const AudioParameters& params,
+ RenderCallback* renderer) {
+ base::AutoLock auto_lock(sink_lock_);
+ CHECK(!renderer_);
+ renderer_ = renderer;
+
+ DCHECK_EQ(state_, kStopped);
+ sink_->Initialize(params, renderer);
+
+ // Keep track of the format in case the client hasn't yet been set.
+ channels_ = params.channels();
+ sample_rate_ = params.sample_rate();
+
+ if (!set_format_cb_.is_null())
+ base::ResetAndReturn(&set_format_cb_).Run();
+}
+
+void WebAudioSourceProviderImpl::OnSetFormat() {
+ base::AutoLock auto_lock(sink_lock_);
+ if (!client_)
+ return;
+
+ // Inform Blink about the audio stream format.
+ client_->setFormat(channels_, sample_rate_);
+}
+
+} // namespace media
diff --git a/media/blink/webaudiosourceprovider_impl.h b/media/blink/webaudiosourceprovider_impl.h
new file mode 100644
index 0000000..e686e9d
--- /dev/null
+++ b/media/blink/webaudiosourceprovider_impl.h
@@ -0,0 +1,90 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_
+#define MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/base/media_export.h"
+#include "third_party/WebKit/public/platform/WebAudioSourceProvider.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+
+namespace blink {
+class WebAudioSourceProviderClient;
+}
+
+namespace media {
+
+// WebAudioSourceProviderImpl provides a bridge between classes:
+// blink::WebAudioSourceProvider <---> AudioRendererSink
+//
+// WebAudioSourceProviderImpl wraps an existing audio sink that is used unless
+// WebKit has set a client via setClient(). While a client is set WebKit will
+// periodically call provideInput() to render a certain number of audio
+// sample-frames using the sink's RenderCallback to get the data.
+//
+// All calls are protected by a lock.
+class MEDIA_EXPORT WebAudioSourceProviderImpl
+ : NON_EXPORTED_BASE(public blink::WebAudioSourceProvider),
+ NON_EXPORTED_BASE(public AudioRendererSink) {
+ public:
+ explicit WebAudioSourceProviderImpl(
+ const scoped_refptr<AudioRendererSink>& sink);
+
+ // blink::WebAudioSourceProvider implementation.
+ virtual void setClient(blink::WebAudioSourceProviderClient* client);
+ virtual void provideInput(const blink::WebVector<float*>& audio_data,
+ size_t number_of_frames);
+
+ // AudioRendererSink implementation.
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void Play() OVERRIDE;
+ virtual void Pause() OVERRIDE;
+ virtual bool SetVolume(double volume) OVERRIDE;
+ virtual void Initialize(const AudioParameters& params,
+ RenderCallback* renderer) OVERRIDE;
+
+ protected:
+ virtual ~WebAudioSourceProviderImpl();
+
+ private:
+ // Calls setFormat() on |client_| from the Blink renderer thread.
+ void OnSetFormat();
+
+ // Closure that posts a task to call OnSetFormat() on the renderer thread.
+ base::Closure set_format_cb_;
+
+ // Set to true when Initialize() is called.
+ int channels_;
+ int sample_rate_;
+ double volume_;
+
+ // Tracks the current playback state.
+ enum PlaybackState { kStopped, kStarted, kPlaying };
+ PlaybackState state_;
+
+ // Where audio comes from.
+ AudioRendererSink::RenderCallback* renderer_;
+
+ // When set via setClient() it overrides |sink_| for consuming audio.
+ blink::WebAudioSourceProviderClient* client_;
+
+ // Where audio ends up unless overridden by |client_|.
+ base::Lock sink_lock_;
+ scoped_refptr<AudioRendererSink> sink_;
+ scoped_ptr<AudioBus> bus_wrapper_;
+
+ // NOTE: Weak pointers must be invalidated before all other member variables.
+ base::WeakPtrFactory<WebAudioSourceProviderImpl> weak_factory_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebAudioSourceProviderImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_
diff --git a/media/blink/webaudiosourceprovider_impl_unittest.cc b/media/blink/webaudiosourceprovider_impl_unittest.cc
new file mode 100644
index 0000000..c1725fa
--- /dev/null
+++ b/media/blink/webaudiosourceprovider_impl_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 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/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/fake_audio_render_callback.h"
+#include "media/base/mock_audio_renderer_sink.h"
+#include "media/blink/webaudiosourceprovider_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebAudioSourceProviderClient.h"
+
+namespace media {
+
+namespace {
+const float kTestVolume = 0.25;
+} // namespace
+
+class WebAudioSourceProviderImplTest
+ : public testing::Test,
+ public blink::WebAudioSourceProviderClient {
+ public:
+ WebAudioSourceProviderImplTest()
+ : params_(AudioParameters::AUDIO_PCM_LINEAR,
+ CHANNEL_LAYOUT_STEREO, 48000, 16, 64),
+ fake_callback_(0.1),
+ mock_sink_(new MockAudioRendererSink()),
+ wasp_impl_(new WebAudioSourceProviderImpl(mock_sink_)) {
+ }
+
+ virtual ~WebAudioSourceProviderImplTest() {}
+
+ void CallAllSinkMethodsAndVerify(bool verify) {
+ testing::InSequence s;
+
+ EXPECT_CALL(*mock_sink_.get(), Start()).Times(verify);
+ wasp_impl_->Start();
+
+ EXPECT_CALL(*mock_sink_.get(), Play()).Times(verify);
+ wasp_impl_->Play();
+
+ EXPECT_CALL(*mock_sink_.get(), Pause()).Times(verify);
+ wasp_impl_->Pause();
+
+ EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume)).Times(verify);
+ wasp_impl_->SetVolume(kTestVolume);
+
+ EXPECT_CALL(*mock_sink_.get(), Stop()).Times(verify);
+ wasp_impl_->Stop();
+
+ testing::Mock::VerifyAndClear(mock_sink_.get());
+ }
+
+ void SetClient(blink::WebAudioSourceProviderClient* client) {
+ testing::InSequence s;
+
+ if (client) {
+ EXPECT_CALL(*mock_sink_.get(), Stop());
+ EXPECT_CALL(*this, setFormat(params_.channels(), params_.sample_rate()));
+ }
+ wasp_impl_->setClient(client);
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClear(mock_sink_.get());
+ testing::Mock::VerifyAndClear(this);
+ }
+
+ bool CompareBusses(const AudioBus* bus1, const AudioBus* bus2) {
+ EXPECT_EQ(bus1->channels(), bus2->channels());
+ EXPECT_EQ(bus1->frames(), bus2->frames());
+ for (int ch = 0; ch < bus1->channels(); ++ch) {
+ if (memcmp(bus1->channel(ch), bus2->channel(ch),
+ sizeof(*bus1->channel(ch)) * bus1->frames()) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // blink::WebAudioSourceProviderClient implementation.
+ MOCK_METHOD2(setFormat, void(size_t numberOfChannels, float sampleRate));
+
+ protected:
+ AudioParameters params_;
+ FakeAudioRenderCallback fake_callback_;
+ scoped_refptr<MockAudioRendererSink> mock_sink_;
+ scoped_refptr<WebAudioSourceProviderImpl> wasp_impl_;
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebAudioSourceProviderImplTest);
+};
+
+TEST_F(WebAudioSourceProviderImplTest, SetClientBeforeInitialize) {
+ // setClient() with a NULL client should do nothing if no client is set.
+ wasp_impl_->setClient(NULL);
+
+ EXPECT_CALL(*mock_sink_.get(), Stop());
+ wasp_impl_->setClient(this);
+ base::RunLoop().RunUntilIdle();
+
+ // When Initialize() is called after setClient(), the params should propagate
+ // to the client via setFormat() during the call.
+ EXPECT_CALL(*this, setFormat(params_.channels(), params_.sample_rate()));
+ wasp_impl_->Initialize(params_, &fake_callback_);
+ base::RunLoop().RunUntilIdle();
+
+ // setClient() with the same client should do nothing.
+ wasp_impl_->setClient(this);
+ base::RunLoop().RunUntilIdle();
+}
+
+// Verify AudioRendererSink functionality w/ and w/o a client.
+TEST_F(WebAudioSourceProviderImplTest, SinkMethods) {
+ wasp_impl_->Initialize(params_, &fake_callback_);
+ ASSERT_EQ(mock_sink_->callback(), &fake_callback_);
+
+ // Without a client all WASP calls should fall through to the underlying sink.
+ CallAllSinkMethodsAndVerify(true);
+
+ // With a client no calls should reach the Stop()'d sink. Also, setClient()
+ // should propagate the params provided during Initialize() at call time.
+ SetClient(this);
+ CallAllSinkMethodsAndVerify(false);
+
+ // Removing the client should cause WASP to revert to the underlying sink.
+ EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume));
+ SetClient(NULL);
+ CallAllSinkMethodsAndVerify(true);
+}
+
+// Verify underlying sink state is restored after client removal.
+TEST_F(WebAudioSourceProviderImplTest, SinkStateRestored) {
+ wasp_impl_->Initialize(params_, &fake_callback_);
+
+ // Verify state set before the client is set propagates back afterward.
+ EXPECT_CALL(*mock_sink_.get(), Start());
+ wasp_impl_->Start();
+ SetClient(this);
+
+ EXPECT_CALL(*mock_sink_.get(), SetVolume(1.0));
+ EXPECT_CALL(*mock_sink_.get(), Start());
+ SetClient(NULL);
+
+ // Verify state set while the client was attached propagates back afterward.
+ SetClient(this);
+ wasp_impl_->Play();
+ wasp_impl_->SetVolume(kTestVolume);
+
+ EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume));
+ EXPECT_CALL(*mock_sink_.get(), Start());
+ EXPECT_CALL(*mock_sink_.get(), Play());
+ SetClient(NULL);
+}
+
+// Test the AudioRendererSink state machine and its effects on provideInput().
+TEST_F(WebAudioSourceProviderImplTest, ProvideInput) {
+ scoped_ptr<AudioBus> bus1 = AudioBus::Create(params_);
+ scoped_ptr<AudioBus> bus2 = AudioBus::Create(params_);
+
+ // Point the WebVector into memory owned by |bus1|.
+ blink::WebVector<float*> audio_data(static_cast<size_t>(bus1->channels()));
+ for (size_t i = 0; i < audio_data.size(); ++i)
+ audio_data[i] = bus1->channel(i);
+
+ // Verify provideInput() works before Initialize() and returns silence.
+ bus1->channel(0)[0] = 1;
+ bus2->Zero();
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+
+ wasp_impl_->Initialize(params_, &fake_callback_);
+ SetClient(this);
+
+ // Verify provideInput() is muted prior to Start() and no calls to the render
+ // callback have occurred.
+ bus1->channel(0)[0] = 1;
+ bus2->Zero();
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+ ASSERT_EQ(fake_callback_.last_audio_delay_milliseconds(), -1);
+
+ wasp_impl_->Start();
+
+ // Ditto for Play().
+ bus1->channel(0)[0] = 1;
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+ ASSERT_EQ(fake_callback_.last_audio_delay_milliseconds(), -1);
+
+ wasp_impl_->Play();
+
+ // Now we should get real audio data.
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_FALSE(CompareBusses(bus1.get(), bus2.get()));
+
+ // Ensure volume adjustment is working.
+ fake_callback_.reset();
+ fake_callback_.Render(bus2.get(), 0);
+ bus2->Scale(kTestVolume);
+
+ fake_callback_.reset();
+ wasp_impl_->SetVolume(kTestVolume);
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+
+ // Pause should return to silence.
+ wasp_impl_->Pause();
+ bus1->channel(0)[0] = 1;
+ bus2->Zero();
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+
+ // Ensure if a renderer properly fill silence for partial Render() calls by
+ // configuring the fake callback to return half the data. After these calls
+ // bus1 is full of junk data, and bus2 is partially filled.
+ wasp_impl_->SetVolume(1);
+ fake_callback_.Render(bus1.get(), 0);
+ fake_callback_.reset();
+ fake_callback_.Render(bus2.get(), 0);
+ bus2->ZeroFramesPartial(bus2->frames() / 2,
+ bus2->frames() - bus2->frames() / 2);
+ fake_callback_.reset();
+ fake_callback_.set_half_fill(true);
+ wasp_impl_->Play();
+
+ // Play should return real audio data again, but the last half should be zero.
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+
+ // Stop() should return silence.
+ wasp_impl_->Stop();
+ bus1->channel(0)[0] = 1;
+ bus2->Zero();
+ wasp_impl_->provideInput(audio_data, params_.frames_per_buffer());
+ ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get()));
+}
+
+} // namespace media
diff --git a/media/blink/webinbandtexttrack_impl.cc b/media/blink/webinbandtexttrack_impl.cc
new file mode 100644
index 0000000..49a4880
--- /dev/null
+++ b/media/blink/webinbandtexttrack_impl.cc
@@ -0,0 +1,58 @@
+// Copyright 2013 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 "media/blink/webinbandtexttrack_impl.h"
+
+#include "base/logging.h"
+
+namespace media {
+
+WebInbandTextTrackImpl::WebInbandTextTrackImpl(
+ Kind kind,
+ const blink::WebString& label,
+ const blink::WebString& language,
+ const blink::WebString& id,
+ int index)
+ : client_(NULL),
+ kind_(kind),
+ label_(label),
+ language_(language),
+ id_(id),
+ index_(index) {
+}
+
+WebInbandTextTrackImpl::~WebInbandTextTrackImpl() {
+ DCHECK(!client_);
+}
+
+void WebInbandTextTrackImpl::setClient(
+ blink::WebInbandTextTrackClient* client) {
+ client_ = client;
+}
+
+blink::WebInbandTextTrackClient* WebInbandTextTrackImpl::client() {
+ return client_;
+}
+
+WebInbandTextTrackImpl::Kind WebInbandTextTrackImpl::kind() const {
+ return kind_;
+}
+
+blink::WebString WebInbandTextTrackImpl::label() const {
+ return label_;
+}
+
+blink::WebString WebInbandTextTrackImpl::language() const {
+ return language_;
+}
+
+blink::WebString WebInbandTextTrackImpl::id() const {
+ return id_;
+}
+
+int WebInbandTextTrackImpl::textTrackIndex() const {
+ return index_;
+}
+
+} // namespace media
diff --git a/media/blink/webinbandtexttrack_impl.h b/media/blink/webinbandtexttrack_impl.h
new file mode 100644
index 0000000..c08bfad
--- /dev/null
+++ b/media/blink/webinbandtexttrack_impl.h
@@ -0,0 +1,45 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_
+#define MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_
+
+#include "third_party/WebKit/public/platform/WebInbandTextTrack.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+
+namespace media {
+
+class WebInbandTextTrackImpl : public blink::WebInbandTextTrack {
+ public:
+ WebInbandTextTrackImpl(Kind kind,
+ const blink::WebString& label,
+ const blink::WebString& language,
+ const blink::WebString& id,
+ int index);
+ virtual ~WebInbandTextTrackImpl();
+
+ virtual void setClient(blink::WebInbandTextTrackClient* client);
+ virtual blink::WebInbandTextTrackClient* client();
+
+ virtual Kind kind() const;
+
+ virtual blink::WebString label() const;
+ virtual blink::WebString language() const;
+ virtual blink::WebString id() const;
+
+ virtual int textTrackIndex() const;
+
+ private:
+ blink::WebInbandTextTrackClient* client_;
+ Kind kind_;
+ blink::WebString label_;
+ blink::WebString language_;
+ blink::WebString id_;
+ int index_;
+ DISALLOW_COPY_AND_ASSIGN(WebInbandTextTrackImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_
diff --git a/media/blink/webmediaplayer_delegate.h b/media/blink/webmediaplayer_delegate.h
new file mode 100644
index 0000000..7139676
--- /dev/null
+++ b/media/blink/webmediaplayer_delegate.h
@@ -0,0 +1,34 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_
+#define MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_
+
+namespace blink {
+class WebMediaPlayer;
+}
+namespace media {
+
+// An interface to allow a WebMediaPlayerImpl to communicate changes of state
+// to objects that need to know.
+class WebMediaPlayerDelegate {
+ public:
+ WebMediaPlayerDelegate() {}
+
+ // The specified player started playing media.
+ virtual void DidPlay(blink::WebMediaPlayer* player) = 0;
+
+ // The specified player stopped playing media.
+ virtual void DidPause(blink::WebMediaPlayer* player) = 0;
+
+ // The specified player was destroyed. Do not call any methods on it.
+ virtual void PlayerGone(blink::WebMediaPlayer* player) = 0;
+
+ protected:
+ virtual ~WebMediaPlayerDelegate() {}
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
new file mode 100644
index 0000000..bed9299
--- /dev/null
+++ b/media/blink/webmediaplayer_impl.cc
@@ -0,0 +1,1034 @@
+// Copyright 2013 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 "media/blink/webmediaplayer_impl.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/trace_event.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "cc/blink/web_layer_impl.h"
+#include "cc/layers/video_layer.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
+#include "media/audio/null_audio_sink.h"
+#include "media/base/audio_hardware_config.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/limits.h"
+#include "media/base/media_log.h"
+#include "media/base/pipeline.h"
+#include "media/base/text_renderer.h"
+#include "media/base/video_frame.h"
+#include "media/blink/buffered_data_source.h"
+#include "media/blink/encrypted_media_player_support.h"
+#include "media/blink/texttrack_impl.h"
+#include "media/blink/webaudiosourceprovider_impl.h"
+#include "media/blink/webinbandtexttrack_impl.h"
+#include "media/blink/webmediaplayer_delegate.h"
+#include "media/blink/webmediaplayer_params.h"
+#include "media/blink/webmediaplayer_util.h"
+#include "media/blink/webmediasource_impl.h"
+#include "media/filters/audio_renderer_impl.h"
+#include "media/filters/chunk_demuxer.h"
+#include "media/filters/ffmpeg_audio_decoder.h"
+#include "media/filters/ffmpeg_demuxer.h"
+#include "media/filters/ffmpeg_video_decoder.h"
+#include "media/filters/gpu_video_accelerator_factories.h"
+#include "media/filters/gpu_video_decoder.h"
+#include "media/filters/opus_audio_decoder.h"
+#include "media/filters/renderer_impl.h"
+#include "media/filters/video_renderer_impl.h"
+#include "media/filters/vpx_video_decoder.h"
+#include "third_party/WebKit/public/platform/WebMediaSource.h"
+#include "third_party/WebKit/public/platform/WebRect.h"
+#include "third_party/WebKit/public/platform/WebSize.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+#include "third_party/WebKit/public/platform/WebURL.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
+#include "third_party/WebKit/public/web/WebView.h"
+
+using blink::WebCanvas;
+using blink::WebMediaPlayer;
+using blink::WebRect;
+using blink::WebSize;
+using blink::WebString;
+
+namespace {
+
+// Limits the range of playback rate.
+//
+// TODO(kylep): Revisit these.
+//
+// Vista has substantially lower performance than XP or Windows7. If you speed
+// up a video too much, it can't keep up, and rendering stops updating except on
+// the time bar. For really high speeds, audio becomes a bottleneck and we just
+// use up the data we have, which may not achieve the speed requested, but will
+// not crash the tab.
+//
+// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
+// like a busy loop). It gets unresponsive, although its not completely dead.
+//
+// Also our timers are not very accurate (especially for ogg), which becomes
+// evident at low speeds and on Vista. Since other speeds are risky and outside
+// the norms, we think 1/16x to 16x is a safe and useful range for now.
+const double kMinRate = 0.0625;
+const double kMaxRate = 16.0;
+
+class SyncPointClientImpl : public media::VideoFrame::SyncPointClient {
+ public:
+ explicit SyncPointClientImpl(
+ blink::WebGraphicsContext3D* web_graphics_context)
+ : web_graphics_context_(web_graphics_context) {}
+ virtual ~SyncPointClientImpl() {}
+ virtual uint32 InsertSyncPoint() OVERRIDE {
+ return web_graphics_context_->insertSyncPoint();
+ }
+ virtual void WaitSyncPoint(uint32 sync_point) OVERRIDE {
+ web_graphics_context_->waitSyncPoint(sync_point);
+ }
+
+ private:
+ blink::WebGraphicsContext3D* web_graphics_context_;
+};
+
+} // namespace
+
+namespace media {
+
+class BufferedDataSourceHostImpl;
+
+#define COMPILE_ASSERT_MATCHING_ENUM(name) \
+ COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::CORSMode ## name) == \
+ static_cast<int>(BufferedResourceLoader::k ## name), \
+ mismatching_enums)
+COMPILE_ASSERT_MATCHING_ENUM(Unspecified);
+COMPILE_ASSERT_MATCHING_ENUM(Anonymous);
+COMPILE_ASSERT_MATCHING_ENUM(UseCredentials);
+#undef COMPILE_ASSERT_MATCHING_ENUM
+
+#define BIND_TO_RENDER_LOOP(function) \
+ (DCHECK(main_task_runner_->BelongsToCurrentThread()), \
+ BindToCurrentLoop(base::Bind(function, AsWeakPtr())))
+
+#define BIND_TO_RENDER_LOOP1(function, arg1) \
+ (DCHECK(main_task_runner_->BelongsToCurrentThread()), \
+ BindToCurrentLoop(base::Bind(function, AsWeakPtr(), arg1)))
+
+static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log,
+ const std::string& error) {
+ media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error));
+}
+
+WebMediaPlayerImpl::WebMediaPlayerImpl(
+ blink::WebLocalFrame* frame,
+ blink::WebMediaPlayerClient* client,
+ base::WeakPtr<WebMediaPlayerDelegate> delegate,
+ const WebMediaPlayerParams& params)
+ : frame_(frame),
+ network_state_(WebMediaPlayer::NetworkStateEmpty),
+ ready_state_(WebMediaPlayer::ReadyStateHaveNothing),
+ preload_(BufferedDataSource::AUTO),
+ main_task_runner_(base::MessageLoopProxy::current()),
+ media_task_runner_(params.media_task_runner()),
+ media_log_(params.media_log()),
+ pipeline_(media_task_runner_, media_log_.get()),
+ load_type_(LoadTypeURL),
+ opaque_(false),
+ paused_(true),
+ seeking_(false),
+ playback_rate_(0.0f),
+ ended_(false),
+ pending_seek_(false),
+ pending_seek_seconds_(0.0f),
+ should_notify_time_changed_(false),
+ client_(client),
+ delegate_(delegate),
+ defer_load_cb_(params.defer_load_cb()),
+ gpu_factories_(params.gpu_factories()),
+ supports_save_(true),
+ chunk_demuxer_(NULL),
+ compositor_task_runner_(params.compositor_task_runner()),
+ compositor_(new VideoFrameCompositor(
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnNaturalSizeChanged),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnOpacityChanged))),
+ text_track_index_(0),
+ encrypted_media_support_(
+ params.CreateEncryptedMediaPlayerSupport(client)),
+ audio_hardware_config_(params.audio_hardware_config()) {
+ DCHECK(encrypted_media_support_);
+
+ // Threaded compositing isn't enabled universally yet.
+ if (!compositor_task_runner_.get())
+ compositor_task_runner_ = base::MessageLoopProxy::current();
+
+ media_log_->AddEvent(
+ media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED));
+
+ // |gpu_factories_| requires that its entry points be called on its
+ // |GetTaskRunner()|. Since |pipeline_| will own decoders created from the
+ // factories, require that their message loops are identical.
+ DCHECK(!gpu_factories_.get() ||
+ (gpu_factories_->GetTaskRunner() == media_task_runner_.get()));
+
+ // Use the null sink if no sink was provided.
+ audio_source_provider_ = new WebAudioSourceProviderImpl(
+ params.audio_renderer_sink().get()
+ ? params.audio_renderer_sink()
+ : new NullAudioSink(media_task_runner_));
+}
+
+WebMediaPlayerImpl::~WebMediaPlayerImpl() {
+ client_->setWebLayer(NULL);
+
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ media_log_->AddEvent(
+ media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
+
+ if (delegate_.get())
+ delegate_->PlayerGone(this);
+
+ // Abort any pending IO so stopping the pipeline doesn't get blocked.
+ if (data_source_)
+ data_source_->Abort();
+ if (chunk_demuxer_) {
+ chunk_demuxer_->Shutdown();
+ chunk_demuxer_ = NULL;
+ }
+
+ gpu_factories_ = NULL;
+
+ // Make sure to kill the pipeline so there's no more media threads running.
+ // Note: stopping the pipeline might block for a long time.
+ base::WaitableEvent waiter(false, false);
+ pipeline_.Stop(
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter)));
+ waiter.Wait();
+
+ compositor_task_runner_->DeleteSoon(FROM_HERE, compositor_);
+}
+
+void WebMediaPlayerImpl::load(LoadType load_type, const blink::WebURL& url,
+ CORSMode cors_mode) {
+ DVLOG(1) << __FUNCTION__ << "(" << load_type << ", " << url << ", "
+ << cors_mode << ")";
+ if (!defer_load_cb_.is_null()) {
+ defer_load_cb_.Run(base::Bind(
+ &WebMediaPlayerImpl::DoLoad, AsWeakPtr(), load_type, url, cors_mode));
+ return;
+ }
+ DoLoad(load_type, url, cors_mode);
+}
+
+void WebMediaPlayerImpl::DoLoad(LoadType load_type,
+ const blink::WebURL& url,
+ CORSMode cors_mode) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ GURL gurl(url);
+ ReportMediaSchemeUma(gurl);
+
+ // Set subresource URL for crash reporting.
+ base::debug::SetCrashKeyValue("subresource_url", gurl.spec());
+
+ load_type_ = load_type;
+
+ SetNetworkState(WebMediaPlayer::NetworkStateLoading);
+ SetReadyState(WebMediaPlayer::ReadyStateHaveNothing);
+ media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
+
+ // Media source pipelines can start immediately.
+ if (load_type == LoadTypeMediaSource) {
+ supports_save_ = false;
+ StartPipeline();
+ return;
+ }
+
+ // Otherwise it's a regular request which requires resolving the URL first.
+ data_source_.reset(new BufferedDataSource(
+ url,
+ static_cast<BufferedResourceLoader::CORSMode>(cors_mode),
+ main_task_runner_,
+ frame_,
+ media_log_.get(),
+ &buffered_data_source_host_,
+ base::Bind(&WebMediaPlayerImpl::NotifyDownloading, AsWeakPtr())));
+ data_source_->Initialize(
+ base::Bind(&WebMediaPlayerImpl::DataSourceInitialized, AsWeakPtr()));
+ data_source_->SetPreload(preload_);
+}
+
+void WebMediaPlayerImpl::play() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ paused_ = false;
+ pipeline_.SetPlaybackRate(playback_rate_);
+ if (data_source_)
+ data_source_->MediaIsPlaying();
+
+ media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY));
+
+ if (delegate_.get())
+ delegate_->DidPlay(this);
+}
+
+void WebMediaPlayerImpl::pause() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ paused_ = true;
+ pipeline_.SetPlaybackRate(0.0f);
+ if (data_source_)
+ data_source_->MediaIsPaused();
+ paused_time_ = pipeline_.GetMediaTime();
+
+ media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PAUSE));
+
+ if (delegate_.get())
+ delegate_->DidPause(this);
+}
+
+bool WebMediaPlayerImpl::supportsSave() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ return supports_save_;
+}
+
+void WebMediaPlayerImpl::seek(double seconds) {
+ DVLOG(1) << __FUNCTION__ << "(" << seconds << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ ended_ = false;
+
+ if (ready_state_ > WebMediaPlayer::ReadyStateHaveMetadata)
+ SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
+
+ base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
+
+ if (seeking_) {
+ pending_seek_ = true;
+ pending_seek_seconds_ = seconds;
+ if (chunk_demuxer_)
+ chunk_demuxer_->CancelPendingSeek(seek_time);
+ return;
+ }
+
+ media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
+
+ // Update our paused time.
+ if (paused_)
+ paused_time_ = seek_time;
+
+ seeking_ = true;
+
+ if (chunk_demuxer_)
+ chunk_demuxer_->StartWaitingForSeek(seek_time);
+
+ // Kick off the asynchronous seek!
+ pipeline_.Seek(
+ seek_time,
+ BIND_TO_RENDER_LOOP1(&WebMediaPlayerImpl::OnPipelineSeeked, true));
+}
+
+void WebMediaPlayerImpl::setRate(double rate) {
+ DVLOG(1) << __FUNCTION__ << "(" << rate << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ // TODO(kylep): Remove when support for negatives is added. Also, modify the
+ // following checks so rewind uses reasonable values also.
+ if (rate < 0.0)
+ return;
+
+ // Limit rates to reasonable values by clamping.
+ if (rate != 0.0) {
+ if (rate < kMinRate)
+ rate = kMinRate;
+ else if (rate > kMaxRate)
+ rate = kMaxRate;
+ }
+
+ playback_rate_ = rate;
+ if (!paused_) {
+ pipeline_.SetPlaybackRate(rate);
+ if (data_source_)
+ data_source_->MediaPlaybackRateChanged(rate);
+ }
+}
+
+void WebMediaPlayerImpl::setVolume(double volume) {
+ DVLOG(1) << __FUNCTION__ << "(" << volume << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ pipeline_.SetVolume(volume);
+}
+
+#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
+ COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \
+ static_cast<int>(BufferedDataSource::chromium_name), \
+ mismatching_enums)
+COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE);
+COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA);
+COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO);
+#undef COMPILE_ASSERT_MATCHING_ENUM
+
+void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) {
+ DVLOG(1) << __FUNCTION__ << "(" << preload << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ preload_ = static_cast<BufferedDataSource::Preload>(preload);
+ if (data_source_)
+ data_source_->SetPreload(preload_);
+}
+
+bool WebMediaPlayerImpl::hasVideo() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return pipeline_metadata_.has_video;
+}
+
+bool WebMediaPlayerImpl::hasAudio() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return pipeline_metadata_.has_audio;
+}
+
+blink::WebSize WebMediaPlayerImpl::naturalSize() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return blink::WebSize(pipeline_metadata_.natural_size);
+}
+
+bool WebMediaPlayerImpl::paused() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return pipeline_.GetPlaybackRate() == 0.0f;
+}
+
+bool WebMediaPlayerImpl::seeking() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
+ return false;
+
+ return seeking_;
+}
+
+double WebMediaPlayerImpl::duration() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return GetPipelineDuration();
+}
+
+double WebMediaPlayerImpl::timelineOffset() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ if (pipeline_metadata_.timeline_offset.is_null())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return pipeline_metadata_.timeline_offset.ToJsTime();
+}
+
+double WebMediaPlayerImpl::currentTime() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing);
+
+ // TODO(scherkus): Replace with an explicit ended signal to HTMLMediaElement,
+ // see http://crbug.com/409280
+ if (ended_)
+ return duration();
+
+ return (paused_ ? paused_time_ : pipeline_.GetMediaTime()).InSecondsF();
+}
+
+WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ return network_state_;
+}
+
+WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ return ready_state_;
+}
+
+blink::WebTimeRanges WebMediaPlayerImpl::buffered() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ Ranges<base::TimeDelta> buffered_time_ranges =
+ pipeline_.GetBufferedTimeRanges();
+
+ const base::TimeDelta duration = pipeline_.GetMediaDuration();
+ if (duration != kInfiniteDuration()) {
+ buffered_data_source_host_.AddBufferedTimeRanges(
+ &buffered_time_ranges, duration);
+ }
+ return ConvertToWebTimeRanges(buffered_time_ranges);
+}
+
+double WebMediaPlayerImpl::maxTimeSeekable() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ // If we haven't even gotten to ReadyStateHaveMetadata yet then just
+ // return 0 so that the seekable range is empty.
+ if (ready_state_ < WebMediaPlayer::ReadyStateHaveMetadata)
+ return 0.0;
+
+ // We don't support seeking in streaming media.
+ if (data_source_ && data_source_->IsStreaming())
+ return 0.0;
+ return duration();
+}
+
+bool WebMediaPlayerImpl::didLoadingProgress() {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ bool pipeline_progress = pipeline_.DidLoadingProgress();
+ bool data_progress = buffered_data_source_host_.DidLoadingProgress();
+ return pipeline_progress || data_progress;
+}
+
+void WebMediaPlayerImpl::paint(blink::WebCanvas* canvas,
+ const blink::WebRect& rect,
+ unsigned char alpha) {
+ paint(canvas, rect, alpha, SkXfermode::kSrcOver_Mode);
+}
+
+void WebMediaPlayerImpl::paint(blink::WebCanvas* canvas,
+ const blink::WebRect& rect,
+ unsigned char alpha,
+ SkXfermode::Mode mode) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ TRACE_EVENT0("media", "WebMediaPlayerImpl:paint");
+
+ // TODO(scherkus): Clarify paint() API contract to better understand when and
+ // why it's being called. For example, today paint() is called when:
+ // - We haven't reached HAVE_CURRENT_DATA and need to paint black
+ // - We're painting to a canvas
+ // See http://crbug.com/341225 http://crbug.com/342621 for details.
+ scoped_refptr<VideoFrame> video_frame =
+ GetCurrentFrameFromCompositor();
+
+ gfx::Rect gfx_rect(rect);
+
+ skcanvas_video_renderer_.Paint(video_frame.get(),
+ canvas,
+ gfx_rect,
+ alpha,
+ mode,
+ pipeline_metadata_.video_rotation);
+}
+
+bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const {
+ if (data_source_)
+ return data_source_->HasSingleOrigin();
+ return true;
+}
+
+bool WebMediaPlayerImpl::didPassCORSAccessCheck() const {
+ if (data_source_)
+ return data_source_->DidPassCORSAccessCheck();
+ return false;
+}
+
+double WebMediaPlayerImpl::mediaTimeForTimeValue(double timeValue) const {
+ return ConvertSecondsToTimestamp(timeValue).InSecondsF();
+}
+
+unsigned WebMediaPlayerImpl::decodedFrameCount() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ PipelineStatistics stats = pipeline_.GetStatistics();
+ return stats.video_frames_decoded;
+}
+
+unsigned WebMediaPlayerImpl::droppedFrameCount() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ PipelineStatistics stats = pipeline_.GetStatistics();
+ return stats.video_frames_dropped;
+}
+
+unsigned WebMediaPlayerImpl::audioDecodedByteCount() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ PipelineStatistics stats = pipeline_.GetStatistics();
+ return stats.audio_bytes_decoded;
+}
+
+unsigned WebMediaPlayerImpl::videoDecodedByteCount() const {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ PipelineStatistics stats = pipeline_.GetStatistics();
+ return stats.video_bytes_decoded;
+}
+
+bool WebMediaPlayerImpl::copyVideoTextureToPlatformTexture(
+ blink::WebGraphicsContext3D* web_graphics_context,
+ unsigned int texture,
+ unsigned int level,
+ unsigned int internal_format,
+ unsigned int type,
+ bool premultiply_alpha,
+ bool flip_y) {
+ TRACE_EVENT0("media", "WebMediaPlayerImpl:copyVideoTextureToPlatformTexture");
+
+ scoped_refptr<VideoFrame> video_frame =
+ GetCurrentFrameFromCompositor();
+
+ if (!video_frame.get())
+ return false;
+ if (video_frame->format() != VideoFrame::NATIVE_TEXTURE)
+ return false;
+
+ const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder();
+ if (mailbox_holder->texture_target != GL_TEXTURE_2D)
+ return false;
+
+ web_graphics_context->waitSyncPoint(mailbox_holder->sync_point);
+ uint32 source_texture = web_graphics_context->createAndConsumeTextureCHROMIUM(
+ GL_TEXTURE_2D, mailbox_holder->mailbox.name);
+
+ // The video is stored in a unmultiplied format, so premultiply
+ // if necessary.
+ web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM,
+ premultiply_alpha);
+ // Application itself needs to take care of setting the right flip_y
+ // value down to get the expected result.
+ // flip_y==true means to reverse the video orientation while
+ // flip_y==false means to keep the intrinsic orientation.
+ web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, flip_y);
+ web_graphics_context->copyTextureCHROMIUM(GL_TEXTURE_2D,
+ source_texture,
+ texture,
+ level,
+ internal_format,
+ type);
+ web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, false);
+ web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM,
+ false);
+
+ web_graphics_context->deleteTexture(source_texture);
+ web_graphics_context->flush();
+
+ SyncPointClientImpl client(web_graphics_context);
+ video_frame->UpdateReleaseSyncPoint(&client);
+ return true;
+}
+
+WebMediaPlayer::MediaKeyException
+WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system,
+ const unsigned char* init_data,
+ unsigned init_data_length) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return encrypted_media_support_->GenerateKeyRequest(
+ frame_, key_system, init_data, init_data_length);
+}
+
+WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey(
+ const WebString& key_system,
+ const unsigned char* key,
+ unsigned key_length,
+ const unsigned char* init_data,
+ unsigned init_data_length,
+ const WebString& session_id) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return encrypted_media_support_->AddKey(
+ key_system, key, key_length, init_data, init_data_length, session_id);
+}
+
+WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest(
+ const WebString& key_system,
+ const WebString& session_id) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ return encrypted_media_support_->CancelKeyRequest(key_system, session_id);
+}
+
+void WebMediaPlayerImpl::setContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ encrypted_media_support_->SetContentDecryptionModule(cdm);
+}
+
+void WebMediaPlayerImpl::setContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm,
+ blink::WebContentDecryptionModuleResult result) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ encrypted_media_support_->SetContentDecryptionModule(cdm, result);
+}
+
+void WebMediaPlayerImpl::setContentDecryptionModuleSync(
+ blink::WebContentDecryptionModule* cdm) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ encrypted_media_support_->SetContentDecryptionModuleSync(cdm);
+}
+
+void WebMediaPlayerImpl::OnPipelineSeeked(bool time_changed,
+ PipelineStatus status) {
+ DVLOG(1) << __FUNCTION__ << "(" << time_changed << ", " << status << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ seeking_ = false;
+ if (pending_seek_) {
+ pending_seek_ = false;
+ seek(pending_seek_seconds_);
+ return;
+ }
+
+ if (status != PIPELINE_OK) {
+ OnPipelineError(status);
+ return;
+ }
+
+ // Update our paused time.
+ if (paused_)
+ paused_time_ = pipeline_.GetMediaTime();
+
+ should_notify_time_changed_ = time_changed;
+}
+
+void WebMediaPlayerImpl::OnPipelineEnded() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ // Ignore state changes until we've completed all outstanding seeks.
+ if (seeking_ || pending_seek_)
+ return;
+
+ ended_ = true;
+ client_->timeChanged();
+}
+
+void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(error, PIPELINE_OK);
+
+ if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) {
+ // Any error that occurs before reaching ReadyStateHaveMetadata should
+ // be considered a format error.
+ SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
+ return;
+ }
+
+ SetNetworkState(PipelineErrorToNetworkState(error));
+
+ if (error == PIPELINE_ERROR_DECRYPT)
+ encrypted_media_support_->OnPipelineDecryptError();
+}
+
+void WebMediaPlayerImpl::OnPipelineMetadata(
+ PipelineMetadata metadata) {
+ DVLOG(1) << __FUNCTION__;
+
+ pipeline_metadata_ = metadata;
+
+ UMA_HISTOGRAM_ENUMERATION("Media.VideoRotation",
+ metadata.video_rotation,
+ VIDEO_ROTATION_MAX + 1);
+ SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
+
+ if (hasVideo()) {
+ DCHECK(!video_weblayer_);
+ scoped_refptr<cc::VideoLayer> layer =
+ cc::VideoLayer::Create(compositor_, pipeline_metadata_.video_rotation);
+
+ if (pipeline_metadata_.video_rotation == VIDEO_ROTATION_90 ||
+ pipeline_metadata_.video_rotation == VIDEO_ROTATION_270) {
+ gfx::Size size = pipeline_metadata_.natural_size;
+ pipeline_metadata_.natural_size = gfx::Size(size.height(), size.width());
+ }
+
+ video_weblayer_.reset(new cc_blink::WebLayerImpl(layer));
+ video_weblayer_->setOpaque(opaque_);
+ client_->setWebLayer(video_weblayer_.get());
+ }
+}
+
+void WebMediaPlayerImpl::OnPipelineBufferingStateChanged(
+ BufferingState buffering_state) {
+ DVLOG(1) << __FUNCTION__ << "(" << buffering_state << ")";
+
+ // Ignore buffering state changes until we've completed all outstanding seeks.
+ if (seeking_ || pending_seek_)
+ return;
+
+ // TODO(scherkus): Handle other buffering states when Pipeline starts using
+ // them and translate them ready state changes http://crbug.com/144683
+ DCHECK_EQ(buffering_state, BUFFERING_HAVE_ENOUGH);
+ SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
+
+ // Blink expects a timeChanged() in response to a seek().
+ if (should_notify_time_changed_)
+ client_->timeChanged();
+}
+
+void WebMediaPlayerImpl::OnDemuxerOpened() {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ client_->mediaSourceOpened(new WebMediaSourceImpl(
+ chunk_demuxer_, base::Bind(&LogMediaSourceError, media_log_)));
+}
+
+void WebMediaPlayerImpl::OnAddTextTrack(
+ const TextTrackConfig& config,
+ const AddTextTrackDoneCB& done_cb) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ const WebInbandTextTrackImpl::Kind web_kind =
+ static_cast<WebInbandTextTrackImpl::Kind>(config.kind());
+ const blink::WebString web_label =
+ blink::WebString::fromUTF8(config.label());
+ const blink::WebString web_language =
+ blink::WebString::fromUTF8(config.language());
+ const blink::WebString web_id =
+ blink::WebString::fromUTF8(config.id());
+
+ scoped_ptr<WebInbandTextTrackImpl> web_inband_text_track(
+ new WebInbandTextTrackImpl(web_kind, web_label, web_language, web_id,
+ text_track_index_++));
+
+ scoped_ptr<TextTrack> text_track(new TextTrackImpl(
+ main_task_runner_, client_, web_inband_text_track.Pass()));
+
+ done_cb.Run(text_track.Pass());
+}
+
+void WebMediaPlayerImpl::DataSourceInitialized(bool success) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ if (!success) {
+ SetNetworkState(WebMediaPlayer::NetworkStateFormatError);
+ return;
+ }
+
+ StartPipeline();
+}
+
+void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) {
+ if (!is_downloading && network_state_ == WebMediaPlayer::NetworkStateLoading)
+ SetNetworkState(WebMediaPlayer::NetworkStateIdle);
+ else if (is_downloading && network_state_ == WebMediaPlayer::NetworkStateIdle)
+ SetNetworkState(WebMediaPlayer::NetworkStateLoading);
+ media_log_->AddEvent(
+ media_log_->CreateBooleanEvent(
+ MediaLogEvent::NETWORK_ACTIVITY_SET,
+ "is_downloading_data", is_downloading));
+}
+
+// TODO(xhwang): Move this to a factory class so that we can create different
+// renderers.
+scoped_ptr<Renderer> WebMediaPlayerImpl::CreateRenderer() {
+ SetDecryptorReadyCB set_decryptor_ready_cb =
+ encrypted_media_support_->CreateSetDecryptorReadyCB();
+
+ // Create our audio decoders and renderer.
+ ScopedVector<AudioDecoder> audio_decoders;
+
+ LogCB log_cb = base::Bind(&LogMediaSourceError, media_log_);
+ audio_decoders.push_back(new FFmpegAudioDecoder(media_task_runner_, log_cb));
+ audio_decoders.push_back(new OpusAudioDecoder(media_task_runner_));
+
+ scoped_ptr<AudioRenderer> audio_renderer(new AudioRendererImpl(
+ media_task_runner_,
+ audio_source_provider_.get(),
+ audio_decoders.Pass(),
+ set_decryptor_ready_cb,
+ audio_hardware_config_));
+
+ // Create our video decoders and renderer.
+ ScopedVector<VideoDecoder> video_decoders;
+
+ if (gpu_factories_.get()) {
+ video_decoders.push_back(
+ new GpuVideoDecoder(gpu_factories_, media_log_));
+ }
+
+#if !defined(MEDIA_DISABLE_LIBVPX)
+ video_decoders.push_back(new VpxVideoDecoder(media_task_runner_));
+#endif // !defined(MEDIA_DISABLE_LIBVPX)
+
+ video_decoders.push_back(new FFmpegVideoDecoder(media_task_runner_));
+
+ scoped_ptr<VideoRenderer> video_renderer(
+ new VideoRendererImpl(
+ media_task_runner_,
+ video_decoders.Pass(),
+ set_decryptor_ready_cb,
+ base::Bind(&WebMediaPlayerImpl::FrameReady, base::Unretained(this)),
+ true));
+
+ // Create renderer.
+ return scoped_ptr<Renderer>(new RendererImpl(
+ media_task_runner_,
+ demuxer_.get(),
+ audio_renderer.Pass(),
+ video_renderer.Pass()));
+}
+
+void WebMediaPlayerImpl::StartPipeline() {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ // Keep track if this is a MSE or non-MSE playback.
+ UMA_HISTOGRAM_BOOLEAN("Media.MSE.Playback",
+ (load_type_ == LoadTypeMediaSource));
+
+ LogCB mse_log_cb;
+ Demuxer::NeedKeyCB need_key_cb =
+ encrypted_media_support_->CreateNeedKeyCB();
+
+ // Figure out which demuxer to use.
+ if (load_type_ != LoadTypeMediaSource) {
+ DCHECK(!chunk_demuxer_);
+ DCHECK(data_source_);
+
+ demuxer_.reset(new FFmpegDemuxer(
+ media_task_runner_, data_source_.get(),
+ need_key_cb,
+ media_log_));
+ } else {
+ DCHECK(!chunk_demuxer_);
+ DCHECK(!data_source_);
+
+ mse_log_cb = base::Bind(&LogMediaSourceError, media_log_);
+
+ chunk_demuxer_ = new ChunkDemuxer(
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDemuxerOpened),
+ need_key_cb,
+ mse_log_cb,
+ true);
+ demuxer_.reset(chunk_demuxer_);
+ }
+
+ // ... and we're ready to go!
+ seeking_ = true;
+ pipeline_.Start(
+ demuxer_.get(),
+ CreateRenderer(),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError),
+ BIND_TO_RENDER_LOOP1(&WebMediaPlayerImpl::OnPipelineSeeked, false),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineMetadata),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingStateChanged),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged),
+ BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnAddTextTrack));
+}
+
+void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) {
+ DVLOG(1) << __FUNCTION__ << "(" << state << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ network_state_ = state;
+ // Always notify to ensure client has the latest value.
+ client_->networkStateChanged();
+}
+
+void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) {
+ DVLOG(1) << __FUNCTION__ << "(" << state << ")";
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+
+ if (state == WebMediaPlayer::ReadyStateHaveEnoughData && data_source_ &&
+ data_source_->assume_fully_buffered() &&
+ network_state_ == WebMediaPlayer::NetworkStateLoading)
+ SetNetworkState(WebMediaPlayer::NetworkStateLoaded);
+
+ ready_state_ = state;
+ // Always notify to ensure client has the latest value.
+ client_->readyStateChanged();
+}
+
+blink::WebAudioSourceProvider* WebMediaPlayerImpl::audioSourceProvider() {
+ return audio_source_provider_.get();
+}
+
+double WebMediaPlayerImpl::GetPipelineDuration() const {
+ base::TimeDelta duration = pipeline_.GetMediaDuration();
+
+ // Return positive infinity if the resource is unbounded.
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration
+ if (duration == kInfiniteDuration())
+ return std::numeric_limits<double>::infinity();
+
+ return duration.InSecondsF();
+}
+
+void WebMediaPlayerImpl::OnDurationChanged() {
+ if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing)
+ return;
+
+ client_->durationChanged();
+}
+
+void WebMediaPlayerImpl::OnNaturalSizeChanged(gfx::Size size) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing);
+ TRACE_EVENT0("media", "WebMediaPlayerImpl::OnNaturalSizeChanged");
+
+ media_log_->AddEvent(
+ media_log_->CreateVideoSizeSetEvent(size.width(), size.height()));
+ pipeline_metadata_.natural_size = size;
+
+ client_->sizeChanged();
+}
+
+void WebMediaPlayerImpl::OnOpacityChanged(bool opaque) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing);
+
+ opaque_ = opaque;
+ if (video_weblayer_)
+ video_weblayer_->setOpaque(opaque_);
+}
+
+void WebMediaPlayerImpl::FrameReady(
+ const scoped_refptr<VideoFrame>& frame) {
+ compositor_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&VideoFrameCompositor::UpdateCurrentFrame,
+ base::Unretained(compositor_),
+ frame));
+}
+
+static void GetCurrentFrameAndSignal(
+ VideoFrameCompositor* compositor,
+ scoped_refptr<VideoFrame>* video_frame_out,
+ base::WaitableEvent* event) {
+ TRACE_EVENT0("media", "GetCurrentFrameAndSignal");
+ *video_frame_out = compositor->GetCurrentFrame();
+ event->Signal();
+}
+
+scoped_refptr<VideoFrame>
+WebMediaPlayerImpl::GetCurrentFrameFromCompositor() {
+ TRACE_EVENT0("media", "WebMediaPlayerImpl::GetCurrentFrameFromCompositor");
+ if (compositor_task_runner_->BelongsToCurrentThread())
+ return compositor_->GetCurrentFrame();
+
+ // Use a posted task and waitable event instead of a lock otherwise
+ // WebGL/Canvas can see different content than what the compositor is seeing.
+ scoped_refptr<VideoFrame> video_frame;
+ base::WaitableEvent event(false, false);
+ compositor_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&GetCurrentFrameAndSignal,
+ base::Unretained(compositor_),
+ &video_frame,
+ &event));
+ event.Wait();
+ return video_frame;
+}
+
+} // namespace media
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h
new file mode 100644
index 0000000..bbdf080
--- /dev/null
+++ b/media/blink/webmediaplayer_impl.h
@@ -0,0 +1,319 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_
+#define MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/base/media_export.h"
+// TODO(xhwang): Remove when we remove prefixed EME implementation.
+#include "media/base/media_keys.h"
+#include "media/base/pipeline.h"
+#include "media/base/text_track.h"
+#include "media/blink/buffered_data_source.h"
+#include "media/blink/buffered_data_source_host_impl.h"
+#include "media/blink/video_frame_compositor.h"
+#include "media/filters/skcanvas_video_renderer.h"
+#include "third_party/WebKit/public/platform/WebAudioSourceProvider.h"
+#include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h"
+#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayer.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
+#include "url/gurl.h"
+
+namespace blink {
+class WebLocalFrame;
+}
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace cc_blink {
+class WebLayerImpl;
+}
+
+namespace media {
+class AudioHardwareConfig;
+class ChunkDemuxer;
+class EncryptedMediaPlayerSupport;
+class GpuVideoAcceleratorFactories;
+class MediaLog;
+class VideoFrameCompositor;
+class WebAudioSourceProviderImpl;
+class WebMediaPlayerDelegate;
+class WebMediaPlayerParams;
+class WebTextTrackImpl;
+
+// The canonical implementation of blink::WebMediaPlayer that's backed by
+// Pipeline. Handles normal resource loading, Media Source, and
+// Encrypted Media.
+class MEDIA_EXPORT WebMediaPlayerImpl
+ : public NON_EXPORTED_BASE(blink::WebMediaPlayer),
+ public base::SupportsWeakPtr<WebMediaPlayerImpl> {
+ public:
+ // Constructs a WebMediaPlayer implementation using Chromium's media stack.
+ // |delegate| may be null.
+ WebMediaPlayerImpl(blink::WebLocalFrame* frame,
+ blink::WebMediaPlayerClient* client,
+ base::WeakPtr<WebMediaPlayerDelegate> delegate,
+ const WebMediaPlayerParams& params);
+ virtual ~WebMediaPlayerImpl();
+
+ virtual void load(LoadType load_type,
+ const blink::WebURL& url,
+ CORSMode cors_mode);
+
+ // Playback controls.
+ virtual void play();
+ virtual void pause();
+ virtual bool supportsSave() const;
+ virtual void seek(double seconds);
+ virtual void setRate(double rate);
+ virtual void setVolume(double volume);
+ virtual void setPreload(blink::WebMediaPlayer::Preload preload);
+ virtual blink::WebTimeRanges buffered() const;
+ virtual double maxTimeSeekable() const;
+
+ // Methods for painting.
+ virtual void paint(blink::WebCanvas* canvas,
+ const blink::WebRect& rect,
+ unsigned char alpha,
+ SkXfermode::Mode mode);
+ // TODO(dshwang): remove it because above method replaces. crbug.com/401027
+ virtual void paint(blink::WebCanvas* canvas,
+ const blink::WebRect& rect,
+ unsigned char alpha);
+
+ // True if the loaded media has a playable video/audio track.
+ virtual bool hasVideo() const;
+ virtual bool hasAudio() const;
+
+ // Dimensions of the video.
+ virtual blink::WebSize naturalSize() const;
+
+ // Getters of playback state.
+ virtual bool paused() const;
+ virtual bool seeking() const;
+ virtual double duration() const;
+ virtual double timelineOffset() const;
+ virtual double currentTime() const;
+
+ // Internal states of loading and network.
+ // TODO(hclam): Ask the pipeline about the state rather than having reading
+ // them from members which would cause race conditions.
+ virtual blink::WebMediaPlayer::NetworkState networkState() const;
+ virtual blink::WebMediaPlayer::ReadyState readyState() const;
+
+ virtual bool didLoadingProgress();
+
+ virtual bool hasSingleSecurityOrigin() const;
+ virtual bool didPassCORSAccessCheck() const;
+
+ virtual double mediaTimeForTimeValue(double timeValue) const;
+
+ virtual unsigned decodedFrameCount() const;
+ virtual unsigned droppedFrameCount() const;
+ virtual unsigned audioDecodedByteCount() const;
+ virtual unsigned videoDecodedByteCount() const;
+
+ virtual bool copyVideoTextureToPlatformTexture(
+ blink::WebGraphicsContext3D* web_graphics_context,
+ unsigned int texture,
+ unsigned int level,
+ unsigned int internal_format,
+ unsigned int type,
+ bool premultiply_alpha,
+ bool flip_y);
+
+ virtual blink::WebAudioSourceProvider* audioSourceProvider();
+
+ virtual MediaKeyException generateKeyRequest(
+ const blink::WebString& key_system,
+ const unsigned char* init_data,
+ unsigned init_data_length);
+
+ virtual MediaKeyException addKey(const blink::WebString& key_system,
+ const unsigned char* key,
+ unsigned key_length,
+ const unsigned char* init_data,
+ unsigned init_data_length,
+ const blink::WebString& session_id);
+
+ virtual MediaKeyException cancelKeyRequest(
+ const blink::WebString& key_system,
+ const blink::WebString& session_id);
+
+ // TODO(jrummell): Remove this method once Blink updated to use the other
+ // two methods.
+ virtual void setContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm);
+ virtual void setContentDecryptionModule(
+ blink::WebContentDecryptionModule* cdm,
+ blink::WebContentDecryptionModuleResult result);
+ virtual void setContentDecryptionModuleSync(
+ blink::WebContentDecryptionModule* cdm);
+
+ void OnPipelineSeeked(bool time_changed, PipelineStatus status);
+ void OnPipelineEnded();
+ void OnPipelineError(PipelineStatus error);
+ void OnPipelineMetadata(PipelineMetadata metadata);
+ void OnPipelineBufferingStateChanged(BufferingState buffering_state);
+ void OnDemuxerOpened();
+ void OnAddTextTrack(const TextTrackConfig& config,
+ const AddTextTrackDoneCB& done_cb);
+
+ private:
+ // Called after |defer_load_cb_| has decided to allow the load. If
+ // |defer_load_cb_| is null this is called immediately.
+ void DoLoad(LoadType load_type,
+ const blink::WebURL& url,
+ CORSMode cors_mode);
+
+ // Called after asynchronous initialization of a data source completed.
+ void DataSourceInitialized(bool success);
+
+ // Called when the data source is downloading or paused.
+ void NotifyDownloading(bool is_downloading);
+
+ // Creates a Renderer that will be used by the |pipeline_|.
+ scoped_ptr<Renderer> CreateRenderer();
+
+ // Finishes starting the pipeline due to a call to load().
+ void StartPipeline();
+
+ // Helpers that set the network/ready state and notifies the client if
+ // they've changed.
+ void SetNetworkState(blink::WebMediaPlayer::NetworkState state);
+ void SetReadyState(blink::WebMediaPlayer::ReadyState state);
+
+ // Gets the duration value reported by the pipeline.
+ double GetPipelineDuration() const;
+
+ // Callbacks from |pipeline_| that are forwarded to |client_|.
+ void OnDurationChanged();
+ void OnNaturalSizeChanged(gfx::Size size);
+ void OnOpacityChanged(bool opaque);
+
+ // Called by VideoRendererImpl on its internal thread with the new frame to be
+ // painted.
+ void FrameReady(const scoped_refptr<VideoFrame>& frame);
+
+ // Returns the current video frame from |compositor_|. Blocks until the
+ // compositor can return the frame.
+ scoped_refptr<VideoFrame> GetCurrentFrameFromCompositor();
+
+ blink::WebLocalFrame* frame_;
+
+ // TODO(hclam): get rid of these members and read from the pipeline directly.
+ blink::WebMediaPlayer::NetworkState network_state_;
+ blink::WebMediaPlayer::ReadyState ready_state_;
+
+ // Preload state for when |data_source_| is created after setPreload().
+ BufferedDataSource::Preload preload_;
+
+ // Task runner for posting tasks on Chrome's main thread. Also used
+ // for DCHECKs so methods calls won't execute in the wrong thread.
+ const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
+ scoped_refptr<MediaLog> media_log_;
+ Pipeline pipeline_;
+
+ // The LoadType passed in the |load_type| parameter of the load() call.
+ LoadType load_type_;
+
+ // Cache of metadata for answering hasAudio(), hasVideo(), and naturalSize().
+ PipelineMetadata pipeline_metadata_;
+
+ // Whether the video is known to be opaque or not.
+ bool opaque_;
+
+ // Playback state.
+ //
+ // TODO(scherkus): we have these because Pipeline favours the simplicity of a
+ // single "playback rate" over worrying about paused/stopped etc... It forces
+ // all clients to manage the pause+playback rate externally, but is that
+ // really a bad thing?
+ //
+ // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't want
+ // to hang the render thread during pause(), we record the time at the same
+ // time we pause and then return that value in currentTime(). Otherwise our
+ // clock can creep forward a little bit while the asynchronous
+ // SetPlaybackRate(0) is being executed.
+ bool paused_;
+ bool seeking_;
+ double playback_rate_;
+ base::TimeDelta paused_time_;
+
+ // TODO(scherkus): Replace with an explicit ended signal to HTMLMediaElement,
+ // see http://crbug.com/409280
+ bool ended_;
+
+ // Seek gets pending if another seek is in progress. Only last pending seek
+ // will have effect.
+ bool pending_seek_;
+ double pending_seek_seconds_;
+
+ // Tracks whether to issue time changed notifications during buffering state
+ // changes.
+ bool should_notify_time_changed_;
+
+ blink::WebMediaPlayerClient* client_;
+
+ base::WeakPtr<WebMediaPlayerDelegate> delegate_;
+
+ base::Callback<void(const base::Closure&)> defer_load_cb_;
+
+ // Factories for supporting video accelerators. May be null.
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_;
+
+ // Routes audio playback to either AudioRendererSink or WebAudio.
+ scoped_refptr<WebAudioSourceProviderImpl> audio_source_provider_;
+
+ bool supports_save_;
+
+ // These two are mutually exclusive:
+ // |data_source_| is used for regular resource loads.
+ // |chunk_demuxer_| is used for Media Source resource loads.
+ //
+ // |demuxer_| will contain the appropriate demuxer based on which resource
+ // load strategy we're using.
+ scoped_ptr<BufferedDataSource> data_source_;
+ scoped_ptr<Demuxer> demuxer_;
+ ChunkDemuxer* chunk_demuxer_;
+
+ BufferedDataSourceHostImpl buffered_data_source_host_;
+
+ // Video rendering members.
+ scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
+ VideoFrameCompositor* compositor_; // Deleted on |compositor_task_runner_|.
+ SkCanvasVideoRenderer skcanvas_video_renderer_;
+
+ // The compositor layer for displaying the video content when using composited
+ // playback.
+ scoped_ptr<cc_blink::WebLayerImpl> video_weblayer_;
+
+ // Text track objects get a unique index value when they're created.
+ int text_track_index_;
+
+ scoped_ptr<EncryptedMediaPlayerSupport> encrypted_media_support_;
+
+ const AudioHardwareConfig& audio_hardware_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_
diff --git a/media/blink/webmediaplayer_params.cc b/media/blink/webmediaplayer_params.cc
new file mode 100644
index 0000000..c157e34
--- /dev/null
+++ b/media/blink/webmediaplayer_params.cc
@@ -0,0 +1,44 @@
+// Copyright 2013 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 "media/blink/webmediaplayer_params.h"
+
+#include "base/single_thread_task_runner.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/base/media_log.h"
+#include "media/filters/gpu_video_accelerator_factories.h"
+
+namespace media {
+
+WebMediaPlayerParams::WebMediaPlayerParams(
+ const base::Callback<void(const base::Closure&)>& defer_load_cb,
+ const scoped_refptr<AudioRendererSink>& audio_renderer_sink,
+ const AudioHardwareConfig& audio_hardware_config,
+ const scoped_refptr<MediaLog>& media_log,
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ media_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ compositor_task_runner,
+ const EncryptedMediaPlayerSupportCreateCB&
+ encrypted_media_player_support_cb)
+ : defer_load_cb_(defer_load_cb),
+ audio_renderer_sink_(audio_renderer_sink),
+ audio_hardware_config_(audio_hardware_config),
+ media_log_(media_log),
+ gpu_factories_(gpu_factories),
+ media_task_runner_(media_task_runner),
+ compositor_task_runner_(compositor_task_runner),
+ encrypted_media_player_support_cb_(encrypted_media_player_support_cb) {
+}
+
+WebMediaPlayerParams::~WebMediaPlayerParams() {}
+
+scoped_ptr<EncryptedMediaPlayerSupport>
+WebMediaPlayerParams::CreateEncryptedMediaPlayerSupport(
+ blink::WebMediaPlayerClient* client) const {
+ return encrypted_media_player_support_cb_.Run(client);
+}
+
+} // namespace media
diff --git a/media/blink/webmediaplayer_params.h b/media/blink/webmediaplayer_params.h
new file mode 100644
index 0000000..5cdf95e
--- /dev/null
+++ b/media/blink/webmediaplayer_params.h
@@ -0,0 +1,102 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_
+#define MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+#include "media/blink/encrypted_media_player_support.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace blink {
+class WebMediaPlayerClient;
+}
+
+namespace media {
+class AudioHardwareConfig;
+class AudioRendererSink;
+class GpuVideoAcceleratorFactories;
+class MediaLog;
+
+// Holds parameters for constructing WebMediaPlayerImpl without having
+// to plumb arguments through various abstraction layers.
+class MEDIA_EXPORT WebMediaPlayerParams {
+ public:
+ // Callback used to create EncryptedMediaPlayerSupport instances. This
+ // callback must always return a valid EncryptedMediaPlayerSupport object.
+ typedef base::Callback<scoped_ptr<EncryptedMediaPlayerSupport>(
+ blink::WebMediaPlayerClient*)> EncryptedMediaPlayerSupportCreateCB;
+
+ // |defer_load_cb|, |audio_renderer_sink|, and |compositor_task_runner| may be
+ // null.
+ WebMediaPlayerParams(
+ const base::Callback<void(const base::Closure&)>& defer_load_cb,
+ const scoped_refptr<AudioRendererSink>& audio_renderer_sink,
+ const AudioHardwareConfig& audio_hardware_config,
+ const scoped_refptr<MediaLog>& media_log,
+ const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ media_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ compositor_task_runner,
+ const EncryptedMediaPlayerSupportCreateCB&
+ encrypted_media_player_support_cb);
+
+ ~WebMediaPlayerParams();
+
+ base::Callback<void(const base::Closure&)> defer_load_cb() const {
+ return defer_load_cb_;
+ }
+
+ const scoped_refptr<AudioRendererSink>& audio_renderer_sink() const {
+ return audio_renderer_sink_;
+ }
+
+ const AudioHardwareConfig& audio_hardware_config() const {
+ return audio_hardware_config_;
+ }
+
+ const scoped_refptr<MediaLog>& media_log() const {
+ return media_log_;
+ }
+
+ const scoped_refptr<GpuVideoAcceleratorFactories>&
+ gpu_factories() const {
+ return gpu_factories_;
+ }
+
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ media_task_runner() const {
+ return media_task_runner_;
+ }
+
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ compositor_task_runner() const {
+ return compositor_task_runner_;
+ }
+
+ scoped_ptr<EncryptedMediaPlayerSupport>
+ CreateEncryptedMediaPlayerSupport(blink::WebMediaPlayerClient* client) const;
+
+ private:
+ base::Callback<void(const base::Closure&)> defer_load_cb_;
+ scoped_refptr<AudioRendererSink> audio_renderer_sink_;
+ const AudioHardwareConfig& audio_hardware_config_;
+ scoped_refptr<MediaLog> media_log_;
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_;
+ scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
+ EncryptedMediaPlayerSupportCreateCB encrypted_media_player_support_cb_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebMediaPlayerParams);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_
diff --git a/media/blink/webmediaplayer_util.cc b/media/blink/webmediaplayer_util.cc
new file mode 100644
index 0000000..bcaf135
--- /dev/null
+++ b/media/blink/webmediaplayer_util.cc
@@ -0,0 +1,120 @@
+// Copyright 2013 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 "media/blink/webmediaplayer_util.h"
+
+#include <math.h>
+
+#include "base/metrics/histogram.h"
+#include "media/base/media_keys.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h"
+
+namespace media {
+
+// Compile asserts shared by all platforms.
+
+#define COMPILE_ASSERT_MATCHING_ENUM(name) \
+ COMPILE_ASSERT( \
+ static_cast<int>(blink::WebMediaPlayerClient::MediaKeyErrorCode ## name) == \
+ static_cast<int>(MediaKeys::k ## name ## Error), \
+ mismatching_enums)
+COMPILE_ASSERT_MATCHING_ENUM(Unknown);
+COMPILE_ASSERT_MATCHING_ENUM(Client);
+#undef COMPILE_ASSERT_MATCHING_ENUM
+
+base::TimeDelta ConvertSecondsToTimestamp(double seconds) {
+ double microseconds = seconds * base::Time::kMicrosecondsPerSecond;
+ return base::TimeDelta::FromMicroseconds(
+ microseconds > 0 ? microseconds + 0.5 : ceil(microseconds - 0.5));
+}
+
+blink::WebTimeRanges ConvertToWebTimeRanges(
+ const Ranges<base::TimeDelta>& ranges) {
+ blink::WebTimeRanges result(ranges.size());
+ for (size_t i = 0; i < ranges.size(); ++i) {
+ result[i].start = ranges.start(i).InSecondsF();
+ result[i].end = ranges.end(i).InSecondsF();
+ }
+ return result;
+}
+
+blink::WebMediaPlayer::NetworkState PipelineErrorToNetworkState(
+ PipelineStatus error) {
+ DCHECK_NE(error, PIPELINE_OK);
+
+ switch (error) {
+ case PIPELINE_ERROR_NETWORK:
+ case PIPELINE_ERROR_READ:
+ return blink::WebMediaPlayer::NetworkStateNetworkError;
+
+ // TODO(vrk): Because OnPipelineInitialize() directly reports the
+ // NetworkStateFormatError instead of calling OnPipelineError(), I believe
+ // this block can be deleted. Should look into it! (crbug.com/126070)
+ case PIPELINE_ERROR_INITIALIZATION_FAILED:
+ case PIPELINE_ERROR_COULD_NOT_RENDER:
+ case PIPELINE_ERROR_URL_NOT_FOUND:
+ case DEMUXER_ERROR_COULD_NOT_OPEN:
+ case DEMUXER_ERROR_COULD_NOT_PARSE:
+ case DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
+ case DECODER_ERROR_NOT_SUPPORTED:
+ return blink::WebMediaPlayer::NetworkStateFormatError;
+
+ case PIPELINE_ERROR_DECODE:
+ case PIPELINE_ERROR_ABORT:
+ case PIPELINE_ERROR_OPERATION_PENDING:
+ case PIPELINE_ERROR_INVALID_STATE:
+ return blink::WebMediaPlayer::NetworkStateDecodeError;
+
+ case PIPELINE_ERROR_DECRYPT:
+ // TODO(xhwang): Change to use NetworkStateDecryptError once it's added in
+ // Webkit (see http://crbug.com/124486).
+ return blink::WebMediaPlayer::NetworkStateDecodeError;
+
+ case PIPELINE_OK:
+ NOTREACHED() << "Unexpected status! " << error;
+ }
+ return blink::WebMediaPlayer::NetworkStateFormatError;
+}
+
+namespace {
+
+// Helper enum for reporting scheme histograms.
+enum URLSchemeForHistogram {
+ kUnknownURLScheme,
+ kMissingURLScheme,
+ kHttpURLScheme,
+ kHttpsURLScheme,
+ kFtpURLScheme,
+ kChromeExtensionURLScheme,
+ kJavascriptURLScheme,
+ kFileURLScheme,
+ kBlobURLScheme,
+ kDataURLScheme,
+ kFileSystemScheme,
+ kMaxURLScheme = kFileSystemScheme // Must be equal to highest enum value.
+};
+
+URLSchemeForHistogram URLScheme(const GURL& url) {
+ if (!url.has_scheme()) return kMissingURLScheme;
+ if (url.SchemeIs("http")) return kHttpURLScheme;
+ if (url.SchemeIs("https")) return kHttpsURLScheme;
+ if (url.SchemeIs("ftp")) return kFtpURLScheme;
+ if (url.SchemeIs("chrome-extension")) return kChromeExtensionURLScheme;
+ if (url.SchemeIs("javascript")) return kJavascriptURLScheme;
+ if (url.SchemeIs("file")) return kFileURLScheme;
+ if (url.SchemeIs("blob")) return kBlobURLScheme;
+ if (url.SchemeIs("data")) return kDataURLScheme;
+ if (url.SchemeIs("filesystem")) return kFileSystemScheme;
+
+ return kUnknownURLScheme;
+}
+
+} // namespace
+
+void ReportMediaSchemeUma(const GURL& url) {
+ UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url),
+ kMaxURLScheme + 1);
+}
+
+} // namespace media
diff --git a/media/blink/webmediaplayer_util.h b/media/blink/webmediaplayer_util.h
new file mode 100644
index 0000000..159be77
--- /dev/null
+++ b/media/blink/webmediaplayer_util.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_
+#define MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_
+
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/base/pipeline_status.h"
+#include "media/base/ranges.h"
+#include "third_party/WebKit/public/platform/WebMediaPlayer.h"
+#include "third_party/WebKit/public/platform/WebTimeRange.h"
+#include "url/gurl.h"
+
+namespace media {
+
+// Platform independent method for converting and rounding floating point
+// seconds to an int64 timestamp.
+//
+// Refer to https://bugs.webkit.org/show_bug.cgi?id=52697 for details.
+base::TimeDelta MEDIA_EXPORT ConvertSecondsToTimestamp(double seconds);
+
+blink::WebTimeRanges MEDIA_EXPORT ConvertToWebTimeRanges(
+ const Ranges<base::TimeDelta>& ranges);
+
+blink::WebMediaPlayer::NetworkState MEDIA_EXPORT PipelineErrorToNetworkState(
+ PipelineStatus error);
+
+// Report the scheme of Media URIs.
+void MEDIA_EXPORT ReportMediaSchemeUma(const GURL& url);
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_
diff --git a/media/blink/webmediasource_impl.cc b/media/blink/webmediasource_impl.cc
new file mode 100644
index 0000000..aecad6f
--- /dev/null
+++ b/media/blink/webmediasource_impl.cc
@@ -0,0 +1,86 @@
+// Copyright 2013 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 "media/blink/webmediasource_impl.h"
+
+#include "base/guid.h"
+#include "media/blink/websourcebuffer_impl.h"
+#include "media/filters/chunk_demuxer.h"
+#include "third_party/WebKit/public/platform/WebCString.h"
+#include "third_party/WebKit/public/platform/WebString.h"
+
+using ::blink::WebString;
+using ::blink::WebMediaSource;
+
+namespace media {
+
+#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(webkit_name, chromium_name) \
+ COMPILE_ASSERT(static_cast<int>(WebMediaSource::webkit_name) == \
+ static_cast<int>(ChunkDemuxer::chromium_name), \
+ mismatching_status_enums)
+COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusOk, kOk);
+COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusNotSupported, kNotSupported);
+COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusReachedIdLimit, kReachedIdLimit);
+#undef COMPILE_ASSERT_MATCHING_STATUS_ENUM
+
+WebMediaSourceImpl::WebMediaSourceImpl(
+ ChunkDemuxer* demuxer, LogCB log_cb)
+ : demuxer_(demuxer),
+ log_cb_(log_cb) {
+ DCHECK(demuxer_);
+}
+
+WebMediaSourceImpl::~WebMediaSourceImpl() {}
+
+WebMediaSource::AddStatus WebMediaSourceImpl::addSourceBuffer(
+ const blink::WebString& type,
+ const blink::WebVector<blink::WebString>& codecs,
+ blink::WebSourceBuffer** source_buffer) {
+ std::string id = base::GenerateGUID();
+ std::vector<std::string> new_codecs(codecs.size());
+ for (size_t i = 0; i < codecs.size(); ++i)
+ new_codecs[i] = codecs[i].utf8().data();
+
+ WebMediaSource::AddStatus result =
+ static_cast<WebMediaSource::AddStatus>(
+ demuxer_->AddId(id, type.utf8().data(), new_codecs));
+
+ if (result == WebMediaSource::AddStatusOk)
+ *source_buffer = new WebSourceBufferImpl(id, demuxer_);
+
+ return result;
+}
+
+double WebMediaSourceImpl::duration() {
+ return demuxer_->GetDuration();
+}
+
+void WebMediaSourceImpl::setDuration(double new_duration) {
+ DCHECK_GE(new_duration, 0);
+ demuxer_->SetDuration(new_duration);
+}
+
+void WebMediaSourceImpl::markEndOfStream(
+ WebMediaSource::EndOfStreamStatus status) {
+ PipelineStatus pipeline_status = PIPELINE_OK;
+
+ switch (status) {
+ case WebMediaSource::EndOfStreamStatusNoError:
+ break;
+ case WebMediaSource::EndOfStreamStatusNetworkError:
+ pipeline_status = PIPELINE_ERROR_NETWORK;
+ break;
+ case WebMediaSource::EndOfStreamStatusDecodeError:
+ pipeline_status = PIPELINE_ERROR_DECODE;
+ break;
+ }
+
+ demuxer_->MarkEndOfStream(pipeline_status);
+}
+
+void WebMediaSourceImpl::unmarkEndOfStream() {
+ demuxer_->UnmarkEndOfStream();
+}
+
+} // namespace media
diff --git a/media/blink/webmediasource_impl.h b/media/blink/webmediasource_impl.h
new file mode 100644
index 0000000..ac442f0
--- /dev/null
+++ b/media/blink/webmediasource_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_
+#define MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "media/base/media_export.h"
+#include "media/base/media_log.h"
+#include "third_party/WebKit/public/platform/WebMediaSource.h"
+
+namespace media {
+class ChunkDemuxer;
+
+class MEDIA_EXPORT WebMediaSourceImpl
+ : NON_EXPORTED_BASE(public blink::WebMediaSource) {
+ public:
+ WebMediaSourceImpl(ChunkDemuxer* demuxer, LogCB log_cb);
+ virtual ~WebMediaSourceImpl();
+
+ // blink::WebMediaSource implementation.
+ virtual AddStatus addSourceBuffer(
+ const blink::WebString& type,
+ const blink::WebVector<blink::WebString>& codecs,
+ blink::WebSourceBuffer** source_buffer);
+ virtual double duration();
+ virtual void setDuration(double duration);
+ virtual void markEndOfStream(EndOfStreamStatus status);
+ virtual void unmarkEndOfStream();
+
+ private:
+ ChunkDemuxer* demuxer_; // Owned by WebMediaPlayerImpl.
+ LogCB log_cb_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMediaSourceImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_
diff --git a/media/blink/websourcebuffer_impl.cc b/media/blink/websourcebuffer_impl.cc
new file mode 100644
index 0000000..c87c2b9
--- /dev/null
+++ b/media/blink/websourcebuffer_impl.cc
@@ -0,0 +1,134 @@
+// Copyright 2013 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 "media/blink/websourcebuffer_impl.h"
+
+#include <limits>
+
+#include "base/float_util.h"
+#include "media/filters/chunk_demuxer.h"
+
+namespace media {
+
+static base::TimeDelta DoubleToTimeDelta(double time) {
+ DCHECK(!base::IsNaN(time));
+ DCHECK_NE(time, -std::numeric_limits<double>::infinity());
+
+ if (time == std::numeric_limits<double>::infinity())
+ return kInfiniteDuration();
+
+ // Don't use base::TimeDelta::Max() here, as we want the largest finite time
+ // delta.
+ base::TimeDelta max_time = base::TimeDelta::FromInternalValue(kint64max - 1);
+ double max_time_in_seconds = max_time.InSecondsF();
+
+ if (time >= max_time_in_seconds)
+ return max_time;
+
+ return base::TimeDelta::FromMicroseconds(
+ time * base::Time::kMicrosecondsPerSecond);
+}
+
+WebSourceBufferImpl::WebSourceBufferImpl(
+ const std::string& id, ChunkDemuxer* demuxer)
+ : id_(id),
+ demuxer_(demuxer),
+ append_window_end_(kInfiniteDuration()) {
+ DCHECK(demuxer_);
+}
+
+WebSourceBufferImpl::~WebSourceBufferImpl() {
+ DCHECK(!demuxer_) << "Object destroyed w/o removedFromMediaSource() call";
+}
+
+bool WebSourceBufferImpl::setMode(WebSourceBuffer::AppendMode mode) {
+ if (demuxer_->IsParsingMediaSegment(id_))
+ return false;
+
+ switch (mode) {
+ case WebSourceBuffer::AppendModeSegments:
+ demuxer_->SetSequenceMode(id_, false);
+ return true;
+ case WebSourceBuffer::AppendModeSequence:
+ demuxer_->SetSequenceMode(id_, true);
+ return true;
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+blink::WebTimeRanges WebSourceBufferImpl::buffered() {
+ Ranges<base::TimeDelta> ranges = demuxer_->GetBufferedRanges(id_);
+ blink::WebTimeRanges result(ranges.size());
+ for (size_t i = 0; i < ranges.size(); i++) {
+ result[i].start = ranges.start(i).InSecondsF();
+ result[i].end = ranges.end(i).InSecondsF();
+ }
+ return result;
+}
+
+void WebSourceBufferImpl::append(
+ const unsigned char* data,
+ unsigned length,
+ double* timestamp_offset) {
+ base::TimeDelta old_offset = timestamp_offset_;
+ demuxer_->AppendData(id_, data, length,
+ append_window_start_, append_window_end_,
+ &timestamp_offset_);
+
+ // Coded frame processing may update the timestamp offset. If the caller
+ // provides a non-NULL |timestamp_offset| and frame processing changes the
+ // timestamp offset, report the new offset to the caller. Do not update the
+ // caller's offset otherwise, to preserve any pre-existing value that may have
+ // more than microsecond precision.
+ if (timestamp_offset && old_offset != timestamp_offset_)
+ *timestamp_offset = timestamp_offset_.InSecondsF();
+}
+
+void WebSourceBufferImpl::abort() {
+ demuxer_->Abort(id_,
+ append_window_start_, append_window_end_,
+ &timestamp_offset_);
+
+ // TODO(wolenetz): abort should be able to modify the caller timestamp offset
+ // (just like WebSourceBufferImpl::append).
+ // See http://crbug.com/370229 for further details.
+}
+
+void WebSourceBufferImpl::remove(double start, double end) {
+ DCHECK_GE(start, 0);
+ DCHECK_GE(end, 0);
+ demuxer_->Remove(id_, DoubleToTimeDelta(start), DoubleToTimeDelta(end));
+}
+
+bool WebSourceBufferImpl::setTimestampOffset(double offset) {
+ if (demuxer_->IsParsingMediaSegment(id_))
+ return false;
+
+ timestamp_offset_ = DoubleToTimeDelta(offset);
+
+ // http://www.w3.org/TR/media-source/#widl-SourceBuffer-timestampOffset
+ // Step 6: If the mode attribute equals "sequence", then set the group start
+ // timestamp to new timestamp offset.
+ demuxer_->SetGroupStartTimestampIfInSequenceMode(id_, timestamp_offset_);
+ return true;
+}
+
+void WebSourceBufferImpl::setAppendWindowStart(double start) {
+ DCHECK_GE(start, 0);
+ append_window_start_ = DoubleToTimeDelta(start);
+}
+
+void WebSourceBufferImpl::setAppendWindowEnd(double end) {
+ DCHECK_GE(end, 0);
+ append_window_end_ = DoubleToTimeDelta(end);
+}
+
+void WebSourceBufferImpl::removedFromMediaSource() {
+ demuxer_->RemoveId(id_);
+ demuxer_ = NULL;
+}
+
+} // namespace media
diff --git a/media/blink/websourcebuffer_impl.h b/media/blink/websourcebuffer_impl.h
new file mode 100644
index 0000000..e938178
--- /dev/null
+++ b/media/blink/websourcebuffer_impl.h
@@ -0,0 +1,54 @@
+// Copyright 2013 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 MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_
+#define MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "third_party/WebKit/public/platform/WebSourceBuffer.h"
+
+namespace media {
+class ChunkDemuxer;
+
+class WebSourceBufferImpl : public blink::WebSourceBuffer {
+ public:
+ WebSourceBufferImpl(const std::string& id, ChunkDemuxer* demuxer);
+ virtual ~WebSourceBufferImpl();
+
+ // blink::WebSourceBuffer implementation.
+ virtual bool setMode(AppendMode mode);
+ virtual blink::WebTimeRanges buffered();
+ virtual void append(
+ const unsigned char* data,
+ unsigned length,
+ double* timestamp_offset);
+ virtual void abort();
+ virtual void remove(double start, double end);
+ virtual bool setTimestampOffset(double offset);
+ virtual void setAppendWindowStart(double start);
+ virtual void setAppendWindowEnd(double end);
+ virtual void removedFromMediaSource();
+
+ private:
+ std::string id_;
+ ChunkDemuxer* demuxer_; // Owned by WebMediaPlayerImpl.
+
+ // Controls the offset applied to timestamps when processing appended media
+ // segments. It is initially 0, which indicates that no offset is being
+ // applied. Both setTimestampOffset() and append() may update this value.
+ base::TimeDelta timestamp_offset_;
+
+ base::TimeDelta append_window_start_;
+ base::TimeDelta append_window_end_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSourceBufferImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_