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