summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-22 21:37:17 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-22 21:37:17 +0000
commit8e296bbd2d147c270d1839ab9dd93418532322e5 (patch)
tree250887882d757ad020aba85af7db935c4107a105
parent944a14b8d46e63e603850c6c8b0d0638dc492a14 (diff)
downloadchromium_src-8e296bbd2d147c270d1839ab9dd93418532322e5.zip
chromium_src-8e296bbd2d147c270d1839ab9dd93418532322e5.tar.gz
chromium_src-8e296bbd2d147c270d1839ab9dd93418532322e5.tar.bz2
Changes to provide asynchronous read in data source:
1. FFmpegGlue now taks a FFmpegProtocol instead of DataSource as input 2. FFmpegDemuxr implements FFmpegProtocol and does the blocking read and submit asynchronous read request to DataSource (with unit tests) 3. Changed SimpleDataSource to work with asynchronous read (with unit tests) 4. Reimplemented BufferedDataSource to work with asynchronous read (with unit tests) 5. Moved BufferedDataSource from chrome/renderer/media to webkit/glue/media (for faster build/debug and better coverage in automated testing) TEST=BufferedDataSourceTest.*, SimpleDataSourceTest.*, FFmpegDemuxerTest.* Review URL: http://codereview.chromium.org/149567 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@21326 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/chrome.gyp2
-rw-r--r--chrome/renderer/media/buffered_data_source.cc721
-rw-r--r--chrome/renderer/media/buffered_data_source.h271
-rw-r--r--chrome/renderer/render_view.cc6
-rw-r--r--media/base/filters.h22
-rw-r--r--media/base/mock_filters.h5
-rw-r--r--media/base/pipeline_impl.cc6
-rw-r--r--media/filters/ffmpeg_demuxer.cc99
-rw-r--r--media/filters/ffmpeg_demuxer.h45
-rw-r--r--media/filters/ffmpeg_demuxer_unittest.cc111
-rw-r--r--media/filters/ffmpeg_glue.cc88
-rw-r--r--media/filters/ffmpeg_glue.h58
-rw-r--r--media/filters/ffmpeg_glue_unittest.cc199
-rw-r--r--media/filters/file_data_source.cc59
-rw-r--r--media/filters/file_data_source.h5
-rw-r--r--media/filters/file_data_source_unittest.cc39
-rw-r--r--webkit/glue/media/buffered_data_source.cc734
-rw-r--r--webkit/glue/media/buffered_data_source.h286
-rw-r--r--webkit/glue/media/buffered_data_source_unittest.cc627
-rw-r--r--webkit/glue/media/media_resource_loader_bridge_factory.h5
-rw-r--r--webkit/glue/media/mock_media_resource_loader_bridge_factory.h36
-rw-r--r--webkit/glue/media/simple_data_source.cc31
-rw-r--r--webkit/glue/media/simple_data_source.h6
-rw-r--r--webkit/glue/media/simple_data_source_unittest.cc246
-rw-r--r--webkit/glue/mock_resource_loader_bridge.h40
-rw-r--r--webkit/tools/test_shell/test_shell.gyp6
-rw-r--r--webkit/tools/test_shell/test_webview_delegate.cc4
-rw-r--r--webkit/webkit.gyp2
28 files changed, 2501 insertions, 1258 deletions
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index ab74ae8..8adc0f7 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -2458,8 +2458,6 @@
'renderer/loadtimes_extension_bindings.cc',
'renderer/media/audio_renderer_impl.cc',
'renderer/media/audio_renderer_impl.h',
- 'renderer/media/buffered_data_source.cc',
- 'renderer/media/buffered_data_source.h',
'renderer/net/render_dns_master.cc',
'renderer/net/render_dns_master.h',
'renderer/net/render_dns_queue.cc',
diff --git a/chrome/renderer/media/buffered_data_source.cc b/chrome/renderer/media/buffered_data_source.cc
deleted file mode 100644
index fab726f..0000000
--- a/chrome/renderer/media/buffered_data_source.cc
+++ /dev/null
@@ -1,721 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
-// source code is governed by a BSD-style license that can be found in the
-// LICENSE file.
-
-#include "base/compiler_specific.h"
-#include "base/message_loop.h"
-#include "base/process_util.h"
-#include "base/stl_util-inl.h"
-#include "base/string_util.h"
-#include "chrome/common/extensions/url_pattern.h"
-#include "chrome/renderer/media/buffered_data_source.h"
-#include "chrome/renderer/render_view.h"
-#include "media/base/filter_host.h"
-#include "net/base/load_flags.h"
-#include "net/base/net_errors.h"
-#include "net/http/http_response_headers.h"
-#include "webkit/glue/webappcachecontext.h"
-
-namespace {
-
-const char kHttpScheme[] = "http";
-const char kHttpsScheme[] = "https";
-const int64 kPositionNotSpecified = -1;
-const int kHttpOK = 200;
-const int kHttpPartialContent = 206;
-
-// Define the number of bytes in a megabyte.
-const size_t kMegabyte = 1024 * 1024;
-
-// Backward capacity of the buffer, by default 2MB.
-const size_t kBackwardCapcity = 2 * kMegabyte;
-
-// Forward capacity of the buffer, by default 10MB.
-const size_t kForwardCapacity = 10 * kMegabyte;
-
-// The threshold of bytes that we should wait until the data arrives in the
-// future instead of restarting a new connection. This number is defined in the
-// number of bytes, we should determine this value from typical connection speed
-// and amount of time for a suitable wait. Now I just make a guess for this
-// number to be 2MB.
-// TODO(hclam): determine a better value for this.
-const size_t kForwardWaitThreshold = 2 * kMegabyte;
-
-// Defines how long we should wait for more data before we declare a connection
-// timeout and start a new request.
-// TODO(hclam): set it to 5s, calibrate this value later.
-const int64 kDataTransferTimeoutSeconds = 5;
-
-// Defines how many times we should try to read from a buffered resource loader
-// before we declare a read error. After each failure of read from a buffered
-// resource loader, a new one is created to be read.
-const size_t kReadTrials = 3;
-
-// A helper method that accepts only HTTP, HTTPS and FILE protocol.
-bool IsSchemeSupported(const GURL& url) {
- return url.SchemeIs(kHttpScheme) ||
- url.SchemeIs(kHttpsScheme) ||
- url.SchemeIsFile();
-}
-
-} // namespace
-
-/////////////////////////////////////////////////////////////////////////////
-// BufferedResourceLoader
-BufferedResourceLoader::BufferedResourceLoader(
- MessageLoop* message_loop,
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory,
- const GURL& url,
- int64 first_byte_position,
- int64 last_byte_position)
- : start_callback_(NULL),
- bridge_(NULL),
- offset_(0),
- content_length_(kPositionNotSpecified),
- completion_error_(net::OK),
- buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)),
- deferred_(false),
- stopped_(false),
- completed_(false),
- range_requested_(false),
- async_start_(false),
- bridge_factory_(bridge_factory),
- url_(url),
- first_byte_position_(first_byte_position),
- last_byte_position_(last_byte_position),
- render_loop_(message_loop),
- buffer_available_(&lock_) {
-}
-
-BufferedResourceLoader::~BufferedResourceLoader() {
-}
-
-int BufferedResourceLoader::Start(net::CompletionCallback* start_callback) {
- AutoLock auto_lock(lock_);
-
- // Make sure we only start no more than once.
- DCHECK(!bridge_.get());
- DCHECK(!start_callback_.get());
- start_callback_.reset(start_callback);
-
- // Save the information that we are doing an asynchronous start since
- // start_callback_ may get reset, we can't rely on it.
- if (start_callback_.get())
- async_start_ = true;
-
- // We may receive stop signal while we are inside this method, it's because
- // Start() may get called on demuxer thread while Stop() is called on
- // pipeline thread.
- if (!stopped_) {
- render_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this, &BufferedResourceLoader::OnStart));
-
- // Wait for response to arrive if we don't have an async start.
- // TODO(hclam): implement start timeout.
- if (!async_start_)
- buffer_available_.Wait();
- }
-
- // We may have stopped because of a bad response from the server.
- if (stopped_)
- return net::ERR_ABORTED;
- else if (completed_)
- return completion_error_;
- else if (async_start_)
- return net::ERR_IO_PENDING;
- return net::OK;
-}
-
-void BufferedResourceLoader::Stop() {
- AutoLock auto_lock(lock_);
- stopped_ = true;
- buffer_.reset();
-
- // Wakes up the waiting thread so they can catch the stop signal.
- buffer_available_.Signal();
-
- render_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this, &BufferedResourceLoader::OnDestroy));
-}
-
-int BufferedResourceLoader::Read(uint8* buffer,
- size_t* bytes_read,
- int64 position,
- size_t read_size) {
- // We are given the position to read from, so we will perform a seek first.
- int error = SeekInternal(position);
- if (error != net::OK)
- return error;
-
- // Then we perform a read.
- error = ReadInternal(buffer, bytes_read, read_size);
- if (error != net::OK)
- return error;
-
- // After a read operation, determine whether or not we need to disable
- // defer loading.
- if (ShouldDisableDefer()) {
- AutoLock auto_lock(lock_);
- if (!stopped_) {
- render_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this,
- &BufferedResourceLoader::OnDisableDeferLoading));
- }
- }
-
- return net::OK;
-}
-
-int64 BufferedResourceLoader::GetOffset() {
- AutoLock auto_lock(lock_);
- return offset_;
-}
-
-/////////////////////////////////////////////////////////////////////////////
-// BufferedResourceLoader,
-// webkit_glue::ResourceLoaderBridge::Peer implementations
-void BufferedResourceLoader::OnReceivedRedirect(const GURL& new_url) {
- url_ = new_url;
-
- // If we got redirected to an unsupported protocol then stop.
- if (!IsSchemeSupported(new_url))
- Stop();
-}
-
-void BufferedResourceLoader::OnReceivedResponse(
- const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
- bool content_filtered) {
- int64 first_byte_position = -1;
- int64 last_byte_position = -1;
- int64 instance_size = -1;
-
- // The file:// protocol should be able to serve any request we want, so we
- // take an exception for file protocol.
- if (!url_.SchemeIsFile()) {
- if (!info.headers) {
- // We expect to receive headers because this is a HTTP or HTTPS protocol,
- // if not report failure.
- InvokeAndResetStartCallback(net::ERR_INVALID_RESPONSE);
- Stop();
- return;
- } else if (range_requested_) {
- if (info.headers->response_code() != kHttpPartialContent ||
- !info.headers->GetContentRange(&first_byte_position,
- &last_byte_position,
- &instance_size)) {
- // We requested a range, but server didn't reply with partial content or
- // the "Content-Range" header is corrupted.
- InvokeAndResetStartCallback(net::ERR_INVALID_RESPONSE);
- Stop();
- return;
- }
- } else if (info.headers->response_code() != kHttpOK) {
- // We didn't request a range but server didn't reply with "200 OK".
- InvokeAndResetStartCallback(net::ERR_FAILED);
- Stop();
- return;
- }
- }
-
- {
- AutoLock auto_lock(lock_);
- // |info.content_length| can be -1, in that case |content_length_| is
- // not specified and this is a streaming response.
- content_length_ = info.content_length;
- // We only care about the first byte position if it's given by the server.
- if (first_byte_position != kPositionNotSpecified)
- offset_ = first_byte_position;
-
- // If this is not an asynchronous start, signal the thread called Start().
- if (!async_start_)
- buffer_available_.Signal();
- }
-
- // If we have started asynchronously we need to invoke the callback.
- if (async_start_)
- InvokeAndResetStartCallback(net::OK);
-}
-
-void BufferedResourceLoader::OnReceivedData(const char* data, int len) {
- DCHECK(bridge_.get());
-
- AppendToBuffer(reinterpret_cast<const uint8*>(data), len);
- if (ShouldEnableDefer())
- bridge_->SetDefersLoading(true);
-}
-
-void BufferedResourceLoader::OnCompletedRequest(
- const URLRequestStatus& status, const std::string& security_info) {
- // Save the status error before we signal a the completion of the request
- // so blocked methods can pick the error and do interpretations.
- SignalComplete(status.os_error());
-
- // After the response has completed, we don't need the bridge any more.
- bridge_.reset();
-
- if (async_start_)
- InvokeAndResetStartCallback(status.os_error());
-}
-
-/////////////////////////////////////////////////////////////////////////////
-// BufferedResourceLoader, private
-int BufferedResourceLoader::ReadInternal(uint8* buffer,
- size_t* bytes_read,
- size_t read_size) {
- DCHECK(buffer);
- DCHECK(bytes_read);
-
- AutoLock auto_lock(lock_);
-
- // Return value for this method.
- int error = net::OK;
- // Ever waited for data to be downloaded.
- bool waited_for_buffer = false;
- // Total amount of bytes read from buffer.
- size_t taken = 0;
- while (taken < read_size) {
- // If stopped or the request has been completed, break the loop.
- if (stopped_) {
- error = net::ERR_ABORTED;
- break;
- }
-
- // Read into |data|.
- size_t unread_bytes = read_size - taken;
- size_t bytes_read_this_time = buffer_->Read(unread_bytes, buffer + taken);
-
- // Increment the total bytes read from the buffer so we know when to stop
- // reading.
- taken += bytes_read_this_time;
- DCHECK_LE(taken, read_size);
-
- // There are three conditions when we should terminate this loop:
- // 1. We have read enough bytes so we don't need to read anymore.
- // 2. The request has completed and the buffer is empty. We don't want to
- // wait an additional iteration, so break on condition of empty forward
- // buffer instead of breaking on failed read.
- // 3. The request has timed out. We waited for data in the last iteration
- // but there isn't any more data in the buffer.
- if (taken == read_size) {
- break;
- }
- if (completed_ && buffer_->forward_bytes() == 0) {
- // We should return the completion error received.
- error = completion_error_;
- break;
- }
- if (waited_for_buffer && bytes_read_this_time == 0) {
- error = net::ERR_TIMED_OUT;
- break;
- }
-
- buffer_available_.TimedWait(
- base::TimeDelta::FromSeconds(kDataTransferTimeoutSeconds));
- waited_for_buffer = true;
- }
-
- // Adjust the offset and disable defer loading if needed.
- if (taken > 0)
- offset_ += taken;
- *bytes_read = taken;
- return error;
-}
-
-int BufferedResourceLoader::SeekInternal(int64 position) {
- // Use of |offset_| is safe without a lock, because it's modified only in
- // Read() and this method after Start(). Read() and Seek() happens
- // on the same thread.
- if (position == offset_)
- return net::OK;
-
- // Use the conditions to avoid overflow, since |position| and |offset_| are
- // int64, their difference can be greater than an int32.
- if (position > offset_ + kint32max)
- return net::ERR_FAILED;
- else if (position < offset_ + kint32min)
- return net::ERR_FAILED;
-
- int32 offset = static_cast<int32>(position - offset_);
- // Backward data are served directly from the buffer and will not be
- // downloaded in the future so we perform a backward seek now.
- if (offset < 0) {
- AutoLock auto_lock(lock_);
- if (buffer_->Seek(offset)) {
- offset_ = position;
- return net::OK;
- }
- return net::ERR_FAILED;
- }
-
- // If we are seeking too far ahead that we'll wait too long, return false.
- // We only perform this check for forward seeking because we don't want to
- // wait too long for data very far ahead to be downloaded.
- if (position >= offset_ + kForwardWaitThreshold)
- return net::ERR_FAILED;
-
- AutoLock auto_lock(lock_);
-
- // Ever waited for data to be downloaded.
- bool waited_for_buffer = false;
- // Perform seeking forward until we get to the offset.
- while(offset_ < position) {
- // Loader has stopped.
- if (stopped_)
- return net::ERR_ABORTED;
-
- // Response completed and seek position exceeds buffered range, we won't
- // receive any more data so return immediately. We have |completion_error_|
- // from OnCompletedRequest() so return it straight.
- if (completed_ && position >= offset_ + buffer_->forward_bytes())
- return completion_error_;
-
- // Seek as much as possible until we get the desired position.
- int32 forward_seek = std::min(static_cast<int32>(buffer_->forward_bytes()),
- offset);
-
- // If we have waited for buffer in the last loop iteration and we don't
- // get any more buffer, declare a timeout situation.
- if (waited_for_buffer && !forward_seek)
- return net::ERR_TIMED_OUT;
-
- if (!buffer_->Seek(forward_seek)) {
- NOTREACHED() << "We should have enough bytes but forward seek failed";
- }
-
- // Keep track of the total amount that we should seek forward. Decrements
- // this so we will hit the end of loop condition.
- offset -= forward_seek;
-
- // Increments the offset in the whole instance.
- offset_ += forward_seek;
- DCHECK_LE(offset_, position);
-
- // Break the loop if we reached the target position so we don't wait.
- if (offset_ == position)
- break;
-
- buffer_available_.TimedWait(
- base::TimeDelta::FromSeconds(kDataTransferTimeoutSeconds));
- waited_for_buffer = true;
- }
- return net::OK;
-}
-
-void BufferedResourceLoader::AppendToBuffer(const uint8* data, size_t size) {
- AutoLock auto_lock(lock_);
- if (!stopped_)
- buffer_->Append(size, data);
- buffer_available_.Signal();
-}
-
-void BufferedResourceLoader::SignalComplete(int error) {
- AutoLock auto_lock(lock_);
- completed_ = true;
- completion_error_ = error;
- buffer_available_.Signal();
-}
-
-bool BufferedResourceLoader::ShouldEnableDefer() {
- AutoLock auto_lock(lock_);
-
- // If the resource loader has been stopped, we should not use |buffer_|.
- if (stopped_)
- return false;
-
- if (!deferred_ && buffer_->forward_bytes() >= buffer_->forward_capacity()) {
- deferred_ = true;
- return true;
- }
- return false;
-}
-
-bool BufferedResourceLoader::ShouldDisableDefer() {
- AutoLock auto_lock(lock_);
-
- // If the resource loader has been stopped, we should not use |buffer_|.
- if (stopped_)
- return false;
-
- if (deferred_ && buffer_->forward_bytes() < buffer_->forward_capacity() / 2) {
- deferred_ = false;
- return true;
- }
- return false;
-}
-
-void BufferedResourceLoader::OnStart() {
- DCHECK(MessageLoop::current() == render_loop_);
- DCHECK(!bridge_.get());
-
- AutoLock auto_lock(lock_);
- if (stopped_)
- return;
-
- if (first_byte_position_ != kPositionNotSpecified) {
- range_requested_ = true;
- // TODO(hclam): server may not support range request so |offset_| may not
- // equal to |first_byte_position_|.
- offset_ = first_byte_position_;
- }
-
- // Creates the bridge on render thread since we can only access
- // ResourceDispatcher on this thread.
- bridge_.reset(bridge_factory_->CreateBridge(url_,
- net::LOAD_BYPASS_CACHE,
- first_byte_position_,
- last_byte_position_));
-
- // And start the resource loading.
- bridge_->Start(this);
-}
-
-void BufferedResourceLoader::OnDestroy() {
- DCHECK(MessageLoop::current() == render_loop_);
- if (bridge_.get()) {
- // Cancel the resource request.
- bridge_->Cancel();
- bridge_.reset();
- }
-}
-
-void BufferedResourceLoader::OnEnableDeferLoading() {
- DCHECK(MessageLoop::current() == render_loop_);
- // This message may arrive after the bridge is destroyed.
- if (bridge_.get())
- bridge_->SetDefersLoading(true);
-}
-
-void BufferedResourceLoader::OnDisableDeferLoading() {
- DCHECK(MessageLoop::current() == render_loop_);
- // This message may arrive after the bridge is destroyed.
- if (bridge_.get())
- bridge_->SetDefersLoading(false);
-}
-
-void BufferedResourceLoader::InvokeAndResetStartCallback(int error) {
- AutoLock auto_lock(lock_);
- if (start_callback_.get()) {
- start_callback_->Run(error);
- start_callback_.reset();
- }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// BufferedDataSource
-BufferedDataSource::BufferedDataSource(
- MessageLoop* render_loop,
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory)
- : stopped_(false),
- position_(0),
- total_bytes_(kPositionNotSpecified),
- bridge_factory_(bridge_factory),
- buffered_resource_loader_(NULL),
- render_loop_(render_loop),
- pipeline_loop_(MessageLoop::current()) {
-}
-
-BufferedDataSource::~BufferedDataSource() {
-}
-
-void BufferedDataSource::Stop() {
- scoped_refptr<BufferedResourceLoader> resource_loader = NULL;
- // Set the stop signal first.
- {
- AutoLock auto_lock(lock_);
- stopped_ = true;
- resource_loader = buffered_resource_loader_;
- // Release the reference to the resource loader.
- buffered_resource_loader_ = NULL;
- }
- // Tell the loader to stop.
- if (resource_loader)
- resource_loader->Stop();
-}
-
-void BufferedDataSource::Initialize(const std::string& url,
- media::FilterCallback* callback) {
- DCHECK(callback);
- initialize_callback_.reset(callback);
-
- // Save the url.
- url_ = GURL(url);
-
- // Make sure we support the scheme of the URL.
- if (!IsSchemeSupported(url_)) {
- host()->SetError(media::PIPELINE_ERROR_NETWORK);
- initialize_callback_->Run();
- initialize_callback_.reset();
- return;
- }
-
- media_format_.SetAsString(media::MediaFormat::kMimeType,
- media::mime_type::kApplicationOctetStream);
- media_format_.SetAsString(media::MediaFormat::kURL, url);
-
- // Setup the BufferedResourceLoader here.
- scoped_refptr<BufferedResourceLoader> resource_loader = NULL;
- {
- AutoLock auto_lock(lock_);
- if (!stopped_) {
- buffered_resource_loader_ = new BufferedResourceLoader(
- render_loop_,
- bridge_factory_.get(),
- url_,
- kPositionNotSpecified,
- kPositionNotSpecified);
- resource_loader = buffered_resource_loader_;
- }
- }
-
- // Use the local reference to start the request.
- if (!resource_loader) {
- host()->SetError(media::PIPELINE_ERROR_NETWORK);
- initialize_callback_->Run();
- initialize_callback_.reset();
- return;
- }
-
- if (net::ERR_IO_PENDING != resource_loader->Start(
- NewCallback(this, &BufferedDataSource::InitialRequestStarted))) {
- host()->SetError(media::PIPELINE_ERROR_NETWORK);
- initialize_callback_->Run();
- initialize_callback_.reset();
- }
-}
-
-size_t BufferedDataSource::Read(uint8* data, size_t size) {
- // We try two times here:
- // 1. Use the existing resource loader to seek and read from it.
- // 2. If any of the above operations failed, we create a new resource loader
- // starting with a new range. Goto 1.
- for (size_t trials = kReadTrials; trials > 0; --trials) {
- scoped_refptr<BufferedResourceLoader> resource_loader = NULL;
- {
- AutoLock auto_lock(lock_);
- resource_loader = buffered_resource_loader_;
- }
-
- size_t read = 0;
- int error = net::ERR_FAILED;
- if (resource_loader)
- error = resource_loader->Read(data, &read, position_, size);
-
- if (error == net::OK) {
- if (read >= 0) {
- position_ += read;
- return read;
- } else {
- return DataSource::kReadError;
- }
- } else {
- // We don't need the old BufferedResourceLoader, stop it and release the
- // reference.
- if (resource_loader) {
- resource_loader->Stop();
- resource_loader = NULL;
- }
-
- // Create a new request if we have not stopped.
- {
- AutoLock auto_lock(lock_);
- if (stopped_) {
- buffered_resource_loader_ = NULL;
- return DataSource::kReadError;
- }
-
- // Create a new resource loader.
- buffered_resource_loader_ =
- new BufferedResourceLoader(render_loop_,
- bridge_factory_.get(),
- url_,
- position_,
- kPositionNotSpecified);
- // Save the local copy.
- resource_loader = buffered_resource_loader_;
- }
-
- // Start the new resource loader.
- DCHECK(resource_loader);
- int error = resource_loader->Start(NULL);
-
- // Always fail if we can't start.
- // TODO(hclam): should handle timeout.
- if (error != net::OK) {
- HandleError(media::PIPELINE_ERROR_NETWORK);
- return DataSource::kReadError;
- }
- }
- }
- return DataSource::kReadError;
-}
-
-bool BufferedDataSource::GetPosition(int64* position_out) {
- *position_out = position_;
- return true;
-}
-
-bool BufferedDataSource::SetPosition(int64 position) {
- // |total_bytes_| can be -1 for pure streaming. There may be a problem with
- // seeking for this case.
- if (position < total_bytes_) {
- position_ = position;
- return true;
- }
- return false;
-}
-
-bool BufferedDataSource::GetSize(int64* size_out) {
- if (total_bytes_ != kPositionNotSpecified) {
- *size_out = total_bytes_;
- return true;
- }
- *size_out = 0;
- return false;
-}
-
-bool BufferedDataSource::IsSeekable() {
- return total_bytes_ != kPositionNotSpecified;
-}
-
-void BufferedDataSource::HandleError(media::PipelineError error) {
- AutoLock auto_lock(lock_);
- if (!stopped_) {
- host()->SetError(error);
- }
-}
-
-void BufferedDataSource::InitialRequestStarted(int error) {
- // Don't take any lock and call to |host_| here, this method is called from
- // BufferedResourceLoader after the response has started or failed, it is
- // very likely we are called within a lock in BufferedResourceLoader.
- // Acquiring an additional lock here we might have a deadlock situation,
- // but one thing very sure is that pipeline thread is still alive, so we
- // just need to post a task on that thread.
- pipeline_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this,
- &BufferedDataSource::OnInitialRequestStarted, error));
-}
-
-void BufferedDataSource::OnInitialRequestStarted(int error) {
- // Acquiring a lock should not be needed because |stopped_| is only written
- // on pipeline thread and we are on pipeline thread but just to be safe.
- AutoLock auto_lock(lock_);
- if (!stopped_) {
- if (error == net::OK) {
- total_bytes_ = buffered_resource_loader_->content_length();
- if (IsSeekable()) {
- host()->SetTotalBytes(total_bytes_);
- // TODO(hclam): report the amount of bytes buffered accurately.
- host()->SetBufferedBytes(total_bytes_);
- }
- } else {
- host()->SetError(media::PIPELINE_ERROR_NETWORK);
- }
- }
- initialize_callback_->Run();
- initialize_callback_.reset();
-}
-
-const media::MediaFormat& BufferedDataSource::media_format() {
- return media_format_;
-}
diff --git a/chrome/renderer/media/buffered_data_source.h b/chrome/renderer/media/buffered_data_source.h
deleted file mode 100644
index d7fa080..0000000
--- a/chrome/renderer/media/buffered_data_source.h
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright (c) 2009 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 CHROME_RENDERER_MEDIA_BUFFERED_DATA_SOURCE_H_
-#define CHROME_RENDERER_MEDIA_BUFFERED_DATA_SOURCE_H_
-
-#include <string>
-
-#include "base/lock.h"
-#include "base/scoped_ptr.h"
-#include "base/condition_variable.h"
-#include "googleurl/src/gurl.h"
-#include "media/base/factory.h"
-#include "media/base/filters.h"
-#include "media/base/media_format.h"
-#include "media/base/pipeline.h"
-#include "media/base/seekable_buffer.h"
-#include "net/base/completion_callback.h"
-#include "net/base/file_stream.h"
-#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
-
-/////////////////////////////////////////////////////////////////////////////
-// BufferedResourceLoader
-// This class works inside demuxer thread and render thread. It contains a
-// resource loader bridge and does the actual resource loading. This object
-// does buffering internally, it defers the resource loading if buffer is
-// full and un-defers the resource loading if it is under buffered.
-class BufferedResourceLoader :
- public base::RefCountedThreadSafe<BufferedResourceLoader>,
- public webkit_glue::ResourceLoaderBridge::Peer {
- public:
- // |message_loop| - The message loop this resource loader should run on.
- // |bridge_factory| - Factory to create a ResourceLoaderBridge.
- // |url| - URL for the resource to be loaded.
- // |first_byte_position| - First byte to start loading from, -1 for not
- // specified.
- // |last_byte_position| - Last byte to be loaded, -1 for not specified.
- BufferedResourceLoader(
- MessageLoop* message_loop,
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory,
- const GURL& url,
- int64 first_byte_position,
- int64 last_byte_position);
- virtual ~BufferedResourceLoader();
-
- // Start the resource loading with the specified URL and range.
- // This method call can operate in two modes, synchronous and asynchronous.
- // If |start_callback| is NULL, this method operates in synchronous mode. It
- // has the following return values in this mode:
- // net::OK
- // The load has started successfully.
- // net::ERR_FAILED
- // The load cannot be started.
- // net::ERR_TIMEOUT
- // The start operation took too long and has timed out.
- //
- // If |start_callback| is not NULL, this method operates in asynchronous mode
- // and it returns net::ERR_IO_PENDING if the request is going to start.
- // Once there's a response from the server, success or fail |start_callback|
- // is called with the result.
- // Note that |start_callback| is called within a lock to prevent invoking an
- // invalid callback method while this object is called to stop.
- int Start(net::CompletionCallback* callback);
-
- // Stops this loader. Wakes up all synchronous actions.
- void Stop();
-
- // Reads the specified |read_size| from |position| into |buffer| and returns
- // number of bytes read into variable |bytes_read|. This method call is
- // synchronous, when it returns it may not be able to fulfill what has been
- // requested.
- //
- // Produces the following return values:
- // net::OK
- // The read operation was successful. |bytes_read| may be less than
- // |read_size| even if the request was successful, which happens when the
- // request has completed normally but there wasn't enough bytes to serve
- // the request.
- // net::ERR_TIMEOUT
- // The read operation has timed out and we didn't get enough bytes for what
- // we requested.
- // net::ERR_FAILED
- // The read operation has failed because the request completed abnormally
- // or |position| is too far from the current position of this loader that
- // we cannot serve.
- // net::ERR_ABORTED
- // The loader has been stopped.
- int Read(uint8* buffer, size_t* bytes_read, int64 position, size_t read_size);
-
- // Returns the position in bytes that this loader is downloading from.
- int64 GetOffset();
-
- // Gets the content length in bytes of the instance after this loader has been
- // started.
- int64 content_length() { return content_length_; }
-
- /////////////////////////////////////////////////////////////////////////////
- // webkit_glue::ResourceLoaderBridge::Peer implementations.
- virtual void OnUploadProgress(uint64 position, uint64 size) {}
- virtual void OnReceivedRedirect(const GURL& new_url);
- virtual void OnReceivedResponse(
- const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
- bool content_filtered);
- virtual void OnReceivedData(const char* data, int len);
- virtual void OnCompletedRequest(const URLRequestStatus& status,
- const std::string& security_info);
- std::string GetURLForDebugging() { return url_.spec(); }
-
- private:
- // Reads the specified |read_size| into |buffer| and returns number of bytes
- // read into variable |bytes_read|. This method call is synchronous, when it
- // returns it may not be able to fulfill what has been requested.
- //
- // Produces the following return values:
- // net::OK
- // The read operation was successful. |bytes_read| may be less than
- // |read_size| even if the request was successful, this happens when the
- // request has completed normally but there isn't enough bytes to serve
- // the request.
- // net::ERR_TIMEOUT
- // The read operation has timed out and we didn't get enough bytes for what
- // we have requested.
- // net::ERR_FAILED
- // The read operation has failed because the request was completed
- // abnormally.
- // net::ERR_ABORTED
- // The loader has been stopped.
- int ReadInternal(uint8* buffer, size_t* bytes_read, size_t read_size);
-
- // Seek to |position| in bytes in the entire instance of the media
- // object. This method call is synchronous. It has the following return
- // values:
- //
- // net::OK
- // The seek operation was successful.
- // net::ERR_FAILED
- // The desired |position| is too far from the current offset, and we decided
- // not to serve the |position| either because we don't want to wait too long
- // for data to be downloaded or |position| was too far in the past that we
- // don't have the data buffered.
- // We may get this error if the request has completed abnormally.
- // net::ERR_TIMEOUT
- // The seek operation took too long and timed out.
- // net::ERR_ABORTED
- // The loader has been stopped.
- int SeekInternal(int64 position);
-
- // Append buffer to the queue of buffers.
- void AppendToBuffer(const uint8* buffer, size_t size);
-
- void SignalComplete(int error);
- bool ShouldEnableDefer();
- bool ShouldDisableDefer();
-
- void OnStart();
- void OnDestroy();
- void OnDisableDeferLoading();
- void OnEnableDeferLoading();
-
- void InvokeAndResetStartCallback(int error);
-
- scoped_ptr<net::CompletionCallback> start_callback_;
- scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_;
- int64 offset_;
- int64 content_length_;
- int completion_error_;
-
- scoped_ptr<media::SeekableBuffer> buffer_;
-
- bool deferred_;
- bool stopped_;
- bool completed_;
- bool range_requested_;
- bool async_start_;
-
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_;
- GURL url_;
- int64 first_byte_position_;
- int64 last_byte_position_;
-
- MessageLoop* render_loop_;
- // A lock that protects usage of the following members:
- // - buffer_
- // - deferred_
- // - stopped_
- // - completed_
- Lock lock_;
- ConditionVariable buffer_available_;
-
- DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader);
-};
-
-class BufferedDataSource : public media::DataSource {
- public:
- // Methods called from pipeline thread
- // Static methods for creating this class.
- static media::FilterFactory* CreateFactory(
- MessageLoop* message_loop,
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) {
- return new media::FilterFactoryImpl2<
- BufferedDataSource,
- MessageLoop*,
- webkit_glue::MediaResourceLoaderBridgeFactory*>(
- message_loop, bridge_factory);
- }
- virtual void Initialize(const std::string& url,
- media::FilterCallback* callback);
-
- // media::MediaFilter implementation.
- virtual void Stop();
-
- // media::DataSource implementation.
- // Called from demuxer thread.
- virtual size_t Read(uint8* data, size_t size);
- virtual bool GetPosition(int64* position_out);
- virtual bool SetPosition(int64 position);
- virtual bool GetSize(int64* size_out);
- virtual bool IsSeekable();
-
- const media::MediaFormat& media_format();
-
- private:
- friend class media::FilterFactoryImpl2<
- BufferedDataSource,
- MessageLoop*,
- webkit_glue::MediaResourceLoaderBridgeFactory*>;
- // Call to filter host to trigger an error, be sure not to call this method
- // while the lock is acquired.
- void HandleError(media::PipelineError error);
-
- // Callback method from BufferedResourceLoader for the initial url request.
- // |error| is net::OK if the request has started successfully or |error| is
- // a code representing the actual network error.
- void InitialRequestStarted(int error);
- void OnInitialRequestStarted(int error);
-
- explicit BufferedDataSource(
- MessageLoop* render_loop,
- webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory);
- virtual ~BufferedDataSource();
-
- media::MediaFormat media_format_;
- GURL url_;
-
- // A common lock for protecting members accessed by multiple threads.
- Lock lock_;
- bool stopped_;
-
- // Members used for reading.
- int64 position_;
- // Members for total bytes of the requested object.
- int64 total_bytes_;
-
- // Members related to resource loading with RenderView.
- scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_;
- scoped_refptr<BufferedResourceLoader> buffered_resource_loader_;
-
- // The message loop of the render thread.
- MessageLoop* render_loop_;
-
- // The message loop of the pipeline thread.
- MessageLoop* pipeline_loop_;
-
- // Filter callbacks.
- scoped_ptr<media::FilterCallback> initialize_callback_;
-
- DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
-};
-
-#endif // CHROME_RENDERER_MEDIA_BUFFERED_DATA_SOURCE_H_
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index 34a415f..21c40e2 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -41,7 +41,6 @@
#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/renderer/localized_error.h"
#include "chrome/renderer/media/audio_renderer_impl.h"
-#include "chrome/renderer/media/buffered_data_source.h"
#include "chrome/renderer/navigation_state.h"
#include "chrome/renderer/print_web_view_helper.h"
#include "chrome/renderer/render_process.h"
@@ -74,6 +73,7 @@
#include "webkit/glue/dom_operations.h"
#include "webkit/glue/dom_serializer.h"
#include "webkit/glue/image_decoder.h"
+#include "webkit/glue/media/buffered_data_source.h"
#include "webkit/glue/media/simple_data_source.h"
#include "webkit/glue/password_form.h"
#include "webkit/glue/plugins/plugin_list.h"
@@ -1859,8 +1859,8 @@ WebKit::WebMediaPlayer* RenderView::CreateWebMediaPlayer(
if (!cmd_line->HasSwitch(switches::kSimpleDataSource)) {
// Add the chrome specific media data source.
factory->AddFactory(
- BufferedDataSource::CreateFactory(MessageLoop::current(),
- bridge_factory));
+ webkit_glue::BufferedDataSource::CreateFactory(MessageLoop::current(),
+ bridge_factory));
} else {
factory->AddFactory(
webkit_glue::SimpleDataSource::CreateFactory(MessageLoop::current(),
diff --git a/media/base/filters.h b/media/base/filters.h
index 84df0966..3029cbc 100644
--- a/media/base/filters.h
+++ b/media/base/filters.h
@@ -123,6 +123,9 @@ class MediaFilter : public base::RefCountedThreadSafe<MediaFilter> {
class DataSource : public MediaFilter {
public:
+ typedef Callback1<size_t>::Type ReadCallback;
+ static const size_t kReadError = static_cast<size_t>(-1);
+
static const FilterType filter_type() {
return FILTER_DATA_SOURCE;
}
@@ -133,8 +136,6 @@ class DataSource : public MediaFilter {
mime_type == mime_type::kURL);
}
- static const size_t kReadError = static_cast<size_t>(-1);
-
// Initialize a DataSource for the given URL, executing the callback upon
// completion.
virtual void Initialize(const std::string& url, FilterCallback* callback) = 0;
@@ -142,16 +143,13 @@ class DataSource : public MediaFilter {
// Returns the MediaFormat for this filter.
virtual const MediaFormat& media_format() = 0;
- // Read the given amount of bytes into data, returns the number of bytes read
- // if successful, kReadError otherwise.
- virtual size_t Read(uint8* data, size_t size) = 0;
-
- // Returns true and the current file position for this file, false if the
- // file position could not be retrieved.
- virtual bool GetPosition(int64* position_out) = 0;
-
- // Returns true if the file position could be set, false otherwise.
- virtual bool SetPosition(int64 position) = 0;
+ // Reads |size| bytes from |position| into |data|. And when the read is done
+ // or failed, |read_callback| is called with the number of bytes read or
+ // kReadError in case of error.
+ // TODO(hclam): should change |size| to int! It makes the code so messy
+ // with size_t and int all over the place..
+ virtual void Read(int64 position, size_t size,
+ uint8* data, ReadCallback* read_callback) = 0;
// Returns true and the file size, false if the file size could not be
// retrieved.
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index 8c44996..c933bd2 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -99,9 +99,8 @@ class MockDataSource : public DataSource {
MOCK_METHOD2(Initialize, void(const std::string& url,
FilterCallback* callback));
const MediaFormat& media_format() { return media_format_; }
- MOCK_METHOD2(Read, size_t(uint8* data, size_t size));
- MOCK_METHOD1(GetPosition, bool(int64* position_out));
- MOCK_METHOD1(SetPosition, bool(int64 position));
+ MOCK_METHOD4(Read, void(int64 position, size_t size, uint8* data,
+ DataSource::ReadCallback* callback));
MOCK_METHOD1(GetSize, bool(int64* size_out));
MOCK_METHOD0(IsSeekable, bool());
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index 9c2fb10..7fc4695 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -524,7 +524,11 @@ void PipelineInternal::ErrorTask(PipelineError error) {
DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
// Suppress executing additional error logic.
- if (state_ == kError) {
+ // TODO(hclam): Remove the condition for kStopped. It is there only because
+ // FFmpegDemuxer submits a read error while reading after it is called to
+ // stop. After FFmpegDemuxer is cleaned up we should remove this condition
+ // and add an extra assert.
+ if (state_ == kError || state_ == kStopped) {
return;
}
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index bf595e2..60c6fcd 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -215,7 +215,11 @@ base::TimeDelta FFmpegDemuxerStream::ConvertTimestamp(int64 timestamp) {
// FFmpegDemuxer
//
FFmpegDemuxer::FFmpegDemuxer()
- : format_context_(NULL) {
+ : format_context_(NULL),
+ read_event_(false, false),
+ read_has_failed_(false),
+ last_read_bytes_(0),
+ read_position_(0) {
}
FFmpegDemuxer::~FFmpegDemuxer() {
@@ -259,6 +263,9 @@ void FFmpegDemuxer::Stop() {
// Post a task to notify the streams to stop as well.
message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this, &FFmpegDemuxer::StopTask));
+
+ // Then wakes up the thread from reading.
+ SignalReadCompleted(DataSource::kReadError);
}
void FFmpegDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) {
@@ -273,7 +280,7 @@ void FFmpegDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) {
void FFmpegDemuxer::Initialize(DataSource* data_source,
FilterCallback* callback) {
message_loop()->PostTask(FROM_HERE,
- NewRunnableMethod(this, &FFmpegDemuxer::InititalizeTask, data_source,
+ NewRunnableMethod(this, &FFmpegDemuxer::InitializeTask, data_source,
callback));
}
@@ -287,30 +294,76 @@ scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(int stream) {
return streams_[stream].get();
}
-void FFmpegDemuxer::InititalizeTask(DataSource* data_source,
- FilterCallback* callback) {
+int FFmpegDemuxer::Read(int size, uint8* data) {
+ DCHECK(data_source_);
+
+ // If read has ever failed, return with an error.
+ // TODO(hclam): use a more meaningful constant as error.
+ if (read_has_failed_)
+ return AVERROR_IO;
+
+ // Asynchronous read from data source.
+ data_source_->Read(read_position_, size, data,
+ NewCallback(this, &FFmpegDemuxer::OnReadCompleted));
+
+ // TODO(hclam): The method is called on the demuxer thread and this method
+ // call will block the thread. We need to implemented an additional thread to
+ // let FFmpeg demuxer methods to run on.
+ size_t last_read_bytes = WaitForRead();
+ if (last_read_bytes == DataSource::kReadError) {
+ host()->SetError(PIPELINE_ERROR_READ);
+
+ // Returns with a negative number to signal an error to FFmpeg.
+ read_has_failed_ = true;
+ return AVERROR_IO;
+ }
+ read_position_ += last_read_bytes;
+ return last_read_bytes;
+}
+
+bool FFmpegDemuxer::GetPosition(int64* position_out) {
+ *position_out = read_position_;
+ return true;
+}
+
+bool FFmpegDemuxer::SetPosition(int64 position) {
+ DCHECK(data_source_);
+
+ int64 file_size;
+ if (!data_source_->GetSize(&file_size) || position >= file_size)
+ return false;
+
+ read_position_ = position;
+ return true;
+}
+
+bool FFmpegDemuxer::GetSize(int64* size_out) {
+ DCHECK(data_source_);
+
+ return data_source_->GetSize(size_out);
+}
+
+bool FFmpegDemuxer::IsStreamed() {
+ return false;
+}
+
+void FFmpegDemuxer::InitializeTask(DataSource* data_source,
+ FilterCallback* callback) {
DCHECK_EQ(MessageLoop::current(), message_loop());
scoped_ptr<FilterCallback> c(callback);
- // In order to get FFmpeg to use |data_source| for file IO we must transfer
- // ownership via FFmpegGlue. We'll add |data_source| to FFmpegGlue and pass
- // the resulting key to FFmpeg. FFmpeg will pass the key to FFmpegGlue which
- // will take care of attaching |data_source| to an FFmpeg context. After
- // we finish initializing the FFmpeg context we can remove |data_source| from
- // FFmpegGlue.
- //
- // Refer to media/filters/ffmpeg_glue.h for details.
+ data_source_ = data_source;
- // Add our data source and get our unique key.
- std::string key = FFmpegGlue::get()->AddDataSource(data_source);
+ // Add ourself to Protocol list and get our unique key.
+ std::string key = FFmpegGlue::get()->AddProtocol(this);
// Open FFmpeg AVFormatContext.
DCHECK(!format_context_);
AVFormatContext* context = NULL;
int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL);
- // Remove our data source.
- FFmpegGlue::get()->RemoveDataSource(data_source);
+ // Remove ourself from protocol list.
+ FFmpegGlue::get()->RemoveProtocol(this);
if (result < 0) {
host()->SetError(DEMUXER_ERROR_COULD_NOT_OPEN);
@@ -473,4 +526,18 @@ void FFmpegDemuxer::StreamHasEnded() {
}
}
+void FFmpegDemuxer::OnReadCompleted(size_t size) {
+ SignalReadCompleted(size);
+}
+
+size_t FFmpegDemuxer::WaitForRead() {
+ read_event_.Wait();
+ return last_read_bytes_;
+}
+
+void FFmpegDemuxer::SignalReadCompleted(size_t size) {
+ last_read_bytes_ = size;
+ read_event_.Signal();
+}
+
} // namespace media
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index 20b77e4..ebd1274 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -25,11 +25,14 @@
#include <deque>
#include <vector>
+#include "base/waitable_event.h"
#include "media/base/buffers.h"
#include "media/base/factory.h"
#include "media/base/filters.h"
#include "media/base/media_format.h"
+#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_interfaces.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
// FFmpeg forward declarations.
struct AVCodecContext;
@@ -108,7 +111,8 @@ class FFmpegDemuxerStream : public DemuxerStream, public AVStreamProvider {
DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerStream);
};
-class FFmpegDemuxer : public Demuxer {
+class FFmpegDemuxer : public Demuxer,
+ public FFmpegURLProtocol {
public:
// FilterFactory provider.
static FilterFactory* CreateFilterFactory() {
@@ -127,14 +131,24 @@ class FFmpegDemuxer : public Demuxer {
virtual size_t GetNumberOfStreams();
virtual scoped_refptr<DemuxerStream> GetStream(int stream_id);
+ // FFmpegProtocol implementation.
+ virtual int Read(int size, uint8* data);
+ virtual bool GetPosition(int64* position_out);
+ virtual bool SetPosition(int64 position);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsStreamed();
+
private:
// Only allow a factory to create this class.
friend class FilterFactoryImpl0<FFmpegDemuxer>;
+ friend class MockFFmpegDemuxer;
+ FRIEND_TEST(FFmpegDemuxerTest, ProtocolRead);
+
FFmpegDemuxer();
virtual ~FFmpegDemuxer();
// Carries out initialization on the demuxer thread.
- void InititalizeTask(DataSource* data_source, FilterCallback* callback);
+ void InitializeTask(DataSource* data_source, FilterCallback* callback);
// Carries out a seek on the demuxer thread.
void SeekTask(base::TimeDelta time, FilterCallback* callback);
@@ -157,6 +171,17 @@ class FFmpegDemuxer : public Demuxer {
// Must be called on the demuxer thread.
void StreamHasEnded();
+ // Read callback method to be passed to DataSource. When the asynchronous
+ // read has completed, this method will be called from DataSource with
+ // number of bytes read or kDataSource in case of error.
+ void OnReadCompleted(size_t size);
+
+ // Wait for asynchronous read to complete and return number of bytes read.
+ virtual size_t WaitForRead();
+
+ // Signal that read has completed, and |size| bytes have been read.
+ virtual void SignalReadCompleted(size_t size);
+
// FFmpeg context handle.
AVFormatContext* format_context_;
@@ -178,6 +203,22 @@ class FFmpegDemuxer : public Demuxer {
StreamVector streams_;
StreamVector packet_streams_;
+ // Reference to the data source. Asynchronous read requests are submitted to
+ // this object.
+ scoped_refptr<DataSource> data_source_;
+
+ // This member is used to block on read method calls from FFmpeg and wait
+ // until the asynchronous reads in the data source to complete. It is also
+ // signaled when the demuxer is being stopped.
+ base::WaitableEvent read_event_;
+
+ // Flag to indicate if read has ever failed. Once set to true, it will
+ // never be reset. This flag is set true and accessed in Read().
+ bool read_has_failed_;
+
+ size_t last_read_bytes_;
+ int64 read_position_;
+
DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxer);
};
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index 6f05004..08580da 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -4,6 +4,7 @@
#include <deque>
+#include "base/thread.h"
#include "media/base/filters.h"
#include "media/base/mock_ffmpeg.h"
#include "media/base/mock_filter_host.h"
@@ -16,9 +17,12 @@
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgumentPointee;
using ::testing::StrictMock;
+using ::testing::WithArgs;
namespace media {
@@ -76,7 +80,7 @@ class FFmpegDemuxerTest : public testing::Test {
memset(&streams_, 0, sizeof(streams_));
memset(&codecs_, 0, sizeof(codecs_));
- // Initialize AVCodexContext structures.
+ // Initialize AVCodecContext structures.
codecs_[AV_STREAM_DATA].codec_type = CODEC_TYPE_DATA;
codecs_[AV_STREAM_DATA].codec_id = CODEC_ID_NONE;
@@ -662,4 +666,109 @@ TEST_F(FFmpegDemuxerTest, Stop) {
MockFFmpeg::get()->CheckPoint(1);
}
+class MockFFmpegDemuxer : public FFmpegDemuxer {
+ public:
+ MockFFmpegDemuxer() {}
+ virtual ~MockFFmpegDemuxer() {}
+
+ MOCK_METHOD0(WaitForRead, size_t());
+ MOCK_METHOD1(SignalReadCompleted, void(size_t size));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockFFmpegDemuxer);
+};
+
+// A gmock helper method to execute the callback and deletes it.
+void RunCallback(size_t size, DataSource::ReadCallback* callback) {
+ DCHECK(callback);
+ callback->RunWithParams(Tuple1<size_t>(size));
+ delete callback;
+}
+
+TEST_F(FFmpegDemuxerTest, ProtocolRead) {
+ // Creates a demuxer.
+ scoped_refptr<MockFFmpegDemuxer> demuxer = new MockFFmpegDemuxer();
+ ASSERT_TRUE(demuxer);
+ demuxer->set_host(&host_);
+ demuxer->set_message_loop(&message_loop_);
+ demuxer->data_source_ = data_source_;
+
+ uint8 kBuffer[1];
+ InSequence s;
+ // Actions taken in the first read.
+ EXPECT_CALL(*data_source_, Read(0, 512, kBuffer, NotNull()))
+ .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback)));
+ EXPECT_CALL(*demuxer, SignalReadCompleted(512));
+ EXPECT_CALL(*demuxer, WaitForRead())
+ .WillOnce(Return(512));
+
+ // Second read.
+ EXPECT_CALL(*data_source_, Read(512, 512, kBuffer, NotNull()))
+ .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback)));
+ EXPECT_CALL(*demuxer, SignalReadCompleted(512));
+ EXPECT_CALL(*demuxer, WaitForRead())
+ .WillOnce(Return(512));
+
+ // Called during SetPosition().
+ EXPECT_CALL(*data_source_, GetSize(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
+
+ // This read complete signal is generated when demuxer is stopped.
+ EXPECT_CALL(*demuxer, SignalReadCompleted(DataSource::kReadError));
+
+ EXPECT_EQ(512, demuxer->Read(512, kBuffer));
+ int64 position;
+ EXPECT_TRUE(demuxer->GetPosition(&position));
+ EXPECT_EQ(512, position);
+ EXPECT_EQ(512, demuxer->Read(512, kBuffer));
+ EXPECT_FALSE(demuxer->SetPosition(1024));
+
+ demuxer->Stop();
+}
+
+TEST_F(FFmpegDemuxerTest, ProtocolGetSetPosition) {
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxer();
+ }
+
+ InSequence s;
+
+ EXPECT_CALL(*data_source_, GetSize(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
+ EXPECT_CALL(*data_source_, GetSize(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
+
+ int64 position;
+ EXPECT_TRUE(demuxer_->GetPosition(&position));
+ EXPECT_EQ(0, position);
+
+ EXPECT_TRUE(demuxer_->SetPosition(512));
+ EXPECT_FALSE(demuxer_->SetPosition(2048));
+ EXPECT_TRUE(demuxer_->GetPosition(&position));
+ EXPECT_EQ(512, position);
+}
+
+TEST_F(FFmpegDemuxerTest, ProtocolGetSize) {
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxer();
+ }
+
+ EXPECT_CALL(*data_source_, GetSize(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true)));
+
+ int64 size;
+ EXPECT_TRUE(demuxer_->GetSize(&size));
+ EXPECT_EQ(1024, size);
+}
+
+TEST_F(FFmpegDemuxerTest, ProtocolIsStreamed) {
+ {
+ SCOPED_TRACE("");
+ InitializeDemuxer();
+ }
+ EXPECT_FALSE(demuxer_->IsStreamed());
+}
+
} // namespace media
diff --git a/media/filters/ffmpeg_glue.cc b/media/filters/ffmpeg_glue.cc
index ca3ed1d..135b3bb 100644
--- a/media/filters/ffmpeg_glue.cc
+++ b/media/filters/ffmpeg_glue.cc
@@ -9,24 +9,26 @@
namespace {
+media::FFmpegURLProtocol* ToProtocol(void* data) {
+ return reinterpret_cast<media::FFmpegURLProtocol*>(data);
+}
+
// FFmpeg protocol interface.
int OpenContext(URLContext* h, const char* filename, int flags) {
- scoped_refptr<media::DataSource> data_source;
- media::FFmpegGlue::get()->GetDataSource(filename, &data_source);
- if (!data_source)
+ media::FFmpegURLProtocol* protocol;
+ media::FFmpegGlue::get()->GetProtocol(filename, &protocol);
+ if (!protocol)
return AVERROR_IO;
- data_source->AddRef();
- h->priv_data = data_source;
+ h->priv_data = protocol;
h->flags = URL_RDONLY;
- h->is_streamed = !data_source->IsSeekable();
+ h->is_streamed = protocol->IsStreamed();
return 0;
}
int ReadContext(URLContext* h, unsigned char* buf, int size) {
- media::DataSource* data_source =
- reinterpret_cast<media::DataSource*>(h->priv_data);
- int result = data_source->Read(buf, size);
+ media::FFmpegURLProtocol* protocol = ToProtocol(h->priv_data);
+ int result = protocol->Read(size, buf);
if (result < 0)
result = AVERROR_IO;
return result;
@@ -38,33 +40,32 @@ int WriteContext(URLContext* h, unsigned char* buf, int size) {
}
offset_t SeekContext(URLContext* h, offset_t offset, int whence) {
- media::DataSource* data_source =
- reinterpret_cast<media::DataSource*>(h->priv_data);
+ media::FFmpegURLProtocol* protocol = ToProtocol(h->priv_data);
offset_t new_offset = AVERROR_IO;
switch (whence) {
case SEEK_SET:
- if (data_source->SetPosition(offset))
- data_source->GetPosition(&new_offset);
+ if (protocol->SetPosition(offset))
+ protocol->GetPosition(&new_offset);
break;
case SEEK_CUR:
int64 pos;
- if (!data_source->GetPosition(&pos))
+ if (!protocol->GetPosition(&pos))
break;
- if (data_source->SetPosition(pos + offset))
- data_source->GetPosition(&new_offset);
+ if (protocol->SetPosition(pos + offset))
+ protocol->GetPosition(&new_offset);
break;
case SEEK_END:
int64 size;
- if (!data_source->GetSize(&size))
+ if (!protocol->GetSize(&size))
break;
- if (data_source->SetPosition(size + offset))
- data_source->GetPosition(&new_offset);
+ if (protocol->SetPosition(size + offset))
+ protocol->GetPosition(&new_offset);
break;
case AVSEEK_SIZE:
- data_source->GetSize(&new_offset);
+ protocol->GetSize(&new_offset);
break;
default:
@@ -76,9 +77,6 @@ offset_t SeekContext(URLContext* h, offset_t offset, int whence) {
}
int CloseContext(URLContext* h) {
- media::DataSource* data_source =
- reinterpret_cast<media::DataSource*>(h->priv_data);
- data_source->Release();
h->priv_data = NULL;
return 0;
}
@@ -93,7 +91,7 @@ namespace media {
static const char kProtocol[] = "http";
// Fill out our FFmpeg protocol definition.
-static URLProtocol kFFmpegProtocol = {
+static URLProtocol kFFmpegURLProtocol = {
kProtocol,
&OpenContext,
&ReadContext,
@@ -105,7 +103,7 @@ static URLProtocol kFFmpegProtocol = {
FFmpegGlue::FFmpegGlue() {
// Register our protocol glue code with FFmpeg.
avcodec_init();
- av_register_protocol(&kFFmpegProtocol);
+ av_register_protocol(&kFFmpegURLProtocol);
// Now register the rest of FFmpeg.
av_register_all();
@@ -114,43 +112,43 @@ FFmpegGlue::FFmpegGlue() {
FFmpegGlue::~FFmpegGlue() {
}
-std::string FFmpegGlue::AddDataSource(DataSource* data_source) {
+std::string FFmpegGlue::AddProtocol(FFmpegURLProtocol* protocol) {
AutoLock auto_lock(lock_);
- std::string key = GetDataSourceKey(data_source);
- if (data_sources_.find(key) == data_sources_.end()) {
- data_sources_[key] = data_source;
+ std::string key = GetProtocolKey(protocol);
+ if (protocols_.find(key) == protocols_.end()) {
+ protocols_[key] = protocol;
}
return key;
}
-void FFmpegGlue::RemoveDataSource(DataSource* data_source) {
+void FFmpegGlue::RemoveProtocol(FFmpegURLProtocol* protocol) {
AutoLock auto_lock(lock_);
- for (DataSourceMap::iterator cur, iter = data_sources_.begin();
- iter != data_sources_.end();) {
+ for (ProtocolMap::iterator cur, iter = protocols_.begin();
+ iter != protocols_.end();) {
cur = iter;
iter++;
- if (cur->second == data_source)
- data_sources_.erase(cur);
+ if (cur->second == protocol)
+ protocols_.erase(cur);
}
}
-void FFmpegGlue::GetDataSource(const std::string& key,
- scoped_refptr<DataSource>* data_source) {
+void FFmpegGlue::GetProtocol(const std::string& key,
+ FFmpegURLProtocol** protocol) {
AutoLock auto_lock(lock_);
- DataSourceMap::iterator iter = data_sources_.find(key);
- if (iter == data_sources_.end()) {
- *data_source = NULL;
+ ProtocolMap::iterator iter = protocols_.find(key);
+ if (iter == protocols_.end()) {
+ *protocol = NULL;
return;
}
- *data_source = iter->second;
+ *protocol = iter->second;
}
-std::string FFmpegGlue::GetDataSourceKey(DataSource* data_source) {
- // Use the DataSource's memory address to generate the unique string. This
- // also has the nice property that adding the same DataSource reference will
- // not generate duplicate entries.
- return StringPrintf("%s://0x%lx", kProtocol, static_cast<void*>(data_source));
+std::string FFmpegGlue::GetProtocolKey(FFmpegURLProtocol* protocol) {
+ // Use the FFmpegURLProtocol's memory address to generate the unique string.
+ // This also has the nice property that adding the same FFmpegURLProtocol
+ // reference will not generate duplicate entries.
+ return StringPrintf("%s://0x%lx", kProtocol, static_cast<void*>(protocol));
}
} // namespace media
diff --git a/media/filters/ffmpeg_glue.h b/media/filters/ffmpeg_glue.h
index 0db1ce8..228c964 100644
--- a/media/filters/ffmpeg_glue.h
+++ b/media/filters/ffmpeg_glue.h
@@ -38,22 +38,50 @@ typedef int64 offset_t;
namespace media {
-class DataSource;
+class FFmpegURLProtocol {
+ public:
+ FFmpegURLProtocol() {
+ }
+
+ virtual ~FFmpegURLProtocol() {
+ }
+
+ // Read the given amount of bytes into data, returns the number of bytes read
+ // if successful, kReadError otherwise.
+ virtual int Read(int size, uint8* data) = 0;
+
+ // Returns true and the current file position for this file, false if the
+ // file position could not be retrieved.
+ virtual bool GetPosition(int64* position_out) = 0;
+
+ // Returns true if the file position could be set, false otherwise.
+ virtual bool SetPosition(int64 position) = 0;
+
+ // Returns true and the file size, false if the file size could not be
+ // retrieved.
+ virtual bool GetSize(int64* size_out) = 0;
+
+ // Returns false if this protocol supports random seeking.
+ virtual bool IsStreamed() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FFmpegURLProtocol);
+};
class FFmpegGlue : public Singleton<FFmpegGlue> {
public:
- // Adds a DataSource to the FFmpeg glue layer and returns a unique string that
- // can be passed to FFmpeg to identify the data source.
- std::string AddDataSource(DataSource* data_source);
+ // Adds a FFmpegProtocol to the FFmpeg glue layer and returns a unique string
+ // that can be passed to FFmpeg to identify the data source.
+ std::string AddProtocol(FFmpegURLProtocol* protocol);
- // Removes a DataSource from the FFmpeg glue layer. Using strings from
- // previously added DataSources will no longer work.
- void RemoveDataSource(DataSource* data_source);
+ // Removes a FFmpegProtocol from the FFmpeg glue layer. Using strings from
+ // previously added FFmpegProtocols will no longer work.
+ void RemoveProtocol(FFmpegURLProtocol* protocol);
- // Assigns the DataSource identified with by the given key to |data_source|,
- // or assigns NULL if no such DataSource could be found.
- void GetDataSource(const std::string& key,
- scoped_refptr<DataSource>* data_source);
+ // Assigns the FFmpegProtocol identified with by the given key to
+ // |protocol|, or assigns NULL if no such FFmpegProtocol could be found.
+ void GetProtocol(const std::string& key,
+ FFmpegURLProtocol** protocol);
private:
// Only allow Singleton to create and delete FFmpegGlue.
@@ -63,14 +91,14 @@ class FFmpegGlue : public Singleton<FFmpegGlue> {
// Returns the unique key for this data source, which can be passed to
// av_open_input_file as the filename.
- std::string GetDataSourceKey(DataSource* data_source);
+ std::string GetProtocolKey(FFmpegURLProtocol* protocol);
// Mutual exclusion while adding/removing items from the map.
Lock lock_;
- // Map between keys and DataSource references.
- typedef std::map< std::string, scoped_refptr<DataSource> > DataSourceMap;
- DataSourceMap data_sources_;
+ // Map between keys and FFmpegProtocol references.
+ typedef std::map<std::string, FFmpegURLProtocol*> ProtocolMap;
+ ProtocolMap protocols_;
DISALLOW_COPY_AND_ASSIGN(FFmpegGlue);
};
diff --git a/media/filters/ffmpeg_glue_unittest.cc b/media/filters/ffmpeg_glue_unittest.cc
index 9f60868..904f6a5 100644
--- a/media/filters/ffmpeg_glue_unittest.cc
+++ b/media/filters/ffmpeg_glue_unittest.cc
@@ -17,6 +17,21 @@ using ::testing::StrictMock;
namespace media {
+class MockProtocol : public FFmpegURLProtocol {
+ public:
+ MockProtocol() {
+ }
+
+ MOCK_METHOD2(Read, int(int size, uint8* data));
+ MOCK_METHOD1(GetPosition, bool(int64* position_out));
+ MOCK_METHOD1(SetPosition, bool(int64 position));
+ MOCK_METHOD1(GetSize, bool(int64* size_out));
+ MOCK_METHOD0(IsStreamed, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockProtocol);
+};
+
class FFmpegGlueTest : public ::testing::Test {
public:
FFmpegGlueTest() {
@@ -27,17 +42,17 @@ class FFmpegGlueTest : public ::testing::Test {
MockFFmpeg::set(NULL);
}
- // Helper to open a URLContext pointing to the given mocked data source.
+ // Helper to open a URLContext pointing to the given mocked protocol.
// Callers are expected to close the context at the end of their test.
- virtual void OpenContext(MockDataSource* data_source, URLContext* context) {
- // IsSeekable() is called when opening.
- EXPECT_CALL(*data_source, IsSeekable()).WillOnce(Return(false));
+ virtual void OpenContext(MockProtocol* protocol, URLContext* context) {
+ // IsStreamed() is called when opening.
+ EXPECT_CALL(*protocol, IsStreamed()).WillOnce(Return(true));
- // Add the data source to the glue layer and open a context.
- std::string key = FFmpegGlue::get()->AddDataSource(data_source);
+ // Add the protocol to the glue layer and open a context.
+ std::string key = FFmpegGlue::get()->AddProtocol(protocol);
memset(context, 0, sizeof(*context));
EXPECT_EQ(0, protocol_->url_open(context, key.c_str(), 0));
- FFmpegGlue::get()->RemoveDataSource(data_source);
+ FFmpegGlue::get()->RemoveProtocol(protocol);
}
protected:
@@ -68,55 +83,55 @@ TEST_F(FFmpegGlueTest, InitializeFFmpeg) {
EXPECT_TRUE(protocol_->url_write);
}
-TEST_F(FFmpegGlueTest, AddRemoveGetDataSource) {
+TEST_F(FFmpegGlueTest, AddRemoveGetProtocol) {
// Prepare testing data.
FFmpegGlue* glue = FFmpegGlue::get();
- // Create our data sources and add them to the glue layer.
- scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source_a
- = new StrictMock<Destroyable<MockDataSource> >();
- scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source_b
- = new StrictMock<Destroyable<MockDataSource> >();
+ // Create our protocols and add them to the glue layer.
+ scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_a(
+ new StrictMock<Destroyable<MockProtocol> >());
+ scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_b(
+ new StrictMock<Destroyable<MockProtocol> >());
// Make sure the keys are unique.
- std::string key_a = glue->AddDataSource(data_source_a);
- std::string key_b = glue->AddDataSource(data_source_b);
+ std::string key_a = glue->AddProtocol(protocol_a.get());
+ std::string key_b = glue->AddProtocol(protocol_b.get());
EXPECT_EQ(0u, key_a.find("http://"));
EXPECT_EQ(0u, key_b.find("http://"));
EXPECT_NE(key_a, key_b);
- // Our keys should return our data sources.
- scoped_refptr<DataSource> data_source_c;
- scoped_refptr<DataSource> data_source_d;
- glue->GetDataSource(key_a, &data_source_c);
- glue->GetDataSource(key_b, &data_source_d);
- EXPECT_EQ(data_source_a, data_source_c);
- EXPECT_EQ(data_source_b, data_source_d);
+ // Our keys should return our protocols.
+ FFmpegURLProtocol* protocol_c;
+ FFmpegURLProtocol* protocol_d;
+ glue->GetProtocol(key_a, &protocol_c);
+ glue->GetProtocol(key_b, &protocol_d);
+ EXPECT_EQ(protocol_a.get(), protocol_c);
+ EXPECT_EQ(protocol_b.get(), protocol_d);
- // Adding the same DataSource should create the same key and not add an extra
+ // Adding the same Protocol should create the same key and not add an extra
// reference.
- std::string key_a2 = glue->AddDataSource(data_source_a);
+ std::string key_a2 = glue->AddProtocol(protocol_a.get());
EXPECT_EQ(key_a, key_a2);
- glue->GetDataSource(key_a2, &data_source_c);
- EXPECT_EQ(data_source_a, data_source_c);
+ glue->GetProtocol(key_a2, &protocol_c);
+ EXPECT_EQ(protocol_a.get(), protocol_c);
- // Removes the data sources then releases our references. They should be
+ // Removes the protocols then releases our references. They should be
// destroyed.
InSequence s;
- EXPECT_CALL(*data_source_a, OnDestroy());
- EXPECT_CALL(*data_source_b, OnDestroy());
+ EXPECT_CALL(*protocol_a, OnDestroy());
+ EXPECT_CALL(*protocol_b, OnDestroy());
EXPECT_CALL(mock_ffmpeg_, CheckPoint(0));
- glue->RemoveDataSource(data_source_a);
- glue->GetDataSource(key_a, &data_source_c);
- EXPECT_FALSE(data_source_c);
- glue->GetDataSource(key_b, &data_source_d);
- EXPECT_EQ(data_source_b, data_source_d);
- glue->RemoveDataSource(data_source_b);
- glue->GetDataSource(key_b, &data_source_d);
- EXPECT_FALSE(data_source_d);
- data_source_a = NULL;
- data_source_b = NULL;
+ glue->RemoveProtocol(protocol_a.get());
+ glue->GetProtocol(key_a, &protocol_c);
+ EXPECT_FALSE(protocol_c);
+ glue->GetProtocol(key_b, &protocol_d);
+ EXPECT_EQ(protocol_b.get(), protocol_d);
+ glue->RemoveProtocol(protocol_b.get());
+ glue->GetProtocol(key_b, &protocol_d);
+ EXPECT_FALSE(protocol_d);
+ protocol_a.reset();
+ protocol_b.reset();
// Data sources should be deleted by this point.
mock_ffmpeg_.CheckPoint(0);
@@ -126,41 +141,41 @@ TEST_F(FFmpegGlueTest, OpenClose) {
// Prepare testing data.
FFmpegGlue* glue = FFmpegGlue::get();
- // Create our data source and add them to the glue layer.
- scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source
- = new StrictMock<Destroyable<MockDataSource> >();
- EXPECT_CALL(*data_source, IsSeekable()).WillOnce(Return(false));
- std::string key = glue->AddDataSource(data_source);
+ // Create our protocol and add them to the glue layer.
+ scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol(
+ new StrictMock<Destroyable<MockProtocol> >());
+ EXPECT_CALL(*protocol, IsStreamed()).WillOnce(Return(true));
+ std::string key = glue->AddProtocol(protocol.get());
// Prepare FFmpeg URLContext structure.
URLContext context;
memset(&context, 0, sizeof(context));
- // Test opening a URLContext with a data source that doesn't exist.
+ // Test opening a URLContext with a protocol that doesn't exist.
EXPECT_EQ(AVERROR_IO, protocol_->url_open(&context, "foobar", 0));
- // Test opening a URLContext with our data source.
+ // Test opening a URLContext with our protocol.
EXPECT_EQ(0, protocol_->url_open(&context, key.c_str(), 0));
EXPECT_EQ(URL_RDONLY, context.flags);
- EXPECT_EQ(data_source, context.priv_data);
+ EXPECT_EQ(protocol.get(), context.priv_data);
EXPECT_TRUE(context.is_streamed);
// We're going to remove references one by one until the last reference is
- // held by FFmpeg. Once we close the URLContext, the data source should be
+ // held by FFmpeg. Once we close the URLContext, the protocol should be
// destroyed.
InSequence s;
EXPECT_CALL(mock_ffmpeg_, CheckPoint(0));
EXPECT_CALL(mock_ffmpeg_, CheckPoint(1));
- EXPECT_CALL(*data_source, OnDestroy());
+ EXPECT_CALL(*protocol, OnDestroy());
EXPECT_CALL(mock_ffmpeg_, CheckPoint(2));
- // Remove the data source from the glue layer, releasing a reference.
- glue->RemoveDataSource(data_source);
+ // Remove the protocol from the glue layer, releasing a reference.
+ glue->RemoveProtocol(protocol.get());
mock_ffmpeg_.CheckPoint(0);
// Remove our own reference -- URLContext should maintain a reference.
- data_source = NULL;
mock_ffmpeg_.CheckPoint(1);
+ protocol.reset();
// Close the URLContext, which should release the final reference.
EXPECT_EQ(0, protocol_->url_close(&context));
@@ -168,64 +183,64 @@ TEST_F(FFmpegGlueTest, OpenClose) {
}
TEST_F(FFmpegGlueTest, Write) {
- scoped_refptr<StrictMock<MockDataSource> > data_source
- = new StrictMock<MockDataSource>();
+ scoped_ptr<StrictMock<MockProtocol> > protocol(
+ new StrictMock<MockProtocol>());
URLContext context;
- OpenContext(data_source, &context);
+ OpenContext(protocol.get(), &context);
const int kBufferSize = 16;
uint8 buffer[kBufferSize];
- // Writing should always fail and never call the data source.
+ // Writing should always fail and never call the protocol.
EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, NULL, 0));
EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, buffer, 0));
EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, buffer, kBufferSize));
- // Destroy the data source.
+ // Destroy the protocol.
protocol_->url_close(&context);
}
TEST_F(FFmpegGlueTest, Read) {
- scoped_refptr<StrictMock<MockDataSource> > data_source
- = new StrictMock<MockDataSource>();
+ scoped_ptr<StrictMock<MockProtocol> > protocol(
+ new StrictMock<MockProtocol>());
URLContext context;
- OpenContext(data_source, &context);
+ OpenContext(protocol.get(), &context);
const int kBufferSize = 16;
uint8 buffer[kBufferSize];
// Reads are for the most part straight-through calls to Read().
InSequence s;
- EXPECT_CALL(*data_source, Read(buffer, 0))
+ EXPECT_CALL(*protocol, Read(0, buffer))
.WillOnce(Return(0));
- EXPECT_CALL(*data_source, Read(buffer, kBufferSize))
+ EXPECT_CALL(*protocol, Read(kBufferSize, buffer))
.WillOnce(Return(kBufferSize));
- EXPECT_CALL(*data_source, Read(buffer, kBufferSize))
+ EXPECT_CALL(*protocol, Read(kBufferSize, buffer))
.WillOnce(Return(DataSource::kReadError));
EXPECT_EQ(0, protocol_->url_read(&context, buffer, 0));
EXPECT_EQ(kBufferSize, protocol_->url_read(&context, buffer, kBufferSize));
EXPECT_EQ(AVERROR_IO, protocol_->url_read(&context, buffer, kBufferSize));
- // Destroy the data source.
+ // Destroy the protocol.
protocol_->url_close(&context);
}
TEST_F(FFmpegGlueTest, Seek) {
- scoped_refptr<StrictMock<MockDataSource> > data_source
- = new StrictMock<MockDataSource>();
+ scoped_ptr<StrictMock<MockProtocol> > protocol(
+ new StrictMock<MockProtocol>());
URLContext context;
- OpenContext(data_source, &context);
+ OpenContext(protocol.get(), &context);
// SEEK_SET should be a straight-through call to SetPosition(), which when
// successful will return the result from GetPosition().
InSequence s;
- EXPECT_CALL(*data_source, SetPosition(-16))
+ EXPECT_CALL(*protocol, SetPosition(-16))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, SetPosition(16))
+ EXPECT_CALL(*protocol, SetPosition(16))
.WillOnce(Return(true));
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, -16, SEEK_SET));
@@ -233,19 +248,19 @@ TEST_F(FFmpegGlueTest, Seek) {
// SEEK_CUR should call GetPosition() first, and if it succeeds add the offset
// to the result then call SetPosition()+GetPosition().
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
- EXPECT_CALL(*data_source, SetPosition(16))
+ EXPECT_CALL(*protocol, SetPosition(16))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
- EXPECT_CALL(*data_source, SetPosition(16))
+ EXPECT_CALL(*protocol, SetPosition(16))
.WillOnce(Return(true));
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, 8, SEEK_CUR));
@@ -254,19 +269,19 @@ TEST_F(FFmpegGlueTest, Seek) {
// SEEK_END should call GetSize() first, and if it succeeds add the offset
// to the result then call SetPosition()+GetPosition().
- EXPECT_CALL(*data_source, GetSize(_))
+ EXPECT_CALL(*protocol, GetSize(_))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, GetSize(_))
+ EXPECT_CALL(*protocol, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
- EXPECT_CALL(*data_source, SetPosition(8))
+ EXPECT_CALL(*protocol, SetPosition(8))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, GetSize(_))
+ EXPECT_CALL(*protocol, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
- EXPECT_CALL(*data_source, SetPosition(8))
+ EXPECT_CALL(*protocol, SetPosition(8))
.WillOnce(Return(true));
- EXPECT_CALL(*data_source, GetPosition(_))
+ EXPECT_CALL(*protocol, GetPosition(_))
.WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true)));
EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, -8, SEEK_END));
@@ -274,34 +289,34 @@ TEST_F(FFmpegGlueTest, Seek) {
EXPECT_EQ(8, protocol_->url_seek(&context, -8, SEEK_END));
// AVSEEK_SIZE should be a straight-through call to GetSize().
- EXPECT_CALL(*data_source, GetSize(_))
+ EXPECT_CALL(*protocol, GetSize(_))
.WillOnce(Return(false));
- EXPECT_CALL(*data_source, GetSize(_))
+ EXPECT_CALL(*protocol, GetSize(_))
.WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true)));
EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, 0, AVSEEK_SIZE));
EXPECT_EQ(16, protocol_->url_seek(&context, 0, AVSEEK_SIZE));
- // Destroy the data source.
+ // Destroy the protocol.
protocol_->url_close(&context);
}
TEST_F(FFmpegGlueTest, Destroy) {
- // Create our data source and add them to the glue layer.
- scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source
- = new StrictMock<Destroyable<MockDataSource> >();
- std::string key = FFmpegGlue::get()->AddDataSource(data_source);
+ // Create our protocol and add them to the glue layer.
+ scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol(
+ new StrictMock<Destroyable<MockProtocol> >());
+ std::string key = FFmpegGlue::get()->AddProtocol(protocol.get());
- // We should expect the data source to get destroyed when the unit test
+ // We should expect the protocol to get destroyed when the unit test
// exits.
InSequence s;
EXPECT_CALL(mock_ffmpeg_, CheckPoint(0));
- EXPECT_CALL(*data_source, OnDestroy());
+ EXPECT_CALL(*protocol, OnDestroy());
// Remove our own reference, we shouldn't be destroyed yet.
- data_source = NULL;
mock_ffmpeg_.CheckPoint(0);
+ protocol.reset();
// ~FFmpegGlue() will be called when this unit test finishes execution. By
// leaving something inside FFmpegGlue's map we get to test our cleanup code.
diff --git a/media/filters/file_data_source.cc b/media/filters/file_data_source.cc
index 36e0582..d440918 100644
--- a/media/filters/file_data_source.cc
+++ b/media/filters/file_data_source.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 <limits>
+
#include "base/file_util.h"
#include "base/string_util.h"
#include "media/base/filter_host.h"
@@ -59,50 +61,35 @@ const MediaFormat& FileDataSource::media_format() {
return media_format_;
}
-size_t FileDataSource::Read(uint8* data, size_t size) {
+void FileDataSource::Read(int64 position, size_t size, uint8* data,
+ ReadCallback* read_callback) {
DCHECK(file_);
AutoLock l(lock_);
if (file_) {
+#if defined(OS_WIN)
+ if (_fseeki64(file_, position, SEEK_SET)) {
+ read_callback->RunWithParams(
+ Tuple1<size_t>(static_cast<size_t>(DataSource::kReadError)));
+ return;
+ }
+#else
+ CHECK(position <= std::numeric_limits<int32>::max());
+ // TODO(hclam): Change fseek() to support 64-bit position.
+ if (fseek(file_, static_cast<int32>(position), SEEK_SET)) {
+ read_callback->RunWithParams(
+ Tuple1<size_t>(static_cast<size_t>(DataSource::kReadError)));
+ return;
+ }
+#endif
size_t size_read = fread(data, 1, size, file_);
if (size_read == size || !ferror(file_)) {
- return size_read;
+ read_callback->RunWithParams(
+ Tuple1<size_t>(static_cast<size_t>(size_read)));
+ return;
}
}
- return kReadError;
-}
-
-bool FileDataSource::GetPosition(int64* position_out) {
- DCHECK(position_out);
- DCHECK(file_);
- AutoLock l(lock_);
- if (!file_) {
- *position_out = 0;
- return false;
- }
-// Linux and mac libraries don't seem to support 64 versions of seek and
-// ftell. TODO(ralph): Try to figure out how to enable int64 versions on
-// these platforms.
-#if defined(OS_WIN)
- *position_out = _ftelli64(file_);
-#else
- *position_out = ftell(file_);
-#endif
- return true;
-}
-bool FileDataSource::SetPosition(int64 position) {
- DCHECK(file_);
- AutoLock l(lock_);
-#if defined(OS_WIN)
- if (file_ && 0 == _fseeki64(file_, position, SEEK_SET)) {
- return true;
- }
-#else
- if (file_ && 0 == fseek(file_, static_cast<int32>(position), SEEK_SET)) {
- return true;
- }
-#endif
- return false;
+ read_callback->RunWithParams(Tuple1<size_t>(static_cast<size_t>(kReadError)));
}
bool FileDataSource::GetSize(int64* size_out) {
diff --git a/media/filters/file_data_source.h b/media/filters/file_data_source.h
index 5c91ce1..760b7c5 100644
--- a/media/filters/file_data_source.h
+++ b/media/filters/file_data_source.h
@@ -28,9 +28,8 @@ class FileDataSource : public DataSource {
// Implementation of DataSource.
virtual void Initialize(const std::string& url, FilterCallback* callback);
virtual const MediaFormat& media_format();
- virtual size_t Read(uint8* data, size_t size);
- virtual bool GetPosition(int64* position_out);
- virtual bool SetPosition(int64 position);
+ virtual void Read(int64 position, size_t size, uint8* data,
+ ReadCallback* read_callback);
virtual bool GetSize(int64* size_out);
virtual bool IsSeekable();
diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc
index 9c4b9ee8..193f31a 100644
--- a/media/filters/file_data_source_unittest.cc
+++ b/media/filters/file_data_source_unittest.cc
@@ -14,6 +14,21 @@
using ::testing::NiceMock;
using ::testing::StrictMock;
+namespace {
+
+class ReadCallbackHandler {
+ public:
+ ReadCallbackHandler() {
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadCallbackHandler);
+};
+
+} // namespace
+
namespace media {
// Returns a path to the test file which contains the string "0123456789"
@@ -52,7 +67,6 @@ TEST(FileDataSourceTest, OpenFile) {
// Use the mock filter host to directly call the Read and GetPosition methods.
TEST(FileDataSourceTest, ReadData) {
- int64 position;
int64 size;
uint8 ten_bytes[10];
@@ -60,28 +74,29 @@ TEST(FileDataSourceTest, ReadData) {
NiceMock<MockFilterHost> host;
NiceMock<MockFilterCallback> callback;
scoped_refptr<FileDataSource> filter = new FileDataSource();
+
filter->set_host(&host);
filter->Initialize(TestFileURL(), callback.NewCallback());
EXPECT_TRUE(filter->GetSize(&size));
EXPECT_EQ(10, size);
- EXPECT_TRUE(filter->GetPosition(&position));
- EXPECT_EQ(0, position);
-
- EXPECT_EQ(10u, filter->Read(ten_bytes, sizeof(ten_bytes)));
+ ReadCallbackHandler handler;
+ EXPECT_CALL(handler, ReadCallback(10));
+ filter->Read(0, 10, ten_bytes,
+ NewCallback(&handler, &ReadCallbackHandler::ReadCallback));
EXPECT_EQ('0', ten_bytes[0]);
EXPECT_EQ('5', ten_bytes[5]);
EXPECT_EQ('9', ten_bytes[9]);
- EXPECT_TRUE(filter->GetPosition(&position));
- EXPECT_EQ(10, position);
- EXPECT_EQ(0u, filter->Read(ten_bytes, sizeof(ten_bytes)));
- EXPECT_TRUE(filter->SetPosition(5));
- EXPECT_EQ(5u, filter->Read(ten_bytes, sizeof(ten_bytes)));
+ EXPECT_CALL(handler, ReadCallback(0));
+ filter->Read(10, 10, ten_bytes,
+ NewCallback(&handler, &ReadCallbackHandler::ReadCallback));
+
+ EXPECT_CALL(handler, ReadCallback(5));
+ filter->Read(5, 10, ten_bytes,
+ NewCallback(&handler, &ReadCallbackHandler::ReadCallback));
EXPECT_EQ('5', ten_bytes[0]);
- EXPECT_TRUE(filter->GetPosition(&position));
- EXPECT_EQ(10, position);
}
// Test that FileDataSource does nothing on Seek().
diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc
new file mode 100644
index 0000000..76e6e9d
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.cc
@@ -0,0 +1,734 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this
+// source code is governed by a BSD-style license that can be found in the
+// LICENSE file.
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "chrome/common/extensions/url_pattern.h"
+#include "media/base/filter_host.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "webkit/glue/media/buffered_data_source.h"
+#include "webkit/glue/webappcachecontext.h"
+
+namespace {
+
+const char kHttpScheme[] = "http";
+const char kHttpsScheme[] = "https";
+const int64 kPositionNotSpecified = -1;
+const int kHttpOK = 200;
+const int kHttpPartialContent = 206;
+
+// Define the number of bytes in a megabyte.
+const size_t kMegabyte = 1024 * 1024;
+
+// Backward capacity of the buffer, by default 2MB.
+const size_t kBackwardCapcity = 2 * kMegabyte;
+
+// Forward capacity of the buffer, by default 10MB.
+const size_t kForwardCapacity = 10 * kMegabyte;
+
+// The maximum offset to seek to, this value limits the range of seek to
+// prevent corruption of calculations. The buffer has a maximum size of 12MB
+// so this is enough to contain every valid operations.
+const int kMaxSeek = 100 * kMegabyte;
+
+// The threshold of bytes that we should wait until the data arrives in the
+// future instead of restarting a new connection. This number is defined in the
+// number of bytes, we should determine this value from typical connection speed
+// and amount of time for a suitable wait. Now I just make a guess for this
+// number to be 2MB.
+// TODO(hclam): determine a better value for this.
+const int kForwardWaitThreshold = 2 * kMegabyte;
+
+// Defines how long we should wait for more data before we declare a connection
+// timeout and start a new request.
+// TODO(hclam): Use this value when retry is implemented.
+// TODO(hclam): Set it to 5s, calibrate this value later.
+const int kDataTransferTimeoutSeconds = 5;
+
+// Defines how many times we should try to read from a buffered resource loader
+// before we declare a read error. After each failure of read from a buffered
+// resource loader, a new one is created to be read.
+// TODO(hclam): Use this value when retry is implemented.
+const int kReadTrials = 3;
+
+// BufferedDataSource has an intermediate buffer, this value governs the initial
+// size of that buffer. It is set to 32KB because this is a typical read size
+// of FFmpeg.
+const int kInitialReadBufferSize = 32768;
+
+// A helper method that accepts only HTTP, HTTPS and FILE protocol.
+bool IsSchemeSupported(const GURL& url) {
+ return url.SchemeIs(kHttpScheme) ||
+ url.SchemeIs(kHttpsScheme) ||
+ url.SchemeIsFile();
+}
+
+} // namespace
+
+namespace webkit_glue {
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader
+BufferedResourceLoader::BufferedResourceLoader(
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory,
+ const GURL& url,
+ int64 first_byte_position,
+ int64 last_byte_position)
+ : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)),
+ deferred_(false),
+ completed_(false),
+ range_requested_(false),
+ bridge_factory_(bridge_factory),
+ url_(url),
+ first_byte_position_(first_byte_position),
+ last_byte_position_(last_byte_position),
+ start_callback_(NULL),
+ bridge_(NULL),
+ offset_(0),
+ content_length_(kPositionNotSpecified),
+ read_callback_(NULL),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ first_offset_(0),
+ last_offset_(0) {
+}
+
+BufferedResourceLoader::~BufferedResourceLoader() {
+}
+
+void BufferedResourceLoader::Start(net::CompletionCallback* start_callback) {
+ // Make sure we have not started.
+ DCHECK(!bridge_.get());
+ DCHECK(!start_callback_.get());
+ DCHECK(start_callback);
+
+ start_callback_.reset(start_callback);
+
+ if (first_byte_position_ != kPositionNotSpecified) {
+ range_requested_ = true;
+ // TODO(hclam): server may not support range request so |offset_| may not
+ // equal to |first_byte_position_|.
+ offset_ = first_byte_position_;
+ }
+
+ // Creates the bridge on render thread since we can only access
+ // ResourceDispatcher on this thread.
+ bridge_.reset(bridge_factory_->CreateBridge(url_,
+ net::LOAD_BYPASS_CACHE,
+ first_byte_position_,
+ last_byte_position_));
+
+ // And start the resource loading.
+ bridge_->Start(this);
+}
+
+void BufferedResourceLoader::Stop() {
+ // Reset callbacks.
+ start_callback_.reset();
+ read_callback_.reset();
+
+ // Destroy internal buffer.
+ buffer_.reset();
+
+ if (bridge_.get()) {
+ // Cancel the resource request.
+ bridge_->Cancel();
+ bridge_.reset();
+ }
+}
+
+void BufferedResourceLoader::Read(int64 position,
+ int read_size,
+ uint8* buffer,
+ net::CompletionCallback* read_callback) {
+ DCHECK(!read_callback_.get());
+ DCHECK(buffer_.get());
+ DCHECK(read_callback);
+ DCHECK(buffer);
+
+ // Saves the parameter of reading.
+ read_callback_.reset(read_callback);
+ read_position_ = position;
+ read_size_ = read_size;
+ read_buffer_ = buffer;
+
+ // Check that the read parameters are within the range that we can handle.
+ // If the read request is made too far from the current offset, report that
+ // we cannot serve the request.
+ if (VerifyRead()) {
+ // If we can serve the request now, do the actual read.
+ if (CanFulfillRead()) {
+ ReadInternal();
+ DisableDeferIfNeeded();
+ return;
+ }
+
+ // If we expected the read request to be fulfilled later, returns
+ // immediately and let more data to flow in.
+ if (WillFulfillRead())
+ return;
+
+ // Make a callback to report failure.
+ DoneRead(net::ERR_CACHE_MISS);
+ return;
+ }
+
+ // TODO(hclam): We should report a better error code than just 0.
+ DoneRead(0);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader,
+// webkit_glue::ResourceLoaderBridge::Peer implementations
+void BufferedResourceLoader::OnReceivedRedirect(const GURL& new_url) {
+ DCHECK(bridge_.get());
+ DCHECK(start_callback_.get());
+
+ // Saves the new URL.
+ url_ = new_url;
+
+ // If we got redirected to an unsupported protocol then stop.
+ if (!IsSchemeSupported(new_url)) {
+ DoneStart(net::ERR_ADDRESS_INVALID);
+ Stop();
+ }
+}
+
+void BufferedResourceLoader::OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered) {
+ DCHECK(bridge_.get());
+ DCHECK(start_callback_.get());
+
+ int64 first_byte_position = -1;
+ int64 last_byte_position = -1;
+ int64 instance_size = -1;
+
+ // The file:// protocol should be able to serve any request we want, so we
+ // take an exception for file protocol.
+ if (!url_.SchemeIsFile()) {
+ int error = net::OK;
+ if (!info.headers) {
+ // We expect to receive headers because this is a HTTP or HTTPS protocol,
+ // if not report failure.
+ error = net::ERR_INVALID_RESPONSE;
+ } else if (range_requested_) {
+ if (info.headers->response_code() != kHttpPartialContent ||
+ !info.headers->GetContentRange(&first_byte_position,
+ &last_byte_position,
+ &instance_size)) {
+ // We requested a range, but server didn't reply with partial content or
+ // the "Content-Range" header is corrupted.
+ // TODO(hclam): should also make sure this is the range we requested.
+ error = net::ERR_INVALID_RESPONSE;
+ }
+ } else if (info.headers->response_code() != kHttpOK) {
+ // We didn't request a range but server didn't reply with "200 OK".
+ error = net::ERR_FAILED;
+ }
+
+ if (error != net::OK) {
+ DoneStart(error);
+ Stop();
+ return;
+ }
+ }
+
+ // |info.content_length| can be -1, in that case |content_length_| is
+ // not specified and this is a streaming response.
+ content_length_ = info.content_length;
+
+ // We only care about the first byte position if it's given by the server.
+ // TODO(hclam): If server replies with a different offset, consider failing
+ // here.
+ if (first_byte_position != kPositionNotSpecified)
+ offset_ = first_byte_position;
+
+ // Calls with a successful response.
+ DoneStart(net::OK);
+}
+
+void BufferedResourceLoader::OnReceivedData(const char* data, int len) {
+ DCHECK(bridge_.get());
+ DCHECK(buffer_.get());
+
+ // Writes more data to |buffer_|.
+ buffer_->Append(len, reinterpret_cast<const uint8*>(data));
+
+ // If there is an active read request, try to fulfill the request.
+ if (HasPendingRead() && CanFulfillRead()) {
+ ReadInternal();
+ }
+
+ // At last see if the buffer is full and we need to defer the downloading.
+ EnableDeferIfNeeded();
+}
+
+void BufferedResourceLoader::OnCompletedRequest(
+ const URLRequestStatus& status, const std::string& security_info) {
+ DCHECK(bridge_.get());
+ DCHECK(buffer_.get());
+
+ // Saves the information that the request has completed.
+ completed_ = true;
+
+ // After the response has completed, we don't need the bridge any more.
+ bridge_.reset();
+
+ // If there is a start callback, calls it.
+ if (start_callback_.get()) {
+ DoneStart(status.os_error());
+ }
+
+ // If there is a pending read but the request has ended, returns with what we
+ // have.
+ if (HasPendingRead()) {
+ // If the request has failed, then fail the read.
+ if (!status.is_success()) {
+ DoneRead(net::ERR_FAILED);
+ return;
+ }
+
+ // Otherwise try to fulfill with what is in the buffer.
+ if (CanFulfillRead())
+ ReadInternal();
+ else
+ DoneRead(net::ERR_CACHE_MISS);
+ }
+
+ // There must not be any outstanding read request.
+ DCHECK(!read_callback_.get());
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader, private
+void BufferedResourceLoader::EnableDeferIfNeeded() {
+ if (!deferred_ &&
+ buffer_->forward_bytes() >= buffer_->forward_capacity()) {
+ deferred_ = true;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(true);
+ }
+}
+
+void BufferedResourceLoader::DisableDeferIfNeeded() {
+ if (deferred_ &&
+ buffer_->forward_bytes() < buffer_->forward_capacity() / 2) {
+ deferred_ = false;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(false);
+ }
+}
+
+bool BufferedResourceLoader::CanFulfillRead() {
+ // If we are reading too far in the backward direction.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // If the start offset is too far ahead.
+ if (first_offset_ >= static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ // At the point, we verified that first byte requested is within the buffer.
+ // If the request has completed, then just returns with what we have now.
+ if (completed_)
+ return true;
+
+ // If the resource request is still active, make sure the whole requested
+ // range is covered.
+ if (last_offset_ > static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::WillFulfillRead() {
+ // Reading too far in the backward direction.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // Try to read too far ahead.
+ if (last_offset_ > kForwardWaitThreshold)
+ return false;
+
+ // The resource request has completed, there's no way we can fulfill the
+ // read request.
+ if (completed_)
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::VerifyRead() {
+ // Make sure |offset_| and |read_position_| does not differ by a large
+ // amount
+ if (read_position_ > offset_ + kMaxSeek)
+ return false;
+ else if (read_position_ < offset_ - kMaxSeek)
+ return false;
+
+ // If we can manage the read request with int32 math, then prepare the
+ // parameters.
+ first_offset_ = static_cast<int>(read_position_ - offset_);
+ last_offset_ = first_offset_ + read_size_;
+ return true;
+}
+
+void BufferedResourceLoader::ReadInternal() {
+ // Seek to the first byte requested.
+ bool ret = buffer_->Seek(first_offset_);
+ DCHECK(ret);
+
+ // Then do the read.
+ int read = static_cast<int>(buffer_->Read(read_size_, read_buffer_));
+ offset_ += first_offset_ + read;
+
+ // And report with what we have read.
+ DoneRead(read);
+}
+
+void BufferedResourceLoader::DoneRead(int error) {
+ read_callback_->RunWithParams(Tuple1<int>(error));
+ read_callback_.reset();
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = NULL;
+ first_offset_ = 0;
+ last_offset_ = 0;
+}
+
+void BufferedResourceLoader::DoneStart(int error) {
+ start_callback_->RunWithParams(Tuple1<int>(error));
+ start_callback_.reset();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, protected
+BufferedDataSource::BufferedDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory)
+ : total_bytes_(kPositionNotSpecified),
+ bridge_factory_(bridge_factory),
+ loader_(NULL),
+ read_callback_(NULL),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
+ intermediate_read_buffer_size_(kInitialReadBufferSize),
+ render_loop_(render_loop),
+ initialize_callback_(NULL),
+ stopped_(false) {
+}
+
+BufferedDataSource::~BufferedDataSource() {
+}
+
+// A factory method to create BufferedResourceLoader using the read parameters.
+// This method can be overrided to inject mock BufferedResourceLoader object
+// for testing purpose.
+BufferedResourceLoader* BufferedDataSource::CreateLoader(
+ int64 first_byte_position, int64 last_byte_position) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ return new BufferedResourceLoader(bridge_factory_.get(), url_,
+ first_byte_position,
+ last_byte_position);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::MediaFilter implementation
+void BufferedDataSource::Initialize(const std::string& url,
+ media::FilterCallback* callback) {
+ DCHECK(callback);
+ initialize_callback_.reset(callback);
+
+ // Saves the url.
+ url_ = GURL(url);
+
+ if (!IsSchemeSupported(url_)) {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ DoneInitialization();
+ return;
+ }
+
+ media_format_.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kApplicationOctetStream);
+ media_format_.SetAsString(media::MediaFormat::kURL, url);
+
+ // Post a task to complete the initialization task.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::InitializeTask));
+}
+
+void BufferedDataSource::Stop() {
+ {
+ AutoLock auto_lock(lock_);
+ stopped_ = true;
+ }
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::StopTask));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::DataSource implementation
+void BufferedDataSource::Read(int64 position, size_t size,
+ uint8* data,
+ media::DataSource::ReadCallback* read_callback) {
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::ReadTask,
+ position, static_cast<int>(size), data, read_callback));
+}
+
+bool BufferedDataSource::GetSize(int64* size_out) {
+ if (total_bytes_ != kPositionNotSpecified) {
+ *size_out = total_bytes_;
+ return true;
+ }
+ *size_out = 0;
+ return false;
+}
+
+bool BufferedDataSource::IsSeekable() {
+ return total_bytes_ != kPositionNotSpecified;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, render thread tasks
+void BufferedDataSource::InitializeTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!loader_.get());
+
+ // Creates a new resource loader with the full range.
+ loader_.reset(CreateLoader(-1, -1));
+
+ // And then start the resource request.
+ loader_->Start(NewCallback(this,
+ &BufferedDataSource::InitializeStartCallback));
+}
+
+void BufferedDataSource::ReadTask(
+ int64 position, int read_size, uint8* buffer,
+ media::DataSource::ReadCallback* read_callback) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!read_callback_.get());
+ DCHECK(read_callback);
+
+ // Saves the read parameters.
+ read_position_ = position;
+ read_size_ = read_size;
+ read_callback_.reset(read_callback);
+ read_buffer_ = buffer;
+
+ // Call to read internal to perform the actual read.
+ ReadInternal();
+}
+
+void BufferedDataSource::StopTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We just need to stop the loader, so it stops activity.
+ if (loader_.get()) {
+ loader_->Stop();
+ loader_.reset();
+ }
+}
+
+void BufferedDataSource::SwapLoaderTask(BufferedResourceLoader* loader) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader);
+
+ loader_.reset(loader);
+ loader_->Start(NewCallback(this,
+ &BufferedDataSource::PartialReadStartCallback));
+}
+
+// This method is the place where actual read happens, |loader_| must be valid
+// prior to make this method call.
+void BufferedDataSource::ReadInternal() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ // First we prepare the intermediate read buffer for BufferedResourceLoader
+ // to write to.
+ if (read_size_ > intermediate_read_buffer_size_) {
+ intermediate_read_buffer_.reset(new uint8[read_size_]);
+ }
+
+ // Perform the actual read with BufferedResourceLoader.
+ loader_->Read(read_position_, read_size_,
+ intermediate_read_buffer_.get(),
+ NewCallback(this, &BufferedDataSource::ReadCallback));
+}
+
+// Method to report the results of the current read request. Also reset all
+// the read parameters.
+void BufferedDataSource::DoneRead(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ lock_.AssertAcquired();
+
+ if (error >= 0) {
+ read_callback_->RunWithParams(Tuple1<size_t>(error));
+ } else {
+ read_callback_->RunWithParams(
+ Tuple1<size_t>(static_cast<size_t>(media::DataSource::kReadError)));
+ }
+
+ read_callback_.reset();
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = 0;
+}
+
+void BufferedDataSource::DoneInitialization() {
+ DCHECK(initialize_callback_.get());
+ lock_.AssertAcquired();
+
+ initialize_callback_->Run();
+ initialize_callback_.reset();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, callback methods.
+// These methods are called on the render thread for the events reported by
+// BufferedResourceLoader.
+void BufferedDataSource::InitializeStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. The only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is safe
+ // because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ DCHECK(loader_.get());
+
+ if (error == net::OK) {
+ total_bytes_ = loader_->content_length();
+ // TODO(hclam): Figure out what to do when total bytes is not known.
+ if (total_bytes_ >= 0) {
+ host()->SetTotalBytes(total_bytes_);
+
+ // This value governs the range that we can seek to.
+ // TODO(hclam): Report the correct value of buffered bytes.
+ host()->SetBufferedBytes(total_bytes_);
+ }
+ } else {
+ // TODO(hclam): In case of failure, we can retry several times.
+ // Also it might be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Stops the loader, just to be safe.
+ loader_->Stop();
+ }
+
+ DoneInitialization();
+}
+
+void BufferedDataSource::PartialReadStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ if (error == net::OK) {
+ // Once the range request has start successfully, we can proceed with
+ // reading from it.
+ ReadInternal();
+ } else {
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. So only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is
+ // safe because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ // TODO(hclam): It may be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Kill the loader just to be safe.
+ loader_->Stop();
+ }
+}
+
+void BufferedDataSource::ReadCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We need to prevent calling to filter host and running the callback if
+ // we have received the stop signal. We need to lock down the whole callback
+ // method to prevent bad things from happening. The reason behind this is
+ // that we cannot guarantee tasks on render thread have completely stopped
+ // when we receive the Stop() method call. So only way to solve this is to
+ // let tasks on render thread to run but make sure they don't call outside
+ // this object when Stop() method is ever called. Locking this method is safe
+ // because |lock_| is only acquired in tasks on render thread.
+ AutoLock auto_lock(lock_);
+ if (stopped_)
+ return;
+
+ DCHECK(loader_.get());
+ DCHECK(read_callback_.get());
+
+ if (error >= 0) {
+ // If a position error code is received, read was successful. So copy
+ // from intermediate read buffer to the target read buffer.
+ memcpy(read_buffer_, intermediate_read_buffer_.get(), error);
+
+ DoneRead(error);
+ } else if (error == net::ERR_CACHE_MISS) {
+ // If the current loader cannot serve this read request, we need to create
+ // a new one.
+ // We have the following conditions:
+ // 1. Read is beyond the content length of the file (if known).
+ // 2. We have tried too many times (TODO here).
+ if (read_position_ >= total_bytes_) {
+ DoneRead(0);
+ return;
+ }
+
+ // TODO(hclam): we need to count how many times it failed to prevent
+ // excessive trials.
+
+ // Stops the current resource loader.
+ loader_->Stop();
+
+ // Since this method is called from the current buffered resource loader,
+ // we cannot delete it. So we need to post a task to swap in a new
+ // resource loader and starts it.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &BufferedDataSource::SwapLoaderTask,
+ CreateLoader(read_position_, -1)));
+ } else {
+ // The read has finished with error.
+ DoneRead(error);
+
+ // TODO(hclam): It may be bad to access host() here.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+
+ // Stops the laoder.
+ loader_->Stop();
+ }
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h
new file mode 100644
index 0000000..5d6f1c3
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.h
@@ -0,0 +1,286 @@
+// Copyright (c) 2009 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 WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_
+#define WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_
+
+#include <string>
+
+#include "base/lock.h"
+#include "base/scoped_ptr.h"
+#include "base/condition_variable.h"
+#include "googleurl/src/gurl.h"
+#include "media/base/factory.h"
+#include "media/base/filters.h"
+#include "media/base/media_format.h"
+#include "media/base/pipeline.h"
+#include "media/base/seekable_buffer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/file_stream.h"
+#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
+// resource loader bridge and does the actual resource loading. This object
+// does buffering internally, it defers the resource loading if buffer is
+// full and un-defers the resource loading if it is under buffered.
+class BufferedResourceLoader : public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ // |bridge_factory| - Factory to create a ResourceLoaderBridge.
+ // |url| - URL for the resource to be loaded.
+ // |first_byte_position| - First byte to start loading from, -1 for not
+ // specified.
+ // |last_byte_position| - Last byte to be loaded, -1 for not specified.
+ BufferedResourceLoader(
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory,
+ const GURL& url,
+ int64 first_byte_position,
+ int64 last_byte_position);
+ virtual ~BufferedResourceLoader();
+
+ // Start the resource loading with the specified URL and range.
+ // This method operates in asynchronous mode. Once there's a response from the
+ // server, success or fail |start_callback| is called with the result.
+ virtual void Start(net::CompletionCallback* callback);
+
+ // Stop this loader, cancels and request and release internal buffer.
+ virtual void Stop();
+
+ // Reads the specified |read_size| from |position| into |buffer| and when
+ // the operation is done invoke |callback| with number of bytes read or an
+ // error code.
+ virtual void Read(int64 position, int read_size,
+ uint8* buffer, net::CompletionCallback* callback);
+
+ // Gets the content length in bytes of the instance after this loader has been
+ // started.
+ virtual int64 content_length() { return content_length_; }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // webkit_glue::ResourceLoaderBridge::Peer implementations.
+ virtual void OnUploadProgress(uint64 position, uint64 size) {}
+ virtual void OnReceivedRedirect(const GURL& new_url);
+ virtual void OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered);
+ virtual void OnReceivedData(const char* data, int len);
+ virtual void OnCompletedRequest(const URLRequestStatus& status,
+ const std::string& security_info);
+ std::string GetURLForDebugging() { return url_.spec(); }
+
+ protected:
+ // An empty constructor so mock classes can be constructed.
+ BufferedResourceLoader() {
+ }
+
+ private:
+ // Defer the resource loading if the buffer is full.
+ void EnableDeferIfNeeded();
+
+ // Disable defer loading if we are under-buffered.
+ void DisableDeferIfNeeded();
+
+ // Returns true if the current read request can be fulfilled by what is in
+ // the buffer.
+ bool CanFulfillRead();
+
+ // Returns true if the current read request will be fulfilled in the future.
+ bool WillFulfillRead();
+
+ // Checks parameters and make sure they are valid.
+ bool VerifyRead();
+
+ // Method that does the actual read and calls the |read_callbac_|, assuming
+ // the request range is in |buffer_|.
+ void ReadInternal();
+
+ // Done with read. Invokes the read callback and reset parameters for the
+ // read request.
+ void DoneRead(int error);
+
+ // Done with start. Invokes the start callback and reset it.
+ void DoneStart(int error);
+
+ bool HasPendingRead() { return read_callback_.get() != NULL; }
+
+ // A sliding window of buffer.
+ scoped_ptr<media::SeekableBuffer> buffer_;
+
+ bool deferred_;
+ bool completed_;
+ bool range_requested_;
+
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_;
+ GURL url_;
+ int64 first_byte_position_;
+ int64 last_byte_position_;
+
+ // Members used during request start.
+ scoped_ptr<net::CompletionCallback> start_callback_;
+ scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_;
+ int64 offset_;
+ int64 content_length_;
+
+ // Members used during a read operation. They should be reset after each
+ // read has completed or failed.
+ scoped_ptr<net::CompletionCallback> read_callback_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+
+ // Offsets of the requested first byte and last byte in |buffer_|. They are
+ // written by VerifyRead().
+ int first_offset_;
+ int last_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader);
+};
+
+class BufferedDataSource : public media::DataSource {
+ public:
+ // Methods called from pipeline thread
+ // Static methods for creating this class.
+ static media::FilterFactory* CreateFactory(
+ MessageLoop* message_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) {
+ return new media::FilterFactoryImpl2<
+ BufferedDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>(
+ message_loop, bridge_factory);
+ }
+
+ // media::MediaFilter implementation.
+ virtual void Initialize(const std::string& url,
+ media::FilterCallback* callback);
+ virtual void Stop();
+
+ // media::DataSource implementation.
+ // Called from demuxer thread.
+ virtual void Read(int64 position, size_t size,
+ uint8* data,
+ media::DataSource::ReadCallback* read_callback);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsSeekable();
+
+ const media::MediaFormat& media_format() {
+ return media_format_;
+ }
+
+ protected:
+ BufferedDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory);
+ virtual ~BufferedDataSource();
+
+ // A factory method to create a BufferedResourceLoader based on the read
+ // parameters. We can override this file to object a mock
+ // BufferedResourceLoader for testing.
+ virtual BufferedResourceLoader* CreateLoader(int64 first_byte_position,
+ int64 last_byte_position);
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ BufferedDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>;
+
+ // Posted to perform initialization on render thread.
+ void InitializeTask();
+
+ // Task posted to perform resource loading and actual reading on the render
+ // thread.
+ void ReadTask(int64 position, int read_size,
+ uint8* read_buffer,
+ media::DataSource::ReadCallback* read_callback);
+
+ // Task posted when Stop() is called.
+ void StopTask();
+
+ // Reset |loader_| with |loader| and starts it. This task is posted from
+ // callback method from the current buffered resource loader.
+ void SwapLoaderTask(BufferedResourceLoader* loader);
+
+ // The method that performs actual read. This method can only be executed on
+ // the render thread.
+ void ReadInternal();
+
+ // Calls |read_callback_| and reset all read parameters.
+ void DoneRead(int error);
+
+ // Calls |initialize_callback_| and reset it.
+ void DoneInitialization();
+
+ // Callback method to perform BufferedResourceLoader::Start() during
+ // initialization.
+ void InitializeStartCallback(int error);
+
+ // Callback method to be passed to BufferedResourceLoader during range
+ // request. Once a resource request has started, this method will be called
+ // with the error code. This method will be executed on the thread
+ // BufferedResourceLoader lives, i.e. render thread.
+ void PartialReadStartCallback(int error);
+
+ // Callback method for making a read request to BufferedResourceLoader.
+ // If data arrives or the request has failed, this method is called with
+ // the error code or the number of bytes read.
+ void ReadCallback(int error);
+
+ media::MediaFormat media_format_;
+
+ // URL of the resource requested.
+ GURL url_;
+
+ // Members for total bytes of the requested object. It is written once on
+ // render thread but may be read from any thread. However reading of this
+ // member is guaranteed to happen after it is first written, so we don't
+ // need to protect it.
+ int64 total_bytes_;
+
+ // A factory object to produce ResourceLoaderBridge.
+ scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_;
+
+ // A downloader object for loading the media resource.
+ scoped_ptr<BufferedResourceLoader> loader_;
+
+ // Read parameters received from the Read() method call.
+ scoped_ptr<media::DataSource::ReadCallback> read_callback_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+
+ // This buffer is intermediate, we use it for BufferedResourceLoader to write
+ // to. And when read in BufferedResourceLoader is done, we copy data from
+ // this buffer to |read_buffer_|. The reason for an additional copy is that
+ // we don't own |read_buffer_|. But since the read operation is asynchronous,
+ // |read_buffer| can be destroyed at any time, so we only copy into
+ // |read_buffer| in the final step when it is safe.
+ // Memory is allocated for this member during initialization of this object
+ // because we want buffer to be passed into BufferedResourceLoader to be
+ // always non-null. And by initializing this member with a default size we can
+ // avoid creating zero-sized buffered if the first read has zero size.
+ scoped_array<uint8> intermediate_read_buffer_;
+ int intermediate_read_buffer_size_;
+
+ // The message loop of the render thread.
+ MessageLoop* render_loop_;
+
+ // Filter callbacks.
+ scoped_ptr<media::FilterCallback> initialize_callback_;
+
+ // Protects |stopped_|.
+ Lock lock_;
+
+ // Stop signal to suppressing activities.
+ bool stopped_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_
diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc
new file mode 100644
index 0000000..40c0b41
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source_unittest.cc
@@ -0,0 +1,627 @@
+// Copyright (c) 2009 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 "base/string_util.h"
+#include "media/base/filters.h"
+#include "media/base/mock_filter_host.h"
+#include "media/base/mock_filters.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "webkit/glue/media/buffered_data_source.h"
+#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h"
+#include "webkit/glue/mock_resource_loader_bridge.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::WithArgs;
+
+namespace {
+
+const char* kHttpUrl = "http://test";
+const int kDataSize = 1024;
+
+} // namespace
+
+namespace webkit_glue {
+
+class BufferedResourceLoaderTest : public testing::Test {
+ public:
+ BufferedResourceLoaderTest() {
+ bridge_.reset(new StrictMock<MockResourceLoaderBridge>());
+
+ for (int i = 0; i < kDataSize; ++i)
+ data_[i] = i;
+ }
+
+ ~BufferedResourceLoaderTest() {
+ if (bridge_.get())
+ EXPECT_CALL(*bridge_, OnDestroy());
+ EXPECT_CALL(bridge_factory_, OnDestroy());
+ }
+
+ void Initialize(const char* url, int first_position, int last_position) {
+ gurl_ = GURL(url);
+ first_position_ = first_position;
+ last_position_ = last_position;
+
+ loader_.reset(new BufferedResourceLoader(&bridge_factory_, gurl_,
+ first_position_, last_position_));
+ EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging());
+ }
+
+ void Start() {
+ InSequence s;
+ EXPECT_CALL(bridge_factory_,
+ CreateBridge(gurl_, _, first_position_, last_position_))
+ .WillOnce(Return(bridge_.get()));
+ EXPECT_CALL(*bridge_, Start(loader_.get()));
+ loader_->Start(NewCallback(this,
+ &BufferedResourceLoaderTest::StartCallback));
+ }
+
+ void FullResponse(int64 content_length) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 200 OK\n"
+ "Content-Length: %lld", content_length);
+ replace(header.begin(), header.end(), '\n', '\0');
+ info.headers = new net::HttpResponseHeaders(header);
+ info.content_length = content_length;
+ loader_->OnReceivedResponse(info, false);
+ EXPECT_EQ(content_length, loader_->content_length());
+ }
+
+ void PartialResponse(int64 content_length) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes %lld-%lld/%lld",
+ first_position_,
+ last_position_,
+ content_length);
+ replace(header.begin(), header.end(), '\n', '\0');
+ info.headers = new net::HttpResponseHeaders(header);
+ info.content_length = content_length;
+ loader_->OnReceivedResponse(info, false);
+ // TODO(hclam): Right now BufferedResourceLoader doesn't care about the
+ // partial range replied by the server. Do the check here.
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ loader_->Stop();
+ }
+
+ void ReleaseBridge() {
+ bridge_.release();
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ loader_->OnReceivedData(reinterpret_cast<char*>(data_ + position), size);
+ }
+
+ // Helper method to read from |loader_|.
+ void ReadLoader(int64 position, int size, uint8* buffer) {
+ loader_->Read(position, size, buffer,
+ NewCallback(this, &BufferedResourceLoaderTest::ReadCallback));
+ }
+
+ // Verifis 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));
+ }
+
+ MOCK_METHOD1(StartCallback, void(int error));
+ MOCK_METHOD1(ReadCallback, void(int error));
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+ int64 last_position_;
+
+ scoped_ptr<BufferedResourceLoader> loader_;
+ StrictMock<MockMediaResourceLoaderBridgeFactory> bridge_factory_;
+ scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_;
+
+ uint8 data_[kDataSize];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
+};
+
+TEST_F(BufferedResourceLoaderTest, StartStop) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ StopWhenLoad();
+}
+
+// Tests that HTTP header is missing in the response.
+TEST_F(BufferedResourceLoaderTest, MissingHttpHeader) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests that a bad HTTP response is recived, e.g. file not found.
+TEST_F(BufferedResourceLoaderTest, BadHttpResponse) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(net::ERR_FAILED));
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Bot Found\n");
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests that partial content is requested but not fulfilled.
+TEST_F(BufferedResourceLoaderTest, NotPartialRange) {
+ Initialize(kHttpUrl, 100, -1);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ info.headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK\n");
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests that a 200 response is received.
+TEST_F(BufferedResourceLoaderTest, FullResponse) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ FullResponse(1024);
+ StopWhenLoad();
+}
+
+// Tests that a partial content response is received.
+TEST_F(BufferedResourceLoaderTest, PartialResponse) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(1024);
+ StopWhenLoad();
+}
+
+// Tests the logic of sliding window for data buffering and reading.
+TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Writes 10 bytes and read them back.
+ WriteLoader(10, 10);
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+
+ // Writes 10 bytes and read 2 times.
+ WriteLoader(20, 10);
+ EXPECT_CALL(*this, ReadCallback(5));
+ ReadLoader(20, 5, buffer);
+ VerifyBuffer(buffer, 20, 5);
+ EXPECT_CALL(*this, ReadCallback(5));
+ ReadLoader(25, 5, buffer);
+ VerifyBuffer(buffer, 25, 5);
+
+ // Read backward within buffer.
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+
+ // Read backwith outside buffer.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(9, 10, buffer);
+
+ // Response has completed.
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ loader_->OnCompletedRequest(status, "");
+
+ // Try to read 10 from position 25 will just return with 5 bytes.
+ EXPECT_CALL(*this, ReadCallback(5));
+ ReadLoader(25, 10, buffer);
+ VerifyBuffer(buffer, 25, 5);
+
+ // Try to read outside buffered range after request has completed.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(5, 10, buffer);
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(30, 10, buffer);
+}
+
+TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
+ Initialize(kHttpUrl, 10, 0x00FFFFFF);
+ Start();
+ PartialResponse(0x01000000);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Read very far aheard will get a cache miss.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(0x00FFFFFF, 1, buffer);
+
+ // The following call will not call ReadCallback() because it is waiting for
+ // data to arrive.
+ ReadLoader(10, 10, buffer);
+
+ // Writing to loader will fulfill the read request.
+ EXPECT_CALL(*this, ReadCallback(10));
+ WriteLoader(10, 20);
+ VerifyBuffer(buffer, 10, 10);
+
+ // The following call cannot be fulfilled now.
+ ReadLoader(25, 10, buffer);
+
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ EXPECT_CALL(*this, ReadCallback(5));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ loader_->OnCompletedRequest(status, "");
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ ReadLoader(10, 10, buffer);
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ loader_->OnCompletedRequest(status, "");
+}
+
+// TODO(hclam): add unit test for defer loading.
+
+class MockBufferedResourceLoader : public BufferedResourceLoader {
+ public:
+ MockBufferedResourceLoader() : BufferedResourceLoader() {
+ }
+
+ ~MockBufferedResourceLoader() {
+ OnDestroy();
+ }
+
+ MOCK_METHOD1(Start, void(net::CompletionCallback* read_callback));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer,
+ net::CompletionCallback* callback));
+ MOCK_METHOD0(content_length, int64());
+ MOCK_METHOD0(OnDestroy, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader);
+};
+
+// A mock BufferedDataSource to inject mock BufferedResourceLoader through
+// CreateLoader() method.
+class MockBufferedDataSource : public BufferedDataSource {
+ public:
+ // Static methods for creating this class.
+ static media::FilterFactory* CreateFactory(
+ MessageLoop* message_loop,
+ MediaResourceLoaderBridgeFactory* bridge_factory) {
+ return new media::FilterFactoryImpl2<
+ MockBufferedDataSource,
+ MessageLoop*,
+ MediaResourceLoaderBridgeFactory*>(message_loop,
+ bridge_factory);
+ }
+
+ MOCK_METHOD2(CreateLoader, BufferedResourceLoader*(int64 first_position,
+ int64 last_position));
+
+ protected:
+ MockBufferedDataSource(
+ MessageLoop* message_loop,
+ MediaResourceLoaderBridgeFactory* factory)
+ : BufferedDataSource(message_loop, factory) {
+ }
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ MockBufferedDataSource,
+ MessageLoop*,
+ MediaResourceLoaderBridgeFactory*>;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource);
+};
+
+class BufferedDataSourceTest : public testing::Test {
+ public:
+ BufferedDataSourceTest() {
+ message_loop_.reset(MessageLoop::current());
+ bridge_factory_.reset(
+ new StrictMock<MockMediaResourceLoaderBridgeFactory>());
+ ReleaseLoader();
+ factory_ = MockBufferedDataSource::CreateFactory(message_loop_.get(),
+ bridge_factory_.get());
+
+ // Prepare test data.
+ for (size_t i = 0; i < sizeof(data_); ++i) {
+ data_[i] = i;
+ }
+ }
+
+ ~BufferedDataSourceTest() {
+ if (data_source_) {
+ // Expects bridge factory to be destroyed along with data source.
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this,
+ &BufferedDataSourceTest::ReleaseBridgeFactory));
+ }
+
+ // We don't own the message loop so release it.
+ message_loop_.release();
+ }
+
+ void InitializeDataSource(const char* url, int error, int64 content_length) {
+ // Saves the url first.
+ gurl_ = GURL(url);
+
+ media::MediaFormat url_format;
+ url_format.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kURL);
+ url_format.SetAsString(media::MediaFormat::kURL, url);
+ data_source_ = factory_->Create<MockBufferedDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ // Creates the first mock loader to be injected.
+ loader_.reset(new StrictMock<MockBufferedResourceLoader>());
+
+ InSequence s;
+ StrictMock<media::MockFilterCallback> callback;
+ EXPECT_CALL(*data_source_, CreateLoader(-1, -1))
+ .WillOnce(Return(loader_.get()));
+ EXPECT_CALL(*loader_, Start(NotNull()))
+ .WillOnce(DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ if (error != net::OK) {
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(*loader_, Stop());
+ } else {
+ EXPECT_CALL(*loader_, content_length())
+ .WillOnce(Return(content_length));
+ EXPECT_CALL(host_, SetTotalBytes(content_length));
+ EXPECT_CALL(host_, SetBufferedBytes(content_length));
+ }
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+
+ data_source_->Initialize(url, callback.NewCallback());
+ message_loop_->RunAllPending();
+
+ if (error == net::OK) {
+ int64 size;
+ EXPECT_TRUE(data_source_->GetSize(&size));
+ EXPECT_EQ(content_length, size);
+ }
+ }
+
+ void StopDataSource() {
+ if (loader_.get()) {
+ InSequence s;
+ EXPECT_CALL(*loader_, Stop());
+ EXPECT_CALL(*loader_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedDataSourceTest::ReleaseLoader));
+ }
+
+ data_source_->Stop();
+ message_loop_->RunAllPending();
+ }
+
+ void ReleaseBridgeFactory() {
+ bridge_factory_.release();
+ }
+
+ void ReleaseLoader() {
+ loader_.release();
+ }
+
+ void InvokeStartCallback(net::CompletionCallback* callback) {
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ }
+
+ void InvokeReadCallback(int64 position, int size, uint8* buffer,
+ net::CompletionCallback* callback) {
+ if (error_ > 0)
+ memcpy(buffer, data_ + static_cast<int>(position), error_);
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ }
+
+ void ReadDataSourceHit(int64 position, int size, int read_size) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // Expect the read is delegated to the resource loader.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, read_size),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // The read has succeeded, so read callback will be called.
+ EXPECT_CALL(*this, ReadCallback(read_size));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0,
+ memcmp(buffer_, data_ + static_cast<int>(position), read_size));
+ }
+
+ void ReadDataSourceMiss(int64 position, int size) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // 1. Reply with a cache miss for the read.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // 2. Then the current loader will be stop and destroyed.
+ StrictMock<MockBufferedResourceLoader> *new_loader =
+ new StrictMock<MockBufferedResourceLoader>();
+ EXPECT_CALL(*loader_, Stop());
+ EXPECT_CALL(*data_source_, CreateLoader(position, -1))
+ .WillOnce(Return(new_loader));
+ EXPECT_CALL(*loader_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedDataSourceTest::ReleaseLoader));
+
+ // 3. Then the new loader will be started.
+ EXPECT_CALL(*new_loader, Start(NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::OK),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+
+ // 4. Then again a read request is made to the new loader.
+ EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, size),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ EXPECT_CALL(*this, ReadCallback(size));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size));
+
+ EXPECT_TRUE(loader_.get() == NULL);
+ loader_.reset(new_loader);
+ }
+
+ void ReadDataSourceFailed(int64 position, int size, int error) {
+ EXPECT_TRUE(loader_.get() != NULL);
+
+ InSequence s;
+ // 1. Expect the read is delegated to the resource loader.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ // 2. The read has failed, so read callback will be called.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+
+ // 3. Host will then receive an error.
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+
+ // 4. The the loader is destroyed.
+ EXPECT_CALL(*loader_, Stop());
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+
+ message_loop_->RunAllPending();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> >
+ bridge_factory_;
+ scoped_ptr<StrictMock<MockBufferedResourceLoader> > loader_;
+ scoped_refptr<MockBufferedDataSource > data_source_;
+ scoped_refptr<media::FilterFactory> factory_;
+
+ StrictMock<media::MockFilterHost> host_;
+ GURL gurl_;
+ scoped_ptr<MessageLoop> message_loop_;
+
+ int error_;
+ uint8 buffer_[1024];
+ uint8 data_[1024];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest);
+};
+
+TEST_F(BufferedDataSourceTest, InitializationSuccess) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, InitiailizationFailed) {
+ InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, 0);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadCacheHit) {
+ InitializeDataSource(kHttpUrl, net::OK, 25);
+
+ // Performs read with cache hit.
+ ReadDataSourceHit(10, 10, 10);
+
+ // Performs read with cache hit but partially filled.
+ ReadDataSourceHit(20, 10, 5);
+
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadCacheMiss) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ ReadDataSourceMiss(1000, 10);
+ ReadDataSourceMiss(20, 10);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadFailed) {
+ InitializeDataSource(kHttpUrl, net::OK, 1024);
+ ReadDataSourceHit(10, 10, 10);
+ ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET);
+ StopDataSource();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.h b/webkit/glue/media/media_resource_loader_bridge_factory.h
index 80c0ed2..453f655 100644
--- a/webkit/glue/media/media_resource_loader_bridge_factory.h
+++ b/webkit/glue/media/media_resource_loader_bridge_factory.h
@@ -38,6 +38,11 @@ class MediaResourceLoaderBridgeFactory {
int64 first_byte_position,
int64 last_byte_position);
+ protected:
+ // An empty constructor only used by inherited classes.
+ MediaResourceLoaderBridgeFactory() {
+ }
+
private:
FRIEND_TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders);
diff --git a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h
new file mode 100644
index 0000000..7bb27fe
--- /dev/null
+++ b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2009 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 WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_
+#define WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
+
+namespace webkit_glue {
+
+class MockMediaResourceLoaderBridgeFactory
+ : public webkit_glue::MediaResourceLoaderBridgeFactory {
+ public:
+ MockMediaResourceLoaderBridgeFactory() {
+ }
+
+ virtual ~MockMediaResourceLoaderBridgeFactory() {
+ OnDestroy();
+ }
+
+ MOCK_METHOD4(CreateBridge,
+ webkit_glue::ResourceLoaderBridge*(const GURL& url,
+ int load_flags,
+ int64 first_byte_position,
+ int64 last_byte_position));
+ MOCK_METHOD0(OnDestroy, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockMediaResourceLoaderBridgeFactory);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_MOCK_RESOURCE_LOADER_BRIDGE_H_
diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc
index f426a14..27d286e 100644
--- a/webkit/glue/media/simple_data_source.cc
+++ b/webkit/glue/media/simple_data_source.cc
@@ -36,7 +36,6 @@ SimpleDataSource::SimpleDataSource(
: render_loop_(render_loop),
bridge_factory_(bridge_factory),
size_(-1),
- position_(0),
state_(UNINITIALIZED) {
DCHECK(render_loop);
}
@@ -81,24 +80,20 @@ const media::MediaFormat& SimpleDataSource::media_format() {
return media_format_;
}
-size_t SimpleDataSource::Read(uint8* data, size_t size) {
+void SimpleDataSource::Read(int64 position,
+ size_t size,
+ uint8* data,
+ ReadCallback* read_callback) {
DCHECK_GE(size_, 0);
- size_t copied = std::min(size, static_cast<size_t>(size_ - position_));
- memcpy(data, data_.c_str() + position_, copied);
- position_ += copied;
- return copied;
-}
-
-bool SimpleDataSource::GetPosition(int64* position_out) {
- *position_out = position_;
- return true;
-}
-
-bool SimpleDataSource::SetPosition(int64 position) {
- if (position < 0 || position > size_)
- return false;
- position_ = position;
- return true;
+ if (position >= size_) {
+ read_callback->RunWithParams(Tuple1<size_t>(0));
+ delete read_callback;
+ } else {
+ size_t copied = std::min(size, static_cast<size_t>(size_ - position));
+ memcpy(data, data_.c_str() + position, copied);
+ read_callback->RunWithParams(Tuple1<size_t>(copied));
+ delete read_callback;
+ }
}
bool SimpleDataSource::GetSize(int64* size_out) {
diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h
index 8a63df4..1d15389 100644
--- a/webkit/glue/media/simple_data_source.h
+++ b/webkit/glue/media/simple_data_source.h
@@ -41,9 +41,8 @@ class SimpleDataSource : public media::DataSource,
virtual void Initialize(const std::string& url,
media::FilterCallback* callback);
virtual const media::MediaFormat& media_format();
- virtual size_t Read(uint8* data, size_t size);
- virtual bool GetPosition(int64* position_out);
- virtual bool SetPosition(int64 position);
+ virtual void Read(int64 position, size_t size,
+ uint8* data, ReadCallback* read_callback);
virtual bool GetSize(int64* size_out);
virtual bool IsSeekable();
@@ -91,7 +90,6 @@ class SimpleDataSource : public media::DataSource,
GURL url_;
std::string data_;
int64 size_;
- int64 position_;
// Simple state tracking variable.
enum State {
diff --git a/webkit/glue/media/simple_data_source_unittest.cc b/webkit/glue/media/simple_data_source_unittest.cc
new file mode 100644
index 0000000..9d367aa
--- /dev/null
+++ b/webkit/glue/media/simple_data_source_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2009 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/base/filters.h"
+#include "media/base/mock_filter_host.h"
+#include "media/base/mock_filters.h"
+#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h"
+#include "webkit/glue/media/simple_data_source.h"
+#include "webkit/glue/mock_resource_loader_bridge.h"
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::WithArgs;
+
+namespace {
+
+const int kDataSize = 1024;
+const char kHttpUrl[] = "http://test";
+const char kFtpUrl[] = "ftp://test";
+const char kHttpsUrl[] = "https://test";
+const char kFileUrl[] = "file://test";
+const char kInvalidUrl[] = "whatever://test";
+
+} // namespace
+
+namespace webkit_glue {
+
+class SimpleDataSourceTest : public testing::Test {
+ public:
+ SimpleDataSourceTest() {
+ bridge_factory_.reset(
+ new StrictMock<MockMediaResourceLoaderBridgeFactory>());
+ bridge_.reset(new StrictMock<MockResourceLoaderBridge>());
+ factory_ = SimpleDataSource::CreateFactory(MessageLoop::current(),
+ bridge_factory_.get());
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~SimpleDataSourceTest() {
+ if (bridge_.get()) {
+ EXPECT_CALL(*bridge_, OnDestroy());
+ }
+ }
+
+ void InitializeDataSource(const char* url) {
+ // Saves the url first.
+ gurl_ = GURL(url);
+
+ media::MediaFormat url_format;
+ url_format.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kURL);
+ url_format.SetAsString(media::MediaFormat::kURL, url);
+ data_source_ = factory_->Create<SimpleDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ // First a bridge is created.
+ InSequence s;
+ EXPECT_CALL(*bridge_factory_, CreateBridge(gurl_, _, -1, -1))
+ .WillOnce(Return(bridge_.get()));
+ EXPECT_CALL(*bridge_, Start(data_source_.get()))
+ .WillOnce(Return(true));
+
+ // TODO(hclam): need to add expectations to initialization callback.
+ data_source_->Initialize(url, callback_.NewCallback());
+
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestSucceeded() {
+ ResourceLoaderBridge::ResponseInfo info;
+ info.content_length = kDataSize;
+
+ data_source_->OnReceivedResponse(info, false);
+ int64 size;
+ EXPECT_TRUE(data_source_->GetSize(&size));
+ EXPECT_EQ(kDataSize, size);
+
+ for (int i = 0; i < kDataSize; ++i)
+ data_source_->OnReceivedData(data_ + i, 1);
+
+ InSequence s;
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge));
+ EXPECT_CALL(host_, SetTotalBytes(kDataSize));
+ EXPECT_CALL(host_, SetBufferedBytes(kDataSize));
+ EXPECT_CALL(callback_, OnFilterCallback());
+ EXPECT_CALL(callback_, OnCallbackDestroyed());
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ status.set_os_error(0);
+ data_source_->OnCompletedRequest(status, "");
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestFailed() {
+ InSequence s;
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge));
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(callback_, OnFilterCallback());
+ EXPECT_CALL(callback_, OnCallbackDestroyed());
+
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ status.set_os_error(100);
+ data_source_->OnCompletedRequest(status, "");
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void DestroyDataSource() {
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory));
+
+ data_source_->Stop();
+ MessageLoop::current()->RunAllPending();
+
+ data_source_ = NULL;
+ }
+
+ void AsyncRead() {
+ for (int i = 0; i < kDataSize; ++i) {
+ uint8 buffer[1];
+
+ EXPECT_CALL(*this, ReadCallback(1));
+ data_source_->Read(
+ i, 1, buffer,
+ NewCallback(this, &SimpleDataSourceTest::ReadCallback));
+ EXPECT_EQ(static_cast<uint8>(data_[i]), buffer[0]);
+ }
+ }
+
+ void ReleaseBridge() {
+ bridge_.release();
+ }
+
+ void ReleaseBridgeFactory() {
+ bridge_factory_.release();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ protected:
+ scoped_ptr<MessageLoop> message_loop_;
+ scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> > bridge_factory_;
+ scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_;
+ scoped_refptr<media::FilterFactory> factory_;
+ scoped_refptr<SimpleDataSource> data_source_;
+ StrictMock<media::MockFilterHost> host_;
+ StrictMock<media::MockFilterCallback> callback_;
+ GURL gurl_;
+ char data_[kDataSize];
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest);
+};
+
+TEST_F(SimpleDataSourceTest, InitializeHTTP) {
+ InitializeDataSource(kHttpUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeHTTPS) {
+ InitializeDataSource(kHttpsUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFTP) {
+ InitializeDataSource(kFtpUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFile) {
+ InitializeDataSource(kFileUrl);
+ RequestSucceeded();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeInvalid) {
+ StrictMock<media::MockFilterCallback> callback;
+ media::MediaFormat url_format;
+ url_format.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kURL);
+ url_format.SetAsString(media::MediaFormat::kURL, kInvalidUrl);
+ data_source_ = factory_->Create<SimpleDataSource>(url_format);
+ CHECK(data_source_);
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+
+ data_source_->Initialize(kInvalidUrl, callback.NewCallback());
+ data_source_->Stop();
+ MessageLoop::current()->RunAllPending();
+
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory));
+ data_source_ = NULL;
+}
+
+TEST_F(SimpleDataSourceTest, RequestFailed) {
+ InitializeDataSource(kHttpUrl);
+ RequestFailed();
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, StopWhenDownloading) {
+ InitializeDataSource(kHttpUrl);
+
+ EXPECT_CALL(*bridge_, Cancel());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge));
+ EXPECT_CALL(callback_, OnCallbackDestroyed());
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, AsyncRead) {
+ InitializeDataSource(kFileUrl);
+ RequestSucceeded();
+ AsyncRead();
+ DestroyDataSource();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/mock_resource_loader_bridge.h b/webkit/glue/mock_resource_loader_bridge.h
new file mode 100644
index 0000000..a3c40ba
--- /dev/null
+++ b/webkit/glue/mock_resource_loader_bridge.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2009 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 WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+#define WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+
+#include "base/file_path.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+namespace webkit_glue {
+
+class MockResourceLoaderBridge : public webkit_glue::ResourceLoaderBridge {
+ public:
+ MockResourceLoaderBridge() {
+ }
+
+ virtual ~MockResourceLoaderBridge() {
+ OnDestroy();
+ }
+
+ MOCK_METHOD2(AppendDataToUpload, void(const char* data, int data_len));
+ MOCK_METHOD3(AppendFileRangeToUpload, void(const FilePath& file_path,
+ uint64 offset,
+ uint64 length));
+ MOCK_METHOD1(SetUploadIdentifier, void(int64 identifier));
+ MOCK_METHOD1(Start, bool(ResourceLoaderBridge::Peer* peer));
+ MOCK_METHOD0(Cancel, void());
+ MOCK_METHOD1(SetDefersLoading, void(bool value));
+ MOCK_METHOD1(SyncLoad, void(SyncLoadResponse* response));
+ MOCK_METHOD0(OnDestroy, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridge);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp
index 2746641..539ad34 100644
--- a/webkit/tools/test_shell/test_shell.gyp
+++ b/webkit/tools/test_shell/test_shell.gyp
@@ -35,6 +35,7 @@
'../../../media/media.gyp:media',
'../../../net/net.gyp:net',
'../../../skia/skia.gyp:skia',
+ '../../../testing/gmock.gyp:gmock',
'../../../testing/gtest.gyp:gtest',
'../../../third_party/npapi/npapi.gyp:npapi',
'../../webkit.gyp:glue',
@@ -449,6 +450,7 @@
'dependencies': [
'test_shell_common',
'../../../skia/skia.gyp:skia',
+ '../../../testing/gmock.gyp:gmock',
'../../../testing/gtest.gyp:gtest',
],
'sources': [
@@ -467,8 +469,12 @@
'../../glue/dom_serializer_unittest.cc',
'../../glue/glue_serialize_unittest.cc',
'../../glue/iframe_redirect_unittest.cc',
+ '../../glue/media/buffered_data_source_unittest.cc',
'../../glue/media/media_resource_loader_bridge_factory_unittest.cc',
+ '../../glue/media/mock_media_resource_loader_bridge_factory.h',
+ '../../glue/media/simple_data_source_unittest.cc',
'../../glue/mimetype_unittest.cc',
+ '../../glue/mock_resource_loader_bridge.h',
'../../glue/multipart_response_delegate_unittest.cc',
'../../glue/password_autocomplete_listener_unittest.cc',
'../../glue/regular_expression_unittest.cc',
diff --git a/webkit/tools/test_shell/test_webview_delegate.cc b/webkit/tools/test_shell/test_webview_delegate.cc
index 6fbaa1f..55a2d8b 100644
--- a/webkit/tools/test_shell/test_webview_delegate.cc
+++ b/webkit/tools/test_shell/test_webview_delegate.cc
@@ -29,6 +29,7 @@
#include "webkit/api/public/WebURLError.h"
#include "webkit/api/public/WebURLRequest.h"
#include "webkit/glue/glue_serialize.h"
+#include "webkit/glue/media/buffered_data_source.h"
#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
#include "webkit/glue/media/simple_data_source.h"
#include "webkit/glue/webappcachecontext.h"
@@ -148,8 +149,9 @@ WebKit::WebMediaPlayer* TestWebViewDelegate::CreateWebMediaPlayer(
base::GetCurrentProcId(),
WebAppCacheContext::kNoAppCacheContextId,
0);
- factory->AddFactory(webkit_glue::SimpleDataSource::CreateFactory(
+ factory->AddFactory(webkit_glue::BufferedDataSource::CreateFactory(
MessageLoop::current(), bridge_factory));
+ // TODO(hclam): Use command line switch to determine which data source to use.
return new webkit_glue::WebMediaPlayerImpl(client, factory);
}
diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp
index 0b16eb4..c27323d 100644
--- a/webkit/webkit.gyp
+++ b/webkit/webkit.gyp
@@ -1242,6 +1242,8 @@
'glue/devtools/dom_agent_impl.cc',
'glue/devtools/dom_agent_impl.h',
'glue/devtools/tools_agent.h',
+ 'glue/media/buffered_data_source.cc',
+ 'glue/media/buffered_data_source.h',
'glue/media/media_resource_loader_bridge_factory.cc',
'glue/media/media_resource_loader_bridge_factory.h',
'glue/media/simple_data_source.cc',