summaryrefslogtreecommitdiffstats
path: root/webkit/glue/media
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-07-29 17:14:53 +0100
committerBen Murdoch <benm@google.com>2010-08-04 14:29:45 +0100
commitc407dc5cd9bdc5668497f21b26b09d988ab439de (patch)
tree7eaf8707c0309516bdb042ad976feedaf72b0bb1 /webkit/glue/media
parent0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff)
downloadexternal_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'webkit/glue/media')
-rw-r--r--webkit/glue/media/buffered_data_source.cc1052
-rw-r--r--webkit/glue/media/buffered_data_source.h413
-rw-r--r--webkit/glue/media/buffered_data_source_unittest.cc934
-rw-r--r--webkit/glue/media/media_resource_loader_bridge_factory.cc76
-rw-r--r--webkit/glue/media/media_resource_loader_bridge_factory.h76
-rw-r--r--webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc44
-rw-r--r--webkit/glue/media/mock_media_resource_loader_bridge_factory.h36
-rw-r--r--webkit/glue/media/simple_data_source.cc236
-rw-r--r--webkit/glue/media/simple_data_source.h125
-rw-r--r--webkit/glue/media/simple_data_source_unittest.cc252
-rw-r--r--webkit/glue/media/video_renderer_impl.cc321
-rw-r--r--webkit/glue/media/video_renderer_impl.h122
-rw-r--r--webkit/glue/media/web_video_renderer.h39
13 files changed, 3726 insertions, 0 deletions
diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc
new file mode 100644
index 0000000..dfac588
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.cc
@@ -0,0 +1,1052 @@
+// Copyright (c) 2010 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/callback.h"
+#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 "media/base/filter_host.h"
+#include "media/base/media_format.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/webkit_glue.h"
+
+namespace {
+
+const char kHttpScheme[] = "http";
+const char kHttpsScheme[] = "https";
+const char kDataScheme[] = "data";
+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 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): Set it to 5s, calibrate this value later.
+const int kTimeoutMilliseconds = 5000;
+
+// 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 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;
+
+// Returns true if |url| operates on HTTP protocol.
+bool IsHttpProtocol(const GURL& url) {
+ return url.SchemeIs(kHttpScheme) || url.SchemeIs(kHttpsScheme);
+}
+
+bool IsDataProtocol(const GURL& url) {
+ return url.SchemeIs(kDataScheme);
+}
+
+} // 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),
+ defer_allowed_(true),
+ completed_(false),
+ range_requested_(false),
+ partial_response_(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),
+ instance_size_(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,
+ NetworkEventCallback* event_callback) {
+ // Make sure we have not started.
+ DCHECK(!bridge_.get());
+ DCHECK(!start_callback_.get());
+ DCHECK(!event_callback_.get());
+ DCHECK(start_callback);
+ DCHECK(event_callback);
+
+ start_callback_.reset(start_callback);
+ event_callback_.reset(event_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_,
+ IsMediaCacheEnabled() ? net::LOAD_NORMAL : net::LOAD_BYPASS_CACHE,
+ first_byte_position_,
+ last_byte_position_));
+
+ // Increment the reference count right before we start the request. This
+ // reference will be release when this request has ended.
+ AddRef();
+
+ // And start the resource loading.
+ bridge_->Start(this);
+}
+
+void BufferedResourceLoader::Stop() {
+ // Reset callbacks.
+ start_callback_.reset();
+ event_callback_.reset();
+ read_callback_.reset();
+
+ // Use the internal buffer to signal that we have been stopped.
+ // TODO(hclam): Not so pretty to do this.
+ if (!buffer_.get())
+ return;
+
+ // Destroy internal buffer.
+ buffer_.reset();
+
+ if (bridge_.get()) {
+ // Cancel the request. This method call will cancel the request
+ // asynchronously. We may still get data or messages until we receive
+ // a response completed message.
+ if (deferred_)
+ bridge_->SetDefersLoading(false);
+ deferred_ = false;
+ bridge_->Cancel();
+ }
+}
+
+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);
+
+ // Save the parameter of reading.
+ read_callback_.reset(read_callback);
+ read_position_ = position;
+ read_size_ = read_size;
+ read_buffer_ = buffer;
+
+ // If read position is beyond the instance size, we cannot read there.
+ if (instance_size_ != kPositionNotSpecified &&
+ instance_size_ <= read_position_) {
+ DoneRead(0);
+ return;
+ }
+
+ // Make sure |offset_| and |read_position_| does not differ by a large
+ // amount.
+ if (read_position_ > offset_ + kint32max ||
+ read_position_ < offset_ + kint32min) {
+ DoneRead(net::ERR_CACHE_MISS);
+ return;
+ }
+
+ // Prepare the parameters.
+ first_offset_ = static_cast<int>(read_position_ - offset_);
+ last_offset_ = first_offset_ + read_size_;
+
+ // 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);
+}
+
+int64 BufferedResourceLoader::GetBufferedFirstBytePosition() {
+ if (buffer_.get())
+ return offset_ - static_cast<int>(buffer_->backward_bytes());
+ return kPositionNotSpecified;
+}
+
+int64 BufferedResourceLoader::GetBufferedLastBytePosition() {
+ if (buffer_.get())
+ return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1;
+ return kPositionNotSpecified;
+}
+
+void BufferedResourceLoader::SetAllowDefer(bool is_allowed) {
+ defer_allowed_ = is_allowed;
+ DisableDeferIfNeeded();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader,
+// webkit_glue::ResourceLoaderBridge::Peer implementations
+bool BufferedResourceLoader::OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies) {
+ DCHECK(bridge_.get());
+
+ // Save the new URL.
+ url_ = new_url;
+ // TODO(wtc): should we return a new first party for cookies URL?
+ *has_new_first_party_for_cookies = false;
+
+ // The load may have been stopped and |start_callback| is destroyed.
+ // In this case we shouldn't do anything.
+ if (!start_callback_.get())
+ return true;
+
+ if (!IsProtocolSupportedForMedia(new_url)) {
+ DoneStart(net::ERR_ADDRESS_INVALID);
+ Stop();
+ return false;
+ }
+ return true;
+}
+
+void BufferedResourceLoader::OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered) {
+ DCHECK(bridge_.get());
+
+ // The loader may have been stopped and |start_callback| is destroyed.
+ // In this case we shouldn't do anything.
+ if (!start_callback_.get())
+ return;
+
+ // We make a strong assumption that when we reach here we have either
+ // received a response from HTTP/HTTPS protocol or the request was
+ // successful (in particular range request). So we only verify the partial
+ // response for HTTP and HTTPS protocol.
+ if (IsHttpProtocol(url_)) {
+ 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 (info.headers->response_code() == kHttpPartialContent)
+ partial_response_ = true;
+
+ if (range_requested_ && partial_response_) {
+ // If we have verified the partial response and it is correct, we will
+ // return net::OK.
+ if (!VerifyPartialResponse(info))
+ 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;
+ }
+ } else {
+ // For any protocol other than HTTP and HTTPS, assume range request is
+ // always fulfilled.
+ partial_response_ = range_requested_;
+ }
+
+ // |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;
+
+ // If we have not requested a range, then the size of the instance is equal
+ // to the content length.
+ if (!partial_response_)
+ instance_size_ = content_length_;
+
+ // Calls with a successful response.
+ DoneStart(net::OK);
+}
+
+void BufferedResourceLoader::OnReceivedData(const char* data, int len) {
+ DCHECK(bridge_.get());
+
+ // If this loader has been stopped, |buffer_| would be destroyed.
+ // In this case we shouldn't do anything.
+ if (!buffer_.get())
+ return;
+
+ // Writes more data to |buffer_|.
+ buffer_->Append(reinterpret_cast<const uint8*>(data), len);
+
+ // If there is an active read request, try to fulfill the request.
+ if (HasPendingRead() && CanFulfillRead()) {
+ ReadInternal();
+ } else if (!defer_allowed_) {
+ // If we're not allowed to defer, slide the buffer window forward instead
+ // of deferring.
+ if (buffer_->forward_bytes() > buffer_->forward_capacity()) {
+ size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity();
+ bool success = buffer_->Seek(excess);
+ DCHECK(success);
+ offset_ += first_offset_ + excess;
+ }
+ }
+
+ // At last see if the buffer is full and we need to defer the downloading.
+ EnableDeferIfNeeded();
+
+ // Notify that we have received some data.
+ NotifyNetworkEvent();
+}
+
+void BufferedResourceLoader::OnCompletedRequest(
+ const URLRequestStatus& status, const std::string& security_info) {
+ DCHECK(bridge_.get());
+
+ // Saves the information that the request has completed.
+ completed_ = true;
+
+ // 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()) {
+ // Make sure we have a valid buffer before we satisfy a read request.
+ DCHECK(buffer_.get());
+
+ if (status.is_success()) {
+ // Try to fulfill with what is in the buffer.
+ if (CanFulfillRead())
+ ReadInternal();
+ else
+ DoneRead(net::ERR_CACHE_MISS);
+ } else {
+ // If the request has failed, then fail the read.
+ DoneRead(net::ERR_FAILED);
+ }
+ }
+
+ // There must not be any outstanding read request.
+ DCHECK(!HasPendingRead());
+
+ // Notify that network response is completed.
+ NotifyNetworkEvent();
+
+ // We incremented the reference count when the loader was started. We balance
+ // that reference here so that we get destroyed. This is also the only safe
+ // place to destroy the ResourceLoaderBridge.
+ bridge_.reset();
+ Release();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader, private
+void BufferedResourceLoader::EnableDeferIfNeeded() {
+ if (!defer_allowed_)
+ return;
+
+ if (!deferred_ &&
+ buffer_->forward_bytes() >= buffer_->forward_capacity()) {
+ deferred_ = true;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(true);
+
+ NotifyNetworkEvent();
+ }
+}
+
+void BufferedResourceLoader::DisableDeferIfNeeded() {
+ if (deferred_ &&
+ (!defer_allowed_ ||
+ buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) {
+ deferred_ = false;
+
+ if (bridge_.get())
+ bridge_->SetDefersLoading(false);
+
+ NotifyNetworkEvent();
+ }
+}
+
+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;
+}
+
+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_buffer_, read_size_));
+ offset_ += first_offset_ + read;
+
+ // And report with what we have read.
+ DoneRead(read);
+}
+
+bool BufferedResourceLoader::VerifyPartialResponse(
+ const ResourceLoaderBridge::ResponseInfo& info) {
+ int64 first_byte_position, last_byte_position, instance_size;
+ if (!info.headers->GetContentRange(&first_byte_position,
+ &last_byte_position,
+ &instance_size)) {
+ return false;
+ }
+
+ if (instance_size != kPositionNotSpecified)
+ instance_size_ = instance_size;
+
+ if (first_byte_position_ != -1 &&
+ first_byte_position_ != first_byte_position) {
+ return false;
+ }
+
+ // TODO(hclam): I should also check |last_byte_position|, but since
+ // we will never make such a request that it is ok to leave it unimplemented.
+ return true;
+}
+
+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();
+}
+
+void BufferedResourceLoader::NotifyNetworkEvent() {
+ if (event_callback_.get())
+ event_callback_->Run();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, static methods
+bool BufferedDataSource::IsMediaFormatSupported(
+ const media::MediaFormat& media_format) {
+ std::string mime_type;
+ std::string url;
+ if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) &&
+ media_format.GetAsString(media::MediaFormat::kURL, &url)) {
+ GURL gurl(url);
+
+ // This data source doesn't support data:// protocol, so reject it
+ // explicitly.
+ if (IsProtocolSupportedForMedia(gurl) && !IsDataProtocol(gurl))
+ return true;
+ }
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, protected
+BufferedDataSource::BufferedDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory)
+ : total_bytes_(kPositionNotSpecified),
+ loaded_(false),
+ streaming_(false),
+ bridge_factory_(bridge_factory),
+ loader_(NULL),
+ network_activity_(false),
+ initialize_callback_(NULL),
+ read_callback_(NULL),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ read_attempts_(0),
+ intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
+ intermediate_read_buffer_size_(kInitialReadBufferSize),
+ render_loop_(render_loop),
+ stop_signal_received_(false),
+ stopped_on_render_loop_(false),
+ media_is_paused_(true) {
+}
+
+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::CreateResourceLoader(
+ 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);
+}
+
+// This method simply returns kTimeoutMilliseconds. The purpose of this
+// method is to be overidded so as to provide a different timeout value
+// for testing purpose.
+base::TimeDelta BufferedDataSource::GetTimeoutMilliseconds() {
+ return base::TimeDelta::FromMilliseconds(kTimeoutMilliseconds);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::MediaFilter implementation
+void BufferedDataSource::Initialize(const std::string& url,
+ media::FilterCallback* callback) {
+ // Saves the url.
+ url_ = GURL(url);
+
+ if (!IsProtocolSupportedForMedia(url_)) {
+ // This method is called on the thread where host() lives so it is safe
+ // to make this call.
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ callback->Run();
+ delete callback;
+ return;
+ }
+
+ DCHECK(callback);
+ initialize_callback_.reset(callback);
+
+ 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(media::FilterCallback* callback) {
+ {
+ AutoLock auto_lock(lock_);
+ stop_signal_received_ = true;
+ }
+ if (callback) {
+ callback->Run();
+ delete callback;
+ }
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::CleanupTask));
+}
+
+void BufferedDataSource::SetPlaybackRate(float playback_rate) {
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::SetPlaybackRateTask,
+ playback_rate));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, media::DataSource implementation
+void BufferedDataSource::Read(int64 position, size_t size, uint8* data,
+ 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::IsStreaming() {
+ return streaming_;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedDataSource, render thread tasks
+void BufferedDataSource::InitializeTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!loader_.get());
+ DCHECK(!stopped_on_render_loop_);
+
+ // Kick starts the watch dog task that will handle connection timeout.
+ // We run the watch dog 2 times faster the actual timeout so as to catch
+ // the timeout more accurately.
+ watch_dog_timer_.Start(
+ GetTimeoutMilliseconds() / 2,
+ this,
+ &BufferedDataSource::WatchDogTask);
+
+ if (IsHttpProtocol(url_)) {
+ // Fetch only first 1024 bytes as this usually covers the header portion
+ // of a media file that gives enough information about the codecs, etc.
+ // This also serve as a probe to determine server capability to serve
+ // range request.
+ // TODO(hclam): Do some experiments for the best approach.
+ loader_ = CreateResourceLoader(0, 1024);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::HttpInitialStartCallback),
+ NewCallback(this, &BufferedDataSource::NetworkEventCallback));
+ } else {
+ // For all other protocols, assume they support range request. We fetch
+ // the full range of the resource to obtain the instance size because
+ // we won't be served HTTP headers.
+ loader_ = CreateResourceLoader(-1, -1);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback),
+ NewCallback(this, &BufferedDataSource::NetworkEventCallback));
+ }
+}
+
+void BufferedDataSource::ReadTask(
+ int64 position, int read_size, uint8* buffer,
+ media::DataSource::ReadCallback* read_callback) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // If CleanupTask() was executed we should return immediately. We check this
+ // variable to prevent doing any actual work after clean up was done. We do
+ // not check |stop_signal_received_| because anything use of it has to be
+ // within |lock_| which is not desirable.
+ if (stopped_on_render_loop_)
+ return;
+
+ 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;
+ read_submitted_time_ = base::Time::Now();
+ read_attempts_ = 0;
+
+ // Call to read internal to perform the actual read.
+ ReadInternal();
+}
+
+void BufferedDataSource::CleanupTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!stopped_on_render_loop_);
+
+ // Stop the watch dog.
+ watch_dog_timer_.Stop();
+
+ // We just need to stop the loader, so it stops activity.
+ if (loader_.get())
+ loader_->Stop();
+
+ // Reset the parameters of the current read request.
+ read_callback_.reset();
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = 0;
+ read_submitted_time_ = base::Time();
+ read_attempts_ = 0;
+
+ // Signal that stop task has finished execution.
+ stopped_on_render_loop_ = true;
+}
+
+void BufferedDataSource::RestartLoadingTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // This variable is set in CleanupTask(). We check this and do an early
+ // return. The sequence of actions which enable this conditions is:
+ // 1. Stop() is called from the pipeline.
+ // 2. ReadCallback() is called from the resource loader.
+ // 3. CleanupTask() is executed.
+ // 4. RestartLoadingTask() is executed.
+ if (stopped_on_render_loop_)
+ return;
+
+ // If there's no outstanding read then return early.
+ if (!read_callback_.get())
+ return;
+
+ loader_ = CreateResourceLoader(read_position_, -1);
+ loader_->SetAllowDefer(!media_is_paused_);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::PartialReadStartCallback),
+ NewCallback(this, &BufferedDataSource::NetworkEventCallback));
+}
+
+void BufferedDataSource::WatchDogTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!stopped_on_render_loop_);
+
+ // We only care if there is an active read request.
+ if (!read_callback_.get())
+ return;
+
+ DCHECK(loader_.get());
+ base::TimeDelta delta = base::Time::Now() - read_submitted_time_;
+ if (delta < GetTimeoutMilliseconds())
+ return;
+
+ // TODO(hclam): Maybe raise an error here. But if an error is reported
+ // the whole pipeline may get destroyed...
+ if (read_attempts_ >= kReadTrials)
+ return;
+
+ ++read_attempts_;
+ read_submitted_time_ = base::Time::Now();
+
+ // Stops the current loader and creates a new resource loader and
+ // retry the request.
+ loader_->Stop();
+ loader_ = CreateResourceLoader(read_position_, -1);
+ loader_->SetAllowDefer(!media_is_paused_);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::PartialReadStartCallback),
+ NewCallback(this, &BufferedDataSource::NetworkEventCallback));
+}
+
+void BufferedDataSource::SetPlaybackRateTask(float playback_rate) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ bool previously_paused = media_is_paused_;
+ media_is_paused_ = (playback_rate == 0.0);
+
+ // Disallow deferring data when we are pausing, allow deferring data
+ // when we resume playing.
+ if (previously_paused && !media_is_paused_) {
+ loader_->SetAllowDefer(true);
+ } else if (!previously_paused && media_is_paused_) {
+ loader_->SetAllowDefer(false);
+ }
+}
+
+// This method is the place where actual read happens, |loader_| must be valid
+// prior to make this method call.
+void BufferedDataSource::ReadInternal() {
+ 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_Locked(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(read_callback_.get());
+ 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_Locked() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ 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::HttpInitialStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ int64 instance_size = loader_->instance_size();
+ bool partial_response = loader_->partial_response();
+ bool success = error == net::OK;
+
+ if (success) {
+ // TODO(hclam): Needs more thinking about supporting servers without range
+ // request or their partial response is not complete.
+ total_bytes_ = instance_size;
+ loaded_ = false;
+ streaming_ = (instance_size == kPositionNotSpecified) || !partial_response;
+ } else {
+ // TODO(hclam): In case of failure, we can retry several times.
+ loader_->Stop();
+ }
+
+ // 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 (stop_signal_received_)
+ return;
+
+ if (!success) {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ DoneInitialization_Locked();
+ return;
+ }
+
+ if (streaming_) {
+ // If the server didn't reply with an instance size, it is likely this
+ // is a streaming response.
+ host()->SetStreaming(true);
+ } else {
+ // This value governs the range that we can seek to.
+ // TODO(hclam): Report the correct value of buffered bytes.
+ host()->SetTotalBytes(total_bytes_);
+ host()->SetBufferedBytes(0);
+ }
+
+ // Currently, only files can be used reliably w/o a network.
+ host()->SetLoaded(false);
+ DoneInitialization_Locked();
+}
+
+void BufferedDataSource::NonHttpInitialStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ int64 instance_size = loader_->instance_size();
+ bool success = error == net::OK && instance_size != kPositionNotSpecified;
+
+ if (success) {
+ total_bytes_ = instance_size;
+ loaded_ = true;
+ } else {
+ loader_->Stop();
+ }
+
+ // 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 (stop_signal_received_)
+ return;
+
+ if (success) {
+ host()->SetTotalBytes(total_bytes_);
+ host()->SetBufferedBytes(total_bytes_);
+ host()->SetLoaded(loaded_);
+ } else {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ }
+ DoneInitialization_Locked();
+}
+
+void BufferedDataSource::PartialReadStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ // This callback method is invoked after we have verified the server has
+ // range request capability, so as a safety guard verify again the response
+ // is partial.
+ if (error == net::OK && loader_->partial_response()) {
+ // Once the range request has started successfully, we can proceed with
+ // reading from it.
+ ReadInternal();
+ return;
+ }
+
+ // Stop the resource loader since we have received an error.
+ loader_->Stop();
+
+ // 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 (stop_signal_received_)
+ return;
+ DoneRead_Locked(net::ERR_INVALID_RESPONSE);
+}
+
+void BufferedDataSource::ReadCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ if (error < 0) {
+ DCHECK(loader_.get());
+
+ // Stop the resource load if it failed.
+ loader_->Stop();
+
+ if (error == net::ERR_CACHE_MISS) {
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &BufferedDataSource::RestartLoadingTask));
+ return;
+ }
+ }
+
+ // 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 (stop_signal_received_)
+ return;
+
+ 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_Locked(error);
+}
+
+void BufferedDataSource::NetworkEventCallback() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ // In case of non-HTTP request we don't need to report network events,
+ // so return immediately.
+ if (loaded_)
+ return;
+
+ bool network_activity = loader_->network_activity();
+ int64 buffered_last_byte_position = loader_->GetBufferedLastBytePosition();
+
+ // If we get an unspecified value, return immediately.
+ if (buffered_last_byte_position == kPositionNotSpecified)
+ return;
+
+ // 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 (stop_signal_received_)
+ return;
+
+ if (network_activity != network_activity_) {
+ network_activity_ = network_activity;
+ host()->SetNetworkActivity(network_activity);
+ }
+ host()->SetBufferedBytes(buffered_last_byte_position + 1);
+}
+
+} // 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..0dc2115
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source.h
@@ -0,0 +1,413 @@
+// Copyright (c) 2010 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 <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/lock.h"
+#include "base/scoped_ptr.h"
+#include "base/timer.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 base::RefCountedThreadSafe<BufferedResourceLoader>,
+ public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ typedef Callback0::Type NetworkEventCallback;
+
+ // |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);
+
+ // 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 |callback| is called with the result.
+ // |callback| is called with the following values:
+ // - net::OK
+ // The request has started successfully.
+ // - net::ERR_FAILED
+ // The request has failed because of an error with the network.
+ // - net::ERR_INVALID_RESPONSE
+ // An invalid response is received from the server.
+ // - (Anything else)
+ // An error code that indicates the request has failed.
+ // |event_callback| is called when the response is completed, data is
+ // received, the request is suspended or resumed.
+ virtual void Start(net::CompletionCallback* callback,
+ NetworkEventCallback* event_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.
+ // |callback| is called with the following values:
+ // - (Anything greater than or equal 0)
+ // Read was successful with the indicated number of bytes read.
+ // - net::ERR_FAILED
+ // The read has failed because of an error with the network.
+ // - net::ERR_CACHE_MISS
+ // The read was made too far away from the current buffered position.
+ virtual void Read(int64 position, int read_size,
+ uint8* buffer, net::CompletionCallback* callback);
+
+ // Returns the position of the first byte buffered. Returns -1 if such value
+ // is not available.
+ virtual int64 GetBufferedFirstBytePosition();
+
+ // Returns the position of the last byte buffered. Returns -1 if such value
+ // is not available.
+ virtual int64 GetBufferedLastBytePosition();
+
+ // Sets whether deferring data is allowed or disallowed.
+ virtual void SetAllowDefer(bool is_allowed);
+
+ // Gets the content length in bytes of the instance after this loader has been
+ // started. If this value is -1, then content length is unknown.
+ virtual int64 content_length() { return content_length_; }
+
+ // Gets the original size of the file requested. If this value is -1, then
+ // the size is unknown.
+ virtual int64 instance_size() { return instance_size_; }
+
+ // Returns true if the response for this loader is a partial response.
+ // It means a 206 response in HTTP/HTTPS protocol.
+ virtual bool partial_response() { return partial_response_; }
+
+ // Returns true if network is currently active.
+ virtual bool network_activity() { return !completed_ && !deferred_; }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // webkit_glue::ResourceLoaderBridge::Peer implementations.
+ virtual void OnUploadProgress(uint64 position, uint64 size) {}
+ virtual bool OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies);
+ 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);
+ GURL GetURLForDebugging() const { return url_; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<BufferedResourceLoader>;
+
+ virtual ~BufferedResourceLoader();
+
+ private:
+ friend class BufferedResourceLoaderTest;
+
+ // 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();
+
+ // Method that does the actual read and calls the |read_callbac_|, assuming
+ // the request range is in |buffer_|.
+ void ReadInternal();
+
+ // If we have made a range request, verify the response from the server.
+ bool VerifyPartialResponse(const ResourceLoaderBridge::ResponseInfo& info);
+
+ // 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);
+
+ // Calls |event_callback_| in terms of a network event.
+ void NotifyNetworkEvent();
+
+ bool HasPendingRead() { return read_callback_.get() != NULL; }
+
+ // A sliding window of buffer.
+ scoped_ptr<media::SeekableBuffer> buffer_;
+
+ // True if resource loading was deferred.
+ bool deferred_;
+
+ // True if resource loader is allowed to defer, false otherwise.
+ bool defer_allowed_;
+
+ // True if resource loading has completed.
+ bool completed_;
+
+ // True if a range request was made.
+ bool range_requested_;
+
+ // True if response data received is a partial range.
+ bool partial_response_;
+
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_;
+ GURL url_;
+ int64 first_byte_position_;
+ int64 last_byte_position_;
+
+ // Callback method that listens to network events.
+ scoped_ptr<NetworkEventCallback> event_callback_;
+
+ // Members used during request start.
+ scoped_ptr<net::CompletionCallback> start_callback_;
+ scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_;
+ int64 offset_;
+ int64 content_length_;
+ int64 instance_size_;
+
+ // 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::FilterFactoryImpl2 implementation.
+ static bool IsMediaFormatSupported(
+ const media::MediaFormat& media_format);
+
+ // media::MediaFilter implementation.
+ virtual void Initialize(const std::string& url,
+ media::FilterCallback* callback);
+ virtual void Stop(media::FilterCallback* callback);
+ virtual void SetPlaybackRate(float playback_rate);
+
+ // media::DataSource implementation.
+ // Called from demuxer thread.
+ virtual void Read(int64 position, size_t size,
+ uint8* data,
+ media::DataSource::ReadCallback* read_callback);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsStreaming();
+
+ 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* CreateResourceLoader(
+ int64 first_byte_position, int64 last_byte_position);
+
+ // Gets the number of milliseconds to declare a request timeout since
+ // the request was made. This method is made virtual so as to inject a
+ // different number for testing purpose.
+ virtual base::TimeDelta GetTimeoutMilliseconds();
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ BufferedDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>;
+
+ // Posted to perform initialization on render thread and start resource
+ // loading.
+ void InitializeTask();
+
+ // Task posted to perform 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. Stops |watch_dog_timer_| and
+ // |loader_|, reset Read() variables, and set |stopped_on_render_loop_|
+ // to signal any remaining tasks to stop.
+ void CleanupTask();
+
+ // Restart resource loading on render thread.
+ void RestartLoadingTask();
+
+ // This task monitors the current active read request. If the current read
+ // request has timed out, this task will destroy the current loader and
+ // creates a new one to accommodate the read request.
+ void WatchDogTask();
+
+ // This task uses the current playback rate with the previous playback rate
+ // to determine whether we are going from pause to play and play to pause,
+ // and signals the buffered resource loader accordingly.
+ void SetPlaybackRateTask(float playback_rate);
+
+ // The method that performs actual read. This method can only be executed on
+ // the render thread.
+ void ReadInternal();
+
+ // Calls |read_callback_| and reset all read parameters.
+ void DoneRead_Locked(int error);
+
+ // Calls |initialize_callback_| and reset it.
+ void DoneInitialization_Locked();
+
+ // Callback method for |loader_| if URL for the resource requested is using
+ // HTTP protocol. This method is called when response for initial request is
+ // received.
+ void HttpInitialStartCallback(int error);
+
+ // Callback method for |loader_| if URL for the resource requested is using
+ // a non-HTTP protocol, e.g. local files. This method is called when response
+ // for initial request is received.
+ void NonHttpInitialStartCallback(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);
+
+ // Callback method when a network event is received.
+ void NetworkEventCallback();
+
+ 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_;
+
+ // True if this data source is considered loaded.
+ bool loaded_;
+
+ // This value will be true if this data source can only support streaming.
+ // i.e. range request is not supported.
+ bool streaming_;
+
+ // A factory object to produce ResourceLoaderBridge.
+ scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_;
+
+ // A resource loader for the media resource.
+ scoped_refptr<BufferedResourceLoader> loader_;
+
+ // True if network is active.
+ bool network_activity_;
+
+ // Callback method from the pipeline for initialization.
+ scoped_ptr<media::FilterCallback> initialize_callback_;
+
+ // Read parameters received from the Read() method call.
+ scoped_ptr<media::DataSource::ReadCallback> read_callback_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+ base::Time read_submitted_time_;
+ int read_attempts_;
+
+ // 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_;
+
+ // Protects |stopped_|.
+ Lock lock_;
+
+ // Stop signal to suppressing activities. This variable is set on the pipeline
+ // thread and read from the render thread.
+ bool stop_signal_received_;
+
+ // This variable is set by CleanupTask() that indicates this object is stopped
+ // on the render thread.
+ bool stopped_on_render_loop_;
+
+ // This variable is true when we are in a paused state and false when we
+ // are in a playing state.
+ bool media_is_paused_;
+
+ // This timer is to run the WatchDogTask repeatedly. We use a timer instead
+ // of doing PostDelayedTask() reduce the extra reference held by the message
+ // loop. The RepeatingTimer does PostDelayedTask() internally, by using it
+ // the message loop doesn't hold a reference for the watch dog task.
+ base::RepeatingTimer<BufferedDataSource> watch_dog_timer_;
+
+ 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..ce42437
--- /dev/null
+++ b/webkit/glue/media/buffered_data_source_unittest.cc
@@ -0,0 +1,934 @@
+// Copyright (c) 2010 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/callback.h"
+#include "base/format_macros.h"
+#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::DeleteArg;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::WithArgs;
+
+namespace {
+
+const char* kHttpUrl = "http://test";
+const char* kFileUrl = "file://test";
+const int kDataSize = 1024;
+
+enum NetworkState {
+ NONE,
+ LOADED,
+ LOADING
+};
+
+} // namespace
+
+namespace webkit_glue {
+
+// Submit a request completed event to the resource loader due to request
+// being canceled. Pretending the event is from external.
+ACTION_P(RequestCanceled, loader) {
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::CANCELED);
+ status.set_os_error(net::ERR_ABORTED);
+ loader->OnCompletedRequest(status, "");
+}
+
+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_ = new BufferedResourceLoader(&bridge_factory_, gurl_,
+ first_position_, last_position_);
+ EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging().spec());
+ }
+
+ void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) {
+ loader_->buffer_.reset(
+ new media::SeekableBuffer(backward_capacity, forward_capacity));
+ }
+
+ void Start() {
+ InSequence s;
+ EXPECT_CALL(bridge_factory_,
+ CreateBridge(gurl_, _, first_position_, last_position_))
+ .WillOnce(Return(bridge_.get()));
+ EXPECT_CALL(*bridge_, Start(loader_.get()));
+ loader_->Start(
+ NewCallback(this, &BufferedResourceLoaderTest::StartCallback),
+ NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback));
+ }
+
+ void FullResponse(int64 instance_size) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 200 OK\n"
+ "Content-Length: %" PRId64,
+ instance_size);
+ replace(header.begin(), header.end(), '\n', '\0');
+ info.headers = new net::HttpResponseHeaders(header);
+ info.content_length = instance_size;
+ loader_->OnReceivedResponse(info, false);
+ EXPECT_EQ(instance_size, loader_->content_length());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+ EXPECT_FALSE(loader_->partial_response());
+ }
+
+ void PartialResponse(int64 first_position, int64 last_position,
+ int64 instance_size) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+ int64 content_length = last_position - first_position + 1;
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes "
+ "%" PRId64 "-%" PRId64 "/%" PRId64,
+ first_position,
+ last_position,
+ instance_size);
+ 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());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+ EXPECT_TRUE(loader_->partial_response());
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*bridge_, Cancel())
+ .WillOnce(RequestCanceled(loader_));
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ loader_->Stop();
+ }
+
+ void ReleaseBridge() {
+ ignore_result(bridge_.release());
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ EXPECT_CALL(*this, NetworkCallback())
+ .RetiresOnSaturation();
+ 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));
+ }
+
+ // Helper method to disallow deferring in |loader_|.
+ void DisallowLoaderDefer() {
+ if (loader_->deferred_) {
+ EXPECT_CALL(*bridge_, SetDefersLoading(false));
+ EXPECT_CALL(*this, NetworkCallback());
+ }
+ loader_->SetAllowDefer(false);
+ }
+
+ // Helper method to allow deferring in |loader_|.
+ void AllowLoaderDefer() {
+ loader_->SetAllowDefer(true);
+ }
+
+ MOCK_METHOD1(StartCallback, void(int error));
+ MOCK_METHOD1(ReadCallback, void(int error));
+ MOCK_METHOD0(NetworkCallback, void());
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+ int64 last_position_;
+
+ scoped_refptr<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())
+ .WillOnce(RequestCanceled(loader_));
+ 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())
+ .WillOnce(RequestCanceled(loader_));
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Not Found\n");
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests that partial content is requested but not fulfilled.
+TEST_F(BufferedResourceLoaderTest, NotPartialResponse) {
+ Initialize(kHttpUrl, 100, -1);
+ Start();
+ FullResponse(1024);
+ StopWhenLoad();
+}
+
+// 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(100, 200, 1024);
+ StopWhenLoad();
+}
+
+// Tests that an invalid partial response is received.
+TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) {
+ Initialize(kHttpUrl, 0, 10);
+ Start();
+
+ EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE));
+ EXPECT_CALL(*bridge_, Cancel())
+ .WillOnce(RequestCanceled(loader_));
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+
+ ResourceLoaderBridge::ResponseInfo info;
+ std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes %d-%d/%d",
+ 1, 10, 1024);
+ replace(header.begin(), header.end(), '\n', '\0');
+ info.headers = new net::HttpResponseHeaders(header);
+ info.content_length = 10;
+ loader_->OnReceivedResponse(info, false);
+}
+
+// Tests the logic of sliding window for data buffering and reading.
+TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(10, 29, 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 backward outside buffer.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(9, 10, buffer);
+
+ // Response has completed.
+ EXPECT_CALL(*this, NetworkCallback());
+ 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);
+
+ // Try to read beyond the instance size.
+ EXPECT_CALL(*this, ReadCallback(0));
+ ReadLoader(30, 10, buffer);
+}
+
+TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
+ Initialize(kHttpUrl, 10, 0x00FFFFFF);
+ Start();
+ PartialResponse(10, 0x00FFFFFF, 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(*this, ReadCallback(5));
+ EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::SUCCESS);
+ loader_->OnCompletedRequest(status, "");
+}
+
+TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) {
+ Initialize(kHttpUrl, 10, 29);
+ Start();
+ PartialResponse(10, 29, 30);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ ReadLoader(10, 10, buffer);
+ EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
+ EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*bridge_, OnDestroy())
+ .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge));
+ URLRequestStatus status;
+ status.set_status(URLRequestStatus::FAILED);
+ loader_->OnCompletedRequest(status, "");
+}
+
+// Tests the logic of caching data to disk when media is paused.
+TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ // Start in undeferred state, then disallow defer, then allow defer
+ // without receiving data in between.
+ DisallowLoaderDefer();
+ AllowLoaderDefer();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in undeferred state, disallow defer, receive data but don't shift
+ // buffer window, then allow defer and read.
+ DisallowLoaderDefer();
+ WriteLoader(10, 10);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(10, 10, buffer);
+ VerifyBuffer(buffer, 10, 10);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Not deferred, disallow defer, received data and shift buffer window,
+ // allow defer, then read in area outside of buffer window.
+ DisallowLoaderDefer();
+ WriteLoader(10, 10);
+ WriteLoader(20, 50);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(10, 10, buffer);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, then disallow defer, receive no data, and
+ // allow defer and read.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 40);
+
+ DisallowLoaderDefer();
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(20, 10, buffer);
+ VerifyBuffer(buffer, 20, 10);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, disallow defer, receive data and shift buffer
+ // window, allow defer, and read in a place that's still in the window.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 30);
+
+ DisallowLoaderDefer();
+ WriteLoader(40, 5);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(10));
+ ReadLoader(20, 10, buffer);
+ VerifyBuffer(buffer, 20, 10);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Start in deferred state, disallow defer, receive data and shift buffer
+ // window, allow defer, and read outside of the buffer window.
+ EXPECT_CALL(*bridge_, SetDefersLoading(true));
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteLoader(10, 40);
+
+ DisallowLoaderDefer();
+ WriteLoader(50, 20);
+ WriteLoader(70, 40);
+ AllowLoaderDefer();
+
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(20, 5, buffer);
+ StopWhenLoad();
+}
+
+// TODO(hclam): add unit test for defer loading.
+
+class MockBufferedResourceLoader : public BufferedResourceLoader {
+ public:
+ MockBufferedResourceLoader() : BufferedResourceLoader(NULL, GURL(), 0, 0) {
+ }
+
+ MOCK_METHOD2(Start, void(net::CompletionCallback* read_callback,
+ NetworkEventCallback* network_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(instance_size, int64());
+ MOCK_METHOD0(partial_response, bool());
+ MOCK_METHOD0(network_activity, bool());
+ MOCK_METHOD0(GetBufferedFirstBytePosition, int64());
+ MOCK_METHOD0(GetBufferedLastBytePosition, int64());
+
+ protected:
+ ~MockBufferedResourceLoader() {}
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader);
+};
+
+// A mock BufferedDataSource to inject mock BufferedResourceLoader through
+// CreateResourceLoader() 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);
+ }
+
+ virtual base::TimeDelta GetTimeoutMilliseconds() {
+ // It is 100 ms because we don't want the test to run too long.
+ return base::TimeDelta::FromMilliseconds(100);
+ }
+
+ MOCK_METHOD2(CreateResourceLoader, 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_ = MessageLoop::current();
+ bridge_factory_.reset(
+ new StrictMock<MockMediaResourceLoaderBridgeFactory>());
+ factory_ = MockBufferedDataSource::CreateFactory(message_loop_,
+ bridge_factory_.get());
+
+ // Prepare test data.
+ for (size_t i = 0; i < sizeof(data_); ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~BufferedDataSourceTest() {
+ if (data_source_) {
+ // Release the bridge factory because we don't own it.
+ // Expects bridge factory to be destroyed along with data source.
+ EXPECT_CALL(*bridge_factory_, OnDestroy())
+ .WillOnce(Invoke(this,
+ &BufferedDataSourceTest::ReleaseBridgeFactory));
+ }
+ }
+
+ void InitializeDataSource(const char* url, int error,
+ bool partial_response, int64 instance_size,
+ NetworkState networkState) {
+ // 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 mock loader to be injected.
+ loader_ = new StrictMock<MockBufferedResourceLoader>();
+
+ bool loaded = networkState == LOADED;
+ {
+ InSequence s;
+ EXPECT_CALL(*data_source_, CreateResourceLoader(_, _))
+ .WillOnce(Return(loader_.get()));
+
+ // The initial response loader will be started.
+ EXPECT_CALL(*loader_, Start(NotNull(), NotNull()))
+ .WillOnce(
+ DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ }
+
+ StrictMock<media::MockFilterCallback> callback;
+ EXPECT_CALL(*loader_, instance_size())
+ .WillRepeatedly(Return(instance_size));
+ EXPECT_CALL(*loader_, partial_response())
+ .WillRepeatedly(Return(partial_response));
+ if (error == net::OK) {
+ // Expected loaded or not.
+ EXPECT_CALL(host_, SetLoaded(loaded));
+
+ // TODO(hclam): The condition for streaming needs to be adjusted.
+ if (instance_size != -1 && (loaded || partial_response)) {
+ EXPECT_CALL(host_, SetTotalBytes(instance_size));
+ if (loaded)
+ EXPECT_CALL(host_, SetBufferedBytes(instance_size));
+ else
+ EXPECT_CALL(host_, SetBufferedBytes(0));
+ } else {
+ EXPECT_CALL(host_, SetStreaming(true));
+ }
+
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+ } else {
+ EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK));
+ EXPECT_CALL(*loader_, Stop());
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+ }
+
+ // Actual initialization of the data source.
+ data_source_->Initialize(url, callback.NewCallback());
+ message_loop_->RunAllPending();
+
+ if (error == net::OK) {
+ // Verify the size of the data source.
+ int64 size;
+ if (instance_size != -1 && (loaded || partial_response)) {
+ EXPECT_TRUE(data_source_->GetSize(&size));
+ EXPECT_EQ(instance_size, size);
+ } else {
+ EXPECT_TRUE(data_source_->IsStreaming());
+ }
+ }
+ }
+
+ void StopDataSource() {
+ if (loader_) {
+ InSequence s;
+ EXPECT_CALL(*loader_, Stop());
+ }
+
+ StrictMock<media::MockFilterCallback> callback;
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+ data_source_->Stop(callback.NewCallback());
+ message_loop_->RunAllPending();
+ }
+
+ void ReleaseBridgeFactory() {
+ ignore_result(bridge_factory_.release());
+ }
+
+ void InvokeStartCallback(
+ net::CompletionCallback* callback,
+ BufferedResourceLoader::NetworkEventCallback* network_callback) {
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ // TODO(hclam): Save this callback.
+ delete network_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_);
+
+ 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_);
+
+ // 1. Reply with a cache miss for the read.
+ {
+ InSequence s;
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+ EXPECT_CALL(*loader_, Stop());
+ }
+
+ // 2. Then the current loader will be stop and destroyed.
+ StrictMock<MockBufferedResourceLoader> *new_loader =
+ new StrictMock<MockBufferedResourceLoader>();
+ EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1))
+ .WillOnce(Return(new_loader));
+
+ // 3. Then the new loader will be started.
+ EXPECT_CALL(*new_loader, Start(NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::OK),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ EXPECT_CALL(*new_loader, partial_response())
+ .WillRepeatedly(Return(loader_->partial_response()));
+
+ // 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));
+
+ loader_ = new_loader;
+ }
+
+ void ReadDataSourceFailed(int64 position, int size, int error) {
+ EXPECT_TRUE(loader_);
+
+ // 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. Host will then receive an error.
+ EXPECT_CALL(*loader_, Stop());
+
+ // 3. The read has failed, so read callback will be called.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+
+ message_loop_->RunAllPending();
+ }
+
+ void ReadDataSourceTimesOut(int64 position, int size) {
+ // 1. Drop the request and let it times out.
+ {
+ InSequence s;
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DeleteArg<3>());
+ EXPECT_CALL(*loader_, Stop());
+ }
+
+ // 2. Then the current loader will be stop and destroyed.
+ StrictMock<MockBufferedResourceLoader> *new_loader =
+ new StrictMock<MockBufferedResourceLoader>();
+ EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1))
+ .WillOnce(Return(new_loader));
+
+ // 3. Then the new loader will be started and respond to queries about
+ // whether this is a partial response using the value of the previous
+ // loader.
+ EXPECT_CALL(*new_loader, Start(NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::OK),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ EXPECT_CALL(*new_loader, partial_response())
+ .WillRepeatedly(Return(loader_->partial_response()));
+
+ // 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),
+ InvokeWithoutArgs(message_loop_,
+ &MessageLoop::Quit)));
+
+ EXPECT_CALL(*this, ReadCallback(size));
+
+ data_source_->Read(
+ position, size, buffer_,
+ NewCallback(this, &BufferedDataSourceTest::ReadCallback));
+
+ // This blocks the current thread until the watch task is executed and
+ // triggers a read callback to quit this message loop.
+ message_loop_->Run();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size));
+
+ loader_ = new_loader;
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> >
+ bridge_factory_;
+ scoped_refptr<StrictMock<MockBufferedResourceLoader> > loader_;
+ scoped_refptr<MockBufferedDataSource> data_source_;
+ scoped_refptr<media::FilterFactory> factory_;
+
+ StrictMock<media::MockFilterHost> host_;
+ GURL gurl_;
+ 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, true, 1024, LOADING);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, InitiailizationFailed) {
+ InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, false, 0, NONE);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, MissingContentLength) {
+ InitializeDataSource(kHttpUrl, net::OK, true, -1, LOADING);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, RangeRequestNotSupported) {
+ InitializeDataSource(kHttpUrl, net::OK, false, 1024, LOADING);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest,
+ MissingContentLengthAndRangeRequestNotSupported) {
+ InitializeDataSource(kHttpUrl, net::OK, false, -1, LOADING);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadCacheHit) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING);
+
+ // 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, true, 1024, LOADING);
+ ReadDataSourceMiss(1000, 10);
+ ReadDataSourceMiss(20, 10);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadFailed) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING);
+ ReadDataSourceHit(10, 10, 10);
+ ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadTimesOut) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING);
+ ReadDataSourceTimesOut(20, 10);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, FileHasLoadedState) {
+ InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED);
+ ReadDataSourceTimesOut(20, 10);
+ StopDataSource();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.cc b/webkit/glue/media/media_resource_loader_bridge_factory.cc
new file mode 100644
index 0000000..1961bcc
--- /dev/null
+++ b/webkit/glue/media/media_resource_loader_bridge_factory.cc
@@ -0,0 +1,76 @@
+// 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 "webkit/glue/media/media_resource_loader_bridge_factory.h"
+
+#include "base/format_macros.h"
+#include "base/string_util.h"
+
+namespace {
+
+// A constant for an unknown position.
+const int64 kPositionNotSpecified = -1;
+
+} // namespace
+
+namespace webkit_glue {
+
+MediaResourceLoaderBridgeFactory::MediaResourceLoaderBridgeFactory(
+ const GURL& referrer,
+ const std::string& frame_origin,
+ const std::string& main_frame_origin,
+ int origin_pid,
+ int appcache_host_id,
+ int32 routing_id)
+ : referrer_(referrer),
+ frame_origin_(frame_origin),
+ main_frame_origin_(main_frame_origin),
+ origin_pid_(origin_pid),
+ appcache_host_id_(appcache_host_id),
+ routing_id_(routing_id) {
+}
+
+ResourceLoaderBridge* MediaResourceLoaderBridgeFactory::CreateBridge(
+ const GURL& url,
+ int load_flags,
+ int64 first_byte_position,
+ int64 last_byte_position) {
+ webkit_glue::ResourceLoaderBridge::RequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = url;
+ request_info.first_party_for_cookies = url;
+ request_info.referrer = referrer_;
+ request_info.frame_origin = frame_origin_;
+ request_info.main_frame_origin = main_frame_origin_;
+ request_info.headers = GenerateHeaders(first_byte_position,
+ last_byte_position);
+ request_info.load_flags = load_flags;
+ request_info.requestor_pid = origin_pid_;
+ request_info.request_type = ResourceType::MEDIA;
+ request_info.appcache_host_id = appcache_host_id_;
+ request_info.routing_id = routing_id_;
+ return webkit_glue::ResourceLoaderBridge::Create(request_info);
+}
+
+// static
+const std::string MediaResourceLoaderBridgeFactory::GenerateHeaders (
+ int64 first_byte_position, int64 last_byte_position) {
+ // Construct the range header.
+ std::string header;
+ if (first_byte_position > kPositionNotSpecified &&
+ last_byte_position > kPositionNotSpecified) {
+ if (first_byte_position <= last_byte_position) {
+ header = StringPrintf("Range: bytes=%" PRId64 "-%" PRId64,
+ first_byte_position,
+ last_byte_position);
+ }
+ } else if (first_byte_position > kPositionNotSpecified) {
+ header = StringPrintf("Range: bytes=%" PRId64 "-", first_byte_position);
+ } else if (last_byte_position > kPositionNotSpecified) {
+ NOTIMPLEMENTED() << "Suffix range not implemented";
+ }
+ return header;
+}
+
+} // 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
new file mode 100644
index 0000000..6408949
--- /dev/null
+++ b/webkit/glue/media/media_resource_loader_bridge_factory.h
@@ -0,0 +1,76 @@
+// 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_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+#define WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
+
+#include "testing/gtest/include/gtest/gtest_prod.h"
+#include "webkit/glue/resource_loader_bridge.h"
+
+namespace webkit_glue {
+
+// A factory used to create a ResourceLoaderBridge for the media player.
+// This factory is used also for testing. Testing code can use this class and
+// override CreateBridge() to inject a mock ResourceLoaderBridge for code that
+// interacts with it, e.g. BufferedDataSource.
+class MediaResourceLoaderBridgeFactory {
+ public:
+ MediaResourceLoaderBridgeFactory(
+ const GURL& referrer,
+ const std::string& frame_origin,
+ const std::string& main_frame_origin,
+ int origin_pid,
+ int appcache_host_id,
+ int32 routing_id);
+
+ virtual ~MediaResourceLoaderBridgeFactory() {}
+
+ // Factory method to create a ResourceLoaderBridge with the following
+ // parameters:
+ // |url| - URL of the resource to be loaded.
+ // |load_flags| - Load flags for this loading.
+ // |first_byte_position| - First byte position for a range request, -1 if not.
+ // |last_byte_position| - Last byte position for a range request, -1 if not.
+ virtual ResourceLoaderBridge* CreateBridge(
+ const GURL& url,
+ int load_flags,
+ int64 first_byte_position,
+ int64 last_byte_position);
+
+ protected:
+ // An empty constructor only used by inherited classes.
+ MediaResourceLoaderBridgeFactory() {
+ }
+
+ private:
+ FRIEND_TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders);
+
+ // Returns a range request header using parameters |first_byte_position| and
+ // |last_byte_position|.
+ // Negative numbers other than -1 are not allowed for |first_byte_position|
+ // and |last_byte_position|. |first_byte_position| should always be less than
+ // or equal to |last_byte_position| if they are both not -1.
+ // Here's a list of valid parameters:
+ // |first_byte_position| |last_byte_position|
+ // 0 1000
+ // 4096 4096
+ // 0 -1
+ // -1 -1
+ // Empty string is returned on invalid parameters.
+ static const std::string GenerateHeaders(int64 first_byte_position,
+ int64 last_byte_position);
+
+ GURL first_party_for_cookies_;
+ GURL referrer_;
+ std::string frame_origin_;
+ std::string main_frame_origin_;
+ std::string headers_;
+ int origin_pid_;
+ int appcache_host_id_;
+ int32 routing_id_;
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_
diff --git a/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc
new file mode 100644
index 0000000..4c0126b
--- /dev/null
+++ b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc
@@ -0,0 +1,44 @@
+// 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 "net/http/http_util.h"
+#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webkit_glue {
+
+TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders) {
+ static const struct {
+ const bool success;
+ const int64 first_byte_position;
+ const int64 last_byte_position;
+ } data[] = {
+ { false, -1, -1 },
+ { false, -5, 0 },
+ { false, 100, 0 },
+ { true, 0, -1 },
+ { true, 0, 0 },
+ { true, 100, 100 },
+ { true, 50, -1 },
+ { true, 10000, -1 },
+ { true, 50, 100 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
+ std::string headers = MediaResourceLoaderBridgeFactory::GenerateHeaders(
+ data[i].first_byte_position, data[i].last_byte_position);
+ std::vector<net::HttpByteRange> ranges;
+ bool ret = net::HttpUtil::ParseRanges(headers, &ranges);
+ EXPECT_EQ(data[i].success, ret);
+ if (ret) {
+ EXPECT_EQ(1u, ranges.size());
+ EXPECT_EQ(data[i].first_byte_position,
+ ranges[0].first_byte_position());
+ EXPECT_EQ(data[i].last_byte_position,
+ ranges[0].last_byte_position());
+ }
+ }
+}
+
+} // namespace webkit_glue
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
new file mode 100644
index 0000000..20bf0af
--- /dev/null
+++ b/webkit/glue/media/simple_data_source.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2010 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/message_loop.h"
+#include "base/process_util.h"
+#include "media/base/filter_host.h"
+#include "net/base/load_flags.h"
+#include "net/base/data_url.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_status.h"
+#include "webkit/glue/media/simple_data_source.h"
+#include "webkit/glue/resource_loader_bridge.h"
+#include "webkit/glue/webkit_glue.h"
+
+namespace {
+
+const char kHttpScheme[] = "http";
+const char kHttpsScheme[] = "https";
+const char kDataScheme[] = "data";
+
+// A helper method that accepts only HTTP, HTTPS and FILE protocol.
+bool IsDataProtocol(const GURL& url) {
+ return url.SchemeIs(kDataScheme);
+}
+
+} // namespace
+
+namespace webkit_glue {
+
+bool SimpleDataSource::IsMediaFormatSupported(
+ const media::MediaFormat& media_format) {
+ std::string mime_type;
+ std::string url;
+ if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) &&
+ media_format.GetAsString(media::MediaFormat::kURL, &url)) {
+ GURL gurl(url);
+ if (IsProtocolSupportedForMedia(gurl))
+ return true;
+ }
+ return false;
+}
+
+SimpleDataSource::SimpleDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory)
+ : render_loop_(render_loop),
+ bridge_factory_(bridge_factory),
+ size_(-1),
+ state_(UNINITIALIZED) {
+ DCHECK(render_loop);
+}
+
+SimpleDataSource::~SimpleDataSource() {
+ AutoLock auto_lock(lock_);
+ DCHECK(state_ == UNINITIALIZED || state_ == STOPPED);
+}
+
+void SimpleDataSource::Stop(media::FilterCallback* callback) {
+ AutoLock auto_lock(lock_);
+ state_ = STOPPED;
+ if (callback) {
+ callback->Run();
+ delete callback;
+ }
+
+ // Post a task to the render thread to cancel loading the resource.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &SimpleDataSource::CancelTask));
+}
+
+void SimpleDataSource::Initialize(const std::string& url,
+ media::FilterCallback* callback) {
+ AutoLock auto_lock(lock_);
+ DCHECK_EQ(state_, UNINITIALIZED);
+ DCHECK(callback);
+ state_ = INITIALIZING;
+ initialize_callback_.reset(callback);
+
+ // Validate the URL.
+ SetURL(GURL(url));
+ if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ initialize_callback_->Run();
+ initialize_callback_.reset();
+ return;
+ }
+
+ // Post a task to the render thread to start loading the resource.
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &SimpleDataSource::StartTask));
+}
+
+const media::MediaFormat& SimpleDataSource::media_format() {
+ return media_format_;
+}
+
+void SimpleDataSource::Read(int64 position,
+ size_t size,
+ uint8* data,
+ ReadCallback* read_callback) {
+ DCHECK_GE(size_, 0);
+ 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) {
+ *size_out = size_;
+ return true;
+}
+
+bool SimpleDataSource::IsStreaming() {
+ return false;
+}
+
+void SimpleDataSource::OnDownloadProgress(uint64 position, uint64 size) {}
+
+void SimpleDataSource::OnUploadProgress(uint64 position, uint64 size) {}
+
+bool SimpleDataSource::OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies) {
+ SetURL(new_url);
+ // TODO(wtc): should we return a new first party for cookies URL?
+ *has_new_first_party_for_cookies = false;
+ return true;
+}
+
+void SimpleDataSource::OnReceivedResponse(
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool content_filtered) {
+ size_ = info.content_length;
+}
+
+void SimpleDataSource::OnReceivedData(const char* data, int len) {
+ data_.append(data, len);
+}
+
+void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status,
+ const std::string& security_info) {
+ AutoLock auto_lock(lock_);
+ // It's possible this gets called after Stop(), in which case |host_| is no
+ // longer valid.
+ if (state_ == STOPPED) {
+ return;
+ }
+
+ // Otherwise we should be initializing and have created a bridge.
+ DCHECK_EQ(state_, INITIALIZING);
+ DCHECK(bridge_.get());
+ bridge_.reset();
+
+ // If we don't get a content length or the request has failed, report it
+ // as a network error.
+ DCHECK(size_ == -1 || static_cast<size_t>(size_) == data_.length());
+ if (size_ == -1) {
+ size_ = data_.length();
+ }
+
+ DoneInitialization_Locked(status.is_success());
+}
+
+GURL SimpleDataSource::GetURLForDebugging() const {
+ return url_;
+}
+
+void SimpleDataSource::SetURL(const GURL& url) {
+ url_ = url;
+ media_format_.Clear();
+ media_format_.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kApplicationOctetStream);
+ media_format_.SetAsString(media::MediaFormat::kURL, url.spec());
+}
+
+void SimpleDataSource::StartTask() {
+ AutoLock auto_lock(lock_);
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ // We may have stopped.
+ if (state_ == STOPPED)
+ return;
+
+ DCHECK_EQ(state_, INITIALIZING);
+
+ if (IsDataProtocol(url_)) {
+ // If this using data protocol, we just need to decode it.
+ std::string mime_type, charset;
+ bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_);
+
+ // Don't care about the mime-type just proceed if decoding was successful.
+ size_ = data_.length();
+ DoneInitialization_Locked(success);
+ } else {
+ // Create our bridge and start loading the resource.
+ bridge_.reset(bridge_factory_->CreateBridge(
+ url_, net::LOAD_BYPASS_CACHE, -1, -1));
+ bridge_->Start(this);
+ }
+}
+
+void SimpleDataSource::CancelTask() {
+ AutoLock auto_lock(lock_);
+ DCHECK_EQ(state_, STOPPED);
+
+ // Cancel any pending requests.
+ if (bridge_.get()) {
+ bridge_->Cancel();
+ bridge_.reset();
+ }
+}
+
+void SimpleDataSource::DoneInitialization_Locked(bool success) {
+ lock_.AssertAcquired();
+ if (success) {
+ state_ = INITIALIZED;
+ host()->SetTotalBytes(size_);
+ host()->SetBufferedBytes(size_);
+ // If scheme is file or data, say we are loaded.
+ host()->SetLoaded(url_.SchemeIsFile() || IsDataProtocol(url_));
+ } else {
+ host()->SetError(media::PIPELINE_ERROR_NETWORK);
+ }
+ initialize_callback_->Run();
+ initialize_callback_.reset();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h
new file mode 100644
index 0000000..577d973
--- /dev/null
+++ b/webkit/glue/media/simple_data_source.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2010 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.
+
+// An extremely simple implementation of DataSource that downloads the entire
+// media resource into memory before signaling that initialization has finished.
+// Primarily used to test <audio> and <video> with buffering/caching removed
+// from the equation.
+
+#ifndef WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_
+#define WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_
+
+#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "media/base/factory.h"
+#include "media/base/filters.h"
+#include "webkit/glue/media/media_resource_loader_bridge_factory.h"
+
+class MessageLoop;
+class WebMediaPlayerDelegateImpl;
+
+namespace webkit_glue {
+
+class SimpleDataSource : public media::DataSource,
+ public webkit_glue::ResourceLoaderBridge::Peer {
+ public:
+ static media::FilterFactory* CreateFactory(
+ MessageLoop* message_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) {
+ return new media::FilterFactoryImpl2<
+ SimpleDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>(message_loop,
+ bridge_factory);
+ }
+
+ // media::FilterFactoryImpl2 implementation.
+ static bool IsMediaFormatSupported(
+ const media::MediaFormat& media_format);
+
+ // MediaFilter implementation.
+ virtual void Stop(media::FilterCallback* callback);
+
+ // DataSource implementation.
+ virtual void Initialize(const std::string& url,
+ media::FilterCallback* callback);
+ virtual const media::MediaFormat& media_format();
+ virtual void Read(int64 position, size_t size,
+ uint8* data, ReadCallback* read_callback);
+ virtual bool GetSize(int64* size_out);
+ virtual bool IsStreaming();
+
+ // webkit_glue::ResourceLoaderBridge::Peer implementation.
+ virtual void OnDownloadProgress(uint64 position, uint64 size);
+ virtual void OnUploadProgress(uint64 position, uint64 size);
+ virtual bool OnReceivedRedirect(
+ const GURL& new_url,
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
+ bool* has_new_first_party_for_cookies,
+ GURL* new_first_party_for_cookies);
+ 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);
+ virtual GURL GetURLForDebugging() const;
+
+ private:
+ friend class media::FilterFactoryImpl2<
+ SimpleDataSource,
+ MessageLoop*,
+ webkit_glue::MediaResourceLoaderBridgeFactory*>;
+ SimpleDataSource(
+ MessageLoop* render_loop,
+ webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory);
+ virtual ~SimpleDataSource();
+
+ // Updates |url_| and |media_format_| with the given URL.
+ void SetURL(const GURL& url);
+
+ // Creates and starts the resource loading on the render thread.
+ void StartTask();
+
+ // Cancels and deletes the resource loading on the render thread.
+ void CancelTask();
+
+ // Perform initialization completion tasks under a lock.
+ void DoneInitialization_Locked(bool success);
+
+ // Primarily used for asserting the bridge is loading on the render thread.
+ MessageLoop* render_loop_;
+
+ // Factory to create a bridge.
+ scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_;
+
+ // Bridge used to load the media resource.
+ scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_;
+
+ media::MediaFormat media_format_;
+ GURL url_;
+ std::string data_;
+ int64 size_;
+
+ // Simple state tracking variable.
+ enum State {
+ UNINITIALIZED,
+ INITIALIZING,
+ INITIALIZED,
+ STOPPED,
+ };
+ State state_;
+
+ // Used for accessing |state_|.
+ Lock lock_;
+
+ // Filter callbacks.
+ scoped_ptr<media::FilterCallback> initialize_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSource);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_
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..e6acba9
--- /dev/null
+++ b/webkit/glue/media/simple_data_source_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2010 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/callback.h"
+#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::NiceMock;
+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 kHttpsUrl[] = "https://test";
+const char kFileUrl[] = "file://test";
+const char kDataUrl[] =
+ "data:text/plain;base64,YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoK";
+const char kDataUrlDecoded[] = "abcdefghijklmnopqrstuvwxyz";
+const char kInvalidUrl[] = "whatever://test";
+
+} // namespace
+
+namespace webkit_glue {
+
+class SimpleDataSourceTest : public testing::Test {
+ public:
+ SimpleDataSourceTest() {
+ bridge_factory_.reset(
+ new NiceMock<MockMediaResourceLoaderBridgeFactory>());
+ bridge_.reset(new NiceMock<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());
+ if (bridge_factory_.get())
+ EXPECT_CALL(*bridge_factory_, OnDestroy());
+ }
+
+ void InitializeDataSource(const char* 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(url), _, -1, -1))
+ .WillOnce(Return(bridge_.get()));
+ EXPECT_CALL(*bridge_, Start(data_source_.get()))
+ .WillOnce(Return(true));
+
+ data_source_->Initialize(url, callback_.NewCallback());
+
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestSucceeded(bool is_loaded) {
+ 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);
+
+ EXPECT_CALL(host_, SetLoaded(is_loaded));
+
+ 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));
+
+ StrictMock<media::MockFilterCallback> callback;
+ EXPECT_CALL(callback, OnFilterCallback());
+ EXPECT_CALL(callback, OnCallbackDestroyed());
+ data_source_->Stop(callback.NewCallback());
+ 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() {
+ ignore_result(bridge_.release());
+ }
+
+ void ReleaseBridgeFactory() {
+ ignore_result(bridge_factory_.release());
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ protected:
+ scoped_ptr<MessageLoop> message_loop_;
+ scoped_ptr<NiceMock<MockMediaResourceLoaderBridgeFactory> > bridge_factory_;
+ scoped_ptr<NiceMock<MockResourceLoaderBridge> > bridge_;
+ scoped_refptr<media::FilterFactory> factory_;
+ scoped_refptr<SimpleDataSource> data_source_;
+ StrictMock<media::MockFilterHost> host_;
+ StrictMock<media::MockFilterCallback> callback_;
+ char data_[kDataSize];
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest);
+};
+
+TEST_F(SimpleDataSourceTest, InitializeHTTP) {
+ InitializeDataSource(kHttpUrl);
+ RequestSucceeded(false);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeHTTPS) {
+ InitializeDataSource(kHttpsUrl);
+ RequestSucceeded(false);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFile) {
+ InitializeDataSource(kFileUrl);
+ RequestSucceeded(true);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeData) {
+ media::MediaFormat url_format;
+ url_format.SetAsString(media::MediaFormat::kMimeType,
+ media::mime_type::kURL);
+ url_format.SetAsString(media::MediaFormat::kURL, kDataUrl);
+ 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_, SetLoaded(true));
+ EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded)));
+ EXPECT_CALL(host_, SetBufferedBytes(sizeof(kDataUrlDecoded)));
+ EXPECT_CALL(callback_, OnFilterCallback());
+ EXPECT_CALL(callback_, OnCallbackDestroyed());
+
+ data_source_->Initialize(kDataUrl, callback_.NewCallback());
+ MessageLoop::current()->RunAllPending();
+
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeInvalid) {
+ 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);
+ EXPECT_FALSE(data_source_);
+}
+
+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(true);
+ AsyncRead();
+ DestroyDataSource();
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/video_renderer_impl.cc b/webkit/glue/media/video_renderer_impl.cc
new file mode 100644
index 0000000..796d07f
--- /dev/null
+++ b/webkit/glue/media/video_renderer_impl.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2010 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 "webkit/glue/media/video_renderer_impl.h"
+
+#include "media/base/video_frame.h"
+#include "media/base/yuv_convert.h"
+#include "webkit/glue/webmediaplayer_impl.h"
+
+namespace webkit_glue {
+
+VideoRendererImpl::VideoRendererImpl(WebMediaPlayerImpl::Proxy* proxy,
+ bool pts_logging)
+ : proxy_(proxy),
+ last_converted_frame_(NULL),
+ pts_logging_(pts_logging) {
+ // TODO(hclam): decide whether to do the following line in this thread or
+ // in the render thread.
+ proxy_->SetVideoRenderer(this);
+}
+
+// static
+media::FilterFactory* VideoRendererImpl::CreateFactory(
+ WebMediaPlayerImpl::Proxy* proxy,
+ bool pts_logging) {
+ return new media::FilterFactoryImpl2<VideoRendererImpl,
+ WebMediaPlayerImpl::Proxy*,
+ bool>(proxy, pts_logging);
+}
+
+// static
+bool VideoRendererImpl::IsMediaFormatSupported(
+ const media::MediaFormat& media_format) {
+ return ParseMediaFormat(media_format, NULL, NULL, NULL, NULL);
+}
+
+bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) {
+ video_size_.SetSize(width(), height());
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width(), height());
+ if (bitmap_.allocPixels(NULL, NULL)) {
+ bitmap_.eraseRGB(0x00, 0x00, 0x00);
+ return true;
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+void VideoRendererImpl::OnStop(media::FilterCallback* callback) {
+ if (callback) {
+ callback->Run();
+ delete callback;
+ }
+}
+
+void VideoRendererImpl::OnFrameAvailable() {
+ proxy_->Repaint();
+}
+
+void VideoRendererImpl::SetRect(const gfx::Rect& rect) {
+}
+
+// This method is always called on the renderer's thread.
+void VideoRendererImpl::Paint(skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect) {
+ scoped_refptr<media::VideoFrame> video_frame;
+ GetCurrentFrame(&video_frame);
+ if (!video_frame) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLACK);
+ canvas->drawRectCoords(
+ static_cast<float>(dest_rect.x()),
+ static_cast<float>(dest_rect.y()),
+ static_cast<float>(dest_rect.right()),
+ static_cast<float>(dest_rect.bottom()),
+ paint);
+ } else {
+ if (CanFastPaint(canvas, dest_rect)) {
+ FastPaint(video_frame, canvas, dest_rect);
+ } else {
+ SlowPaint(video_frame, canvas, dest_rect);
+ }
+
+ // Presentation timestamp logging is primarily used to measure performance
+ // on low-end devices. When profiled on an Intel Atom N280 @ 1.66GHz this
+ // code had a ~63 microsecond perf hit when logging to a file (not stdout),
+ // which is neglible enough for measuring playback performance.
+ if (pts_logging_) {
+ LOG(INFO) << "pts="
+ << video_frame->GetTimestamp().InMicroseconds();
+ }
+ }
+
+ PutCurrentFrame(video_frame);
+}
+
+// CanFastPaint is a helper method to determine the conditions for fast
+// painting. The conditions are:
+// 1. No skew in canvas matrix.
+// 2. No flipping nor mirroring.
+// 3. Canvas has pixel format ARGB8888.
+// 4. Canvas is opaque.
+// TODO(hclam): The fast paint method should support flipping and mirroring.
+// Disable the flipping and mirroring checks once we have it.
+bool VideoRendererImpl::CanFastPaint(skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect) {
+ // Fast paint does not handle opacity value other than 1.0. Hence use slow
+ // paint if opacity is not 1.0. Since alpha = opacity * 0xFF, we check that
+ // alpha != 0xFF.
+ //
+ // Additonal notes: If opacity = 0.0, the chrome display engine does not try
+ // to render the video. So, this method is never called. However, if the
+ // opacity = 0.0001, alpha is again 0, but the display engine tries to render
+ // the video. If we use Fast paint, the video shows up with opacity = 1.0.
+ // Hence we use slow paint also in the case where alpha = 0. It would be ideal
+ // if rendering was never called even for cases where alpha is 0. Created
+ // bug 48090 for this.
+ SkCanvas::LayerIter layer_iter(canvas, false);
+ SkColor sk_color = layer_iter.paint().getColor();
+ SkAlpha sk_alpha = SkColorGetA(sk_color);
+ if (sk_alpha != 0xFF) {
+ return false;
+ }
+
+ const SkMatrix& total_matrix = canvas->getTotalMatrix();
+ // Perform the following checks here:
+ // 1. Check for skewing factors of the transformation matrix. They should be
+ // zero.
+ // 2. Check for mirroring and flipping. Make sure they are greater than zero.
+ if (SkScalarNearlyZero(total_matrix.getSkewX()) &&
+ SkScalarNearlyZero(total_matrix.getSkewY()) &&
+ total_matrix.getScaleX() > 0 &&
+ total_matrix.getScaleY() > 0) {
+ // Get the properties of the SkDevice and the clip rect.
+ SkDevice* device = canvas->getDevice();
+
+ // Get the boundary of the device.
+ SkIRect device_rect;
+ device->getBounds(&device_rect);
+
+ // Get the pixel config of the device.
+ const SkBitmap::Config config = device->config();
+ // Get the total clip rect associated with the canvas.
+ const SkRegion& total_clip = canvas->getTotalClip();
+
+ SkIRect dest_irect;
+ TransformToSkIRect(canvas->getTotalMatrix(), dest_rect, &dest_irect);
+
+ if (config == SkBitmap::kARGB_8888_Config && device->isOpaque() &&
+ device_rect.contains(total_clip.getBounds())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame,
+ skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect) {
+ // 1. Convert YUV frame to RGB.
+ base::TimeDelta timestamp = video_frame->GetTimestamp();
+ if (video_frame != last_converted_frame_ ||
+ timestamp != last_converted_timestamp_) {
+ last_converted_frame_ = video_frame;
+ last_converted_timestamp_ = timestamp;
+ DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
+ video_frame->format() == media::VideoFrame::YV16);
+ DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
+ video_frame->stride(media::VideoFrame::kVPlane));
+ DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes);
+ bitmap_.lockPixels();
+ media::YUVType yuv_type =
+ (video_frame->format() == media::VideoFrame::YV12) ?
+ media::YV12 : media::YV16;
+ media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane),
+ video_frame->data(media::VideoFrame::kUPlane),
+ video_frame->data(media::VideoFrame::kVPlane),
+ static_cast<uint8*>(bitmap_.getPixels()),
+ video_frame->width(),
+ video_frame->height(),
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->stride(media::VideoFrame::kUPlane),
+ bitmap_.rowBytes(),
+ yuv_type);
+ bitmap_.unlockPixels();
+ }
+
+ // 2. Paint the bitmap to canvas.
+ SkMatrix matrix;
+ matrix.setTranslate(static_cast<SkScalar>(dest_rect.x()),
+ static_cast<SkScalar>(dest_rect.y()));
+ if (dest_rect.width() != video_size_.width() ||
+ dest_rect.height() != video_size_.height()) {
+ matrix.preScale(SkIntToScalar(dest_rect.width()) /
+ SkIntToScalar(video_size_.width()),
+ SkIntToScalar(dest_rect.height()) /
+ SkIntToScalar(video_size_.height()));
+ }
+ SkPaint paint;
+ paint.setFlags(SkPaint::kFilterBitmap_Flag);
+ canvas->drawBitmapMatrix(bitmap_, matrix, &paint);
+}
+
+void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame,
+ skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect) {
+ DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
+ video_frame->format() == media::VideoFrame::YV16);
+ DCHECK(video_frame->stride(media::VideoFrame::kUPlane) ==
+ video_frame->stride(media::VideoFrame::kVPlane));
+ DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes);
+ const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true);
+ media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ?
+ media::YV12 : media::YV16;
+ int y_shift = yuv_type; // 1 for YV12, 0 for YV16.
+
+ // Create a rectangle backed by SkScalar.
+ SkRect scalar_dest_rect;
+ scalar_dest_rect.iset(dest_rect.x(), dest_rect.y(),
+ dest_rect.right(), dest_rect.bottom());
+
+ // Transform the destination rectangle to local coordinates.
+ const SkMatrix& local_matrix = canvas->getTotalMatrix();
+ SkRect local_dest_rect;
+ local_matrix.mapRect(&local_dest_rect, scalar_dest_rect);
+
+ // After projecting the destination rectangle to local coordinates, round
+ // the projected rectangle to integer values, this will give us pixel values
+ // of the rectangle.
+ SkIRect local_dest_irect, local_dest_irect_saved;
+ local_dest_rect.round(&local_dest_irect);
+ local_dest_rect.round(&local_dest_irect_saved);
+
+ // Only does the paint if the destination rect intersects with the clip
+ // rect.
+ if (local_dest_irect.intersect(canvas->getTotalClip().getBounds())) {
+ // At this point |local_dest_irect| contains the rect that we should draw
+ // to within the clipping rect.
+
+ // Calculate the address for the top left corner of destination rect in
+ // the canvas that we will draw to. The address is obtained by the base
+ // address of the canvas shifted by "left" and "top" of the rect.
+ uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) +
+ local_dest_irect.fTop * bitmap.rowBytes() +
+ local_dest_irect.fLeft * 4;
+
+ // Project the clip rect to the original video frame, obtains the
+ // dimensions of the projected clip rect, "left" and "top" of the rect.
+ // The math here are all integer math so we won't have rounding error and
+ // write outside of the canvas.
+ // We have the assumptions of dest_rect.width() and dest_rect.height()
+ // being non-zero, these are valid assumptions since finding intersection
+ // above rejects empty rectangle so we just do a DCHECK here.
+ DCHECK_NE(0, dest_rect.width());
+ DCHECK_NE(0, dest_rect.height());
+ size_t frame_clip_width = local_dest_irect.width() *
+ video_frame->width() / local_dest_irect_saved.width();
+ size_t frame_clip_height = local_dest_irect.height() *
+ video_frame->height() / local_dest_irect_saved.height();
+
+ // Project the "left" and "top" of the final destination rect to local
+ // coordinates of the video frame, use these values to find the offsets
+ // in the video frame to start reading.
+ size_t frame_clip_left =
+ (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) *
+ video_frame->width() / local_dest_irect_saved.width();
+ size_t frame_clip_top =
+ (local_dest_irect.fTop - local_dest_irect_saved.fTop) *
+ video_frame->height() / local_dest_irect_saved.height();
+
+ // Use the "left" and "top" of the destination rect to locate the offset
+ // in Y, U and V planes.
+ size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) *
+ frame_clip_top + frame_clip_left;
+ // For format YV12, there is one U, V value per 2x2 block.
+ // For format YV16, there is one u, V value per 2x1 block.
+ size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) *
+ (frame_clip_top >> y_shift)) + (frame_clip_left >> 1);
+ uint8* frame_clip_y =
+ video_frame->data(media::VideoFrame::kYPlane) + y_offset;
+ uint8* frame_clip_u =
+ video_frame->data(media::VideoFrame::kUPlane) + uv_offset;
+ uint8* frame_clip_v =
+ video_frame->data(media::VideoFrame::kVPlane) + uv_offset;
+ bitmap.lockPixels();
+
+ // TODO(hclam): do rotation and mirroring here.
+ // TODO(fbarchard): switch filtering based on performance.
+ media::ScaleYUVToRGB32(frame_clip_y,
+ frame_clip_u,
+ frame_clip_v,
+ dest_rect_pointer,
+ frame_clip_width,
+ frame_clip_height,
+ local_dest_irect.width(),
+ local_dest_irect.height(),
+ video_frame->stride(media::VideoFrame::kYPlane),
+ video_frame->stride(media::VideoFrame::kUPlane),
+ bitmap.rowBytes(),
+ yuv_type,
+ media::ROTATE_0,
+ media::FILTER_BILINEAR);
+ bitmap.unlockPixels();
+ }
+}
+
+void VideoRendererImpl::TransformToSkIRect(const SkMatrix& matrix,
+ const gfx::Rect& src_rect,
+ SkIRect* dest_rect) {
+ // Transform destination rect to local coordinates.
+ SkRect transformed_rect;
+ SkRect skia_dest_rect;
+ skia_dest_rect.iset(src_rect.x(), src_rect.y(),
+ src_rect.right(), src_rect.bottom());
+ matrix.mapRect(&transformed_rect, skia_dest_rect);
+ transformed_rect.round(dest_rect);
+}
+
+} // namespace webkit_glue
diff --git a/webkit/glue/media/video_renderer_impl.h b/webkit/glue/media/video_renderer_impl.h
new file mode 100644
index 0000000..30f2e38
--- /dev/null
+++ b/webkit/glue/media/video_renderer_impl.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2010 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.
+//
+// The video renderer implementation to be use by the media pipeline. It lives
+// inside video renderer thread and also WebKit's main thread. We need to be
+// extra careful about members shared by two different threads, especially
+// video frame buffers.
+
+#ifndef WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_
+#define WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_
+
+#include "gfx/rect.h"
+#include "gfx/size.h"
+#include "media/base/buffers.h"
+#include "media/base/filters.h"
+#include "media/filters/video_renderer_base.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebMediaPlayer.h"
+#include "webkit/glue/media/web_video_renderer.h"
+#include "webkit/glue/webmediaplayer_impl.h"
+
+namespace webkit_glue {
+
+class VideoRendererImpl : public WebVideoRenderer {
+ public:
+ // WebVideoRenderer implementation.
+ virtual void SetRect(const gfx::Rect& rect);
+ virtual void Paint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect);
+
+ // Static method for creating factory for this object.
+ static media::FilterFactory* CreateFactory(WebMediaPlayerImpl::Proxy* proxy,
+ bool pts_logging);
+
+ // FilterFactoryImpl2 implementation.
+ static bool IsMediaFormatSupported(const media::MediaFormat& media_format);
+
+ // TODO(scherkus): remove this mega-hack, see http://crbug.com/28207
+ class FactoryFactory : public webkit_glue::WebVideoRendererFactoryFactory {
+ public:
+ FactoryFactory(bool pts_logging)
+ : webkit_glue::WebVideoRendererFactoryFactory(),
+ pts_logging_(pts_logging) {
+ }
+
+ virtual media::FilterFactory* CreateFactory(
+ webkit_glue::WebMediaPlayerImpl::Proxy* proxy) {
+ return VideoRendererImpl::CreateFactory(proxy, pts_logging_);
+ }
+
+ private:
+ // Whether we're logging video presentation timestamps (PTS).
+ bool pts_logging_;
+
+ DISALLOW_COPY_AND_ASSIGN(FactoryFactory);
+ };
+
+ protected:
+ // Method called by VideoRendererBase during initialization.
+ virtual bool OnInitialize(media::VideoDecoder* decoder);
+
+ // Method called by the VideoRendererBase when stopping.
+ virtual void OnStop(media::FilterCallback* callback);
+
+ // Method called by the VideoRendererBase when a frame is available.
+ virtual void OnFrameAvailable();
+
+ private:
+ // Only the filter factories can create instances.
+ friend class media::FilterFactoryImpl2<VideoRendererImpl,
+ WebMediaPlayerImpl::Proxy*,
+ bool>;
+ VideoRendererImpl(WebMediaPlayerImpl::Proxy* proxy, bool pts_logging);
+ virtual ~VideoRendererImpl() {}
+
+ // Determine the conditions to perform fast paint. Returns true if we can do
+ // fast paint otherwise false.
+ bool CanFastPaint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect);
+
+ // Slow paint does a YUV => RGB, and scaled blit in two separate operations.
+ void SlowPaint(media::VideoFrame* video_frame,
+ skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect);
+
+ // Fast paint does YUV => RGB, scaling, blitting all in one step into the
+ // canvas. It's not always safe and appropriate to perform fast paint.
+ // CanFastPaint() is used to determine the conditions.
+ void FastPaint(media::VideoFrame* video_frame,
+ skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect);
+
+ void TransformToSkIRect(const SkMatrix& matrix, const gfx::Rect& src_rect,
+ SkIRect* dest_rect);
+
+ // Pointer to our parent object that is called to request repaints.
+ scoped_refptr<WebMediaPlayerImpl::Proxy> proxy_;
+
+ // An RGB bitmap used to convert the video frames.
+ SkBitmap bitmap_;
+
+ // These two members are used to determine if the |bitmap_| contains
+ // an already converted image of the current frame. IMPORTANT NOTE: The
+ // value of |last_converted_frame_| must only be used for comparison purposes,
+ // and it should be assumed that the value of the pointer is INVALID unless
+ // it matches the pointer returned from GetCurrentFrame(). Even then, just
+ // to make sure, we compare the timestamp to be sure the bits in the
+ // |current_frame_bitmap_| are valid.
+ media::VideoFrame* last_converted_frame_;
+ base::TimeDelta last_converted_timestamp_;
+
+ // The size of the video.
+ gfx::Size video_size_;
+
+ // Whether we're logging video presentation timestamps (PTS).
+ bool pts_logging_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoRendererImpl);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_
diff --git a/webkit/glue/media/web_video_renderer.h b/webkit/glue/media/web_video_renderer.h
new file mode 100644
index 0000000..8bafb1a
--- /dev/null
+++ b/webkit/glue/media/web_video_renderer.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2010 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_WEB_VIDEO_RENDERER_H_
+#define WEBKIT_GLUE_MEDIA_WEB_VIDEO_RENDERER_H_
+
+#include "media/filters/video_renderer_base.h"
+
+namespace webkit_glue {
+
+// A specialized version of a VideoRenderer designed to be used inside WebKit.
+class WebVideoRenderer : public media::VideoRendererBase {
+ public:
+ WebVideoRenderer() : media::VideoRendererBase() {}
+ virtual ~WebVideoRenderer() {}
+
+ // This method is called with the same rect as the Paint() method and could
+ // be used by future implementations to implement an improved color space +
+ // scale code on a separate thread. Since we always do the stretch on the
+ // same thread as the Paint method, we just ignore the call for now.
+ //
+ // Method called on the render thread.
+ virtual void SetRect(const gfx::Rect& rect) = 0;
+
+ // Paint the current front frame on the |canvas| stretching it to fit the
+ // |dest_rect|.
+ //
+ // Method called on the render thread.
+ virtual void Paint(skia::PlatformCanvas* canvas,
+ const gfx::Rect& dest_rect) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebVideoRenderer);
+};
+
+} // namespace webkit_glue
+
+#endif // WEBKIT_GLUE_MEDIA_WEB_VIDEO_RENDERER_H_