summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorvrk@google.com <vrk@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-07 19:40:44 +0000
committervrk@google.com <vrk@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-07 19:40:44 +0000
commit8c728c0d72d060bb9d8ad74c066783215b36b087 (patch)
treea7cbdb38d2d4304d1d808fd418f79b5a3edea68a /webkit
parent91f6e88fefedf3b70fbe5e73a58beabe1dfc4763 (diff)
downloadchromium_src-8c728c0d72d060bb9d8ad74c066783215b36b087.zip
chromium_src-8c728c0d72d060bb9d8ad74c066783215b36b087.tar.gz
chromium_src-8c728c0d72d060bb9d8ad74c066783215b36b087.tar.bz2
Video buffering: Caches data to disk when paused for faster buffering
Added logic to buffered data source and buffered resource loading so that media is continually cached to disk when video is paused and the in-memory buffer is full. When the buffered resource loader sees that disk caching is enabled (which is triggered by the data source when paused), it does not defer but instead writes and reads data from the seekable buffer, essentially caching the data to disk without keeping too much video in memory. Also added unit test for this behavior and made SeekableBuffer methods virtual in order to properly mock the buffer. BUG=none TEST=test_shell_tests Review URL: http://codereview.chromium.org/2824048 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@51762 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/glue/media/buffered_data_source.cc51
-rw-r--r--webkit/glue/media/buffered_data_source.h18
-rw-r--r--webkit/glue/media/buffered_data_source_unittest.cc141
3 files changed, 204 insertions, 6 deletions
diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc
index 64e09b4..1c69e36 100644
--- a/webkit/glue/media/buffered_data_source.cc
+++ b/webkit/glue/media/buffered_data_source.cc
@@ -69,7 +69,6 @@ bool IsDataProtocol(const GURL& url) {
} // namespace
namespace webkit_glue {
-
/////////////////////////////////////////////////////////////////////////////
// BufferedResourceLoader
BufferedResourceLoader::BufferedResourceLoader(
@@ -96,7 +95,8 @@ BufferedResourceLoader::BufferedResourceLoader(
read_size_(0),
read_buffer_(NULL),
first_offset_(0),
- last_offset_(0) {
+ last_offset_(0),
+ defer_allowed_(true) {
}
BufferedResourceLoader::~BufferedResourceLoader() {
@@ -225,6 +225,11 @@ int64 BufferedResourceLoader::GetBufferedLastBytePosition() {
return kPositionNotSpecified;
}
+void BufferedResourceLoader::SetAllowDefer(bool is_allowed) {
+ defer_allowed_ = is_allowed;
+ DisableDeferIfNeeded();
+}
+
/////////////////////////////////////////////////////////////////////////////
// BufferedResourceLoader,
// webkit_glue::ResourceLoaderBridge::Peer implementations
@@ -326,6 +331,15 @@ void BufferedResourceLoader::OnReceivedData(const char* data, int len) {
// If there is an active read request, try to fulfill the request.
if (HasPendingRead() && CanFulfillRead()) {
ReadInternal();
+ } else if (!defer_allowed_) {
+ // If we're not allowed to defer, slide the buffer window forward instead
+ // of deferring.
+ if (buffer_->forward_bytes() > buffer_->forward_capacity()) {
+ size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity();
+ bool success = buffer_->Seek(excess);
+ DCHECK(success);
+ offset_ += first_offset_ + excess;
+ }
}
// At last see if the buffer is full and we need to defer the downloading.
@@ -381,6 +395,9 @@ void BufferedResourceLoader::OnCompletedRequest(
/////////////////////////////////////////////////////////////////////////////
// BufferedResourceLoader, private
void BufferedResourceLoader::EnableDeferIfNeeded() {
+ if (!defer_allowed_)
+ return;
+
if (!deferred_ &&
buffer_->forward_bytes() >= buffer_->forward_capacity()) {
deferred_ = true;
@@ -394,7 +411,8 @@ void BufferedResourceLoader::EnableDeferIfNeeded() {
void BufferedResourceLoader::DisableDeferIfNeeded() {
if (deferred_ &&
- buffer_->forward_bytes() < buffer_->forward_capacity() / 2) {
+ (!defer_allowed_ ||
+ buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) {
deferred_ = false;
if (bridge_.get())
@@ -539,7 +557,8 @@ BufferedDataSource::BufferedDataSource(
intermediate_read_buffer_size_(kInitialReadBufferSize),
render_loop_(render_loop),
stop_signal_received_(false),
- stopped_on_render_loop_(false) {
+ stopped_on_render_loop_(false),
+ media_is_paused_(true) {
}
BufferedDataSource::~BufferedDataSource() {
@@ -605,6 +624,12 @@ void BufferedDataSource::Stop(media::FilterCallback* callback) {
NewRunnableMethod(this, &BufferedDataSource::CleanupTask));
}
+void BufferedDataSource::SetPlaybackRate(float playback_rate) {
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::SetPlaybackRateTask,
+ playback_rate));
+}
+
/////////////////////////////////////////////////////////////////////////////
// BufferedDataSource, media::DataSource implementation
void BufferedDataSource::Read(int64 position, size_t size, uint8* data,
@@ -730,6 +755,7 @@ void BufferedDataSource::RestartLoadingTask() {
return;
loader_ = CreateResourceLoader(read_position_, -1);
+ loader_->SetAllowDefer(!media_is_paused_);
loader_->Start(
NewCallback(this, &BufferedDataSource::PartialReadStartCallback),
NewCallback(this, &BufferedDataSource::NetworkEventCallback));
@@ -760,11 +786,28 @@ void BufferedDataSource::WatchDogTask() {
// retry the request.
loader_->Stop();
loader_ = CreateResourceLoader(read_position_, -1);
+ loader_->SetAllowDefer(!media_is_paused_);
loader_->Start(
NewCallback(this, &BufferedDataSource::PartialReadStartCallback),
NewCallback(this, &BufferedDataSource::NetworkEventCallback));
}
+void BufferedDataSource::SetPlaybackRateTask(float playback_rate) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ bool previously_paused = media_is_paused_;
+ media_is_paused_ = (playback_rate == 0.0);
+
+ // Disallow deferring data when we are pausing, allow deferring data
+ // when we resume playing.
+ if (previously_paused && !media_is_paused_) {
+ loader_->SetAllowDefer(true);
+ } else if (!previously_paused && media_is_paused_) {
+ loader_->SetAllowDefer(false);
+ }
+}
+
// This method is the place where actual read happens, |loader_| must be valid
// prior to make this method call.
void BufferedDataSource::ReadInternal() {
diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h
index 4440f2f..cc7245c 100644
--- a/webkit/glue/media/buffered_data_source.h
+++ b/webkit/glue/media/buffered_data_source.h
@@ -25,7 +25,6 @@
#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
namespace webkit_glue {
-
/////////////////////////////////////////////////////////////////////////////
// BufferedResourceLoader
// This class works inside demuxer thread and render thread. It contains a
@@ -90,6 +89,9 @@ class BufferedResourceLoader :
// is not available.
virtual int64 GetBufferedLastBytePosition();
+ // Sets whether deferring data is allowed or disallowed.
+ virtual void SetAllowDefer(bool is_allowed);
+
// Gets the content length in bytes of the instance after this loader has been
// started. If this value is -1, then content length is unknown.
virtual int64 content_length() { return content_length_; }
@@ -131,6 +133,7 @@ class BufferedResourceLoader :
virtual ~BufferedResourceLoader();
private:
+ friend class BufferedResourceLoaderTest;
// Defer the resource loading if the buffer is full.
void EnableDeferIfNeeded();
@@ -206,6 +209,9 @@ class BufferedResourceLoader :
int first_offset_;
int last_offset_;
+ // True if resource loader is allowed to defer, false otherwise.
+ bool defer_allowed_;
+
DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader);
};
@@ -231,6 +237,7 @@ class BufferedDataSource : public media::DataSource {
virtual void Initialize(const std::string& url,
media::FilterCallback* callback);
virtual void Stop(media::FilterCallback* callback);
+ virtual void SetPlaybackRate(float playback_rate);
// media::DataSource implementation.
// Called from demuxer thread.
@@ -288,6 +295,11 @@ class BufferedDataSource : public media::DataSource {
// creates a new one to accommodate the read request.
void WatchDogTask();
+ // This task uses the current playback rate with the previous playback rate
+ // to determine whether we are going from pause to play and play to pause,
+ // and signals the buffered resource loader accordingly.
+ void SetPlaybackRateTask(float playback_rate);
+
// The method that performs actual read. This method can only be executed on
// the render thread.
void ReadInternal();
@@ -393,6 +405,10 @@ class BufferedDataSource : public media::DataSource {
// the message loop doesn't hold a reference for the watch dog task.
base::RepeatingTimer<BufferedDataSource> watch_dog_timer_;
+ // This variable is true when we are in a paused state and false when we
+ // are in a playing state.
+ bool media_is_paused_;
+
DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
};
diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc
index 14e125c..90f917d 100644
--- a/webkit/glue/media/buffered_data_source_unittest.cc
+++ b/webkit/glue/media/buffered_data_source_unittest.cc
@@ -79,6 +79,11 @@ class BufferedResourceLoaderTest : public testing::Test {
EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging().spec());
}
+ void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) {
+ loader_->buffer_.reset(
+ new media::SeekableBuffer(backward_capacity, forward_capacity));
+ }
+
void Start() {
InSequence s;
EXPECT_CALL(bridge_factory_,
@@ -140,7 +145,8 @@ class BufferedResourceLoaderTest : public testing::Test {
// Helper method to write to |loader_| from |data_|.
void WriteLoader(int position, int size) {
- EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*this, NetworkCallback())
+ .RetiresOnSaturation();
loader_->OnReceivedData(reinterpret_cast<char*>(data_ + position), size);
}
@@ -155,6 +161,20 @@ class BufferedResourceLoaderTest : public testing::Test {
EXPECT_EQ(0, memcmp(buffer, data_ + pos, size));
}
+ // Helper method to disallow deferring in |loader_|.
+ void DisallowLoaderDefer() {
+ if (loader_->deferred_) {
+ EXPECT_CALL(*bridge_, SetDefersLoading(false));
+ EXPECT_CALL(*this, NetworkCallback());
+ }
+ loader_->SetAllowDefer(false);
+ }
+
+ // Helper method to allow deferring in |loader_|.
+ void AllowLoaderDefer() {
+ loader_->SetAllowDefer(true);
+ }
+
MOCK_METHOD1(StartCallback, void(int error));
MOCK_METHOD1(ReadCallback, void(int error));
MOCK_METHOD0(NetworkCallback, void());
@@ -362,6 +382,125 @@ TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
loader_->OnCompletedRequest(status, "");
}
+// Tests the logic of caching data to disk when media is paused.
+TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ // Start in undeferred state, then disallow defer, then allow defer
+ // without receiving data in between.
+ DisallowLoaderDefer();
+ AllowLoaderDefer();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in undeferred state, disallow defer, receive data but don't shift
+ // buffer window, then allow defer and read.
+ DisallowLoaderDefer();
+ WriteLoader(10, 10);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Not deferred, disallow defer, received data and shift buffer window,
+ // allow defer, then read in area outside of buffer window.
+ DisallowLoaderDefer();
+ WriteLoader(10, 10);
+ WriteLoader(20, 50);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(10, 10, buffer);
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, then disallow defer, receive no data, and
+ // allow defer and read.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 40);
+
+ DisallowLoaderDefer();
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(20, 10, buffer);
+ VerifyBuffer(buffer, 20, 10);
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, disallow defer, receive data and shift buffer
+ // window, allow defer, and read in a place that's still in the window.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 30);
+
+ DisallowLoaderDefer();
+ WriteLoader(40, 5);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(20, 10, buffer);
+ VerifyBuffer(buffer, 20, 10);
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, disallow defer, receive data and shift buffer
+ // window, allow defer, and read outside of the buffer window.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 40);
+
+ DisallowLoaderDefer();
+ WriteLoader(50, 20);
+ WriteLoader(70, 40);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(20, 5, buffer);
+}
+
// TODO(hclam): add unit test for defer loading.
class MockBufferedResourceLoader : public BufferedResourceLoader {