diff options
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', |