summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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