summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhubbe <hubbe@chromium.org>2015-11-30 14:22:13 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-30 22:23:16 +0000
commitdd6151100c52e52929d5e09f5f7fb15653c67f14 (patch)
tree6557215461e59cb511cad3e03a73cc0718ad4eb4 /media
parentff54c76b964ccdd548cbec9cba84bb5304e92905 (diff)
downloadchromium_src-dd6151100c52e52929d5e09f5f7fb15653c67f14.zip
chromium_src-dd6151100c52e52929d5e09f5f7fb15653c67f14.tar.gz
chromium_src-dd6151100c52e52929d5e09f5f7fb15653c67f14.tar.bz2
Provides an a way to have one multibuffer per URL and the data providers needed to populate those multibuffers.
This CL used to be bigger, but I've split it into pieces for easier reviewing. More CLs to follow. Media cache design doc: https://docs.google.com/document/d/15q6LTG0iDUe30QcoMtj4XNmKCa_7W_Q2uUIPFsJhS1E/edit Depends on: https://codereview.chromium.org/1420883004/ BUG=514719 Review URL: https://codereview.chromium.org/1399603003 Cr-Commit-Position: refs/heads/master@{#362242}
Diffstat (limited to 'media')
-rw-r--r--media/blink/BUILD.gn5
-rw-r--r--media/blink/cache_util.cc38
-rw-r--r--media/blink/cache_util.h8
-rw-r--r--media/blink/media_blink.gyp6
-rw-r--r--media/blink/multibuffer.cc40
-rw-r--r--media/blink/multibuffer.h38
-rw-r--r--media/blink/multibuffer_unittest.cc104
-rw-r--r--media/blink/resource_multibuffer_data_provider.cc480
-rw-r--r--media/blink/resource_multibuffer_data_provider.h126
-rw-r--r--media/blink/resource_multibuffer_data_provider_unittest.cc330
-rw-r--r--media/blink/url_index.cc237
-rw-r--r--media/blink/url_index.h241
-rw-r--r--media/blink/url_index_unittest.cc156
13 files changed, 1723 insertions, 86 deletions
diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn
index 3e97448..9eff8c1 100644
--- a/media/blink/BUILD.gn
+++ b/media/blink/BUILD.gn
@@ -52,8 +52,12 @@ component("blink") {
"multibuffer_reader.h",
"new_session_cdm_result_promise.cc",
"new_session_cdm_result_promise.h",
+ "resource_multibuffer_data_provider.cc",
+ "resource_multibuffer_data_provider.h",
"texttrack_impl.cc",
"texttrack_impl.h",
+ "url_index.cc",
+ "url_index.h",
"video_frame_compositor.cc",
"video_frame_compositor.h",
"webaudiosourceprovider_impl.cc",
@@ -129,6 +133,7 @@ test("media_blink_unittests") {
"test_random.h",
"test_response_generator.cc",
"test_response_generator.h",
+ "url_index_unittest.cc",
"video_frame_compositor_unittest.cc",
"webaudiosourceprovider_impl_unittest.cc",
]
diff --git a/media/blink/cache_util.cc b/media/blink/cache_util.cc
index e8cb563..0f55b1f 100644
--- a/media/blink/cache_util.cc
+++ b/media/blink/cache_util.cc
@@ -88,4 +88,42 @@ uint32 GetReasonsForUncacheability(const WebURLResponse& response) {
return reasons;
}
+base::TimeDelta GetCacheValidUntil(const WebURLResponse& response) {
+ std::string cache_control_header =
+ base::ToLowerASCII(response.httpHeaderField("cache-control").utf8());
+ if (cache_control_header.find("no-cache") != std::string::npos)
+ return base::TimeDelta();
+ if (cache_control_header.find("must-revalidate") != std::string::npos)
+ return base::TimeDelta();
+
+ // Max cache timeout ~= 1 month.
+ base::TimeDelta ret = base::TimeDelta::FromDays(30);
+
+ 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);
+
+ ret = std::min(ret, TimeDelta::FromSeconds(max_age_seconds));
+ } else {
+ // Note that |date| may be smaller than |expires|, which means we'll
+ // return a timetick some time in the past.
+ 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()) {
+ ret = std::min(ret, expires - date);
+ }
+ }
+
+ return ret;
+}
+
} // namespace media
diff --git a/media/blink/cache_util.h b/media/blink/cache_util.h
index 033b6bd..8dd2a19 100644
--- a/media/blink/cache_util.h
+++ b/media/blink/cache_util.h
@@ -8,6 +8,7 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/time/time.h"
#include "media/blink/media_blink_export.h"
namespace blink {
@@ -36,6 +37,13 @@ enum UncacheableReason {
uint32 MEDIA_BLINK_EXPORT
GetReasonsForUncacheability(const blink::WebURLResponse& response);
+// Returns when we should evict data from this response from our
+// memory cache. Note that we may still cache data longer if
+// a audio/video tag is currently using it. Returns a TimeDelta
+// which is should be added to base::Time::Now() or base::TimeTicks::Now().
+base::TimeDelta MEDIA_BLINK_EXPORT
+GetCacheValidUntil(const blink::WebURLResponse& response);
+
} // namespace media
#endif // MEDIA_BLINK_CACHE_UTIL_H_
diff --git a/media/blink/media_blink.gyp b/media/blink/media_blink.gyp
index 3b08ee7..2617fb5 100644
--- a/media/blink/media_blink.gyp
+++ b/media/blink/media_blink.gyp
@@ -57,8 +57,12 @@
'multibuffer_reader.h',
'new_session_cdm_result_promise.cc',
'new_session_cdm_result_promise.h',
+ 'resource_multibuffer_data_provider.cc',
+ 'resource_multibuffer_data_provider.h',
'texttrack_impl.cc',
'texttrack_impl.h',
+ 'url_index.cc',
+ 'url_index.h',
'video_frame_compositor.cc',
'video_frame_compositor.h',
'webaudiosourceprovider_impl.cc',
@@ -131,10 +135,12 @@
'mock_weburlloader.cc',
'mock_weburlloader.h',
'multibuffer_unittest.cc',
+ 'resource_multibuffer_data_provider_unittest.cc',
'run_all_unittests.cc',
'test_random.h',
'test_response_generator.cc',
'test_response_generator.h',
+ 'url_index_unittest.cc',
'video_frame_compositor_unittest.cc',
'webaudiosourceprovider_impl_unittest.cc',
],
diff --git a/media/blink/multibuffer.cc b/media/blink/multibuffer.cc
index 41c3582..a8d981e 100644
--- a/media/blink/multibuffer.cc
+++ b/media/blink/multibuffer.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <utility>
+
#include "media/blink/multibuffer.h"
#include "base/bind.h"
@@ -113,10 +115,6 @@ MultiBuffer::MultiBuffer(int32_t block_size_shift,
: max_size_(0), block_size_shift_(block_size_shift), lru_(global_lru) {}
MultiBuffer::~MultiBuffer() {
- // Delete all writers.
- for (const auto& i : writer_index_) {
- delete i.second;
- }
// Remove all blocks from the LRU.
for (const auto& i : data_) {
lru_->Remove(this, i.first);
@@ -155,15 +153,14 @@ void MultiBuffer::AddReader(const BlockId& pos, Reader* reader) {
// Make sure that there are no present blocks between the writer and
// the requested position, as that will cause the writer to quit.
if (closest_writer > closest_block) {
- provider = writer_index_[closest_writer];
+ provider = writer_index_[closest_writer].get();
DCHECK(provider);
}
}
if (!provider) {
DCHECK(writer_index_.find(pos) == writer_index_.end());
- provider = writer_index_[pos] = CreateWriter(pos);
- provider->SetAvailableCallback(base::Bind(
- &MultiBuffer::DataProviderEvent, base::Unretained(this), provider));
+ writer_index_[pos] = CreateWriter(pos);
+ provider = writer_index_[pos].get();
}
provider->SetDeferred(false);
}
@@ -183,7 +180,7 @@ void MultiBuffer::CleanupWriters(const BlockId& pos) {
BlockId closest_writer = ClosestPreviousEntry(writer_index_, p2);
while (closest_writer > pos - kMaxWaitForWriterOffset) {
DCHECK(writer_index_[closest_writer]);
- DataProviderEvent(writer_index_[closest_writer]);
+ OnDataProviderEvent(writer_index_[closest_writer].get());
closest_writer = ClosestPreviousEntry(writer_index_, closest_writer - 1);
}
}
@@ -258,25 +255,28 @@ void MultiBuffer::ReleaseBlocks(const std::vector<MultiBufferBlockId>& blocks) {
}
}
}
+ if (data_.empty())
+ OnEmpty();
}
+void MultiBuffer::OnEmpty() {}
+
void MultiBuffer::AddProvider(scoped_ptr<DataProvider> provider) {
// If there is already a provider in the same location, we delete it.
DCHECK(!provider->Available());
BlockId pos = provider->Tell();
- DataProvider** place = &writer_index_[pos];
- DCHECK_NE(*place, provider.get());
- if (*place)
- delete *place;
- *place = provider.release();
+ writer_index_[pos] = std::move(provider);
}
scoped_ptr<MultiBuffer::DataProvider> MultiBuffer::RemoveProvider(
DataProvider* provider) {
BlockId pos = provider->Tell();
- DCHECK_EQ(writer_index_[pos], provider);
- writer_index_.erase(pos);
- return scoped_ptr<DataProvider>(provider);
+ auto iter = writer_index_.find(pos);
+ DCHECK(iter != writer_index_.end());
+ DCHECK_EQ(iter->second.get(), provider);
+ scoped_ptr<DataProvider> ret = std::move(iter->second);
+ writer_index_.erase(iter);
+ return ret;
}
MultiBuffer::ProviderState MultiBuffer::SuggestProviderState(
@@ -324,7 +324,7 @@ void MultiBuffer::Prune(size_t max_to_free) {
lru_->Prune(max_to_free);
}
-void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) {
+void MultiBuffer::OnDataProviderEvent(DataProvider* provider_tmp) {
scoped_ptr<DataProvider> provider(RemoveProvider(provider_tmp));
BlockId start_pos = provider->Tell();
BlockId pos = start_pos;
@@ -333,7 +333,7 @@ void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) {
while (!ProviderCollision(pos) && !eof) {
if (!provider->Available()) {
- AddProvider(provider.Pass());
+ AddProvider(std::move(provider));
break;
}
DCHECK_GE(pos, 0);
@@ -361,7 +361,7 @@ void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) {
// Even if we did call AddProvider, calling NotifyAvailableRange can cause
// readers to seek or self-destruct and clean up any associated writers.
auto i = writer_index_.find(pos);
- if (i != writer_index_.end() && i->second == provider_tmp) {
+ if (i != writer_index_.end() && i->second.get() == provider_tmp) {
switch (SuggestProviderState(pos)) {
case ProviderStateLoad:
// Not sure we actually need to do this
diff --git a/media/blink/multibuffer.h b/media/blink/multibuffer.h
index d469d43..eb3530d 100644
--- a/media/blink/multibuffer.h
+++ b/media/blink/multibuffer.h
@@ -16,6 +16,7 @@
#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
#include "media/base/data_buffer.h"
#include "media/blink/interval_map.h"
#include "media/blink/lru.h"
@@ -23,8 +24,14 @@
namespace media {
+// Used to identify a block of data in the multibuffer.
+// Our blocks are 32kb (1 << 15), so our maximum cacheable file size
+// is 1 << (15 + 31) = 64Tb
typedef int32_t MultiBufferBlockId;
class MultiBuffer;
+
+// This type is used to identify a block in the LRU, which is shared between
+// multibuffers.
typedef std::pair<MultiBuffer*, MultiBufferBlockId> MultiBufferGlobalBlockId;
} // namespace media
@@ -72,8 +79,6 @@ const int kMaxWaitForWriterOffset = 5;
// This is the size of the look-behind region.
const int kMaxWaitForReaderOffset = 50;
-class MultiBuffer;
-
// MultiBuffers are multi-reader multi-writer cache/buffers with
// prefetching and pinning. Data is stored internally in ref-counted
// blocks of identical size. |block_size_shift| is log2 of the block
@@ -119,9 +124,6 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
// DataBuffer.
virtual scoped_refptr<DataBuffer> Read() = 0;
- // |cb| is called every time Available() becomes true.
- virtual void SetAvailableCallback(const base::Closure& cb) = 0;
-
// Ask the data provider to stop giving us data.
// It's ok if the effect is not immediate.
virtual void SetDeferred(bool deferred) = 0;
@@ -229,8 +231,8 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
// not call it anymore.
scoped_ptr<DataProvider> RemoveProvider(DataProvider* provider);
- // Add a writer to this cache. Cache takes ownership and
- // may choose to destroy it.
+ // Add a writer to this cache. Cache takes ownership, and may
+ // destroy |provider| later. (Not during this call.)
void AddProvider(scoped_ptr<DataProvider> provider);
// Transfer all data from |other| to this.
@@ -240,13 +242,23 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
const DataMap& map() const { return data_; }
int32_t block_size_shift() const { return block_size_shift_; }
+ // Callback which notifies us that a data provider has
+ // some data for us. Also called when it might be appropriate
+ // for a provider in a deferred state to wake up.
+ void OnDataProviderEvent(DataProvider* provider);
+
protected:
// Create a new writer at |pos| and return it.
// Users needs to implemement this method.
- virtual DataProvider* CreateWriter(const BlockId& pos) = 0;
+ virtual scoped_ptr<DataProvider> CreateWriter(const BlockId& pos) = 0;
virtual bool RangeSupported() const = 0;
+ // Called when the cache becomes empty. Implementations can use this
+ // as a signal for when we should free this object and any metadata
+ // that goes with it.
+ virtual void OnEmpty();
+
private:
// For testing.
friend class TestMultiBuffer;
@@ -276,11 +288,6 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
void NotifyAvailableRange(const Interval<MultiBufferBlockId>& observer_range,
const Interval<MultiBufferBlockId>& new_range);
- // Callback which notifies us that a data provider has
- // some data for us. Also called when it might be apprperiate
- // for a provider in a deferred state to wake up.
- void DataProviderEvent(DataProvider* provider);
-
// Max number of blocks.
int64_t max_size_;
@@ -295,8 +302,7 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
// Keeps track of writers by their position.
// The writers are owned by this class.
- // TODO(hubbe): Use ScopedPtrMap here. (must add upper/lower_bound first)
- std::map<BlockId, DataProvider*> writer_index_;
+ std::map<BlockId, scoped_ptr<DataProvider>> writer_index_;
// Gloabally shared LRU, decides which block to free next.
scoped_refptr<GlobalLRU> lru_;
@@ -310,6 +316,8 @@ class MEDIA_BLINK_EXPORT MultiBuffer {
// and 0 for all blocks that are not. Used to quickly figure out
// ranges of available/unavailable blocks without iterating.
IntervalMap<BlockId, int32_t> present_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiBuffer);
};
} // namespace media
diff --git a/media/blink/multibuffer_unittest.cc b/media/blink/multibuffer_unittest.cc
index 96c8c07..4a2a64b 100644
--- a/media/blink/multibuffer_unittest.cc
+++ b/media/blink/multibuffer_unittest.cc
@@ -18,27 +18,32 @@ const int kBlockSizeShift = 8;
const size_t kBlockSize = 1UL << kBlockSizeShift;
namespace media {
-class TestMultiBufferDataProvider;
-std::vector<TestMultiBufferDataProvider*> writers;
+class FakeMultiBufferDataProvider;
-class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider {
+namespace {
+std::vector<FakeMultiBufferDataProvider*> writers;
+} // namespace
+
+class FakeMultiBufferDataProvider : public MultiBuffer::DataProvider {
public:
- TestMultiBufferDataProvider(MultiBufferBlockId pos,
+ FakeMultiBufferDataProvider(MultiBufferBlockId pos,
size_t file_size,
int max_blocks_after_defer,
bool must_read_whole_file,
- media::TestRandom* rnd)
+ MultiBuffer* multibuffer,
+ TestRandom* rnd)
: pos_(pos),
blocks_until_deferred_(1 << 30),
max_blocks_after_defer_(max_blocks_after_defer),
file_size_(file_size),
must_read_whole_file_(must_read_whole_file),
+ multibuffer_(multibuffer),
rnd_(rnd) {
writers.push_back(this);
}
- ~TestMultiBufferDataProvider() override {
+ ~FakeMultiBufferDataProvider() override {
if (must_read_whole_file_) {
CHECK_GE(pos_ * kBlockSize, file_size_);
}
@@ -64,11 +69,6 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider {
return ret;
}
- void SetAvailableCallback(const base::Closure& cb) override {
- DCHECK(!Available());
- cb_ = cb;
- }
-
void SetDeferred(bool deferred) override {
if (deferred) {
if (max_blocks_after_defer_ > 0) {
@@ -89,7 +89,7 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider {
--blocks_until_deferred_;
bool ret = true;
- scoped_refptr<media::DataBuffer> block = new media::DataBuffer(kBlockSize);
+ scoped_refptr<DataBuffer> block = new DataBuffer(kBlockSize);
size_t x = 0;
size_t byte_pos = (fifo_.size() + pos_) * kBlockSize;
for (x = 0; x < kBlockSize; x++, byte_pos++) {
@@ -104,28 +104,27 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider {
fifo_.push_back(DataBuffer::CreateEOSBuffer());
ret = false;
}
- cb_.Run();
+ multibuffer_->OnDataProviderEvent(this);
return ret;
}
private:
- std::deque<scoped_refptr<media::DataBuffer>> fifo_;
+ std::deque<scoped_refptr<DataBuffer>> fifo_;
MultiBufferBlockId pos_;
int32_t blocks_until_deferred_;
int32_t max_blocks_after_defer_;
size_t file_size_;
bool must_read_whole_file_;
- base::Closure cb_;
- media::TestRandom* rnd_;
+ MultiBuffer* multibuffer_;
+ TestRandom* rnd_;
};
-class TestMultiBuffer : public media::MultiBuffer {
+class TestMultiBuffer : public MultiBuffer {
public:
- explicit TestMultiBuffer(
- int32_t shift,
- const scoped_refptr<media::MultiBuffer::GlobalLRU>& lru,
- media::TestRandom* rnd)
- : media::MultiBuffer(shift, lru),
+ explicit TestMultiBuffer(int32_t shift,
+ const scoped_refptr<MultiBuffer::GlobalLRU>& lru,
+ TestRandom* rnd)
+ : MultiBuffer(shift, lru),
range_supported_(false),
create_ok_(true),
max_writers_(10000),
@@ -185,12 +184,14 @@ class TestMultiBuffer : public media::MultiBuffer {
void SetRangeSupported(bool supported) { range_supported_ = supported; }
protected:
- DataProvider* CreateWriter(const MultiBufferBlockId& pos) override {
+ scoped_ptr<DataProvider> CreateWriter(
+ const MultiBufferBlockId& pos) override {
DCHECK(create_ok_);
writers_created_++;
CHECK_LT(writers.size(), max_writers_);
- return new TestMultiBufferDataProvider(
- pos, file_size_, max_blocks_after_defer_, must_read_whole_file_, rnd_);
+ return scoped_ptr<DataProvider>(new FakeMultiBufferDataProvider(
+ pos, file_size_, max_blocks_after_defer_, must_read_whole_file_, this,
+ rnd_));
}
void Prune(size_t max_to_free) override {
// Prune should not cause additional writers to be spawned.
@@ -209,35 +210,34 @@ class TestMultiBuffer : public media::MultiBuffer {
int32_t max_blocks_after_defer_;
bool must_read_whole_file_;
int32_t writers_created_;
- media::TestRandom* rnd_;
+ TestRandom* rnd_;
};
-}
class MultiBufferTest : public testing::Test {
public:
MultiBufferTest()
: rnd_(42),
- lru_(new media::MultiBuffer::GlobalLRU()),
+ lru_(new MultiBuffer::GlobalLRU()),
multibuffer_(kBlockSizeShift, lru_, &rnd_) {}
void Advance() {
- CHECK(media::writers.size());
- media::writers[rnd_.Rand() % media::writers.size()]->Advance();
+ CHECK(writers.size());
+ writers[rnd_.Rand() % writers.size()]->Advance();
}
bool AdvanceAll() {
bool advanced = false;
- for (size_t i = 0; i < media::writers.size(); i++) {
- advanced |= media::writers[i]->Advance();
+ for (size_t i = 0; i < writers.size(); i++) {
+ advanced |= writers[i]->Advance();
}
multibuffer_.CheckLRUState();
return advanced;
}
protected:
- media::TestRandom rnd_;
- scoped_refptr<media::MultiBuffer::GlobalLRU> lru_;
- media::TestMultiBuffer multibuffer_;
+ TestRandom rnd_;
+ scoped_refptr<MultiBuffer::GlobalLRU> lru_;
+ TestMultiBuffer multibuffer_;
base::MessageLoop message_loop_;
};
@@ -247,8 +247,8 @@ TEST_F(MultiBufferTest, ReadAll) {
size_t end = 10000;
multibuffer_.SetFileSize(10000);
multibuffer_.SetMustReadWholeFile(true);
- media::MultiBufferReader reader(&multibuffer_, pos, end,
- base::Callback<void(int64_t, int64_t)>());
+ MultiBufferReader reader(&multibuffer_, pos, end,
+ base::Callback<void(int64_t, int64_t)>());
reader.SetMaxBuffer(2000, 5000);
reader.SetPreload(1000, 1000);
while (pos < end) {
@@ -275,8 +275,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst) {
size_t end = 10000;
multibuffer_.SetFileSize(10000);
multibuffer_.SetMustReadWholeFile(true);
- media::MultiBufferReader reader(&multibuffer_, pos, end,
- base::Callback<void(int64_t, int64_t)>());
+ MultiBufferReader reader(&multibuffer_, pos, end,
+ base::Callback<void(int64_t, int64_t)>());
reader.SetMaxBuffer(2000, 5000);
reader.SetPreload(1000, 1000);
while (pos < end) {
@@ -305,8 +305,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst_NeverDefer) {
multibuffer_.SetFileSize(10000);
multibuffer_.SetMaxBlocksAfterDefer(-10000);
multibuffer_.SetRangeSupported(true);
- media::MultiBufferReader reader(&multibuffer_, pos, end,
- base::Callback<void(int64_t, int64_t)>());
+ MultiBufferReader reader(&multibuffer_, pos, end,
+ base::Callback<void(int64_t, int64_t)>());
reader.SetMaxBuffer(2000, 5000);
reader.SetPreload(1000, 1000);
while (pos < end) {
@@ -336,8 +336,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst_NeverDefer2) {
multibuffer_.SetFileSize(10000);
multibuffer_.SetMustReadWholeFile(true);
multibuffer_.SetMaxBlocksAfterDefer(-10000);
- media::MultiBufferReader reader(&multibuffer_, pos, end,
- base::Callback<void(int64_t, int64_t)>());
+ MultiBufferReader reader(&multibuffer_, pos, end,
+ base::Callback<void(int64_t, int64_t)>());
reader.SetMaxBuffer(2000, 5000);
reader.SetPreload(1000, 1000);
while (pos < end) {
@@ -366,8 +366,8 @@ TEST_F(MultiBufferTest, LRUTest) {
size_t pos = 0;
size_t end = 10000;
multibuffer_.SetFileSize(10000);
- media::MultiBufferReader reader(&multibuffer_, pos, end,
- base::Callback<void(int64_t, int64_t)>());
+ MultiBufferReader reader(&multibuffer_, pos, end,
+ base::Callback<void(int64_t, int64_t)>());
reader.SetPreload(10000, 10000);
// Note, no pinning, all data should end up in LRU.
EXPECT_EQ(current_size, lru_->Size());
@@ -390,8 +390,8 @@ class ReadHelper {
public:
ReadHelper(size_t end,
size_t max_read_size,
- media::MultiBuffer* multibuffer,
- media::TestRandom* rnd)
+ MultiBuffer* multibuffer,
+ TestRandom* rnd)
: pos_(0),
end_(end),
max_read_size_(max_read_size),
@@ -446,8 +446,8 @@ class ReadHelper {
int64_t end_;
int64_t max_read_size_;
int64_t read_size_;
- media::TestRandom* rnd_;
- media::MultiBufferReader reader_;
+ TestRandom* rnd_;
+ MultiBufferReader reader_;
};
TEST_F(MultiBufferTest, RandomTest) {
@@ -462,7 +462,7 @@ TEST_F(MultiBufferTest, RandomTest) {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
if (rnd_.Rand() & 1) {
- if (!media::writers.empty())
+ if (!writers.empty())
Advance();
} else {
size_t j = rnd_.Rand() % read_helpers.size();
@@ -493,7 +493,7 @@ TEST_F(MultiBufferTest, RandomTest_RangeSupported) {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
if (rnd_.Rand() & 1) {
- if (!media::writers.empty())
+ if (!writers.empty())
Advance();
} else {
size_t j = rnd_.Rand() % read_helpers.size();
@@ -510,3 +510,5 @@ TEST_F(MultiBufferTest, RandomTest_RangeSupported) {
read_helpers.pop_back();
}
}
+
+} // namespace media
diff --git a/media/blink/resource_multibuffer_data_provider.cc b/media/blink/resource_multibuffer_data_provider.cc
new file mode 100644
index 0000000..78bd2a0
--- /dev/null
+++ b/media/blink/resource_multibuffer_data_provider.cc
@@ -0,0 +1,480 @@
+// Copyright 2015 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/resource_multibuffer_data_provider.h"
+
+#include "base/bind.h"
+#include "base/bits.h"
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "media/blink/active_loader.h"
+#include "media/blink/cache_util.h"
+#include "media/blink/media_blink_export.h"
+#include "media/blink/url_index.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+#include "third_party/WebKit/public/platform/WebURLResponse.h"
+
+using blink::WebFrame;
+using blink::WebString;
+using blink::WebURLError;
+using blink::WebURLLoader;
+using blink::WebURLLoaderOptions;
+using blink::WebURLRequest;
+using blink::WebURLResponse;
+
+namespace media {
+
+// The number of milliseconds to wait before retrying a failed load.
+const int kLoaderFailedRetryDelayMs = 250;
+
+const int kHttpOK = 200;
+const int kHttpPartialContent = 206;
+const int kHttpRangeNotSatisfiable = 416;
+const int kMaxRetries = 3;
+
+ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
+ UrlData* url_data,
+ MultiBufferBlockId pos)
+ : pos_(pos),
+ url_data_(url_data),
+ retries_(0),
+ cors_mode_(url_data->cors_mode()),
+ origin_(url_data->url().GetOrigin()),
+ weak_factory_(this) {
+ DCHECK(url_data_) << " pos = " << pos;
+ DCHECK_GE(pos, 0);
+}
+
+void ResourceMultiBufferDataProvider::Start() {
+ // In the case of a re-start, throw away any half-finished blocks.
+ fifo_.clear();
+ // Prepare the request.
+ WebURLRequest request(url_data_->url());
+ // TODO(mkwst): Split this into video/audio.
+ request.setRequestContext(WebURLRequest::RequestContextVideo);
+
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kRange),
+ WebString::fromUTF8(
+ net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue()));
+
+ url_data_->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 (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) {
+ 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 (url_data_->cors_mode() == UrlData::CORS_USE_CREDENTIALS)
+ options.allowCredentials = true;
+ }
+ loader.reset(url_data_->frame()->createAssociatedURLLoader(options));
+ }
+
+ // Start the resource loading.
+ loader->loadAsynchronously(request, this);
+ active_loader_.reset(new ActiveLoader(loader.Pass()));
+}
+
+ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() {}
+
+/////////////////////////////////////////////////////////////////////////////
+// MultiBuffer::DataProvider implementation.
+MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const {
+ return pos_;
+}
+
+bool ResourceMultiBufferDataProvider::Available() const {
+ if (fifo_.empty())
+ return false;
+ if (fifo_.back()->end_of_stream())
+ return true;
+ if (fifo_.front()->data_size() == block_size())
+ return true;
+ return false;
+}
+
+scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() {
+ DCHECK(Available());
+ scoped_refptr<DataBuffer> ret = fifo_.front();
+ fifo_.pop_front();
+ ++pos_;
+ return ret;
+}
+
+void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) {
+ if (!active_loader_ || active_loader_->deferred() == deferred)
+ return;
+ active_loader_->SetDeferred(deferred);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// WebURLLoaderClient implementation.
+
+void ResourceMultiBufferDataProvider::willFollowRedirect(
+ WebURLLoader* loader,
+ WebURLRequest& newRequest,
+ const WebURLResponse& redirectResponse) {
+ redirects_to_ = newRequest.url();
+ url_data_->set_valid_until(base::Time::Now() +
+ GetCacheValidUntil(redirectResponse));
+
+ // This test is vital for security!
+ if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
+ if (origin_ != redirects_to_.GetOrigin()) {
+ url_data_->Fail();
+ }
+ }
+}
+
+void ResourceMultiBufferDataProvider::didSendData(
+ WebURLLoader* loader,
+ unsigned long long bytes_sent,
+ unsigned long long total_bytes_to_be_sent) {
+ NOTIMPLEMENTED();
+}
+
+void ResourceMultiBufferDataProvider::didReceiveResponse(
+ WebURLLoader* loader,
+ const WebURLResponse& response) {
+#if ENABLE_DLOG
+ string version;
+ switch (response.httpVersion()) {
+ case WebURLResponse::HTTPVersion_0_9:
+ version = "0.9";
+ break;
+ case WebURLResponse::HTTPVersion_1_0:
+ version = "1.0";
+ break;
+ case WebURLResponse::HTTPVersion_1_1:
+ version = "1.1";
+ break;
+ case WebURLResponse::HTTPVersion_2_0:
+ version = "2.1";
+ break;
+ }
+ DVLOG(1) << "didReceiveResponse: HTTP/" << version << " "
+ << response.httpStatusCode();
+#endif
+ DCHECK(active_loader_);
+
+ scoped_refptr<UrlData> destination_url_data(url_data_);
+
+ UrlIndex* url_index = url_data_->url_index();
+
+ if (!redirects_to_.is_empty()) {
+ if (!url_index) {
+ // We've been disconnected from the url index.
+ // That means the url_index_ has been destroyed, which means we do not
+ // need to do anything clever.
+ return;
+ }
+ destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_);
+ redirects_to_ = GURL();
+ }
+
+ base::Time last_modified;
+ if (base::Time::FromString(
+ response.httpHeaderField("Last-Modified").utf8().data(),
+ &last_modified)) {
+ destination_url_data->set_last_modified(last_modified);
+ }
+
+ destination_url_data->set_valid_until(base::Time::Now() +
+ GetCacheValidUntil(response));
+
+ uint32 reasons = GetReasonsForUncacheability(response);
+ destination_url_data->set_cacheable(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.
+ int64 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 (destination_url_data->url().SchemeIsHTTPOrHTTPS()) {
+ bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
+ bool ok_response = (response.httpStatusCode() == kHttpOK);
+
+ // Check to see whether the server supports byte ranges.
+ std::string accept_ranges =
+ response.httpHeaderField("Accept-Ranges").utf8();
+ if (accept_ranges.find("bytes") != std::string::npos)
+ destination_url_data->set_range_supported();
+
+ // If we have verified the partial response and it is correct.
+ // It's also possible for a server to support range requests
+ // without advertising "Accept-Ranges: bytes".
+ if (partial_response && VerifyPartialResponse(response)) {
+ destination_url_data->set_range_supported();
+ } else if (ok_response && pos_ == 0) {
+ // 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.
+ destination_url_data->set_length(content_length);
+ } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) {
+ // Really, we should never request a range that doesn't exist, but
+ // if we do, let's handle it in a sane way.
+ // Unsatisfiable range
+ fifo_.push_back(DataBuffer::CreateEOSBuffer());
+ destination_url_data->multibuffer()->OnDataProviderEvent(this);
+ return;
+ } else {
+ destination_url_data->Fail();
+ return;
+ }
+ } else {
+ destination_url_data->set_range_supported();
+ if (content_length != kPositionNotSpecified) {
+ destination_url_data->set_length(content_length + byte_pos());
+ }
+ }
+
+ if (url_index) {
+ destination_url_data = url_index->TryInsert(destination_url_data);
+ }
+
+ if (destination_url_data != url_data_) {
+ // At this point, we've encountered a redirect, or found a better url data
+ // instance for the data that we're about to download.
+
+ // First, let's take a ref on the current url data.
+ scoped_refptr<UrlData> old_url_data(url_data_);
+ destination_url_data->Use();
+
+ // Take ownership of ourselves. (From the multibuffer)
+ scoped_ptr<DataProvider> self(
+ url_data_->multibuffer()->RemoveProvider(this));
+ url_data_ = destination_url_data.get();
+ // Give the ownership to our new owner.
+ url_data_->multibuffer()->AddProvider(self.Pass());
+
+ // Call callback to let upstream users know about the transfer.
+ // This will merge the data from the two multibuffers and
+ // cause clients to start using the new UrlData.
+ old_url_data->RedirectTo(destination_url_data);
+ }
+}
+
+void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) {
+ DVLOG(1) << "didReceiveData: " << data_length << " bytes";
+ DCHECK(!Available());
+ DCHECK(active_loader_);
+ DCHECK_GT(data_length, 0);
+
+ // When we receive data, we allow more retries.
+ retries_ = 0;
+
+ while (data_length) {
+ if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
+ fifo_.push_back(new DataBuffer(block_size()));
+ fifo_.back()->set_data_size(0);
+ }
+ int last_block_size = fifo_.back()->data_size();
+ int to_append = std::min<int>(data_length, block_size() - last_block_size);
+ DCHECK_GT(to_append, 0);
+ memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append);
+ data += to_append;
+ fifo_.back()->set_data_size(last_block_size + to_append);
+ data_length -= to_append;
+ }
+
+ if (Available())
+ url_data_->multibuffer()->OnDataProviderEvent(this);
+
+ // Beware, this object might be deleted here.
+}
+
+void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
+ int dataLength,
+ int encoded_data_length) {
+ NOTIMPLEMENTED();
+}
+
+void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
+ WebURLLoader* loader,
+ const char* data,
+ int data_length) {
+ NOTIMPLEMENTED();
+}
+
+void ResourceMultiBufferDataProvider::didFinishLoading(
+ WebURLLoader* loader,
+ double finishTime,
+ int64_t total_encoded_data_length) {
+ DVLOG(1) << "didFinishLoading";
+ DCHECK(active_loader_.get());
+ DCHECK(!Available());
+
+ // We're done with the loader.
+ active_loader_.reset();
+
+ // If we didn't know the |instance_size_| we do now.
+ int64_t size = byte_pos();
+ if (!fifo_.empty())
+ size += fifo_.back()->data_size();
+
+ // This request reports something smaller than what we've seen in the past,
+ // Maybe it's transient error?
+ if (url_data_->length() != kPositionNotSpecified &&
+ size < url_data_->length()) {
+ if (retries_ < kMaxRetries) {
+ fifo_.clear();
+ retries_++;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
+ return;
+ } else {
+ scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
+ url_data_->Fail();
+ return;
+ }
+ }
+
+ url_data_->set_length(size);
+ fifo_.push_back(DataBuffer::CreateEOSBuffer());
+
+ DCHECK(Available());
+ url_data_->multibuffer()->OnDataProviderEvent(this);
+
+ // Beware, this object might be deleted here.
+}
+
+void ResourceMultiBufferDataProvider::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());
+
+ if (retries_ < kMaxRetries) {
+ retries_++;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
+ } else {
+ // 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();
+ url_data_->Fail();
+ }
+}
+
+bool ResourceMultiBufferDataProvider::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;
+}
+
+int64_t ResourceMultiBufferDataProvider::byte_pos() const {
+ int64_t ret = pos_;
+ return ret << url_data_->multibuffer()->block_size_shift();
+}
+
+int64_t ResourceMultiBufferDataProvider::block_size() const {
+ int64_t ret = 1;
+ return ret << url_data_->multibuffer()->block_size_shift();
+}
+
+bool ResourceMultiBufferDataProvider::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 (url_data_->length() == kPositionNotSpecified) {
+ url_data_->set_length(instance_size);
+ }
+
+ if (byte_pos() != first_byte_position) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/blink/resource_multibuffer_data_provider.h b/media/blink/resource_multibuffer_data_provider.h
new file mode 100644
index 0000000..89b70e5
--- /dev/null
+++ b/media/blink/resource_multibuffer_data_provider.h
@@ -0,0 +1,126 @@
+// Copyright 2015 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_RESOURCE_MULTIBUFFER_DATA_PROVIDER_H_
+#define MEDIA_BLINK_RESOURCE_MULTIBUFFER_DATA_PROVIDER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "media/blink/active_loader.h"
+#include "media/blink/media_blink_export.h"
+#include "media/blink/multibuffer.h"
+#include "media/blink/url_index.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 MEDIA_BLINK_EXPORT ResourceMultiBufferDataProvider
+ : NON_EXPORTED_BASE(public MultiBuffer::DataProvider),
+ NON_EXPORTED_BASE(public blink::WebURLLoaderClient) {
+ public:
+ ResourceMultiBufferDataProvider(UrlData* url_data, MultiBufferBlockId pos);
+ ~ResourceMultiBufferDataProvider() override;
+
+ // Virtual for testing purposes.
+ virtual void Start();
+
+ // MultiBuffer::DataProvider implementation
+ MultiBufferBlockId Tell() const override;
+ bool Available() const override;
+ scoped_refptr<DataBuffer> Read() override;
+ void SetDeferred(bool defer) override;
+
+ // blink::WebURLLoaderClient implementation.
+ void willFollowRedirect(
+ blink::WebURLLoader* loader,
+ blink::WebURLRequest& newRequest,
+ const blink::WebURLResponse& redirectResponse) override;
+ void didSendData(blink::WebURLLoader* loader,
+ unsigned long long bytesSent,
+ unsigned long long totalBytesToBeSent) override;
+ void didReceiveResponse(blink::WebURLLoader* loader,
+ const blink::WebURLResponse& response) override;
+ void didDownloadData(blink::WebURLLoader* loader,
+ int data_length,
+ int encoded_data_length) override;
+ void didReceiveData(blink::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) override;
+ void didReceiveCachedMetadata(blink::WebURLLoader* loader,
+ const char* data,
+ int dataLength) override;
+ void didFinishLoading(blink::WebURLLoader* loader,
+ double finishTime,
+ int64_t total_encoded_data_length) override;
+ void didFail(blink::WebURLLoader* loader, const blink::WebURLError&) override;
+
+ // Use protected instead of private for testing purposes.
+ protected:
+ friend class MultibufferDataSourceTest;
+ friend class ResourceMultiBufferDataProviderTest;
+ friend class MockBufferedDataSource;
+
+ // 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);
+
+ int64_t byte_pos() const;
+ int64_t block_size() const;
+
+ // If we have made a range request, verify the response from the server.
+ bool VerifyPartialResponse(const blink::WebURLResponse& response);
+
+ // Current Position.
+ MultiBufferBlockId pos_;
+
+ // This is where we actually get read data from.
+ // We don't need (or want) a scoped_refptr for this one, because
+ // we are owned by it. Note that we may change this when we encounter
+ // a redirect because we actually change ownership.
+ UrlData* url_data_;
+
+ // Temporary storage for incoming data.
+ std::list<scoped_refptr<DataBuffer>> fifo_;
+
+ // How many retries have we done at the current position.
+ int retries_;
+
+ // Copy of url_data_->cors_mode()
+ // const to make it obvious that redirects cannot change it.
+ const UrlData::CORSMode cors_mode_;
+
+ // The origin for the initial request.
+ // const to make it obvious that redirects cannot change it.
+ const GURL origin_;
+
+ // Keeps track of an active WebURLLoader and associated state.
+ scoped_ptr<ActiveLoader> active_loader_;
+
+ // Injected WebURLLoader instance for testing purposes.
+ scoped_ptr<blink::WebURLLoader> test_loader_;
+
+ // When we encounter a redirect, this is the source of the redirect.
+ GURL redirects_to_;
+
+ base::WeakPtrFactory<ResourceMultiBufferDataProvider> weak_factory_;
+};
+
+} // namespace media
+
+#endif // MEDIA_BLINK_RESOURCE_MULTIBUFFER_RESOURCE_LOADER_H_
diff --git a/media/blink/resource_multibuffer_data_provider_unittest.cc b/media/blink/resource_multibuffer_data_provider_unittest.cc
new file mode 100644
index 0000000..a60edeb
--- /dev/null
+++ b/media/blink/resource_multibuffer_data_provider_unittest.cc
@@ -0,0 +1,330 @@
+// Copyright 2015 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/mock_webframeclient.h"
+#include "media/blink/mock_weburlloader.h"
+#include "media/blink/resource_multibuffer_data_provider.h"
+#include "media/blink/url_index.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 {
+
+const char kHttpUrl[] = "http://test";
+const char kHttpRedirect[] = "http://test/ing";
+
+const int kDataSize = 1024;
+const int kHttpOK = 200;
+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 ResourceMultiBufferDataProviderTest : public testing::Test {
+ public:
+ ResourceMultiBufferDataProviderTest()
+ : view_(WebView::create(nullptr)),
+ frame_(WebLocalFrame::create(blink::WebTreeScopeType::Document,
+ &client_)) {
+ view_->setMainFrame(frame_);
+ url_index_.reset(new UrlIndex(frame_, 0));
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~ResourceMultiBufferDataProviderTest() {
+ view_->close();
+ frame_->close();
+ }
+
+ void Initialize(const char* url, int first_position) {
+ gurl_ = GURL(url);
+ url_data_ = url_index_->GetByUrl(gurl_, UrlData::CORS_UNSPECIFIED);
+ DCHECK(url_data_);
+ DCHECK(url_data_->frame());
+ url_data_->OnRedirect(
+ base::Bind(&ResourceMultiBufferDataProviderTest::RedirectCallback,
+ base::Unretained(this)));
+
+ first_position_ = first_position;
+
+ scoped_ptr<ResourceMultiBufferDataProvider> loader(
+ new ResourceMultiBufferDataProvider(url_data_.get(), first_position_));
+ loader_ = loader.get();
+ url_data_->multibuffer()->AddProvider(loader.Pass());
+
+ // |test_loader_| will be used when Start() is called.
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+ loader_->test_loader_ = scoped_ptr<blink::WebURLLoader>(url_loader_);
+ }
+
+ void Start() {
+ InSequence s;
+ EXPECT_CALL(*url_loader_,
+ loadAsynchronously(Truly(CorrectAcceptEncoding), loader_));
+
+ loader_->Start();
+ }
+
+ void FullResponse(int64 instance_size, bool ok = true) {
+ 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 (ok) {
+ EXPECT_EQ(instance_size, url_data_->length());
+ }
+
+ EXPECT_FALSE(url_data_->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) {
+ 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);
+
+ EXPECT_EQ(instance_size, url_data_->length());
+
+ // A valid partial response should always result in this being true.
+ EXPECT_TRUE(url_data_->range_supported());
+ }
+
+ void Redirect(const char* url) {
+ GURL redirectUrl(url);
+ blink::WebURLRequest newRequest(redirectUrl);
+ blink::WebURLResponse redirectResponse(gurl_);
+
+ EXPECT_CALL(*this, RedirectCallback(_))
+ .WillOnce(
+ Invoke(this, &ResourceMultiBufferDataProviderTest::SetUrlData));
+
+ loader_->willFollowRedirect(url_loader_, newRequest, redirectResponse);
+
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*url_loader_, cancel());
+ loader_ = nullptr;
+ url_data_ = nullptr;
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ loader_->didReceiveData(
+ url_loader_, reinterpret_cast<char*>(data_ + position), size, size);
+ }
+
+ void WriteData(int size) {
+ scoped_ptr<char[]> data(new char[size]);
+ loader_->didReceiveData(url_loader_, data.get(), size, size);
+ }
+
+ // 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));
+ }
+
+ bool HasActiveLoader() { return loader_->active_loader_; }
+ MOCK_METHOD1(RedirectCallback, void(const scoped_refptr<UrlData>&));
+
+ void SetUrlData(const scoped_refptr<UrlData>& new_url_data) {
+ url_data_ = new_url_data;
+ }
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+
+ scoped_ptr<UrlIndex> url_index_;
+ scoped_refptr<UrlData> url_data_;
+ scoped_refptr<UrlData> redirected_to_;
+ // The loader is owned by the UrlData above.
+ ResourceMultiBufferDataProvider* loader_;
+ NiceMock<MockWebURLLoader>* url_loader_;
+
+ MockWebFrameClient client_;
+ WebView* view_;
+ WebLocalFrame* frame_;
+
+ base::MessageLoop message_loop_;
+
+ uint8 data_[kDataSize];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResourceMultiBufferDataProviderTest);
+};
+
+TEST_F(ResourceMultiBufferDataProviderTest, StartStop) {
+ Initialize(kHttpUrl, 0);
+ Start();
+ StopWhenLoad();
+}
+
+// Tests that a bad HTTP response is recived, e.g. file not found.
+TEST_F(ResourceMultiBufferDataProviderTest, BadHttpResponse) {
+ Initialize(kHttpUrl, 0);
+ Start();
+
+ EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr)));
+
+ 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(ResourceMultiBufferDataProviderTest, NotPartialResponse) {
+ Initialize(kHttpUrl, 100);
+ Start();
+ FullResponse(1024, false);
+ StopWhenLoad();
+}
+
+// Tests that a 200 response is received.
+TEST_F(ResourceMultiBufferDataProviderTest, FullResponse) {
+ Initialize(kHttpUrl, 0);
+ Start();
+ FullResponse(1024);
+ StopWhenLoad();
+}
+
+// Tests that a partial content response is received.
+TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse) {
+ Initialize(kHttpUrl, 100);
+ Start();
+ PartialResponse(100, 200, 1024);
+ StopWhenLoad();
+}
+
+TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_Chunked) {
+ Initialize(kHttpUrl, 100);
+ Start();
+ PartialResponse(100, 200, 1024, true, true);
+ StopWhenLoad();
+}
+
+TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_NoAcceptRanges) {
+ Initialize(kHttpUrl, 100);
+ Start();
+ PartialResponse(100, 200, 1024, false, false);
+ StopWhenLoad();
+}
+
+TEST_F(ResourceMultiBufferDataProviderTest,
+ PartialResponse_ChunkedNoAcceptRanges) {
+ Initialize(kHttpUrl, 100);
+ Start();
+ PartialResponse(100, 200, 1024, true, false);
+ StopWhenLoad();
+}
+
+// Tests that an invalid partial response is received.
+TEST_F(ResourceMultiBufferDataProviderTest, InvalidPartialResponse) {
+ Initialize(kHttpUrl, 0);
+ Start();
+
+ EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr)));
+
+ 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();
+}
+
+TEST_F(ResourceMultiBufferDataProviderTest, TestRedirects) {
+ // Test redirect.
+ Initialize(kHttpUrl, 0);
+ Start();
+ Redirect(kHttpRedirect);
+ FullResponse(1024);
+ StopWhenLoad();
+}
+
+} // namespace media
diff --git a/media/blink/url_index.cc b/media/blink/url_index.cc
new file mode 100644
index 0000000..125c445
--- /dev/null
+++ b/media/blink/url_index.cc
@@ -0,0 +1,237 @@
+// Copyright 2015 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 <set>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "media/blink/resource_multibuffer_data_provider.h"
+#include "media/blink/url_index.h"
+
+namespace media {
+
+const int kBlockSizeShift = 15; // 1<<15 == 32kb
+const int kUrlMappingTimeoutSeconds = 300;
+
+ResourceMultiBuffer::ResourceMultiBuffer(UrlData* url_data, int block_shift)
+ : MultiBuffer(block_shift, url_data->url_index_->lru_),
+ url_data_(url_data) {}
+
+ResourceMultiBuffer::~ResourceMultiBuffer() {}
+
+scoped_ptr<MultiBuffer::DataProvider> ResourceMultiBuffer::CreateWriter(
+ const MultiBufferBlockId& pos) {
+ ResourceMultiBufferDataProvider* ret =
+ new ResourceMultiBufferDataProvider(url_data_, pos);
+ ret->Start();
+ return scoped_ptr<MultiBuffer::DataProvider>(ret);
+}
+
+bool ResourceMultiBuffer::RangeSupported() const {
+ return url_data_->range_supported_;
+}
+
+void ResourceMultiBuffer::OnEmpty() {
+ url_data_->OnEmpty();
+}
+
+UrlData::UrlData(const GURL& url,
+ CORSMode cors_mode,
+ const base::WeakPtr<UrlIndex>& url_index)
+ : url_(url),
+ cors_mode_(cors_mode),
+ url_index_(url_index),
+ length_(kPositionNotSpecified),
+ range_supported_(false),
+ cacheable_(false),
+ last_used_(),
+ multibuffer_(this, url_index_->block_shift_),
+ frame_(url_index->frame()) {}
+
+UrlData::~UrlData() {}
+
+std::pair<GURL, UrlData::CORSMode> UrlData::key() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return std::make_pair(url(), cors_mode());
+}
+
+void UrlData::set_valid_until(base::Time valid_until) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ valid_until_ = valid_until;
+}
+
+void UrlData::MergeFrom(const scoped_refptr<UrlData>& other) {
+ // We're merging from another UrlData that refers to the *same*
+ // resource, so when we merge the metadata, we can use the most
+ // optimistic values.
+ DCHECK(thread_checker_.CalledOnValidThread());
+ valid_until_ = std::max(valid_until_, other->valid_until_);
+ // set_length() will not override the length if already known.
+ set_length(other->length_);
+ cacheable_ |= other->cacheable_;
+ range_supported_ |= other->range_supported_;
+ if (last_modified_.is_null()) {
+ last_modified_ = other->last_modified_;
+ }
+ multibuffer()->MergeFrom(other->multibuffer());
+}
+
+void UrlData::set_cacheable(bool cacheable) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ cacheable_ = cacheable;
+}
+
+void UrlData::set_length(int64 length) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (length != kPositionNotSpecified) {
+ length_ = length;
+ }
+}
+
+void UrlData::RedirectTo(const scoped_refptr<UrlData>& url_data) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Copy any cached data over to the new location.
+ url_data->multibuffer()->MergeFrom(multibuffer());
+
+ std::vector<RedirectCB> redirect_callbacks;
+ redirect_callbacks.swap(redirect_callbacks_);
+ for (const RedirectCB& cb : redirect_callbacks) {
+ cb.Run(url_data);
+ }
+}
+
+void UrlData::Fail() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Handled similar to a redirect.
+ std::vector<RedirectCB> redirect_callbacks;
+ redirect_callbacks.swap(redirect_callbacks_);
+ for (const RedirectCB& cb : redirect_callbacks) {
+ cb.Run(nullptr);
+ }
+}
+
+void UrlData::OnRedirect(const RedirectCB& cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ redirect_callbacks_.push_back(cb);
+}
+
+void UrlData::Use() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ last_used_ = base::Time::Now();
+}
+
+void UrlData::OnEmpty() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&UrlIndex::RemoveUrlDataIfEmpty, url_index_,
+ scoped_refptr<UrlData>(this)));
+}
+
+bool UrlData::Valid() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::Time now = base::Time::Now();
+ if (!range_supported_)
+ return false;
+ // When ranges are not supported, we cannot re-use cached data.
+ if (valid_until_ > now)
+ return true;
+ if (now - last_used_ <
+ base::TimeDelta::FromSeconds(kUrlMappingTimeoutSeconds))
+ return true;
+ return false;
+}
+
+void UrlData::set_last_modified(base::Time last_modified) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ last_modified_ = last_modified;
+}
+
+void UrlData::set_range_supported() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ range_supported_ = true;
+}
+
+ResourceMultiBuffer* UrlData::multibuffer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return &multibuffer_;
+}
+
+size_t UrlData::CachedSize() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return multibuffer()->map().size();
+}
+
+UrlIndex::UrlIndex(blink::WebFrame* frame) : UrlIndex(frame, kBlockSizeShift) {}
+
+UrlIndex::UrlIndex(blink::WebFrame* frame, int block_shift)
+ : frame_(frame),
+ lru_(new MultiBuffer::GlobalLRU()),
+ block_shift_(block_shift),
+ weak_factory_(this) {}
+
+UrlIndex::~UrlIndex() {}
+
+void UrlIndex::RemoveUrlDataIfEmpty(const scoped_refptr<UrlData>& url_data) {
+ if (!url_data->multibuffer()->map().empty())
+ return;
+
+ auto i = by_url_.find(url_data->key());
+ if (i != by_url_.end() && i->second == url_data)
+ by_url_.erase(i);
+}
+
+scoped_refptr<UrlData> UrlIndex::GetByUrl(const GURL& gurl,
+ UrlData::CORSMode cors_mode) {
+ auto i = by_url_.find(std::make_pair(gurl, cors_mode));
+ if (i != by_url_.end() && i->second->Valid()) {
+ return i->second;
+ }
+ return NewUrlData(gurl, cors_mode);
+}
+
+scoped_refptr<UrlData> UrlIndex::NewUrlData(const GURL& url,
+ UrlData::CORSMode cors_mode) {
+ return new UrlData(url, cors_mode, weak_factory_.GetWeakPtr());
+}
+
+scoped_refptr<UrlData> UrlIndex::TryInsert(
+ const scoped_refptr<UrlData>& url_data) {
+ scoped_refptr<UrlData>* by_url_slot;
+ bool urldata_valid = url_data->Valid();
+ if (urldata_valid) {
+ by_url_slot = &by_url_.insert(std::make_pair(url_data->key(), url_data))
+ .first->second;
+ } else {
+ std::map<UrlData::KeyType, scoped_refptr<UrlData>>::iterator iter;
+ iter = by_url_.find(url_data->key());
+ if (iter == by_url_.end())
+ return url_data;
+ by_url_slot = &iter->second;
+ }
+ if (*by_url_slot == url_data)
+ return url_data;
+
+ // TODO(hubbe): Support etag validation.
+ if (!url_data->last_modified().is_null()) {
+ if ((*by_url_slot)->last_modified() != url_data->last_modified()) {
+ if (urldata_valid)
+ *by_url_slot = url_data;
+ return url_data;
+ }
+ }
+ // Check if we should replace the in-cache url data with our url data.
+ if (urldata_valid) {
+ if ((!(*by_url_slot)->Valid() ||
+ url_data->CachedSize() > (*by_url_slot)->CachedSize())) {
+ *by_url_slot = url_data;
+ } else {
+ (*by_url_slot)->MergeFrom(url_data);
+ }
+ }
+ return *by_url_slot;
+}
+
+} // namespace media
diff --git a/media/blink/url_index.h b/media/blink/url_index.h
new file mode 100644
index 0000000..56b73c2
--- /dev/null
+++ b/media/blink/url_index.h
@@ -0,0 +1,241 @@
+// Copyright 2015 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_URL_INDEX_H_
+#define MEDIA_BLINK_URL_INDEX_H_
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "media/blink/lru.h"
+#include "media/blink/media_blink_export.h"
+#include "media/blink/multibuffer.h"
+#include "third_party/WebKit/public/web/WebFrame.h"
+#include "url/gurl.h"
+
+namespace media {
+
+const int64 kPositionNotSpecified = -1;
+
+class UrlData;
+
+// A multibuffer for loading media resources which knows
+// how to create MultiBufferDataProviders to load data
+// into the cache.
+class MEDIA_BLINK_EXPORT ResourceMultiBuffer
+ : NON_EXPORTED_BASE(public MultiBuffer) {
+ public:
+ ResourceMultiBuffer(UrlData* url_data_, int block_shift);
+ ~ResourceMultiBuffer() override;
+
+ // MultiBuffer implementation.
+ scoped_ptr<MultiBuffer::DataProvider> CreateWriter(
+ const BlockId& pos) override;
+ bool RangeSupported() const override;
+ void OnEmpty() override;
+
+ protected:
+ // Do not access from destructor, it is a pointer to the
+ // object that contains us.
+ UrlData* url_data_;
+};
+
+class UrlIndex;
+
+// All the data & metadata for a single resource.
+// Data is cached using a MultiBuffer instance.
+class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> {
+ public:
+ // Keep in sync with WebMediaPlayer::CORSMode.
+ enum CORSMode { CORS_UNSPECIFIED, CORS_ANONYMOUS, CORS_USE_CREDENTIALS };
+ typedef std::pair<GURL, CORSMode> KeyType;
+
+ // Accessors
+ const GURL& url() const { return url_; }
+
+ // Cross-origin access mode
+ CORSMode cors_mode() const { return cors_mode_; }
+
+ // Are HTTP range requests supported?
+ bool range_supported() const { return range_supported_; }
+
+ // True if we found a reason why this URL won't be stored in the
+ // HTTP disk cache.
+ bool cacheable() const { return cacheable_; }
+
+ // Last used time.
+ base::Time last_used() const { return last_used_; }
+
+ // Last modified time.
+ base::Time last_modified() const { return last_modified_; }
+
+ // Expiration time.
+ base::Time valid_until() const { return valid_until_; }
+
+ // The key used by UrlIndex to find this UrlData.
+ KeyType key() const;
+
+ // Length of data associated with url or |kPositionNotSpecified|
+ int64 length() const { return length_; }
+
+ // Returns the number of blocks cached for this resource.
+ size_t CachedSize();
+
+ // Returns our url_index, or nullptr if it's been destroyed.
+ UrlIndex* url_index() const { return url_index_.get(); }
+
+ // Notifies the url index that this is currently used.
+ // The url <-> URLData mapping will be eventually be invalidated if
+ // this is not called regularly.
+ void Use();
+
+ // Setters.
+ void set_length(int64 length);
+ void set_cacheable(bool cacheable);
+ void set_valid_until(base::Time valid_until);
+ void set_range_supported();
+ void set_last_modified(base::Time last_modified);
+
+ // A redirect has occured (or we've found a better UrlData for the same
+ // resource).
+ void RedirectTo(const scoped_refptr<UrlData>& to);
+
+ // Fail, tell all clients that a failure has occured.
+ void Fail();
+
+ // Callback for receving notifications when a redirect occurs.
+ typedef base::Callback<void(const scoped_refptr<UrlData>&)> RedirectCB;
+
+ // Register a callback to be called when a redirect occurs.
+ // Callbacks are cleared when a redirect occurs, so clients must call
+ // OnRedirect again if they wish to continue receiving callbacks.
+ void OnRedirect(const RedirectCB& cb);
+
+ // Returns true it is valid to keep using this to access cached data.
+ // A single media player instance may choose to ignore this for resources
+ // that have already been opened.
+ bool Valid() const;
+
+ // Virtual so we can override it for testing.
+ virtual ResourceMultiBuffer* multibuffer();
+
+ // Accessor
+ blink::WebFrame* frame() const { return frame_; }
+
+ protected:
+ UrlData(const GURL& url,
+ CORSMode cors_mode,
+ const base::WeakPtr<UrlIndex>& url_index);
+ virtual ~UrlData();
+
+ private:
+ friend class ResourceMultiBuffer;
+ friend class UrlIndex;
+ friend class base::RefCounted<UrlData>;
+
+ void OnEmpty();
+ void MergeFrom(const scoped_refptr<UrlData>& other);
+
+ // Url we represent, note that there may be multiple UrlData for
+ // the same url.
+ const GURL url_;
+
+ // Cross-origin access mode.
+ const CORSMode cors_mode_;
+
+ base::WeakPtr<UrlIndex> url_index_;
+
+ // Length of resource this url points to. (in bytes)
+ int64 length_;
+
+ // Does the server support ranges?
+ bool range_supported_;
+
+ // Set to false if we have reason to beleive the chrome disk cache
+ // will not cache this url.
+ bool cacheable_;
+
+ // Last time some media time used this resource.
+ // Note that we use base::Time rather than base::TimeTicks because
+ // TimeTicks will stop advancing when a machine goes to sleep.
+ // base::Time can go backwards, jump hours at a time and be generally
+ // unpredictable, but it doesn't stop, which is preferable here.
+ // (False negatives are better than false positivies.)
+ base::Time last_used_;
+
+ // Expiration time according to http headers.
+ base::Time valid_until_;
+
+ // Last modification time according to http headers.
+ base::Time last_modified_;
+
+ ResourceMultiBuffer multibuffer_;
+ std::vector<RedirectCB> redirect_callbacks_;
+
+ blink::WebFrame* frame_;
+
+ base::ThreadChecker thread_checker_;
+ DISALLOW_COPY_AND_ASSIGN(UrlData);
+};
+
+// The UrlIndex lets you look up UrlData instances by url.
+class MEDIA_BLINK_EXPORT UrlIndex {
+ public:
+ explicit UrlIndex(blink::WebFrame*);
+ UrlIndex(blink::WebFrame*, int block_shift);
+ virtual ~UrlIndex();
+
+ // Look up an UrlData in the index and return it. If none is found,
+ // create a new one. Note that newly created UrlData entries are NOT
+ // added to the index, instead you must call TryInsert on them after
+ // initializing relevant parameters, like whether it support
+ // ranges and it's last modified time.
+ scoped_refptr<UrlData> GetByUrl(const GURL& gurl,
+ UrlData::CORSMode cors_mode);
+
+ // Add the given UrlData to the index if possible. If a better UrlData
+ // is already present in the index, return it instead. (If not, we just
+ // return the given UrlData.) Please make sure to initialize all the data
+ // that can be gathered from HTTP headers in |url_data| before calling this.
+ // In particular, the following fields are important:
+ // o range_supported: Entries which do not support ranges cannot be
+ // shared and are not added to the index.
+ // o valid_until, last_used: Entries have to be valid to be inserted
+ // into the index, this means that they have to have been recently
+ // used or have an Expires: header that says when they stop being valid.
+ // o last_modified: Expired cache entries can be re-used if last_modified
+ // matches.
+ // TODO(hubbe): Add etag support.
+ scoped_refptr<UrlData> TryInsert(const scoped_refptr<UrlData>& url_data);
+
+ blink::WebFrame* frame() const { return frame_; }
+
+ private:
+ friend class UrlData;
+ friend class ResourceMultiBuffer;
+ void RemoveUrlDataIfEmpty(const scoped_refptr<UrlData>& url_data);
+
+ // Virtual so we can override it in tests.
+ virtual scoped_refptr<UrlData> NewUrlData(const GURL& url,
+ UrlData::CORSMode cors_mode);
+
+ std::map<UrlData::KeyType, scoped_refptr<UrlData>> by_url_;
+ blink::WebFrame* frame_;
+ scoped_refptr<MultiBuffer::GlobalLRU> lru_;
+ bool prune_cb_active_;
+
+ // log2 of block size in multibuffer cache. Defaults to kBlockSizeShift.
+ // Currently only changed for testing purposes.
+ const int block_shift_;
+
+ protected:
+ base::WeakPtrFactory<UrlIndex> weak_factory_;
+};
+
+} // namespace media
+#endif // MEDIA_BLINK_URL_INDEX_H_
diff --git a/media/blink/url_index_unittest.cc b/media/blink/url_index_unittest.cc
new file mode 100644
index 0000000..9a309cb
--- /dev/null
+++ b/media/blink/url_index_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright 2015 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 <stdint.h>
+
+#include <list>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "media/blink/url_index.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class UrlIndexTest : public testing::Test {
+ public:
+ UrlIndexTest() : url_index_(nullptr) {}
+
+ scoped_refptr<UrlData> GetByUrl(const GURL& gurl,
+ UrlData::CORSMode cors_mode) {
+ scoped_refptr<UrlData> ret = url_index_.GetByUrl(gurl, cors_mode);
+ EXPECT_EQ(ret->url(), gurl);
+ EXPECT_EQ(ret->cors_mode(), cors_mode);
+ return ret;
+ }
+
+ UrlIndex url_index_;
+};
+
+TEST_F(UrlIndexTest, SimpleTest) {
+ GURL url1("http://foo.bar.com");
+ GURL url2("http://foo.bar.com/urgel");
+ scoped_refptr<UrlData> a = GetByUrl(url1, UrlData::CORS_UNSPECIFIED);
+ // Make sure it's valid, we still shouldn't get the same one.
+ a->Use();
+ a->set_range_supported();
+ EXPECT_TRUE(a->Valid());
+ EXPECT_EQ(a, url_index_.TryInsert(a));
+ scoped_refptr<UrlData> b = GetByUrl(url1, UrlData::CORS_ANONYMOUS);
+ b->Use();
+ b->set_range_supported();
+ EXPECT_TRUE(b->Valid());
+ EXPECT_EQ(b, url_index_.TryInsert(b));
+ scoped_refptr<UrlData> c = GetByUrl(url1, UrlData::CORS_USE_CREDENTIALS);
+ c->Use();
+ c->set_range_supported();
+ EXPECT_TRUE(c->Valid());
+ EXPECT_EQ(c, url_index_.TryInsert(c));
+ scoped_refptr<UrlData> d = GetByUrl(url2, UrlData::CORS_UNSPECIFIED);
+ d->Use();
+ d->set_range_supported();
+ EXPECT_TRUE(d->Valid());
+ EXPECT_EQ(d, url_index_.TryInsert(d));
+
+ EXPECT_NE(a, b);
+ EXPECT_NE(a, c);
+ EXPECT_NE(a, d);
+ EXPECT_NE(b, c);
+ EXPECT_NE(b, d);
+ EXPECT_NE(c, d);
+ EXPECT_EQ(a, GetByUrl(url1, UrlData::CORS_UNSPECIFIED));
+ EXPECT_EQ(b, GetByUrl(url1, UrlData::CORS_ANONYMOUS));
+ EXPECT_EQ(c, GetByUrl(url1, UrlData::CORS_USE_CREDENTIALS));
+ EXPECT_EQ(d, GetByUrl(url2, UrlData::CORS_UNSPECIFIED));
+}
+
+TEST_F(UrlIndexTest, UrlDataTest) {
+ GURL url("http://foo.bar.com");
+ scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED);
+
+ // Check default values.
+ EXPECT_FALSE(a->range_supported());
+ EXPECT_FALSE(a->cacheable());
+ EXPECT_EQ(a->length(), kPositionNotSpecified);
+ EXPECT_FALSE(a->Valid());
+
+ a->set_length(117);
+ EXPECT_EQ(a->length(), 117);
+
+ a->set_cacheable(true);
+ EXPECT_TRUE(a->cacheable());
+
+ base::Time now = base::Time::Now();
+ base::Time valid_until = now + base::TimeDelta::FromSeconds(500);
+ a->set_valid_until(valid_until);
+ a->set_range_supported();
+ EXPECT_EQ(valid_until, a->valid_until());
+ EXPECT_TRUE(a->Valid());
+
+ base::Time last_modified = now - base::TimeDelta::FromSeconds(500);
+ a->set_last_modified(last_modified);
+ EXPECT_EQ(last_modified, a->last_modified());
+}
+
+TEST_F(UrlIndexTest, UseTest) {
+ GURL url("http://foo.bar.com");
+ scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED);
+ EXPECT_FALSE(a->Valid());
+ a->Use();
+ a->set_range_supported();
+ EXPECT_TRUE(a->Valid());
+}
+
+TEST_F(UrlIndexTest, TryInsert) {
+ GURL url("http://foo.bar.com");
+ scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED);
+ scoped_refptr<UrlData> c = GetByUrl(url, UrlData::CORS_UNSPECIFIED);
+ EXPECT_NE(a, c);
+ EXPECT_FALSE(a->Valid());
+ base::Time now = base::Time::Now();
+ base::Time last_modified = now - base::TimeDelta::FromSeconds(500);
+ base::Time valid_until = now + base::TimeDelta::FromSeconds(500);
+
+ // Not sharable yet. (no ranges)
+ EXPECT_EQ(a, url_index_.TryInsert(a));
+ EXPECT_NE(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED));
+ a->set_last_modified(last_modified);
+
+ // Not sharable yet. (no ranges)
+ EXPECT_EQ(a, url_index_.TryInsert(a));
+ EXPECT_NE(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED));
+
+ // Now we should be able to insert it into the index.
+ a->set_range_supported();
+ a->set_valid_until(valid_until);
+ EXPECT_TRUE(a->Valid());
+ EXPECT_EQ(a, url_index_.TryInsert(a));
+ EXPECT_EQ(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED));
+
+ // |a| becomes expired...
+ a->set_valid_until(now - base::TimeDelta::FromSeconds(100));
+ EXPECT_FALSE(a->Valid());
+ scoped_refptr<UrlData> b = GetByUrl(url, UrlData::CORS_UNSPECIFIED);
+ EXPECT_NE(a, b);
+
+ b->set_range_supported();
+ b->set_valid_until(valid_until);
+ b->set_last_modified(last_modified);
+ EXPECT_TRUE(b->Valid());
+
+ EXPECT_EQ(b, url_index_.TryInsert(b));
+ EXPECT_EQ(b, GetByUrl(url, UrlData::CORS_UNSPECIFIED));
+
+ c->set_range_supported();
+ c->set_valid_until(valid_until);
+ c->set_last_modified(last_modified);
+ EXPECT_TRUE(c->Valid());
+
+ // B is still valid, so it should be preferred over C.
+ EXPECT_EQ(b, url_index_.TryInsert(c));
+ EXPECT_EQ(b, GetByUrl(url, UrlData::CORS_UNSPECIFIED));
+}
+
+} // namespace media