summaryrefslogtreecommitdiffstats
path: root/webkit/media
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 05:18:16 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-17 05:18:16 +0000
commita9288f57632869274ef505d86dc020ef2f3f9311 (patch)
tree7daef2b18772b5362eeea3b3b8d55881c5eb97d1 /webkit/media
parent084b6bb674002dddd9839248c3cc6b7d6c6dc4c7 (diff)
downloadchromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.zip
chromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.tar.gz
chromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.tar.bz2
Moving media-related files from webkit/glue/ to webkit/media/.
Files under webkit/media/ are now under their very own webkit_media namespace. Review URL: http://codereview.chromium.org/8570010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110438 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/media')
-rw-r--r--webkit/media/OWNERS1
-rw-r--r--webkit/media/audio_decoder.cc87
-rw-r--r--webkit/media/audio_decoder.h20
-rw-r--r--webkit/media/buffered_data_source.cc672
-rw-r--r--webkit/media/buffered_data_source.h235
-rw-r--r--webkit/media/buffered_data_source_unittest.cc791
-rw-r--r--webkit/media/buffered_resource_loader.cc813
-rw-r--r--webkit/media/buffered_resource_loader.h304
-rw-r--r--webkit/media/buffered_resource_loader_unittest.cc1111
-rw-r--r--webkit/media/media_stream_client.h33
-rw-r--r--webkit/media/simple_data_source.cc361
-rw-r--r--webkit/media/simple_data_source.h153
-rw-r--r--webkit/media/simple_data_source_unittest.cc287
-rw-r--r--webkit/media/video_renderer_impl.cc278
-rw-r--r--webkit/media/video_renderer_impl.h82
-rw-r--r--webkit/media/web_data_source.cc17
-rw-r--r--webkit/media/web_data_source.h59
-rw-r--r--webkit/media/web_data_source_factory.cc110
-rw-r--r--webkit/media/web_data_source_factory.h59
-rw-r--r--webkit/media/web_video_renderer.h47
-rw-r--r--webkit/media/webkit_media.gypi50
-rw-r--r--webkit/media/webmediaplayer_delegate.h31
-rw-r--r--webkit/media/webmediaplayer_impl.cc917
-rw-r--r--webkit/media/webmediaplayer_impl.h276
-rw-r--r--webkit/media/webmediaplayer_proxy.cc238
-rw-r--r--webkit/media/webmediaplayer_proxy.h120
-rw-r--r--webkit/media/webvideoframe_impl.cc83
-rw-r--r--webkit/media/webvideoframe_impl.h36
28 files changed, 7271 insertions, 0 deletions
diff --git a/webkit/media/OWNERS b/webkit/media/OWNERS
new file mode 100644
index 0000000..21a33d5
--- /dev/null
+++ b/webkit/media/OWNERS
@@ -0,0 +1 @@
+scherkus@chromium.org
diff --git a/webkit/media/audio_decoder.cc b/webkit/media/audio_decoder.cc
new file mode 100644
index 0000000..f6b79ed
--- /dev/null
+++ b/webkit/media/audio_decoder.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2011 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/media/audio_decoder.h"
+
+#include <vector>
+#include "base/basictypes.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "media/base/limits.h"
+#include "media/filters/audio_file_reader.h"
+#include "media/filters/in_memory_url_protocol.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebAudioBus.h"
+
+using media::AudioFileReader;
+using media::InMemoryUrlProtocol;
+using std::vector;
+using WebKit::WebAudioBus;
+
+namespace webkit_media {
+
+// Decode in-memory audio file data.
+bool DecodeAudioFileData(
+ WebKit::WebAudioBus* destination_bus,
+ const char* data, size_t data_size, double sample_rate) {
+ DCHECK(destination_bus);
+ if (!destination_bus)
+ return false;
+
+ // Uses the FFmpeg library for audio file reading.
+ InMemoryUrlProtocol url_protocol(reinterpret_cast<const uint8*>(data),
+ data_size, false);
+ AudioFileReader reader(&url_protocol);
+
+ if (!reader.Open())
+ return false;
+
+ size_t number_of_channels = reader.channels();
+ double file_sample_rate = reader.sample_rate();
+ double duration = reader.duration().InSecondsF();
+ size_t number_of_frames = static_cast<size_t>(reader.number_of_frames());
+
+ // Apply sanity checks to make sure crazy values aren't coming out of
+ // FFmpeg.
+ if (!number_of_channels ||
+ number_of_channels > static_cast<size_t>(media::Limits::kMaxChannels) ||
+ file_sample_rate < media::Limits::kMinSampleRate ||
+ file_sample_rate > media::Limits::kMaxSampleRate)
+ return false;
+
+ // TODO(crogers) : do sample-rate conversion with FFmpeg.
+ // For now, we're ignoring the requested 'sample_rate' and returning
+ // the WebAudioBus at the file's sample-rate.
+ // double destination_sample_rate =
+ // (sample_rate != 0.0) ? sample_rate : file_sample_rate;
+ double destination_sample_rate = file_sample_rate;
+
+ DLOG(INFO) << "Decoding file data -"
+ << " data: " << data
+ << " data size: " << data_size
+ << " duration: " << duration
+ << " number of frames: " << number_of_frames
+ << " sample rate: " << file_sample_rate
+ << " number of channels: " << number_of_channels;
+
+ // Change to destination sample-rate.
+ number_of_frames = static_cast<size_t>(number_of_frames *
+ (destination_sample_rate / file_sample_rate));
+
+ // Allocate and configure the output audio channel data.
+ destination_bus->initialize(number_of_channels,
+ number_of_frames,
+ destination_sample_rate);
+
+ // Wrap the channel pointers which will receive the decoded PCM audio.
+ vector<float*> audio_data;
+ audio_data.reserve(number_of_channels);
+ for (size_t i = 0; i < number_of_channels; ++i) {
+ audio_data.push_back(destination_bus->channelData(i));
+ }
+
+ // Decode the audio file data.
+ return reader.Read(audio_data, number_of_frames);
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/audio_decoder.h b/webkit/media/audio_decoder.h
new file mode 100644
index 0000000..aae5581d
--- /dev/null
+++ b/webkit/media/audio_decoder.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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_MEDIA_AUDIO_DECODER_H_
+#define WEBKIT_MEDIA_AUDIO_DECODER_H_
+
+#include "base/basictypes.h"
+
+namespace WebKit { class WebAudioBus; }
+
+namespace webkit_media {
+
+// Decode in-memory audio file data.
+bool DecodeAudioFileData(WebKit::WebAudioBus* destination_bus, const char* data,
+ size_t data_size, double sample_rate);
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_AUDIO_DECODER_H_
diff --git a/webkit/media/buffered_data_source.cc b/webkit/media/buffered_data_source.cc
new file mode 100644
index 0000000..01d4675
--- /dev/null
+++ b/webkit/media/buffered_data_source.cc
@@ -0,0 +1,672 @@
+// Copyright (c) 2011 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/media/buffered_data_source.h"
+
+#include "base/bind.h"
+#include "media/base/filter_host.h"
+#include "media/base/media_log.h"
+#include "net/base/net_errors.h"
+#include "webkit/media/web_data_source_factory.h"
+#include "webkit/glue/webkit_glue.h"
+
+using WebKit::WebFrame;
+
+namespace webkit_media {
+
+// 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.
+static const int kInitialReadBufferSize = 32768;
+
+// Number of cache misses we allow for a single Read() before signalling an
+// error.
+static const int kNumCacheMissRetries = 3;
+
+static WebDataSource* NewBufferedDataSource(MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log) {
+ return new BufferedDataSource(render_loop, frame, media_log);
+}
+
+// static
+media::DataSourceFactory* BufferedDataSource::CreateFactory(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ const WebDataSourceBuildObserverHack& build_observer) {
+ return new WebDataSourceFactory(render_loop, frame, media_log,
+ &NewBufferedDataSource, build_observer);
+}
+
+BufferedDataSource::BufferedDataSource(
+ MessageLoop* render_loop,
+ WebFrame* frame,
+ media::MediaLog* media_log)
+ : total_bytes_(kPositionNotSpecified),
+ buffered_bytes_(0),
+ loaded_(false),
+ streaming_(false),
+ frame_(frame),
+ loader_(NULL),
+ is_downloading_data_(false),
+ read_position_(0),
+ read_size_(0),
+ read_buffer_(NULL),
+ 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),
+ media_has_played_(false),
+ preload_(media::AUTO),
+ using_range_request_(true),
+ cache_miss_retries_left_(kNumCacheMissRetries),
+ bitrate_(0),
+ playback_rate_(0.0),
+ media_log_(media_log) {
+}
+
+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(url_,
+ first_byte_position,
+ last_byte_position,
+ ChooseDeferStrategy(),
+ bitrate_,
+ playback_rate_,
+ media_log_);
+}
+
+void BufferedDataSource::set_host(media::FilterHost* host) {
+ DataSource::set_host(host);
+
+ if (loader_.get()) {
+ base::AutoLock auto_lock(lock_);
+ UpdateHostState_Locked();
+ }
+}
+
+void BufferedDataSource::Initialize(const std::string& url,
+ const media::PipelineStatusCB& callback) {
+ // Saves the url.
+ url_ = GURL(url);
+
+ // This data source doesn't support data:// protocol so reject it.
+ if (url_.SchemeIs(kDataScheme)) {
+ callback.Run(media::DATASOURCE_ERROR_URL_NOT_SUPPORTED);
+ return;
+ } else if (!webkit_glue::IsProtocolSupportedForMedia(url_)) {
+ callback.Run(media::PIPELINE_ERROR_NETWORK);
+ return;
+ }
+
+ DCHECK(!callback.is_null());
+ {
+ base::AutoLock auto_lock(lock_);
+ initialize_cb_ = callback;
+ }
+
+ // Post a task to complete the initialization task.
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&BufferedDataSource::InitializeTask, this));
+}
+
+void BufferedDataSource::CancelInitialize() {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(!initialize_cb_.is_null());
+
+ initialize_cb_.Reset();
+
+ render_loop_->PostTask(
+ FROM_HERE, base::Bind(&BufferedDataSource::CleanupTask, this));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// media::Filter implementation.
+void BufferedDataSource::Stop(const base::Closure& callback) {
+ {
+ base::AutoLock auto_lock(lock_);
+ stop_signal_received_ = true;
+ }
+ if (!callback.is_null())
+ callback.Run();
+
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&BufferedDataSource::CleanupTask, this));
+}
+
+void BufferedDataSource::SetPlaybackRate(float playback_rate) {
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::SetPlaybackRateTask, this, playback_rate));
+}
+
+void BufferedDataSource::SetPreload(media::Preload preload) {
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::SetPreloadTask, this, preload));
+}
+
+void BufferedDataSource::SetBitrate(int bitrate) {
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::SetBitrateTask, this, bitrate));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// media::DataSource implementation.
+void BufferedDataSource::Read(
+ int64 position, size_t size, uint8* data,
+ const media::DataSource::ReadCallback& read_callback) {
+ VLOG(1) << "Read: " << position << " offset, " << size << " bytes";
+ DCHECK(!read_callback.is_null());
+
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(read_callback_.is_null());
+
+ if (stop_signal_received_ || stopped_on_render_loop_) {
+ read_callback.Run(kReadError);
+ return;
+ }
+
+ read_callback_ = read_callback;
+ }
+
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::ReadTask, this,
+ position, static_cast<int>(size), data));
+}
+
+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_;
+}
+
+bool BufferedDataSource::HasSingleOrigin() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ return loader_.get() ? loader_->HasSingleOrigin() : true;
+}
+
+void BufferedDataSource::Abort() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ CleanupTask();
+ frame_ = NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Render thread tasks.
+void BufferedDataSource::InitializeTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!loader_.get());
+
+ {
+ base::AutoLock auto_lock(lock_);
+ if (stopped_on_render_loop_ || initialize_cb_.is_null() ||
+ stop_signal_received_) {
+ return;
+ }
+ }
+
+ if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
+ // Do an unbounded range request starting at the beginning. If the server
+ // responds with 200 instead of 206 we'll fall back into a streaming mode.
+ loader_ = CreateResourceLoader(0, kPositionNotSpecified);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::HttpInitialStartCallback),
+ base::Bind(&BufferedDataSource::NetworkEventCallback, this),
+ frame_);
+ } 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(kPositionNotSpecified,
+ kPositionNotSpecified);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback),
+ base::Bind(&BufferedDataSource::NetworkEventCallback, this),
+ frame_);
+ }
+}
+
+void BufferedDataSource::ReadTask(
+ int64 position,
+ int read_size,
+ uint8* buffer) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ {
+ base::AutoLock auto_lock(lock_);
+ if (stopped_on_render_loop_)
+ return;
+
+ DCHECK(!read_callback_.is_null());
+ }
+
+ // Saves the read parameters.
+ read_position_ = position;
+ read_size_ = read_size;
+ read_buffer_ = buffer;
+ cache_miss_retries_left_ = kNumCacheMissRetries;
+
+ // Call to read internal to perform the actual read.
+ ReadInternal();
+}
+
+void BufferedDataSource::CleanupTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ {
+ base::AutoLock auto_lock(lock_);
+ initialize_cb_.Reset();
+ if (stopped_on_render_loop_)
+ return;
+
+ // Signal that stop task has finished execution.
+ // NOTE: it's vital that this be set under lock, as that's how Read() tests
+ // before registering a new |read_callback_| (which is cleared below).
+ stopped_on_render_loop_ = true;
+
+ if (!read_callback_.is_null())
+ DoneRead_Locked(net::ERR_FAILED);
+ }
+
+ // 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_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = 0;
+}
+
+void BufferedDataSource::RestartLoadingTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (stopped_on_render_loop_)
+ return;
+
+ {
+ // If there's no outstanding read then return early.
+ base::AutoLock auto_lock(lock_);
+ if (read_callback_.is_null())
+ return;
+ }
+
+ loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::PartialReadStartCallback),
+ base::Bind(&BufferedDataSource::NetworkEventCallback, this),
+ frame_);
+}
+
+void BufferedDataSource::SetPlaybackRateTask(float playback_rate) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ playback_rate_ = playback_rate;
+ loader_->SetPlaybackRate(playback_rate);
+
+ bool previously_paused = media_is_paused_;
+ media_is_paused_ = (playback_rate == 0.0);
+
+ if (!media_has_played_ && previously_paused && !media_is_paused_)
+ media_has_played_ = true;
+
+ BufferedResourceLoader::DeferStrategy strategy = ChooseDeferStrategy();
+ loader_->UpdateDeferStrategy(strategy);
+}
+
+void BufferedDataSource::SetPreloadTask(media::Preload preload) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ preload_ = preload;
+}
+
+void BufferedDataSource::SetBitrateTask(int bitrate) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ bitrate_ = bitrate;
+ loader_->SetBitrate(bitrate);
+}
+
+BufferedResourceLoader::DeferStrategy
+BufferedDataSource::ChooseDeferStrategy() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ // If the page indicated preload=metadata, then load exactly what is needed
+ // needed for starting playback.
+ if (!media_has_played_ && preload_ == media::METADATA)
+ return BufferedResourceLoader::kReadThenDefer;
+
+ // If the playback has started (at which point the preload value is ignored)
+ // and we're paused, then try to load as much as possible.
+ if (media_has_played_ && media_is_paused_)
+ return BufferedResourceLoader::kNeverDefer;
+
+ // If media is currently playing or the page indicated preload=auto,
+ // use threshold strategy to enable/disable deferring when the buffer
+ // is full/depleted.
+ return BufferedResourceLoader::kThresholdDefer;
+}
+
+// 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_);
+
+ // 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) {
+ VLOG(1) << "DoneRead: " << error << " bytes";
+
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!read_callback_.is_null());
+ lock_.AssertAcquired();
+
+ if (error >= 0) {
+ read_callback_.Run(static_cast<size_t>(error));
+ } else {
+ read_callback_.Run(kReadError);
+ }
+
+ read_callback_.Reset();
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = 0;
+}
+
+void BufferedDataSource::DoneInitialization_Locked(
+ media::PipelineStatus status) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(!initialize_cb_.is_null());
+ lock_.AssertAcquired();
+
+ initialize_cb_.Run(status);
+ initialize_cb_.Reset();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader callback methods.
+void BufferedDataSource::HttpInitialStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ int64 instance_size = loader_->instance_size();
+ bool success = error == net::OK;
+
+ bool initialize_cb_is_null = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ initialize_cb_is_null = initialize_cb_.is_null();
+ }
+ if (initialize_cb_is_null) {
+ loader_->Stop();
+ return;
+ }
+
+ 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) ||
+ !loader_->range_supported();
+ } else {
+ // TODO(hclam): In case of failure, we can retry several times.
+ loader_->Stop();
+ }
+
+ if (error == net::ERR_INVALID_RESPONSE && using_range_request_) {
+ // Assuming that the Range header was causing the problem. Retry without
+ // the Range header.
+ using_range_request_ = false;
+ loader_ = CreateResourceLoader(kPositionNotSpecified,
+ kPositionNotSpecified);
+ loader_->Start(
+ NewCallback(this, &BufferedDataSource::HttpInitialStartCallback),
+ base::Bind(&BufferedDataSource::NetworkEventCallback, this),
+ frame_);
+ return;
+ }
+
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ // TODO: Review locking in this class and figure out a way to run the callback
+ // w/o the lock.
+ scoped_refptr<BufferedDataSource> destruction_guard(this);
+ {
+ // 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.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (!success) {
+ DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK);
+ return;
+ }
+
+ UpdateHostState_Locked();
+ DoneInitialization_Locked(media::PIPELINE_OK);
+ }
+}
+
+void BufferedDataSource::NonHttpInitialStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ bool initialize_cb_is_null = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ initialize_cb_is_null = initialize_cb_.is_null();
+ }
+ if (initialize_cb_is_null) {
+ loader_->Stop();
+ return;
+ }
+
+ int64 instance_size = loader_->instance_size();
+ bool success = error == net::OK && instance_size != kPositionNotSpecified;
+
+ if (success) {
+ total_bytes_ = instance_size;
+ buffered_bytes_ = total_bytes_;
+ loaded_ = true;
+ } else {
+ loader_->Stop();
+ }
+
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ // TODO: Review locking in this class and figure out a way to run the callback
+ // w/o the lock.
+ scoped_refptr<BufferedDataSource> destruction_guard(this);
+ {
+ // 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.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_ || initialize_cb_.is_null())
+ return;
+
+ if (!success) {
+ DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK);
+ return;
+ }
+
+ UpdateHostState_Locked();
+ DoneInitialization_Locked(media::PIPELINE_OK);
+ }
+}
+
+void BufferedDataSource::PartialReadStartCallback(int error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ DCHECK(loader_.get());
+
+ if (error == net::OK) {
+ // Once the 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.
+ base::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 && cache_miss_retries_left_ > 0) {
+ cache_miss_retries_left_--;
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&BufferedDataSource::RestartLoadingTask, this));
+ 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.
+ base::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);
+ } else if (error == 0 && total_bytes_ == kPositionNotSpecified) {
+ // We've reached the end of the file and we didn't know the total size
+ // before. Update the total size so Read()s past the end of the file will
+ // fail like they would if we had known the file size at the beginning.
+ total_bytes_ = loader_->instance_size();
+
+ if (host() && total_bytes_ != kPositionNotSpecified)
+ host()->SetTotalBytes(total_bytes_);
+ }
+ 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 is_downloading_data = loader_->is_downloading_data();
+ int64 buffered_position = loader_->GetBufferedPosition();
+
+ // If we get an unspecified value, return immediately.
+ if (buffered_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.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (is_downloading_data != is_downloading_data_) {
+ is_downloading_data_ = is_downloading_data;
+ if (host())
+ host()->SetNetworkActivity(is_downloading_data);
+ }
+
+ buffered_bytes_ = buffered_position + 1;
+ if (host())
+ host()->SetBufferedBytes(buffered_bytes_);
+}
+
+void BufferedDataSource::UpdateHostState_Locked() {
+ // Called from various threads, under lock.
+ lock_.AssertAcquired();
+
+ media::FilterHost* filter_host = host();
+ if (!filter_host)
+ return;
+
+ filter_host->SetLoaded(loaded_);
+
+ if (streaming_) {
+ filter_host->SetStreaming(true);
+ } else {
+ filter_host->SetTotalBytes(total_bytes_);
+ filter_host->SetBufferedBytes(buffered_bytes_);
+ }
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/buffered_data_source.h b/webkit/media/buffered_data_source.h
new file mode 100644
index 0000000..7987d09
--- /dev/null
+++ b/webkit/media/buffered_data_source.h
@@ -0,0 +1,235 @@
+// Copyright (c) 2011 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_MEDIA_BUFFERED_DATA_SOURCE_H_
+#define WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/base/filter_factories.h"
+#include "media/base/filters.h"
+#include "webkit/media/buffered_resource_loader.h"
+
+namespace media {
+class MediaLog;
+}
+
+namespace webkit_media {
+
+// This class may be created on any thread, and is callable from the render
+// thread as well as media-specific threads.
+class BufferedDataSource : public WebDataSource {
+ public:
+ // Creates a DataSourceFactory for building BufferedDataSource objects.
+ static media::DataSourceFactory* CreateFactory(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ const WebDataSourceBuildObserverHack& build_observer);
+
+ BufferedDataSource(MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log);
+
+ virtual ~BufferedDataSource();
+
+ // media::Filter implementation.
+ virtual void set_host(media::FilterHost* host) OVERRIDE;
+ virtual void Stop(const base::Closure& callback) OVERRIDE;
+ virtual void SetPlaybackRate(float playback_rate) OVERRIDE;
+
+ // media::DataSource implementation.
+ // Called from demuxer thread.
+ virtual void Read(
+ int64 position,
+ size_t size,
+ uint8* data,
+ const media::DataSource::ReadCallback& read_callback) OVERRIDE;
+ virtual bool GetSize(int64* size_out) OVERRIDE;
+ virtual bool IsStreaming() OVERRIDE;
+ virtual void SetPreload(media::Preload preload) OVERRIDE;
+ virtual void SetBitrate(int bitrate) OVERRIDE;
+
+ // webkit_glue::WebDataSource implementation.
+ virtual void Initialize(const std::string& url,
+ const media::PipelineStatusCB& callback) OVERRIDE;
+ virtual void CancelInitialize() OVERRIDE;
+ virtual bool HasSingleOrigin() OVERRIDE;
+ virtual void Abort() OVERRIDE;
+
+ protected:
+ // 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);
+
+ private:
+ friend class BufferedDataSourceTest2;
+
+ // 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);
+
+ // 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 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);
+
+ // This task saves the preload value for the media.
+ void SetPreloadTask(media::Preload preload);
+
+ // Tells |loader_| the bitrate of the media.
+ void SetBitrateTask(int bitrate);
+
+ // Decides which DeferStrategy to used based on the current state of the
+ // BufferedDataSource.
+ BufferedResourceLoader::DeferStrategy ChooseDeferStrategy();
+
+ // 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_cb_| and reset it.
+ void DoneInitialization_Locked(media::PipelineStatus status);
+
+ // 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();
+
+ void UpdateHostState_Locked();
+
+ // 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_;
+ int64 buffered_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 webframe for loading.
+ WebKit::WebFrame* frame_;
+
+ // A resource loader for the media resource.
+ scoped_refptr<BufferedResourceLoader> loader_;
+
+ // True if |loader| is downloading data.
+ bool is_downloading_data_;
+
+ // Callback method from the pipeline for initialization.
+ media::PipelineStatusCB initialize_cb_;
+
+ // Read parameters received from the Read() method call.
+ media::DataSource::ReadCallback read_callback_;
+ int64 read_position_;
+ int read_size_;
+ uint8* read_buffer_;
+
+ // This buffer is intermediate, we use it for BufferedResourceLoader to write
+ // to. And when read in BufferedResourceLoader is done, we copy data from
+ // this buffer to |read_buffer_|. The reason for an additional copy is that
+ // we don't own |read_buffer_|. But since the read operation is asynchronous,
+ // |read_buffer| can be destroyed at any time, so we only copy into
+ // |read_buffer| in the final step when it is safe.
+ // Memory is allocated for this member during initialization of this object
+ // because we want buffer to be passed into BufferedResourceLoader to be
+ // always non-null. And by initializing this member with a default size we can
+ // avoid creating zero-sized buffered if the first read has zero size.
+ scoped_array<uint8> intermediate_read_buffer_;
+ int intermediate_read_buffer_size_;
+
+ // The message loop of the render thread.
+ MessageLoop* render_loop_;
+
+ // Protects |stop_signal_received_|, |stopped_on_render_loop_| and
+ // |initialize_cb_|.
+ base::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 and work should no longer progress.
+ 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 variable is true when the user has requested the video to play at
+ // least once.
+ bool media_has_played_;
+
+ // This variable holds the value of the preload attribute for the video
+ // element.
+ media::Preload preload_;
+
+ // Keeps track of whether we used a Range header in the initialization
+ // request.
+ bool using_range_request_;
+
+ // Number of cache miss retries left.
+ int cache_miss_retries_left_;
+
+ // Bitrate of the content, 0 if unknown.
+ int bitrate_;
+
+ // Current playback rate.
+ float playback_rate_;
+
+ scoped_refptr<media::MediaLog> media_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSource);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_H_
diff --git a/webkit/media/buffered_data_source_unittest.cc b/webkit/media/buffered_data_source_unittest.cc
new file mode 100644
index 0000000..f7f7131
--- /dev/null
+++ b/webkit/media/buffered_data_source_unittest.cc
@@ -0,0 +1,791 @@
+// Copyright (c) 2011 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/bind.h"
+#include "base/test/test_timeouts.h"
+#include "media/base/media_log.h"
+#include "media/base/mock_callback.h"
+#include "media/base/mock_filter_host.h"
+#include "media/base/mock_filters.h"
+#include "net/base/net_errors.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "webkit/media/buffered_data_source.h"
+#include "webkit/mocks/mock_webframeclient.h"
+#include "webkit/mocks/mock_weburlloader.h"
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::AtLeast;
+using ::testing::DeleteArg;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+using ::testing::WithArgs;
+
+using WebKit::WebFrame;
+using WebKit::WebString;
+using WebKit::WebURLError;
+using WebKit::WebURLResponse;
+using WebKit::WebView;
+
+using webkit_glue::MockWebFrameClient;
+using webkit_glue::MockWebURLLoader;
+
+namespace webkit_media {
+
+static const char* kHttpUrl = "http://test";
+static const char* kFileUrl = "file://test";
+static const int kDataSize = 1024;
+static const int kMaxCacheMissesBeforeFailTest = 20;
+
+enum NetworkState {
+ NONE,
+ LOADED,
+ LOADING
+};
+
+// A mock BufferedDataSource to inject mock BufferedResourceLoader through
+// CreateResourceLoader() method.
+class MockBufferedDataSource : public BufferedDataSource {
+ public:
+ MockBufferedDataSource(MessageLoop* message_loop, WebFrame* frame)
+ : BufferedDataSource(message_loop, frame, new media::MediaLog()) {
+ }
+
+ virtual base::TimeDelta GetTimeoutMilliseconds() {
+ return base::TimeDelta::FromMilliseconds(
+ TestTimeouts::tiny_timeout_ms());
+ }
+
+ MOCK_METHOD2(CreateResourceLoader,
+ BufferedResourceLoader*(int64 first_position,
+ int64 last_position));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource);
+};
+
+class MockBufferedResourceLoader : public BufferedResourceLoader {
+ public:
+ MockBufferedResourceLoader()
+ : BufferedResourceLoader(GURL(), 0, 0, kThresholdDefer,
+ 0, 0, new media::MediaLog()) {
+ }
+
+ MOCK_METHOD3(Start, void(net::OldCompletionCallback* read_callback,
+ const base::Closure& network_callback,
+ WebFrame* frame));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer,
+ net::OldCompletionCallback* callback));
+ MOCK_METHOD0(content_length, int64());
+ MOCK_METHOD0(instance_size, int64());
+ MOCK_METHOD0(range_supported, bool());
+ MOCK_METHOD0(network_activity, bool());
+ MOCK_METHOD0(url, const GURL&());
+ MOCK_METHOD0(GetBufferedFirstBytePosition, int64());
+ MOCK_METHOD0(GetBufferedLastBytePosition, int64());
+
+ protected:
+ ~MockBufferedResourceLoader() {}
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader);
+};
+
+class BufferedDataSourceTest : public testing::Test {
+ public:
+ BufferedDataSourceTest()
+ : view_(WebView::create(NULL)) {
+ view_->initializeMainFrame(&client_);
+ message_loop_ = MessageLoop::current();
+
+ for (size_t i = 0; i < sizeof(data_); ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~BufferedDataSourceTest() {
+ view_->close();
+ }
+
+ void ExpectCreateAndStartResourceLoader(int start_error) {
+ EXPECT_CALL(*data_source_, CreateResourceLoader(_, _))
+ .WillOnce(Return(loader_.get()));
+
+ EXPECT_CALL(*loader_, Start(NotNull(), _, NotNull()))
+ .WillOnce(
+ DoAll(Assign(&error_, start_error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+ }
+
+ void InitializeDataSource(const char* url, int error,
+ bool partial_response, int64 instance_size,
+ NetworkState networkState) {
+ // Saves the url first.
+ gurl_ = GURL(url);
+
+ data_source_ = new MockBufferedDataSource(MessageLoop::current(),
+ view_->mainFrame());
+ data_source_->set_host(&host_);
+
+ scoped_refptr<NiceMock<MockBufferedResourceLoader> > first_loader(
+ new NiceMock<MockBufferedResourceLoader>());
+
+ // Creates the mock loader to be injected.
+ loader_ = first_loader;
+
+ bool initialized_ok = (error == net::OK);
+ bool loaded = networkState == LOADED;
+ {
+ InSequence s;
+ ExpectCreateAndStartResourceLoader(error);
+
+ // In the case of an invalid partial response we expect a second loader
+ // to be created.
+ if (partial_response && (error == net::ERR_INVALID_RESPONSE)) {
+ // Verify that the initial loader is stopped.
+ EXPECT_CALL(*loader_, url())
+ .WillRepeatedly(ReturnRef(gurl_));
+ EXPECT_CALL(*loader_, Stop());
+
+ // Replace loader_ with a new instance.
+ loader_ = new NiceMock<MockBufferedResourceLoader>();
+
+ // Create and start. Make sure Start() is called on the new loader.
+ ExpectCreateAndStartResourceLoader(net::OK);
+
+ // Update initialization variable since we know the second loader will
+ // return OK.
+ initialized_ok = true;
+ }
+ }
+
+ // Attach a static function that deletes the memory referred by the
+ // "callback" parameter.
+ ON_CALL(*loader_, Read(_, _, _ , _))
+ .WillByDefault(DeleteArg<3>());
+
+ ON_CALL(*loader_, instance_size())
+ .WillByDefault(Return(instance_size));
+
+ // range_supported() return true if we expect to get a partial response.
+ ON_CALL(*loader_, range_supported())
+ .WillByDefault(Return(partial_response));
+
+ ON_CALL(*loader_, url())
+ .WillByDefault(ReturnRef(gurl_));
+ media::PipelineStatus expected_init_status = media::PIPELINE_OK;
+ if (initialized_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));
+ }
+ } else {
+ expected_init_status = media::PIPELINE_ERROR_NETWORK;
+ EXPECT_CALL(*loader_, Stop());
+ }
+
+ // Actual initialization of the data source.
+ data_source_->Initialize(url,
+ media::NewExpectedStatusCB(expected_init_status));
+ message_loop_->RunAllPending();
+
+ if (initialized_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());
+ }
+
+ data_source_->Stop(media::NewExpectedClosure());
+ message_loop_->RunAllPending();
+ }
+
+ void InvokeStartCallback(
+ net::OldCompletionCallback* callback,
+ const base::Closure& network_callback,
+ WebFrame* frame) {
+ callback->RunWithParams(Tuple1<int>(error_));
+ delete callback;
+ // TODO(hclam): Save network_callback.
+ }
+
+ void InvokeReadCallback(int64 position, int size, uint8* buffer,
+ net::OldCompletionCallback* 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_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ EXPECT_EQ(0,
+ memcmp(buffer_, data_ + static_cast<int>(position), read_size));
+ }
+
+ void ReadDataSourceHang(int64 position, int size) {
+ EXPECT_TRUE(loader_);
+
+ // Expect a call to read, but the call never returns.
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()));
+ data_source_->Read(
+ position, size, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+ message_loop_->RunAllPending();
+
+ // Now expect the read to return after aborting the data source.
+ EXPECT_CALL(*this, ReadCallback(_));
+ EXPECT_CALL(*loader_, Stop());
+ data_source_->Abort();
+ message_loop_->RunAllPending();
+
+ // The loader has now been stopped. Set this to null so that when the
+ // DataSource is stopped, it does not expect a call to stop the loader.
+ loader_ = NULL;
+ }
+
+ void ReadDataSourceMiss(int64 position, int size, int start_error) {
+ 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.
+ NiceMock<MockBufferedResourceLoader> *new_loader =
+ new NiceMock<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_, start_error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+
+ if (start_error == net::OK) {
+ EXPECT_CALL(*new_loader, range_supported())
+ .WillRepeatedly(Return(loader_->range_supported()));
+
+ // 4a. 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));
+ } else {
+ // 4b. The read callback is called with an error because Start() on the
+ // new loader returned an error.
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+ }
+
+ data_source_->Read(
+ position, size, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+ message_loop_->RunAllPending();
+
+ // Make sure data is correct.
+ if (start_error == net::OK)
+ 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_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+
+ message_loop_->RunAllPending();
+ }
+
+ BufferedResourceLoader* InvokeCacheMissCreateResourceLoader(int64 start,
+ int64 end) {
+ NiceMock<MockBufferedResourceLoader>* new_loader =
+ new NiceMock<MockBufferedResourceLoader>();
+
+ EXPECT_CALL(*new_loader, Start(NotNull(), _, NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::OK),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeStartCallback)));
+
+ EXPECT_CALL(*new_loader, range_supported())
+ .WillRepeatedly(Return(loader_->range_supported()));
+
+ int error = net::ERR_FAILED;
+ if (cache_miss_count_ < kMaxCacheMissesBeforeFailTest) {
+ cache_miss_count_++;
+ error = net::ERR_CACHE_MISS;
+ }
+
+ EXPECT_CALL(*new_loader, Read(start, _, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, error),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ loader_ = new_loader;
+ return new_loader;
+ }
+
+ void ReadDataSourceAlwaysCacheMiss(int64 position, int size) {
+ cache_miss_count_ = 0;
+
+ EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1))
+ .WillRepeatedly(Invoke(
+ this,
+ &BufferedDataSourceTest::InvokeCacheMissCreateResourceLoader));
+
+ EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull()))
+ .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS),
+ Invoke(this,
+ &BufferedDataSourceTest::InvokeReadCallback)));
+
+ EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError));
+
+ data_source_->Read(
+ position, size, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+
+ message_loop_->RunAllPending();
+
+ EXPECT_LT(cache_miss_count_, kMaxCacheMissesBeforeFailTest);
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ scoped_refptr<NiceMock<MockBufferedResourceLoader> > loader_;
+ scoped_refptr<MockBufferedDataSource> data_source_;
+
+ MockWebFrameClient client_;
+ WebView* view_;
+
+ StrictMock<media::MockFilterHost> host_;
+ GURL gurl_;
+ MessageLoop* message_loop_;
+
+ int error_;
+ uint8 buffer_[1024];
+ uint8 data_[1024];
+
+ int cache_miss_count_;
+
+ 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 the case where we get a 206 response, but no Content-Range header.
+TEST_F(BufferedDataSourceTest, MissingContentRange) {
+ InitializeDataSource(kHttpUrl, net::ERR_INVALID_RESPONSE, true, 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, net::OK);
+ ReadDataSourceMiss(20, 10, net::OK);
+ StopDataSource();
+}
+
+// Test the case where the initial response from the server indicates that
+// Range requests are supported, but a later request prove otherwise.
+TEST_F(BufferedDataSourceTest, ServerLiesAboutRangeSupport) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING);
+ ReadDataSourceHit(10, 10, 10);
+ ReadDataSourceMiss(1000, 10, net::ERR_INVALID_RESPONSE);
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest, ReadHang) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING);
+ ReadDataSourceHang(10, 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();
+}
+
+// Helper that sets |*value| to true. Useful for binding into a Closure.
+static void SetTrue(bool* value) {
+ *value = true;
+}
+
+// This test makes sure that Stop() does not require a task to run on
+// |message_loop_| before it calls its callback. This prevents accidental
+// introduction of a pipeline teardown deadlock. The pipeline owner blocks
+// the render message loop while waiting for Stop() to complete. Since this
+// object runs on the render message loop, Stop() will not complete if it
+// requires a task to run on the the message loop that is being blocked.
+TEST_F(BufferedDataSourceTest, StopDoesNotUseMessageLoopForCallback) {
+ InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED);
+
+ // Stop() the data source, using a callback that lets us verify that it was
+ // called before Stop() returns. This is to make sure that the callback does
+ // not require |message_loop_| to execute tasks before being called.
+ bool stop_done_called = false;
+ data_source_->Stop(base::Bind(&SetTrue, &stop_done_called));
+
+ // Verify that the callback was called inside the Stop() call.
+ EXPECT_TRUE(stop_done_called);
+
+ message_loop_->RunAllPending();
+}
+
+TEST_F(BufferedDataSourceTest, AbortDuringPendingRead) {
+ InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED);
+
+ // Setup a way to verify that Read() is not called on the loader.
+ // We are doing this to make sure that the ReadTask() is still on
+ // the message loop queue when Abort() is called.
+ bool read_called = false;
+ ON_CALL(*loader_, Read(_, _, _ , _))
+ .WillByDefault(DoAll(Assign(&read_called, true),
+ DeleteArg<3>()));
+
+ // Initiate a Read() on the data source, but don't allow the
+ // message loop to run.
+ data_source_->Read(
+ 0, 10, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(static_cast<BufferedDataSourceTest*>(this))));
+
+ // Call Abort() with the read pending.
+ EXPECT_CALL(*this, ReadCallback(-1));
+ EXPECT_CALL(*loader_, Stop());
+ data_source_->Abort();
+
+ // Verify that Read()'s after the Abort() issue callback with an error.
+ EXPECT_CALL(*this, ReadCallback(-1));
+ data_source_->Read(
+ 0, 10, buffer_,
+ base::Bind(&BufferedDataSourceTest::ReadCallback,
+ base::Unretained(static_cast<BufferedDataSourceTest*>(this))));
+
+ // Stop() the data source like normal.
+ data_source_->Stop(media::NewExpectedClosure());
+
+ // Allow cleanup task to run.
+ message_loop_->RunAllPending();
+
+ // Verify that Read() was not called on the loader.
+ EXPECT_FALSE(read_called);
+}
+
+// Test that we only allow a limited number of cache misses for a
+// single Read() request.
+TEST_F(BufferedDataSourceTest, BoundedCacheMisses) {
+ InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING);
+
+ ReadDataSourceAlwaysCacheMiss(0, 10);
+
+ StopDataSource();
+}
+
+// TODO(scherkus): de-dupe from buffered_resource_loader_unittest.cc
+ACTION_P(RequestCanceled, loader) {
+ WebURLError error;
+ error.reason = net::ERR_ABORTED;
+ error.domain = WebString::fromUTF8(net::kErrorDomain);
+ loader->didFail(NULL, error);
+}
+
+// A more realistic BufferedDataSource that uses BufferedResourceLoader instead
+// of a mocked version but injects a MockWebURLLoader.
+//
+// TODO(scherkus): re-write these tests to use this class then drop the "2"
+// suffix.
+class MockBufferedDataSource2 : public BufferedDataSource {
+ public:
+ MockBufferedDataSource2(MessageLoop* message_loop, WebFrame* frame)
+ : BufferedDataSource(message_loop, frame, new media::MediaLog()),
+ url_loader_(NULL) {
+ }
+
+ virtual base::TimeDelta GetTimeoutMilliseconds() {
+ return base::TimeDelta::FromMilliseconds(
+ TestTimeouts::tiny_timeout_ms());
+ }
+
+ virtual BufferedResourceLoader* CreateResourceLoader(int64 first_position,
+ int64 last_position) {
+ loader_ = BufferedDataSource::CreateResourceLoader(first_position,
+ last_position);
+
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+ ON_CALL(*url_loader_, cancel())
+ .WillByDefault(RequestCanceled(loader_));
+
+ loader_->SetURLLoaderForTest(url_loader_);
+ return loader_;
+ }
+
+ const scoped_refptr<BufferedResourceLoader>& loader() { return loader_; }
+ NiceMock<MockWebURLLoader>* url_loader() { return url_loader_; }
+
+ private:
+ scoped_refptr<BufferedResourceLoader> loader_;
+ NiceMock<MockWebURLLoader>* url_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource2);
+};
+
+class BufferedDataSourceTest2 : public testing::Test {
+ public:
+ BufferedDataSourceTest2()
+ : view_(WebView::create(NULL)),
+ message_loop_(MessageLoop::current()) {
+ view_->initializeMainFrame(&client_);
+ }
+
+ virtual ~BufferedDataSourceTest2() {
+ view_->close();
+ }
+
+ void InitializeDataSource(const char* url) {
+ gurl_ = GURL(url);
+
+ data_source_ = new MockBufferedDataSource2(message_loop_,
+ view_->mainFrame());
+ data_source_->set_host(&host_);
+ data_source_->Initialize(url,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ message_loop_->RunAllPending();
+
+ // Simulate 206 response for a 5,000,000 byte length file.
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
+ WebString::fromUTF8("bytes"));
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8("bytes 0-4999999/5000000"));
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"),
+ WebString::fromUTF8("5000000"));
+ response.setExpectedContentLength(5000000);
+ response.setHTTPStatusCode(206);
+
+ // We should receive corresponding information about the media resource.
+ EXPECT_CALL(host_, SetLoaded(false));
+ EXPECT_CALL(host_, SetTotalBytes(5000000));
+ EXPECT_CALL(host_, SetBufferedBytes(0));
+
+ data_source_->loader()->didReceiveResponse(data_source_->url_loader(),
+ response);
+
+ message_loop_->RunAllPending();
+ }
+
+ void StopDataSource() {
+ data_source_->Stop(media::NewExpectedClosure());
+ message_loop_->RunAllPending();
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+ media::DataSource::ReadCallback NewReadCallback(size_t size) {
+ EXPECT_CALL(*this, ReadCallback(size));
+ return base::Bind(&BufferedDataSourceTest2::ReadCallback,
+ base::Unretained(this));
+ }
+
+ // Accessors for private variables on |data_source_|.
+ media::Preload preload() { return data_source_->preload_; }
+ BufferedResourceLoader::DeferStrategy defer_strategy() {
+ return data_source_->loader()->defer_strategy_;
+ }
+ int data_source_bitrate() { return data_source_->bitrate_; }
+ int data_source_playback_rate() { return data_source_->playback_rate_; }
+ int loader_bitrate() { return data_source_->loader()->bitrate_; }
+ int loader_playback_rate() { return data_source_->loader()->playback_rate_; }
+
+ scoped_refptr<MockBufferedDataSource2> data_source_;
+
+ GURL gurl_;
+ MockWebFrameClient client_;
+ WebView* view_;
+
+ StrictMock<media::MockFilterHost> host_;
+ MessageLoop* message_loop_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest2);
+};
+
+TEST_F(BufferedDataSourceTest2, Default) {
+ InitializeDataSource("http://localhost/foo.webm");
+
+ // Ensure we have sane values for default loading scenario.
+ EXPECT_EQ(media::AUTO, preload());
+ EXPECT_EQ(BufferedResourceLoader::kThresholdDefer, defer_strategy());
+
+ EXPECT_EQ(0, data_source_bitrate());
+ EXPECT_EQ(0.0f, data_source_playback_rate());
+ EXPECT_EQ(0, loader_bitrate());
+ EXPECT_EQ(0.0f, loader_playback_rate());
+
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest2, SetBitrate) {
+ InitializeDataSource("http://localhost/foo.webm");
+
+ data_source_->SetBitrate(1234);
+ message_loop_->RunAllPending();
+ EXPECT_EQ(1234, data_source_bitrate());
+ EXPECT_EQ(1234, loader_bitrate());
+
+ // Read so far ahead to cause the loader to get recreated.
+ BufferedResourceLoader* old_loader = data_source_->loader();
+
+ uint8 buffer[1024];
+ data_source_->Read(4000000, 1024, buffer,
+ NewReadCallback(media::DataSource::kReadError));
+ message_loop_->RunAllPending();
+
+ // Verify loader changed but still has same bitrate.
+ EXPECT_NE(old_loader, data_source_->loader().get());
+ EXPECT_EQ(1234, loader_bitrate());
+
+ StopDataSource();
+}
+
+TEST_F(BufferedDataSourceTest2, SetPlaybackRate) {
+ InitializeDataSource("http://localhost/foo.webm");
+
+ data_source_->SetPlaybackRate(2.0f);
+ message_loop_->RunAllPending();
+ EXPECT_EQ(2.0f, data_source_playback_rate());
+ EXPECT_EQ(2.0f, loader_playback_rate());
+
+ // Read so far ahead to cause the loader to get recreated.
+ BufferedResourceLoader* old_loader = data_source_->loader();
+
+ uint8 buffer[1024];
+ data_source_->Read(4000000, 1024, buffer,
+ NewReadCallback(media::DataSource::kReadError));
+ message_loop_->RunAllPending();
+
+ // Verify loader changed but still has same bitrate.
+ EXPECT_NE(old_loader, data_source_->loader().get());
+ EXPECT_EQ(2.0f, loader_playback_rate());
+
+ StopDataSource();
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/buffered_resource_loader.cc b/webkit/media/buffered_resource_loader.cc
new file mode 100644
index 0000000..6a9f031
--- /dev/null
+++ b/webkit/media/buffered_resource_loader.cc
@@ -0,0 +1,813 @@
+// Copyright (c) 2011 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/media/buffered_resource_loader.h"
+
+#include "base/format_macros.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "media/base/media_log.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitPlatformSupport.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h"
+#include "webkit/glue/multipart_response_delegate.h"
+#include "webkit/glue/webkit_glue.h"
+
+using WebKit::WebFrame;
+using WebKit::WebString;
+using WebKit::WebURLError;
+using WebKit::WebURLLoader;
+using WebKit::WebURLLoaderOptions;
+using WebKit::WebURLRequest;
+using WebKit::WebURLResponse;
+using webkit_glue::MultipartResponseDelegate;
+
+namespace webkit_media {
+
+static const int kHttpOK = 200;
+static const int kHttpPartialContent = 206;
+
+// Define the number of bytes in a megabyte.
+static const size_t kMegabyte = 1024 * 1024;
+
+// Minimum capacity of the buffer in forward or backward direction.
+//
+// 2MB is an arbitrary limit; it just seems to be "good enough" in practice.
+static const size_t kMinBufferCapacity = 2 * kMegabyte;
+
+// Maximum capacity of the buffer in forward or backward direction. This is
+// effectively the largest single read the code path can handle.
+// 20MB is an arbitrary limit; it just seems to be "good enough" in practice.
+static const size_t kMaxBufferCapacity = 20 * kMegabyte;
+
+// Maximum number of bytes outside the buffer we will wait for in order to
+// fulfill a read. If a read starts more than 2MB away from the data we
+// currently have in the buffer, we will not wait for buffer to reach the read's
+// location and will instead reset the request.
+static const int kForwardWaitThreshold = 2 * kMegabyte;
+
+// Computes the suggested backward and forward capacity for the buffer
+// if one wants to play at |playback_rate| * the natural playback speed.
+// Use a value of 0 for |bitrate| if it is unknown.
+static void ComputeTargetBufferWindow(float playback_rate, int bitrate,
+ size_t* out_backward_capacity,
+ size_t* out_forward_capacity) {
+ static const size_t kDefaultBitrate = 200 * 1024 * 8; // 200 Kbps.
+ static const size_t kMaxBitrate = 20 * kMegabyte * 8; // 20 Mbps.
+ static const float kMaxPlaybackRate = 25.0;
+ static const size_t kTargetSecondsBufferedAhead = 10;
+ static const size_t kTargetSecondsBufferedBehind = 2;
+
+ // Use a default bit rate if unknown and clamp to prevent overflow.
+ if (bitrate <= 0)
+ bitrate = kDefaultBitrate;
+ bitrate = std::min(static_cast<size_t>(bitrate), kMaxBitrate);
+
+ // Only scale the buffer window for playback rates greater than 1.0 in
+ // magnitude and clamp to prevent overflow.
+ bool backward_playback = false;
+ if (playback_rate < 0.0f) {
+ backward_playback = true;
+ playback_rate *= -1.0f;
+ }
+
+ playback_rate = std::max(playback_rate, 1.0f);
+ playback_rate = std::min(playback_rate, kMaxPlaybackRate);
+
+ size_t bytes_per_second = static_cast<size_t>(playback_rate * bitrate / 8.0);
+
+ // Clamp between kMinBufferCapacity and kMaxBufferCapacity.
+ *out_forward_capacity = std::max(
+ kTargetSecondsBufferedAhead * bytes_per_second, kMinBufferCapacity);
+ *out_backward_capacity = std::max(
+ kTargetSecondsBufferedBehind * bytes_per_second, kMinBufferCapacity);
+
+ *out_forward_capacity = std::min(*out_forward_capacity, kMaxBufferCapacity);
+ *out_backward_capacity = std::min(*out_backward_capacity, kMaxBufferCapacity);
+
+ if (backward_playback)
+ std::swap(*out_forward_capacity, *out_backward_capacity);
+}
+
+
+BufferedResourceLoader::BufferedResourceLoader(
+ const GURL& url,
+ int64 first_byte_position,
+ int64 last_byte_position,
+ DeferStrategy strategy,
+ int bitrate,
+ float playback_rate,
+ media::MediaLog* media_log)
+ : deferred_(false),
+ defer_strategy_(strategy),
+ completed_(false),
+ range_requested_(false),
+ range_supported_(false),
+ saved_forward_capacity_(0),
+ url_(url),
+ first_byte_position_(first_byte_position),
+ last_byte_position_(last_byte_position),
+ single_origin_(true),
+ start_callback_(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),
+ keep_test_loader_(false),
+ bitrate_(bitrate),
+ playback_rate_(playback_rate),
+ media_log_(media_log) {
+
+ size_t backward_capacity;
+ size_t forward_capacity;
+ ComputeTargetBufferWindow(
+ playback_rate_, bitrate_, &backward_capacity, &forward_capacity);
+ buffer_.reset(new media::SeekableBuffer(backward_capacity, forward_capacity));
+}
+
+BufferedResourceLoader::~BufferedResourceLoader() {
+ if (!completed_ && url_loader_.get())
+ url_loader_->cancel();
+}
+
+void BufferedResourceLoader::Start(net::OldCompletionCallback* start_callback,
+ const base::Closure& event_callback,
+ WebFrame* frame) {
+ // Make sure we have not started.
+ DCHECK(!start_callback_.get());
+ DCHECK(event_callback_.is_null());
+ DCHECK(start_callback);
+ DCHECK(!event_callback.is_null());
+ CHECK(frame);
+
+ start_callback_.reset(start_callback);
+ event_callback_ = event_callback;
+
+ if (first_byte_position_ != kPositionNotSpecified) {
+ // TODO(hclam): server may not support range request so |offset_| may not
+ // equal to |first_byte_position_|.
+ offset_ = first_byte_position_;
+ }
+
+ // Increment the reference count right before we start the request. This
+ // reference will be release when this request has ended.
+ AddRef();
+
+ // Prepare the request.
+ WebURLRequest request(url_);
+ request.setTargetType(WebURLRequest::TargetIsMedia);
+
+ if (IsRangeRequest()) {
+ range_requested_ = true;
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kRange),
+ WebString::fromUTF8(GenerateHeaders(first_byte_position_,
+ last_byte_position_)));
+ }
+ frame->setReferrerForRequest(request, WebKit::WebURL());
+
+ // Disable compression, compression for audio/video doesn't make sense...
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
+ WebString::fromUTF8("identity;q=1, *;q=0"));
+
+ // This flag is for unittests as we don't want to reset |url_loader|
+ if (!keep_test_loader_) {
+ WebURLLoaderOptions options;
+ options.allowCredentials = true;
+ options.crossOriginRequestPolicy =
+ WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
+ url_loader_.reset(frame->createAssociatedURLLoader(options));
+ }
+
+ // Start the resource loading.
+ url_loader_->loadAsynchronously(request, 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 (url_loader_.get()) {
+ if (deferred_)
+ url_loader_->setDefersLoading(false);
+ deferred_ = false;
+
+ if (!completed_) {
+ url_loader_->cancel();
+ completed_ = true;
+ }
+ }
+}
+
+void BufferedResourceLoader::Read(int64 position,
+ int read_size,
+ uint8* buffer,
+ net::OldCompletionCallback* read_callback) {
+ DCHECK(!read_callback_.get());
+ DCHECK(buffer_.get());
+ DCHECK(read_callback);
+ DCHECK(buffer);
+ DCHECK_GT(read_size, 0);
+
+ // 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;
+ }
+
+ // Make sure |read_size_| is not too large for the buffer to ever be able to
+ // fulfill the read request.
+ if (read_size_ > kMaxBufferCapacity) {
+ DoneRead(net::ERR_FAILED);
+ 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();
+ UpdateDeferBehavior();
+ return;
+ }
+
+ // If we expect the read request to be fulfilled later, expand capacity as
+ // necessary and disable deferring.
+ if (WillFulfillRead()) {
+ // Advance offset as much as possible to create additional capacity.
+ int advance = std::min(first_offset_,
+ static_cast<int>(buffer_->forward_bytes()));
+ bool ret = buffer_->Seek(advance);
+ DCHECK(ret);
+
+ offset_ += advance;
+ first_offset_ -= advance;
+ last_offset_ -= advance;
+
+ // Expand capacity to accomodate a read that extends past the normal
+ // capacity.
+ //
+ // This can happen when reading in a large seek index or when the
+ // first byte of a read request falls within kForwardWaitThreshold.
+ if (last_offset_ > static_cast<int>(buffer_->forward_capacity())) {
+ saved_forward_capacity_ = buffer_->forward_capacity();
+ buffer_->set_forward_capacity(last_offset_);
+ }
+
+ // Make sure we stop deferring now that there's additional capacity.
+ //
+ // XXX: can we DCHECK(url_loader_.get()) at this point in time?
+ if (deferred_)
+ SetDeferred(false);
+
+ DCHECK(!ShouldEnableDefer())
+ << "Capacity was not adjusted properly to prevent deferring.";
+
+ return;
+ }
+
+ // Make a callback to report failure.
+ DoneRead(net::ERR_CACHE_MISS);
+}
+
+int64 BufferedResourceLoader::GetBufferedPosition() {
+ if (buffer_.get())
+ return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1;
+ return kPositionNotSpecified;
+}
+
+int64 BufferedResourceLoader::content_length() {
+ return content_length_;
+}
+
+int64 BufferedResourceLoader::instance_size() {
+ return instance_size_;
+}
+
+bool BufferedResourceLoader::range_supported() {
+ return range_supported_;
+}
+
+bool BufferedResourceLoader::is_downloading_data() {
+ return !completed_ && !deferred_;
+}
+
+const GURL& BufferedResourceLoader::url() {
+ return url_;
+}
+
+void BufferedResourceLoader::SetURLLoaderForTest(WebURLLoader* mock_loader) {
+ url_loader_.reset(mock_loader);
+ keep_test_loader_ = true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// WebKit::WebURLLoaderClient implementation.
+void BufferedResourceLoader::willSendRequest(
+ WebURLLoader* loader,
+ WebURLRequest& newRequest,
+ const WebURLResponse& redirectResponse) {
+
+ // The load may have been stopped and |start_callback| is destroyed.
+ // In this case we shouldn't do anything.
+ if (!start_callback_.get()) {
+ // Set the url in the request to an invalid value (empty url).
+ newRequest.setURL(WebKit::WebURL());
+ return;
+ }
+
+ // Only allow |single_origin_| if we haven't seen a different origin yet.
+ if (single_origin_)
+ single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
+
+ if (!webkit_glue::IsProtocolSupportedForMedia(newRequest.url())) {
+ // Set the url in the request to an invalid value (empty url).
+ newRequest.setURL(WebKit::WebURL());
+ DoneStart(net::ERR_ADDRESS_INVALID);
+ return;
+ }
+
+ url_ = newRequest.url();
+}
+
+void BufferedResourceLoader::didSendData(
+ WebURLLoader* loader,
+ unsigned long long bytes_sent,
+ unsigned long long total_bytes_to_be_sent) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didReceiveResponse(
+ WebURLLoader* loader,
+ const WebURLResponse& response) {
+ VLOG(1) << "didReceiveResponse: " << response.httpStatusCode();
+
+ // The loader may have been stopped and |start_callback| is destroyed.
+ // In this case we shouldn't do anything.
+ if (!start_callback_.get())
+ return;
+
+ bool partial_response = false;
+
+ // 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 (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
+ int error = net::OK;
+
+ // Check to see whether the server supports byte ranges.
+ std::string accept_ranges =
+ response.httpHeaderField("Accept-Ranges").utf8();
+ range_supported_ = (accept_ranges.find("bytes") != std::string::npos);
+
+ partial_response = (response.httpStatusCode() == kHttpPartialContent);
+
+ if (range_requested_) {
+ // If we have verified the partial response and it is correct, we will
+ // return net::OK. It's also possible for a server to support range
+ // requests without advertising Accept-Ranges: bytes.
+ if (partial_response && VerifyPartialResponse(response))
+ range_supported_ = true;
+ else
+ error = net::ERR_INVALID_RESPONSE;
+ } else if (response.httpStatusCode() != 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_;
+ }
+
+ // Expected content length can be |kPositionNotSpecified|, in that case
+ // |content_length_| is not specified and this is a streaming response.
+ content_length_ = response.expectedContentLength();
+
+ // 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::didReceiveData(
+ WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) {
+ VLOG(1) << "didReceiveData: " << data_length << " bytes";
+
+ DCHECK(!completed_);
+ DCHECK_GT(data_length, 0);
+
+ // 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), data_length);
+
+ // If there is an active read request, try to fulfill the request.
+ if (HasPendingRead() && CanFulfillRead())
+ ReadInternal();
+
+ // At last see if the buffer is full and we need to defer the downloading.
+ UpdateDeferBehavior();
+
+ // Consume excess bytes from our in-memory buffer if necessary.
+ 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;
+ }
+
+ // Notify that we have received some data.
+ NotifyNetworkEvent();
+ Log();
+}
+
+void BufferedResourceLoader::didDownloadData(
+ WebKit::WebURLLoader* loader,
+ int dataLength) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didReceiveCachedMetadata(
+ WebURLLoader* loader,
+ const char* data,
+ int data_length) {
+ NOTIMPLEMENTED();
+}
+
+void BufferedResourceLoader::didFinishLoading(
+ WebURLLoader* loader,
+ double finishTime) {
+ VLOG(1) << "didFinishLoading";
+
+ DCHECK(!completed_);
+ completed_ = true;
+
+ // If we didn't know the |instance_size_| we do now.
+ if (instance_size_ == kPositionNotSpecified) {
+ instance_size_ = offset_ + buffer_->forward_bytes();
+ }
+
+ // If there is a start callback, calls it.
+ if (start_callback_.get()) {
+ DoneStart(net::OK);
+ }
+
+ // 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());
+
+ // Try to fulfill with what is in the buffer.
+ if (CanFulfillRead())
+ ReadInternal();
+ else
+ DoneRead(net::ERR_CACHE_MISS);
+ }
+
+ // There must not be any outstanding read request.
+ DCHECK(!HasPendingRead());
+
+ // Notify that network response is completed.
+ NotifyNetworkEvent();
+
+ url_loader_.reset();
+ Release();
+}
+
+void BufferedResourceLoader::didFail(
+ WebURLLoader* loader,
+ const WebURLError& error) {
+ VLOG(1) << "didFail: " << error.reason;
+
+ DCHECK(!completed_);
+ completed_ = true;
+
+ // If there is a start callback, calls it.
+ if (start_callback_.get()) {
+ DoneStart(error.reason);
+ }
+
+ // If there is a pending read but the request failed, return with the
+ // reason for the error.
+ if (HasPendingRead()) {
+ DoneRead(error.reason);
+ }
+
+ // Notify that network response is completed.
+ NotifyNetworkEvent();
+
+ url_loader_.reset();
+ Release();
+}
+
+bool BufferedResourceLoader::HasSingleOrigin() const {
+ return single_origin_;
+}
+
+void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) {
+ defer_strategy_ = strategy;
+ UpdateDeferBehavior();
+}
+
+void BufferedResourceLoader::SetPlaybackRate(float playback_rate) {
+ playback_rate_ = playback_rate;
+
+ // This is a pause so don't bother updating the buffer window as we'll likely
+ // get unpaused in the future.
+ if (playback_rate_ == 0.0)
+ return;
+
+ UpdateBufferWindow();
+}
+
+void BufferedResourceLoader::SetBitrate(int bitrate) {
+ DCHECK(bitrate >= 0);
+ bitrate_ = bitrate;
+ UpdateBufferWindow();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Helper methods.
+
+void BufferedResourceLoader::UpdateBufferWindow() {
+ if (!buffer_.get())
+ return;
+
+ size_t backward_capacity;
+ size_t forward_capacity;
+ ComputeTargetBufferWindow(
+ playback_rate_, bitrate_, &backward_capacity, &forward_capacity);
+
+ // This does not evict data from the buffer if the new capacities are less
+ // than the current capacities; the new limits will be enforced after the
+ // existing excess buffered data is consumed.
+ buffer_->set_backward_capacity(backward_capacity);
+ buffer_->set_forward_capacity(forward_capacity);
+}
+
+void BufferedResourceLoader::UpdateDeferBehavior() {
+ if (!url_loader_.get() || !buffer_.get())
+ return;
+
+ // If necessary, toggle defer state and continue/pause downloading data
+ // accordingly.
+ if (ShouldEnableDefer() || ShouldDisableDefer())
+ SetDeferred(!deferred_);
+}
+
+void BufferedResourceLoader::SetDeferred(bool deferred) {
+ deferred_ = deferred;
+ if (url_loader_.get()) {
+ url_loader_->setDefersLoading(deferred);
+ NotifyNetworkEvent();
+ }
+}
+
+bool BufferedResourceLoader::ShouldEnableDefer() {
+ // If we're already deferring, then enabling makes no sense.
+ if (deferred_)
+ return false;
+
+ switch(defer_strategy_) {
+ // Never defer at all, so never enable defer.
+ case kNeverDefer:
+ return false;
+
+ // Defer if nothing is being requested.
+ case kReadThenDefer:
+ return !read_callback_.get();
+
+ // Defer if we've reached the max capacity of the threshold.
+ case kThresholdDefer:
+ return buffer_->forward_bytes() >= buffer_->forward_capacity();
+ }
+ // Otherwise don't enable defer.
+ return false;
+}
+
+bool BufferedResourceLoader::ShouldDisableDefer() {
+ // If we're not deferring, then disabling makes no sense.
+ if (!deferred_)
+ return false;
+
+ switch(defer_strategy_) {
+ // Always disable deferring.
+ case kNeverDefer:
+ return true;
+
+ // We have an outstanding read request, and we have not buffered enough
+ // yet to fulfill the request; disable defer to get more data.
+ case kReadThenDefer:
+ return read_callback_.get() &&
+ last_offset_ > static_cast<int>(buffer_->forward_bytes());
+
+ // We have less than half the capacity of our threshold, so
+ // disable defer to get more data.
+ case kThresholdDefer: {
+ size_t amount_buffered = buffer_->forward_bytes();
+ size_t half_capacity = buffer_->forward_capacity() / 2;
+ return amount_buffered < half_capacity;
+ }
+ }
+
+ // Otherwise keep deferring.
+ return false;
+}
+
+bool BufferedResourceLoader::CanFulfillRead() {
+ // If we are reading too far in the backward direction.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // If the start offset is too far ahead.
+ if (first_offset_ >= static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ // At the point, we verified that first byte requested is within the buffer.
+ // If the request has completed, then just returns with what we have now.
+ if (completed_)
+ return true;
+
+ // If the resource request is still active, make sure the whole requested
+ // range is covered.
+ if (last_offset_ > static_cast<int>(buffer_->forward_bytes()))
+ return false;
+
+ return true;
+}
+
+bool BufferedResourceLoader::WillFulfillRead() {
+ // Trying to read too far behind.
+ if (first_offset_ < 0 &&
+ first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0)
+ return false;
+
+ // Trying to read too far ahead.
+ if (first_offset_ - static_cast<int>(buffer_->forward_bytes()) >=
+ 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 WebURLResponse& response) {
+ int64 first_byte_position, last_byte_position, instance_size;
+
+ if (!MultipartResponseDelegate::ReadContentRanges(response,
+ &first_byte_position,
+ &last_byte_position,
+ &instance_size)) {
+ return false;
+ }
+
+ if (instance_size != kPositionNotSpecified) {
+ instance_size_ = instance_size;
+ }
+
+ if (first_byte_position_ != kPositionNotSpecified &&
+ 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;
+}
+
+std::string BufferedResourceLoader::GenerateHeaders(
+ int64 first_byte_position,
+ int64 last_byte_position) {
+ // Construct the value for the range header.
+ std::string header;
+ if (first_byte_position > kPositionNotSpecified &&
+ last_byte_position > kPositionNotSpecified) {
+ if (first_byte_position <= last_byte_position) {
+ header = base::StringPrintf("bytes=%" PRId64 "-%" PRId64,
+ first_byte_position,
+ last_byte_position);
+ }
+ } else if (first_byte_position > kPositionNotSpecified) {
+ header = base::StringPrintf("bytes=%" PRId64 "-",
+ first_byte_position);
+ } else if (last_byte_position > kPositionNotSpecified) {
+ NOTIMPLEMENTED() << "Suffix range not implemented";
+ }
+ return header;
+}
+
+void BufferedResourceLoader::DoneRead(int error) {
+ read_callback_->RunWithParams(Tuple1<int>(error));
+ read_callback_.reset();
+ if (buffer_.get() && saved_forward_capacity_) {
+ buffer_->set_forward_capacity(saved_forward_capacity_);
+ saved_forward_capacity_ = 0;
+ }
+ read_position_ = 0;
+ read_size_ = 0;
+ read_buffer_ = NULL;
+ first_offset_ = 0;
+ last_offset_ = 0;
+ Log();
+}
+
+void BufferedResourceLoader::DoneStart(int error) {
+ start_callback_->RunWithParams(Tuple1<int>(error));
+ start_callback_.reset();
+}
+
+void BufferedResourceLoader::NotifyNetworkEvent() {
+ if (!event_callback_.is_null())
+ event_callback_.Run();
+}
+
+bool BufferedResourceLoader::IsRangeRequest() const {
+ return first_byte_position_ != kPositionNotSpecified;
+}
+
+void BufferedResourceLoader::Log() {
+ if (buffer_.get()) {
+ media_log_->AddEvent(
+ media_log_->CreateBufferedExtentsChangedEvent(
+ offset_ - buffer_->backward_bytes(),
+ offset_,
+ offset_ + buffer_->forward_bytes()));
+ }
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/buffered_resource_loader.h b/webkit/media/buffered_resource_loader.h
new file mode 100644
index 0000000..e92eb37
--- /dev/null
+++ b/webkit/media/buffered_resource_loader.h
@@ -0,0 +1,304 @@
+// Copyright (c) 2011 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_MEDIA_BUFFERED_RESOURCE_LOADER_H_
+#define WEBKIT_MEDIA_BUFFERED_RESOURCE_LOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer.h"
+#include "googleurl/src/gurl.h"
+#include "media/base/seekable_buffer.h"
+#include "net/base/file_stream.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
+#include "webkit/media/web_data_source.h"
+#include "webkit/media/webmediaplayer_impl.h"
+
+namespace media {
+class MediaLog;
+}
+
+namespace webkit_media {
+
+const int64 kPositionNotSpecified = -1;
+
+const char kHttpScheme[] = "http";
+const char kHttpsScheme[] = "https";
+const char kDataScheme[] = "data";
+
+// This class works inside demuxer thread and render thread. It contains a
+// WebURLLoader 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::WebURLLoaderClient {
+ public:
+ // kNeverDefer - Aggresively buffer; never defer loading while paused.
+ // kReadThenDefer - Request only enough data to fulfill read requests.
+ // kThresholdDefer - Try to keep amount of buffered data at a threshold.
+ enum DeferStrategy {
+ kNeverDefer,
+ kReadThenDefer,
+ kThresholdDefer,
+ };
+
+ // |url| - URL for the resource to be loaded.
+ // |first_byte_position| - First byte to start loading from,
+ // |kPositionNotSpecified| for not specified.
+ // |last_byte_position| - Last byte to be loaded,
+ // |kPositionNotSpecified| for not specified.
+ // |strategy| is the initial loading strategy to use.
+ // |bitrate| is the bitrate of the media, 0 if unknown.
+ // |playback_rate| is the current playback rate of the media.
+ BufferedResourceLoader(const GURL& url,
+ int64 first_byte_position,
+ int64 last_byte_position,
+ DeferStrategy strategy,
+ int bitrate,
+ float playback_rate,
+ media::MediaLog* media_log);
+
+ // 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::OldCompletionCallback* callback,
+ const base::Closure& event_callback,
+ WebKit::WebFrame* frame);
+
+ // 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. If necessary, will temporarily increase forward capacity of
+ // buffer to accomodate an unusually large read.
+ // |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::OldCompletionCallback* callback);
+
+ // Returns the position of the last byte buffered. Returns
+ // |kPositionNotSpecified| if such value is not available.
+ virtual int64 GetBufferedPosition();
+
+ // Gets the content length in bytes of the instance after this loader has been
+ // started. If this value is |kPositionNotSpecified|, then content length is
+ // unknown.
+ virtual int64 content_length();
+
+ // Gets the original size of the file requested. If this value is
+ // |kPositionNotSpecified|, then the size is unknown.
+ virtual int64 instance_size();
+
+ // Returns true if the server supports byte range requests.
+ virtual bool range_supported();
+
+ // Returns true if the resource loader is currently downloading data.
+ virtual bool is_downloading_data();
+
+ // Returns resulting URL.
+ virtual const GURL& url();
+
+ // Used to inject a mock used for unittests.
+ virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader);
+
+ // WebKit::WebURLLoaderClient implementation.
+ virtual void willSendRequest(
+ WebKit::WebURLLoader* loader,
+ WebKit::WebURLRequest& newRequest,
+ const WebKit::WebURLResponse& redirectResponse);
+ virtual void didSendData(
+ WebKit::WebURLLoader* loader,
+ unsigned long long bytesSent,
+ unsigned long long totalBytesToBeSent);
+ virtual void didReceiveResponse(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLResponse& response);
+ virtual void didDownloadData(
+ WebKit::WebURLLoader* loader,
+ int data_length);
+ virtual void didReceiveData(
+ WebKit::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length);
+ virtual void didReceiveCachedMetadata(
+ WebKit::WebURLLoader* loader,
+ const char* data, int dataLength);
+ virtual void didFinishLoading(
+ WebKit::WebURLLoader* loader,
+ double finishTime);
+ virtual void didFail(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLError&);
+
+ bool HasSingleOrigin() const;
+
+ // Sets the defer strategy to the given value.
+ void UpdateDeferStrategy(DeferStrategy strategy);
+
+ // Sets the playback rate to the given value and updates buffer window
+ // accordingly.
+ void SetPlaybackRate(float playback_rate);
+
+ // Sets the bitrate to the given value and updates buffer window
+ // accordingly.
+ void SetBitrate(int bitrate);
+
+ protected:
+ friend class base::RefCountedThreadSafe<BufferedResourceLoader>;
+ virtual ~BufferedResourceLoader();
+
+ private:
+ friend class BufferedDataSourceTest2;
+ friend class BufferedResourceLoaderTest;
+
+ // Updates the |buffer_|'s forward and backward capacities.
+ void UpdateBufferWindow();
+
+ // Returns true if we should defer resource loading, based
+ // on current buffering scheme.
+ bool ShouldEnableDefer();
+
+ // Returns true if we should enable resource loading, based
+ // on current buffering scheme.
+ bool ShouldDisableDefer();
+
+ // Updates deferring behavior based on current buffering scheme.
+ void UpdateDeferBehavior();
+
+ // Set defer state to |deferred| and cease/continue downloading data
+ // accordingly.
+ void SetDeferred(bool deferred);
+
+ // 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_callback_|, 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 WebKit::WebURLResponse& response);
+
+ // Returns the value for a range request header using parameters
+ // |first_byte_position| and |last_byte_position|. Negative numbers other
+ // than |kPositionNotSpecified| 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 |kPositionNotSpecified|.
+ // Empty string is returned on invalid parameters.
+ std::string GenerateHeaders(int64 first_byte_position,
+ int64 last_byte_position);
+
+ // 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; }
+
+ // Helper function that returns true if a range request was specified.
+ bool IsRangeRequest() const;
+
+ // Log everything interesting to |media_log_|.
+ void Log();
+
+ // A sliding window of buffer.
+ scoped_ptr<media::SeekableBuffer> buffer_;
+
+ // True if resource loading was deferred.
+ bool deferred_;
+
+ // Current buffering algorithm in place for resource loading.
+ DeferStrategy defer_strategy_;
+
+ // True if resource loading has completed.
+ bool completed_;
+
+ // True if a range request was made.
+ bool range_requested_;
+
+ // True if Range header is supported.
+ bool range_supported_;
+
+ // Forward capacity to reset to after an extension.
+ size_t saved_forward_capacity_;
+
+ // Does the work of loading and sends data back to this client.
+ scoped_ptr<WebKit::WebURLLoader> url_loader_;
+
+ GURL url_;
+ int64 first_byte_position_;
+ int64 last_byte_position_;
+ bool single_origin_;
+
+ // Callback method that listens to network events.
+ base::Closure event_callback_;
+
+ // Members used during request start.
+ scoped_ptr<net::OldCompletionCallback> start_callback_;
+ 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::OldCompletionCallback> read_callback_;
+ int64 read_position_;
+ size_t read_size_;
+ uint8* read_buffer_;
+
+ // Offsets of the requested first byte and last byte in |buffer_|. They are
+ // written by Read().
+ int first_offset_;
+ int last_offset_;
+
+ // Used to ensure mocks for unittests are used instead of reset in Start().
+ bool keep_test_loader_;
+
+ // Bitrate of the media. Set to 0 if unknown.
+ int bitrate_;
+
+ // Playback rate of the media.
+ float playback_rate_;
+
+ scoped_refptr<media::MediaLog> media_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_BUFFERED_RESOURCE_LOADER_H_
diff --git a/webkit/media/buffered_resource_loader_unittest.cc b/webkit/media/buffered_resource_loader_unittest.cc
new file mode 100644
index 0000000..e434dff
--- /dev/null
+++ b/webkit/media/buffered_resource_loader_unittest.cc
@@ -0,0 +1,1111 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/bind.h"
+#include "base/format_macros.h"
+#include "base/stringprintf.h"
+#include "media/base/media_log.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "webkit/media/buffered_resource_loader.h"
+#include "webkit/mocks/mock_webframeclient.h"
+#include "webkit/mocks/mock_weburlloader.h"
+
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::Truly;
+using ::testing::NiceMock;
+
+using WebKit::WebString;
+using WebKit::WebURLError;
+using WebKit::WebURLResponse;
+using WebKit::WebView;
+
+using webkit_glue::MockWebFrameClient;
+using webkit_glue::MockWebURLLoader;
+
+namespace webkit_media {
+
+static const char* kHttpUrl = "http://test";
+static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing";
+static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2";
+static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2";
+static const char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing";
+
+static const int kDataSize = 1024;
+static const int kHttpOK = 200;
+static const int kHttpPartialContent = 206;
+
+enum NetworkState {
+ NONE,
+ LOADED,
+ LOADING
+};
+
+// Submit a request completed event to the resource loader due to request
+// being canceled. Pretending the event is from external.
+ACTION_P(RequestCanceled, loader) {
+ WebURLError error;
+ error.reason = net::ERR_ABORTED;
+ error.domain = WebString::fromUTF8(net::kErrorDomain);
+ loader->didFail(NULL, error);
+}
+
+// Predicate that tests that request disallows compressed data.
+static bool CorrectAcceptEncoding(const WebKit::WebURLRequest &request) {
+ std::string value = request.httpHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding)).utf8();
+ return (value.find("identity;q=1") != std::string::npos) &&
+ (value.find("*;q=0") != std::string::npos);
+}
+
+class BufferedResourceLoaderTest : public testing::Test {
+ public:
+ BufferedResourceLoaderTest()
+ : view_(WebView::create(NULL)) {
+ view_->initializeMainFrame(&client_);
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~BufferedResourceLoaderTest() {
+ view_->close();
+ }
+
+ void Initialize(const char* url, int first_position, int last_position) {
+ gurl_ = GURL(url);
+ first_position_ = first_position;
+ last_position_ = last_position;
+
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+ loader_ = new BufferedResourceLoader(
+ gurl_, first_position_, last_position_,
+ BufferedResourceLoader::kThresholdDefer, 0, 0,
+ new media::MediaLog());
+ loader_->SetURLLoaderForTest(url_loader_);
+ }
+
+ 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(*url_loader_, loadAsynchronously(Truly(CorrectAcceptEncoding),
+ loader_.get()));
+ loader_->Start(
+ NewCallback(this, &BufferedResourceLoaderTest::StartCallback),
+ base::Bind(&BufferedResourceLoaderTest::NetworkCallback,
+ base::Unretained(this)),
+ view_->mainFrame());
+ }
+
+ void FullResponse(int64 instance_size) {
+ FullResponse(instance_size, net::OK);
+ }
+
+ void FullResponse(int64 instance_size, int status) {
+ EXPECT_CALL(*this, StartCallback(status));
+ if (status != net::OK) {
+ EXPECT_CALL(*url_loader_, cancel())
+ .WillOnce(RequestCanceled(loader_));
+ }
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"),
+ WebString::fromUTF8(base::StringPrintf("%"
+ PRId64, instance_size)));
+ response.setExpectedContentLength(instance_size);
+ response.setHTTPStatusCode(kHttpOK);
+ loader_->didReceiveResponse(url_loader_, response);
+
+ if (status == net::OK) {
+ EXPECT_EQ(instance_size, loader_->content_length());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+ }
+
+ EXPECT_FALSE(loader_->range_supported());
+ }
+
+ void PartialResponse(int64 first_position, int64 last_position,
+ int64 instance_size) {
+ PartialResponse(first_position, last_position, instance_size, false, true);
+ }
+
+ void PartialResponse(int64 first_position, int64 last_position,
+ int64 instance_size, bool chunked, bool accept_ranges) {
+ EXPECT_CALL(*this, StartCallback(net::OK));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8(base::StringPrintf("bytes "
+ "%" PRId64 "-%" PRId64 "/%" PRId64,
+ first_position,
+ last_position,
+ instance_size)));
+
+ // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked.
+ int64 content_length = -1;
+ if (chunked) {
+ response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"),
+ WebString::fromUTF8("chunked"));
+ } else {
+ content_length = last_position - first_position + 1;
+ }
+ response.setExpectedContentLength(content_length);
+
+ // A server isn't required to return Accept-Ranges even though it might.
+ if (accept_ranges) {
+ response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"),
+ WebString::fromUTF8("bytes"));
+ }
+
+ response.setHTTPStatusCode(kHttpPartialContent);
+ loader_->didReceiveResponse(url_loader_, response);
+
+ // XXX: what's the difference between these two? For example in the chunked
+ // range request case, Content-Length is unspecified (because it's chunked)
+ // but Content-Range: a-b/c can be returned, where c == Content-Length
+ //
+ // Can we eliminate one?
+ EXPECT_EQ(content_length, loader_->content_length());
+ EXPECT_EQ(instance_size, loader_->instance_size());
+
+ // A valid partial response should always result in this being true.
+ EXPECT_TRUE(loader_->range_supported());
+ }
+
+ void Redirect(const char* url) {
+ GURL redirectUrl(url);
+ WebKit::WebURLRequest newRequest(redirectUrl);
+ WebKit::WebURLResponse redirectResponse(gurl_);
+
+ loader_->willSendRequest(url_loader_, newRequest, redirectResponse);
+
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void StopWhenLoad() {
+ InSequence s;
+ EXPECT_CALL(*url_loader_, cancel())
+ .WillOnce(RequestCanceled(loader_));
+ loader_->Stop();
+ loader_ = NULL;
+ }
+
+ // Helper method to write to |loader_| from |data_|.
+ void WriteLoader(int position, int size) {
+ EXPECT_CALL(*this, NetworkCallback())
+ .RetiresOnSaturation();
+ loader_->didReceiveData(url_loader_,
+ reinterpret_cast<char*>(data_ + position),
+ size,
+ size);
+ }
+
+ void WriteData(int size) {
+ EXPECT_CALL(*this, NetworkCallback())
+ .RetiresOnSaturation();
+
+ scoped_array<char> data(new char[size]);
+ loader_->didReceiveData(url_loader_, data.get(), size, size);
+ }
+
+ void WriteUntilThreshold() {
+ size_t buffered = loader_->buffer_->forward_bytes();
+ size_t capacity = loader_->buffer_->forward_capacity();
+ CHECK_LT(buffered, capacity);
+
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteData(capacity - buffered);
+ ConfirmLoaderDeferredState(true);
+ }
+
+ // Helper method to read from |loader_|.
+ void ReadLoader(int64 position, int size, uint8* buffer) {
+ loader_->Read(position, size, buffer,
+ NewCallback(this, &BufferedResourceLoaderTest::ReadCallback));
+ }
+
+ // Verifies 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));
+ }
+
+ void ConfirmLoaderOffsets(int64 expected_offset,
+ int expected_first_offset,
+ int expected_last_offset) {
+ EXPECT_EQ(loader_->offset_, expected_offset);
+ EXPECT_EQ(loader_->first_offset_, expected_first_offset);
+ EXPECT_EQ(loader_->last_offset_, expected_last_offset);
+ }
+
+ void ConfirmBufferState(size_t backward_bytes,
+ size_t backward_capacity,
+ size_t forward_bytes,
+ size_t forward_capacity) {
+ EXPECT_EQ(backward_bytes, loader_->buffer_->backward_bytes());
+ EXPECT_EQ(backward_capacity, loader_->buffer_->backward_capacity());
+ EXPECT_EQ(forward_bytes, loader_->buffer_->forward_bytes());
+ EXPECT_EQ(forward_capacity, loader_->buffer_->forward_capacity());
+ }
+
+ void ConfirmLoaderBufferBackwardCapacity(size_t expected_backward_capacity) {
+ EXPECT_EQ(loader_->buffer_->backward_capacity(),
+ expected_backward_capacity);
+ }
+
+ void ConfirmLoaderBufferForwardCapacity(size_t expected_forward_capacity) {
+ EXPECT_EQ(loader_->buffer_->forward_capacity(), expected_forward_capacity);
+ }
+
+ void ConfirmLoaderDeferredState(bool expectedVal) {
+ EXPECT_EQ(loader_->deferred_, expectedVal);
+ }
+
+ // Makes sure the |loader_| buffer window is in a reasonable range.
+ void CheckBufferWindowBounds() {
+ // Corresponds to value defined in buffered_resource_loader.cc.
+ static const size_t kMinBufferCapacity = 2 * 1024 * 1024;
+ EXPECT_GE(loader_->buffer_->forward_capacity(), kMinBufferCapacity);
+ EXPECT_GE(loader_->buffer_->backward_capacity(), kMinBufferCapacity);
+
+ // Corresponds to value defined in buffered_resource_loader.cc.
+ static const size_t kMaxBufferCapacity = 20 * 1024 * 1024;
+ EXPECT_LE(loader_->buffer_->forward_capacity(), kMaxBufferCapacity);
+ EXPECT_LE(loader_->buffer_->backward_capacity(), kMaxBufferCapacity);
+ }
+
+ MOCK_METHOD1(StartCallback, void(int error));
+ MOCK_METHOD1(ReadCallback, void(int error));
+ MOCK_METHOD0(NetworkCallback, void());
+
+ // Accessors for private variables on |loader_|.
+ size_t forward_bytes() { return loader_->buffer_->forward_bytes(); }
+ size_t forward_capacity() { return loader_->buffer_->forward_capacity(); }
+
+ protected:
+ GURL gurl_;
+ int64 first_position_;
+ int64 last_position_;
+
+ scoped_refptr<BufferedResourceLoader> loader_;
+ NiceMock<MockWebURLLoader>* url_loader_;
+
+ MockWebFrameClient client_;
+ WebView* view_;
+
+ uint8 data_[kDataSize];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest);
+};
+
+TEST_F(BufferedResourceLoaderTest, StartStop) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ StopWhenLoad();
+}
+
+// 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(*url_loader_, cancel())
+ .WillOnce(RequestCanceled(loader_));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPStatusCode(404);
+ response.setHTTPStatusText("Not Found\n");
+ loader_->didReceiveResponse(url_loader_, response);
+}
+
+// Tests that partial content is requested but not fulfilled.
+TEST_F(BufferedResourceLoaderTest, NotPartialResponse) {
+ Initialize(kHttpUrl, 100, -1);
+ Start();
+ FullResponse(1024, net::ERR_INVALID_RESPONSE);
+}
+
+// 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();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_Chunked) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, true, true);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_NoAcceptRanges) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, false, false);
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, PartialResponse_ChunkedNoAcceptRanges) {
+ Initialize(kHttpUrl, 100, 200);
+ Start();
+ PartialResponse(100, 200, 1024, true, false);
+ 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(*url_loader_, cancel())
+ .WillOnce(RequestCanceled(loader_));
+
+ WebURLResponse response(gurl_);
+ response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"),
+ WebString::fromUTF8(base::StringPrintf("bytes "
+ "%d-%d/%d", 1, 10, 1024)));
+ response.setExpectedContentLength(10);
+ response.setHTTPStatusCode(kHttpPartialContent);
+ loader_->didReceiveResponse(url_loader_, response);
+}
+
+// Tests the logic of sliding window for data buffering and reading.
+TEST_F(BufferedResourceLoaderTest, BufferAndRead) {
+ Initialize(kHttpUrl, 10, 29);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer);
+ 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());
+ loader_->didFinishLoading(url_loader_, 0);
+
+ // 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);
+}
+
+// Tests the logic of expanding the data buffer for large reads.
+TEST_F(BufferedResourceLoaderTest, ReadExtendBuffer) {
+ Initialize(kHttpUrl, 10, 0x014FFFFFF);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 0x014FFFFFF, 0x01500000);
+
+ // Don't test for network callbacks (covered by *Strategy tests).
+ EXPECT_CALL(*this, NetworkCallback())
+ .WillRepeatedly(Return());
+
+ uint8 buffer[20];
+ InSequence s;
+
+ // Write more than forward capacity and read it back. Ensure forward capacity
+ // gets reset.
+ WriteLoader(10, 20);
+ EXPECT_CALL(*this, ReadCallback(20));
+ ReadLoader(10, 20, buffer);
+
+ VerifyBuffer(buffer, 10, 20);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Make and outstanding read request larger than forward capacity. Ensure
+ // forward capacity gets extended.
+ ReadLoader(30, 20, buffer);
+
+ ConfirmLoaderBufferForwardCapacity(20);
+
+ // Fulfill outstanding request. Ensure forward capacity gets reset.
+ EXPECT_CALL(*this, ReadCallback(20));
+ WriteLoader(30, 20);
+
+ VerifyBuffer(buffer, 30, 20);
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Try to read further ahead than kForwardWaitThreshold allows. Ensure
+ // forward capacity is not changed.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(0x00300000, 1, buffer);
+
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ // Try to read more than maximum forward capacity. Ensure forward capacity is
+ // not changed.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED));
+ ReadLoader(30, 0x01400001, buffer);
+
+ ConfirmLoaderBufferForwardCapacity(10);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) {
+ Initialize(kHttpUrl, 10, 0x00FFFFFF);
+ Start();
+ PartialResponse(10, 0x00FFFFFF, 0x01000000);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Read very far ahead 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());
+ loader_->didFinishLoading(url_loader_, 0);
+}
+
+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());
+ WebURLError error;
+ error.reason = net::ERR_FAILED;
+ loader_->didFail(url_loader_, error);
+}
+
+// Tests the data buffering logic of NeverDefer strategy.
+TEST_F(BufferedResourceLoaderTest, NeverDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Read past the buffer size; should not defer regardless.
+ WriteLoader(10, 10);
+ WriteLoader(20, 50);
+ ConfirmLoaderDeferredState(false);
+
+ // Should move past window.
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(10, 10, buffer);
+
+ StopWhenLoad();
+}
+
+// Tests the data buffering logic of ReadThenDefer strategy.
+TEST_F(BufferedResourceLoaderTest, ReadThenDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kReadThenDefer);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+
+ // Make an outstanding read request.
+ // We should disable deferring after the read request, so expect
+ // a network event.
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(10, 10, buffer);
+
+ // Receive almost enough data to cover, shouldn't defer.
+ WriteLoader(10, 9);
+ ConfirmLoaderDeferredState(false);
+
+ // As soon as we have received enough data to fulfill the read, defer.
+ EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*this, ReadCallback(10));
+ WriteLoader(19, 1);
+
+ ConfirmLoaderDeferredState(true);
+ VerifyBuffer(buffer, 10, 10);
+
+ // Read again which should disable deferring since there should be nothing
+ // left in our internal buffer.
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(20, 10, buffer);
+
+ ConfirmLoaderDeferredState(false);
+
+ // Over-fulfill requested bytes, then deferring should be enabled again.
+ EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*this, ReadCallback(10));
+ WriteLoader(20, 40);
+
+ ConfirmLoaderDeferredState(true);
+ VerifyBuffer(buffer, 20, 10);
+
+ // Read far ahead, which should disable deferring. In this case we still have
+ // bytes in our internal buffer.
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(80, 10, buffer);
+
+ ConfirmLoaderDeferredState(false);
+
+ // Fulfill requested bytes, then deferring should be enabled again.
+ EXPECT_CALL(*this, NetworkCallback());
+ EXPECT_CALL(*this, ReadCallback(10));
+ WriteLoader(60, 40);
+
+ ConfirmLoaderDeferredState(true);
+ VerifyBuffer(buffer, 80, 10);
+
+ StopWhenLoad();
+}
+
+// Tests the data buffering logic of ThresholdDefer strategy.
+TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 20);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[10];
+ InSequence s;
+
+ // Initial expectation: we're not deferring.
+ ConfirmLoaderDeferredState(false);
+
+ // Write half of threshold: keep not deferring.
+ WriteData(5);
+ ConfirmLoaderDeferredState(false);
+
+ // Write rest of space until threshold: start deferring.
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteData(5);
+ ConfirmLoaderDeferredState(true);
+
+ // Read a little from the buffer: keep deferring.
+ EXPECT_CALL(*this, ReadCallback(2));
+ ReadLoader(10, 2, buffer);
+ ConfirmLoaderDeferredState(true);
+
+ // Read a little more and go under threshold: stop deferring.
+ EXPECT_CALL(*this, ReadCallback(4));
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(12, 4, buffer);
+ ConfirmLoaderDeferredState(false);
+
+ // Write rest of space until threshold: start deferring.
+ EXPECT_CALL(*this, NetworkCallback());
+ WriteData(6);
+ ConfirmLoaderDeferredState(true);
+
+ // Read a little from the buffer: keep deferring.
+ EXPECT_CALL(*this, ReadCallback(4));
+ ReadLoader(16, 4, buffer);
+ ConfirmLoaderDeferredState(true);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadForwardsPastBuffered) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ EXPECT_CALL(*this, ReadCallback(4));
+ ReadLoader(10, 4, buffer);
+ ConfirmBufferState(4, 10, 6, 10);
+ ConfirmLoaderOffsets(14, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. I ***
+ // Read past buffered: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=14 [xxxxxx____]
+ // ^^^^ requested 4 bytes @ offset 20
+ // AFTER
+ // offset=24 [__________]
+ //
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(20, 4, buffer);
+ ConfirmLoaderDeferredState(false);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(2);
+ ConfirmLoaderDeferredState(false);
+
+ // Write the rest, read should complete.
+ EXPECT_CALL(*this, ReadCallback(4));
+ WriteData(2);
+ ConfirmLoaderDeferredState(false);
+
+ // POSTCONDITION
+ ConfirmBufferState(4, 10, 0, 10);
+ ConfirmLoaderOffsets(24, 0, 0);
+ ConfirmLoaderDeferredState(false);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadBackwardsPastBuffered) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. II ***
+ // Read backwards a little too much: cache miss.
+ //
+ // BEFORE
+ // offset=10 [__________|xxxxxxxxxx]
+ // ^ ^^^ requested 10 bytes @ offset 9
+ // AFTER
+ // offset=10 [__________|xxxxxxxxxx] !!! cache miss !!!
+ //
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(9, 4, buffer);
+
+ // POSTCONDITION
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_SmallReadWithinThreshold) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. III ***
+ // Read past forward capacity but within threshold: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset forward to create capacity.
+ // 2) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx]
+ // ^^^^ requested 4 bytes @ offset 24
+ // ADJUSTED OFFSET
+ // offset=20 [__________]
+ // ^^^^ requested 4 bytes @ offset 24
+ // AFTER
+ // offset=28 [__________]
+ //
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(24, 4, buffer);
+ ConfirmLoaderOffsets(20, 4, 8);
+ ConfirmLoaderDeferredState(false);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(4);
+ ConfirmLoaderDeferredState(false);
+
+ // Write the rest, read should complete.
+ EXPECT_CALL(*this, ReadCallback(4));
+ WriteData(4);
+ ConfirmLoaderDeferredState(false);
+
+ // POSTCONDITION
+ ConfirmBufferState(8, 10, 0, 10);
+ ConfirmLoaderOffsets(28, 0, 0);
+ ConfirmLoaderDeferredState(false);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadWithinThreshold) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. IV ***
+ // Read a large amount past forward capacity but within
+ // threshold: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset forward to create capacity.
+ // 2) Expand capacity to make sure we don't defer as data arrives.
+ // 3) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx]
+ // ^^^^^^^^^^^^ requested 12 bytes @ offset 24
+ // ADJUSTED OFFSET
+ // offset=20 [__________]
+ // ^^^^^^ ^^^^^^ requested 12 bytes @ offset 24
+ // ADJUSTED CAPACITY
+ // offset=20 [________________]
+ // ^^^^^^^^^^^^ requested 12 bytes @ offset 24
+ // AFTER
+ // offset=36 [__________]
+ //
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(24, 12, buffer);
+ ConfirmLoaderOffsets(20, 4, 16);
+ ConfirmBufferState(10, 10, 0, 16);
+ ConfirmLoaderDeferredState(false);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(10);
+ ConfirmLoaderDeferredState(false);
+
+ // Write the rest, read should complete and capacity should go back to normal.
+ EXPECT_CALL(*this, ReadCallback(12));
+ WriteData(6);
+ ConfirmLoaderBufferForwardCapacity(10);
+ ConfirmLoaderDeferredState(false);
+
+ // POSTCONDITION
+ ConfirmBufferState(6, 10, 0, 10);
+ ConfirmLoaderOffsets(36, 0, 0);
+ ConfirmLoaderDeferredState(false);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadBackwards) {
+ Initialize(kHttpUrl, 10, 99);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, 99, 100);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ EXPECT_CALL(*this, ReadCallback(10));
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(10, 10, buffer);
+ WriteUntilThreshold();
+ ConfirmBufferState(10, 10, 10, 10);
+ ConfirmLoaderOffsets(20, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. V ***
+ // Read a large amount that involves backwards data: stop deferring.
+ //
+ // In order for the read to complete we must:
+ // 1) Adjust offset *backwards* to create capacity.
+ // 2) Expand capacity to make sure we don't defer as data arrives.
+ // 3) Stop deferring to receive more data.
+ //
+ // BEFORE
+ // offset=20 [xxxxxxxxxx|xxxxxxxxxx]
+ // ^^^^ ^^^^^^^^^^ ^^^^ requested 18 bytes @ offset 16
+ // ADJUSTED OFFSET
+ // offset=16 [____xxxxxx|xxxxxxxxxx]xxxx
+ // ^^^^^^^^^^ ^^^^^^^^ requested 18 bytes @ offset 16
+ // ADJUSTED CAPACITY
+ // offset=16 [____xxxxxx|xxxxxxxxxxxxxx____]
+ // ^^^^^^^^^^^^^^^^^^ requested 18 bytes @ offset 16
+ // AFTER
+ // offset=34 [xxxxxxxxxx|__________]
+ //
+ EXPECT_CALL(*this, NetworkCallback());
+ ReadLoader(16, 18, buffer);
+ ConfirmLoaderOffsets(16, 0, 18);
+ ConfirmBufferState(6, 10, 14, 18);
+ ConfirmLoaderDeferredState(false);
+
+ // Write a little, make sure we didn't start deferring.
+ WriteData(2);
+ ConfirmLoaderDeferredState(false);
+
+ // Write the rest, read should complete and capacity should go back to normal.
+ EXPECT_CALL(*this, ReadCallback(18));
+ WriteData(2);
+ ConfirmLoaderBufferForwardCapacity(10);
+ ConfirmLoaderDeferredState(false);
+
+ // POSTCONDITION
+ ConfirmBufferState(4, 10, 0, 10);
+ ConfirmLoaderOffsets(34, 0, 0);
+ ConfirmLoaderDeferredState(false);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, Tricky_ReadPastThreshold) {
+ const size_t kSize = 5 * 1024 * 1024;
+ const size_t kThreshold = 2 * 1024 * 1024;
+
+ Initialize(kHttpUrl, 10, kSize);
+ SetLoaderBuffer(10, 10);
+ Start();
+ PartialResponse(10, kSize - 1, kSize);
+
+ uint8 buffer[256];
+ InSequence s;
+
+ // PRECONDITION
+ WriteUntilThreshold();
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ // *** TRICKY BUSINESS, PT. VI ***
+ // Read past the forward wait threshold: cache miss.
+ //
+ // BEFORE
+ // offset=10 [xxxxxxxxxx] ...
+ // ^^^^ requested 10 bytes @ threshold
+ // AFTER
+ // offset=10 [xxxxxxxxxx] !!! cache miss !!!
+ //
+ EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS));
+ ReadLoader(kThreshold + 20, 10, buffer);
+
+ // POSTCONDITION
+ ConfirmBufferState(0, 10, 10, 10);
+ ConfirmLoaderOffsets(10, 0, 0);
+ ConfirmLoaderDeferredState(true);
+
+ StopWhenLoad();
+}
+
+// NOTE: This test will need to be reworked a little once
+// http://code.google.com/p/chromium/issues/detail?id=72578
+// is fixed.
+TEST_F(BufferedResourceLoaderTest, HasSingleOrigin) {
+ // Make sure no redirect case works as expected.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to the same domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect twice to the same domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToSameDomainUrl2);
+ FullResponse(1024);
+ EXPECT_TRUE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to a different domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ EXPECT_FALSE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+
+ // Test redirect to the same domain and then to a different domain.
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ EXPECT_FALSE(loader_->HasSingleOrigin());
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Default) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+
+ // Test ensures that default construction of a BufferedResourceLoader has sane
+ // values.
+ //
+ // Please do not change these values in order to make a test pass! Instead,
+ // start a conversation on what the default buffer window capacities should
+ // be.
+ ConfirmLoaderBufferBackwardCapacity(2 * 1024 * 1024);
+ ConfirmLoaderBufferForwardCapacity(2 * 1024 * 1024);
+
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_Unknown) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(0);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_BelowLowerBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(1024 * 8); // 1 Kbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_WithinBounds) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(2 * 1024 * 1024 * 8); // 2 Mbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_AboveUpperBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetBitrate(100 * 1024 * 1024 * 8); // 100 Mbps.
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Negative) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(-10);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Zero) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(0);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_BelowLowerBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(0.1f);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_WithinBounds) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(10);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_AboveUpperBound) {
+ Initialize(kHttpUrl, -1, -1);
+ Start();
+ loader_->SetPlaybackRate(100);
+ CheckBufferWindowBounds();
+ StopWhenLoad();
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/media_stream_client.h b/webkit/media/media_stream_client.h
new file mode 100644
index 0000000..ab1e767
--- /dev/null
+++ b/webkit/media/media_stream_client.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 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_MEDIA_MEDIA_STREAM_CLIENT_H_
+#define WEBKIT_MEDIA_MEDIA_STREAM_CLIENT_H_
+
+#include "base/memory/ref_counted.h"
+
+class GURL;
+
+namespace media {
+class VideoDecoder;
+class MessageLoopFactory;
+}
+
+namespace webkit_media {
+
+// Define an interface for media stream client to get some information about
+// the media stream.
+class MediaStreamClient {
+ public:
+ virtual scoped_refptr<media::VideoDecoder> GetVideoDecoder(
+ const GURL& url,
+ media::MessageLoopFactory* message_loop_factory) = 0;
+
+ protected:
+ virtual ~MediaStreamClient() {}
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_MEDIA_STREAM_CLIENT_H_
diff --git a/webkit/media/simple_data_source.cc b/webkit/media/simple_data_source.cc
new file mode 100644
index 0000000..18b464a
--- /dev/null
+++ b/webkit/media/simple_data_source.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2011 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/media/simple_data_source.h"
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "media/base/filter_host.h"
+#include "media/base/media_log.h"
+#include "net/base/data_url.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_request_status.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitPlatformSupport.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h"
+#include "webkit/glue/webkit_glue.h"
+#include "webkit/media/web_data_source_factory.h"
+
+using WebKit::WebString;
+using WebKit::WebURLLoaderOptions;
+
+namespace webkit_media {
+
+static const char kDataScheme[] = "data";
+
+static WebDataSource* NewSimpleDataSource(MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log) {
+ return new SimpleDataSource(render_loop, frame);
+}
+
+// static
+media::DataSourceFactory* SimpleDataSource::CreateFactory(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ const WebDataSourceBuildObserverHack& build_observer) {
+ return new WebDataSourceFactory(render_loop, frame, media_log,
+ &NewSimpleDataSource, build_observer);
+}
+
+SimpleDataSource::SimpleDataSource(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame)
+ : render_loop_(render_loop),
+ frame_(frame),
+ size_(-1),
+ single_origin_(true),
+ state_(UNINITIALIZED),
+ keep_test_loader_(false) {
+ DCHECK(render_loop);
+}
+
+SimpleDataSource::~SimpleDataSource() {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(state_ == UNINITIALIZED || state_ == STOPPED);
+}
+
+void SimpleDataSource::set_host(media::FilterHost* host) {
+ DataSource::set_host(host);
+
+ base::AutoLock auto_lock(lock_);
+ if (state_ == INITIALIZED) {
+ UpdateHostState();
+ }
+}
+
+void SimpleDataSource::Stop(const base::Closure& callback) {
+ base::AutoLock auto_lock(lock_);
+ state_ = STOPPED;
+ if (!callback.is_null())
+ callback.Run();
+
+ // Post a task to the render thread to cancel loading the resource.
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&SimpleDataSource::CancelTask, this));
+}
+
+void SimpleDataSource::Initialize(
+ const std::string& url,
+ const media::PipelineStatusCB& callback) {
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ scoped_refptr<SimpleDataSource> destruction_guard(this);
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK_EQ(state_, UNINITIALIZED);
+ DCHECK(!callback.is_null());
+ state_ = INITIALIZING;
+ initialize_cb_ = callback;
+
+ // Validate the URL.
+ url_ = GURL(url);
+ if (!url_.is_valid() || !webkit_glue::IsProtocolSupportedForMedia(url_)) {
+ DoneInitialization_Locked(false);
+ return;
+ }
+
+ // Post a task to the render thread to start loading the resource.
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&SimpleDataSource::StartTask, this));
+ }
+}
+
+void SimpleDataSource::CancelInitialize() {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(!initialize_cb_.is_null());
+ state_ = STOPPED;
+ initialize_cb_.Reset();
+
+ // Post a task to the render thread to cancel loading the resource.
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&SimpleDataSource::CancelTask, this));
+}
+
+void SimpleDataSource::Read(int64 position,
+ size_t size,
+ uint8* data,
+ const DataSource::ReadCallback& read_callback) {
+ DCHECK_GE(size_, 0);
+ if (position >= size_) {
+ read_callback.Run(0);
+ } else {
+ size_t copied = std::min(size, static_cast<size_t>(size_ - position));
+ memcpy(data, data_.c_str() + position, copied);
+ read_callback.Run(copied);
+ }
+}
+
+bool SimpleDataSource::GetSize(int64* size_out) {
+ *size_out = size_;
+ return true;
+}
+
+bool SimpleDataSource::IsStreaming() {
+ return false;
+}
+
+void SimpleDataSource::SetPreload(media::Preload preload) {
+}
+
+void SimpleDataSource::SetBitrate(int bitrate) {
+}
+
+void SimpleDataSource::SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader) {
+ url_loader_.reset(mock_loader);
+ keep_test_loader_ = true;
+}
+
+void SimpleDataSource::willSendRequest(
+ WebKit::WebURLLoader* loader,
+ WebKit::WebURLRequest& newRequest,
+ const WebKit::WebURLResponse& redirectResponse) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ base::AutoLock auto_lock(lock_);
+
+ // Only allow |single_origin_| if we haven't seen a different origin yet.
+ if (single_origin_)
+ single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
+
+ url_ = newRequest.url();
+}
+
+void SimpleDataSource::didSendData(
+ WebKit::WebURLLoader* loader,
+ unsigned long long bytesSent,
+ unsigned long long totalBytesToBeSent) {
+ NOTIMPLEMENTED();
+}
+
+void SimpleDataSource::didReceiveResponse(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLResponse& response) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ size_ = response.expectedContentLength();
+}
+
+void SimpleDataSource::didDownloadData(
+ WebKit::WebURLLoader* loader,
+ int dataLength) {
+ NOTIMPLEMENTED();
+}
+
+void SimpleDataSource::didReceiveData(
+ WebKit::WebURLLoader* loader,
+ const char* data,
+ int data_length,
+ int encoded_data_length) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ data_.append(data, data_length);
+}
+
+void SimpleDataSource::didReceiveCachedMetadata(
+ WebKit::WebURLLoader* loader,
+ const char* data,
+ int dataLength) {
+ NOTIMPLEMENTED();
+}
+
+void SimpleDataSource::didFinishLoading(
+ WebKit::WebURLLoader* loader,
+ double finishTime) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ scoped_refptr<SimpleDataSource> destruction_guard(this);
+ {
+ base::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 WebURLLoader.
+ DCHECK_EQ(state_, INITIALIZING);
+
+ // If we don't get a content length or the request has failed, report it
+ // as a network error.
+ if (size_ == -1)
+ size_ = data_.length();
+ DCHECK(static_cast<size_t>(size_) == data_.length());
+
+ DoneInitialization_Locked(true);
+ }
+}
+
+void SimpleDataSource::didFail(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLError& error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ scoped_refptr<SimpleDataSource> destruction_guard(this);
+ {
+ base::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 WebURLLoader.
+ DCHECK_EQ(state_, INITIALIZING);
+
+ // If we don't get a content length or the request has failed, report it
+ // as a network error.
+ if (size_ == -1)
+ size_ = data_.length();
+ DCHECK(static_cast<size_t>(size_) == data_.length());
+
+ DoneInitialization_Locked(false);
+ }
+}
+
+bool SimpleDataSource::HasSingleOrigin() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ return single_origin_;
+}
+
+void SimpleDataSource::Abort() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ frame_ = NULL;
+}
+
+void SimpleDataSource::StartTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ // Reference to prevent destruction while inside the |initialize_cb_|
+ // call. This is a temporary fix to prevent crashes caused by holding the
+ // lock and running the destructor.
+ scoped_refptr<SimpleDataSource> destruction_guard(this);
+ {
+ base::AutoLock auto_lock(lock_);
+
+ // We may have stopped.
+ if (state_ == STOPPED)
+ return;
+
+ CHECK(frame_);
+
+ DCHECK_EQ(state_, INITIALIZING);
+
+ if (url_.SchemeIs(kDataScheme)) {
+ // 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 {
+ // Prepare the request.
+ WebKit::WebURLRequest request(url_);
+ request.setTargetType(WebKit::WebURLRequest::TargetIsMedia);
+
+ frame_->setReferrerForRequest(request, WebKit::WebURL());
+
+ // Disable compression, compression for audio/video doesn't make sense...
+ request.setHTTPHeaderField(
+ WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
+ WebString::fromUTF8("identity;q=1, *;q=0"));
+
+ // This flag is for unittests as we don't want to reset |url_loader|
+ if (!keep_test_loader_) {
+ WebURLLoaderOptions options;
+ options.allowCredentials = true;
+ options.crossOriginRequestPolicy =
+ WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
+ url_loader_.reset(frame_->createAssociatedURLLoader(options));
+ }
+
+ // Start the resource loading.
+ url_loader_->loadAsynchronously(request, this);
+ }
+ }
+}
+
+void SimpleDataSource::CancelTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ base::AutoLock auto_lock(lock_);
+ DCHECK_EQ(state_, STOPPED);
+
+ // Cancel any pending requests.
+ if (url_loader_.get()) {
+ url_loader_->cancel();
+ url_loader_.reset();
+ }
+}
+
+void SimpleDataSource::DoneInitialization_Locked(bool success) {
+ lock_.AssertAcquired();
+ media::PipelineStatus status = media::PIPELINE_ERROR_NETWORK;
+ if (success) {
+ state_ = INITIALIZED;
+
+ UpdateHostState();
+ status = media::PIPELINE_OK;
+ } else {
+ state_ = UNINITIALIZED;
+ url_loader_.reset();
+ }
+
+ initialize_cb_.Run(status);
+ initialize_cb_.Reset();
+}
+
+void SimpleDataSource::UpdateHostState() {
+ if (host()) {
+ host()->SetTotalBytes(size_);
+ host()->SetBufferedBytes(size_);
+ // If scheme is file or data, say we are loaded.
+ host()->SetLoaded(url_.SchemeIsFile() || url_.SchemeIs(kDataScheme));
+ }
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/simple_data_source.h b/webkit/media/simple_data_source.h
new file mode 100644
index 0000000..ca1e78a
--- /dev/null
+++ b/webkit/media/simple_data_source.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2011 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_MEDIA_SIMPLE_DATA_SOURCE_H_
+#define WEBKIT_MEDIA_SIMPLE_DATA_SOURCE_H_
+
+#include <algorithm>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "media/base/filter_factories.h"
+#include "media/base/filters.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
+#include "webkit/media/web_data_source.h"
+
+class MessageLoop;
+
+namespace media {
+class MediaLog;
+}
+
+namespace webkit_media {
+
+class SimpleDataSource
+ : public WebDataSource,
+ public WebKit::WebURLLoaderClient {
+ public:
+ // Creates a DataSourceFactory for building SimpleDataSource objects.
+ static media::DataSourceFactory* CreateFactory(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ const WebDataSourceBuildObserverHack& build_observer);
+
+ SimpleDataSource(MessageLoop* render_loop, WebKit::WebFrame* frame);
+ virtual ~SimpleDataSource();
+
+ // media::Filter implementation.
+ virtual void set_host(media::FilterHost* host) OVERRIDE;
+ virtual void Stop(const base::Closure& callback) OVERRIDE;
+
+ // media::DataSource implementation.
+ virtual void Read(int64 position,
+ size_t size,
+ uint8* data,
+ const DataSource::ReadCallback& read_callback) OVERRIDE;
+ virtual bool GetSize(int64* size_out) OVERRIDE;
+ virtual bool IsStreaming() OVERRIDE;
+ virtual void SetPreload(media::Preload preload) OVERRIDE;
+ virtual void SetBitrate(int bitrate) OVERRIDE;
+
+ // Used to inject a mock used for unittests.
+ virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader);
+
+ // WebKit::WebURLLoaderClient implementations.
+ virtual void willSendRequest(
+ WebKit::WebURLLoader* loader,
+ WebKit::WebURLRequest& newRequest,
+ const WebKit::WebURLResponse& redirectResponse);
+ virtual void didSendData(
+ WebKit::WebURLLoader* loader,
+ unsigned long long bytesSent,
+ unsigned long long totalBytesToBeSent);
+ virtual void didReceiveResponse(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLResponse& response);
+ virtual void didDownloadData(
+ WebKit::WebURLLoader* loader,
+ int dataLength);
+ virtual void didReceiveData(
+ WebKit::WebURLLoader* loader,
+ const char* data,
+ int dataLength,
+ int encodedDataLength);
+ virtual void didReceiveCachedMetadata(
+ WebKit::WebURLLoader* loader,
+ const char* data, int dataLength);
+ virtual void didFinishLoading(
+ WebKit::WebURLLoader* loader,
+ double finishTime);
+ virtual void didFail(
+ WebKit::WebURLLoader* loader,
+ const WebKit::WebURLError&);
+
+ // webkit_glue::WebDataSource implementation.
+ virtual void Initialize(const std::string& url,
+ const media::PipelineStatusCB& callback) OVERRIDE;
+ virtual void CancelInitialize() OVERRIDE;
+ virtual bool HasSingleOrigin() OVERRIDE;
+ virtual void Abort() OVERRIDE;
+
+ private:
+ // 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);
+
+ // Update host() stats like total bytes & buffered bytes.
+ void UpdateHostState();
+
+ // Primarily used for asserting the bridge is loading on the render thread.
+ MessageLoop* render_loop_;
+
+ // A webframe for loading.
+ WebKit::WebFrame* frame_;
+
+ // Does the work of loading and sends data back to this client.
+ scoped_ptr<WebKit::WebURLLoader> url_loader_;
+
+ GURL url_;
+ std::string data_;
+ int64 size_;
+ bool single_origin_;
+
+ // Simple state tracking variable.
+ enum State {
+ UNINITIALIZED,
+ INITIALIZING,
+ INITIALIZED,
+ STOPPED,
+ };
+ State state_;
+
+ // Used for accessing |state_|.
+ base::Lock lock_;
+
+ // Filter callbacks.
+ media::PipelineStatusCB initialize_cb_;
+
+ // Used to ensure mocks for unittests are used instead of reset in Start().
+ bool keep_test_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSource);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_SIMPLE_DATA_SOURCE_H_
diff --git a/webkit/media/simple_data_source_unittest.cc b/webkit/media/simple_data_source_unittest.cc
new file mode 100644
index 0000000..62b933c3
--- /dev/null
+++ b/webkit/media/simple_data_source_unittest.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2011 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/bind.h"
+#include "media/base/filters.h"
+#include "media/base/mock_callback.h"
+#include "media/base/mock_filter_host.h"
+#include "media/base/mock_filters.h"
+#include "net/base/net_errors.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "webkit/media/simple_data_source.h"
+#include "webkit/mocks/mock_webframeclient.h"
+#include "webkit/mocks/mock_weburlloader.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;
+
+using WebKit::WebURLError;
+using WebKit::WebURLLoader;
+using WebKit::WebURLRequest;
+using WebKit::WebURLResponse;
+using WebKit::WebView;
+
+using webkit_glue::MockWebFrameClient;
+using webkit_glue::MockWebURLLoader;
+
+namespace webkit_media {
+
+static const int kDataSize = 1024;
+static const char kHttpUrl[] = "http://test";
+static const char kHttpsUrl[] = "https://test";
+static const char kFileUrl[] = "file://test";
+static const char kDataUrl[] =
+ "data:text/plain;base64,YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoK";
+static const char kDataUrlDecoded[] = "abcdefghijklmnopqrstuvwxyz";
+static const char kInvalidUrl[] = "whatever://test";
+static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing";
+static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2";
+static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2";
+static const char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing";
+
+class SimpleDataSourceTest : public testing::Test {
+ public:
+ SimpleDataSourceTest()
+ : view_(WebView::create(NULL)) {
+ view_->initializeMainFrame(&client_);
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_[i] = i;
+ }
+ }
+
+ virtual ~SimpleDataSourceTest() {
+ view_->close();
+ }
+
+ void InitializeDataSource(const char* url,
+ const media::PipelineStatusCB& callback) {
+ gurl_ = GURL(url);
+
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+
+ data_source_ = new SimpleDataSource(MessageLoop::current(),
+ view_->mainFrame());
+
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+ data_source_->SetURLLoaderForTest(url_loader_);
+
+ data_source_->Initialize(url, callback);
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestSucceeded(bool is_loaded) {
+ WebURLResponse response(gurl_);
+ response.setExpectedContentLength(kDataSize);
+
+ data_source_->didReceiveResponse(NULL, response);
+ int64 size;
+ EXPECT_TRUE(data_source_->GetSize(&size));
+ EXPECT_EQ(kDataSize, size);
+
+ for (int i = 0; i < kDataSize; ++i) {
+ data_source_->didReceiveData(NULL, data_ + i, 1, 1);
+ }
+
+ EXPECT_CALL(host_, SetLoaded(is_loaded));
+
+ InSequence s;
+ EXPECT_CALL(host_, SetTotalBytes(kDataSize));
+ EXPECT_CALL(host_, SetBufferedBytes(kDataSize));
+
+ data_source_->didFinishLoading(NULL, 0);
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void RequestFailed() {
+ InSequence s;
+
+ WebURLError error;
+ error.reason = net::ERR_FAILED;
+ data_source_->didFail(NULL, error);
+
+ // Let the tasks to be executed.
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void Redirect(const char* url) {
+ GURL redirectUrl(url);
+ WebURLRequest newRequest(redirectUrl);
+ WebURLResponse redirectResponse(gurl_);
+
+ data_source_->willSendRequest(url_loader_, newRequest, redirectResponse);
+
+ MessageLoop::current()->RunAllPending();
+ }
+
+ void DestroyDataSource() {
+ data_source_->Stop(media::NewExpectedClosure());
+ 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,
+ base::Bind(&SimpleDataSourceTest::ReadCallback,
+ base::Unretained(this)));
+ EXPECT_EQ(static_cast<uint8>(data_[i]), buffer[0]);
+ }
+ }
+
+ MOCK_METHOD1(ReadCallback, void(size_t size));
+
+ protected:
+ GURL gurl_;
+ scoped_ptr<MessageLoop> message_loop_;
+ NiceMock<MockWebURLLoader>* url_loader_;
+ scoped_refptr<SimpleDataSource> data_source_;
+ StrictMock<media::MockFilterHost> host_;
+
+ MockWebFrameClient client_;
+ WebView* view_;
+
+ char data_[kDataSize];
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest);
+};
+
+TEST_F(SimpleDataSourceTest, InitializeHTTP) {
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ RequestSucceeded(false);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeHTTPS) {
+ InitializeDataSource(kHttpsUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ RequestSucceeded(false);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeFile) {
+ InitializeDataSource(kFileUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ RequestSucceeded(true);
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, InitializeData) {
+ url_loader_ = new NiceMock<MockWebURLLoader>();
+
+ data_source_ = new SimpleDataSource(MessageLoop::current(),
+ view_->mainFrame());
+ // There is no need to provide a message loop to data source.
+ data_source_->set_host(&host_);
+ data_source_->SetURLLoaderForTest(url_loader_);
+
+ EXPECT_CALL(host_, SetLoaded(true));
+ EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded)));
+ EXPECT_CALL(host_, SetBufferedBytes(sizeof(kDataUrlDecoded)));
+
+ data_source_->Initialize(kDataUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ MessageLoop::current()->RunAllPending();
+
+ DestroyDataSource();
+}
+
+TEST_F(SimpleDataSourceTest, RequestFailed) {
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_ERROR_NETWORK));
+ RequestFailed();
+ DestroyDataSource();
+}
+
+static void OnStatusCB(bool* called, media::PipelineStatus status) {
+ *called = true;
+}
+
+TEST_F(SimpleDataSourceTest, StopWhenDownloading) {
+ // The callback should be deleted, but not executed.
+ // TODO(scherkus): should this really be the behaviour? Seems strange...
+ bool was_called = false;
+ InitializeDataSource(kHttpUrl, base::Bind(&OnStatusCB, &was_called));
+
+ EXPECT_CALL(*url_loader_, cancel());
+ DestroyDataSource();
+ EXPECT_FALSE(was_called);
+}
+
+TEST_F(SimpleDataSourceTest, AsyncRead) {
+ InitializeDataSource(kFileUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ RequestSucceeded(true);
+ AsyncRead();
+ DestroyDataSource();
+}
+
+// NOTE: This test will need to be reworked a little once
+// http://code.google.com/p/chromium/issues/detail?id=72578
+// is fixed.
+TEST_F(SimpleDataSourceTest, HasSingleOrigin) {
+ // Make sure no redirect case works as expected.
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ RequestSucceeded(false);
+ EXPECT_TRUE(data_source_->HasSingleOrigin());
+ DestroyDataSource();
+
+ // Test redirect to the same domain.
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ RequestSucceeded(false);
+ EXPECT_TRUE(data_source_->HasSingleOrigin());
+ DestroyDataSource();
+
+ // Test redirect twice to the same domain.
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToSameDomainUrl2);
+ RequestSucceeded(false);
+ EXPECT_TRUE(data_source_->HasSingleOrigin());
+ DestroyDataSource();
+
+ // Test redirect to a different domain.
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ RequestSucceeded(false);
+ EXPECT_FALSE(data_source_->HasSingleOrigin());
+ DestroyDataSource();
+
+ // Test redirect to the same domain and then to a different domain.
+ InitializeDataSource(kHttpUrl,
+ media::NewExpectedStatusCB(media::PIPELINE_OK));
+ Redirect(kHttpRedirectToSameDomainUrl1);
+ Redirect(kHttpRedirectToDifferentDomainUrl1);
+ RequestSucceeded(false);
+ EXPECT_FALSE(data_source_->HasSingleOrigin());
+ DestroyDataSource();
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/video_renderer_impl.cc b/webkit/media/video_renderer_impl.cc
new file mode 100644
index 0000000..955512f
--- /dev/null
+++ b/webkit/media/video_renderer_impl.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2011 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/media/video_renderer_impl.h"
+
+#include "base/logging.h"
+#include "media/base/video_frame.h"
+#include "media/base/yuv_convert.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkDevice.h"
+#include "webkit/media/webmediaplayer_proxy.h"
+
+namespace webkit_media {
+
+VideoRendererImpl::VideoRendererImpl(bool pts_logging)
+ : last_converted_frame_(NULL),
+ pts_logging_(pts_logging) {
+}
+
+VideoRendererImpl::~VideoRendererImpl() {}
+
+bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) {
+ natural_size_ = decoder->natural_size();
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config,
+ natural_size_.width(), natural_size_.height());
+ bitmap_.allocPixels();
+ bitmap_.eraseRGB(0x00, 0x00, 0x00);
+ bitmap_.setIsVolatile(true);
+ return true;
+}
+
+void VideoRendererImpl::OnStop(const base::Closure& callback) {
+ if (!callback.is_null())
+ callback.Run();
+}
+
+void VideoRendererImpl::OnFrameAvailable() {
+ proxy_->Repaint();
+}
+
+void VideoRendererImpl::SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) {
+ proxy_ = proxy;
+}
+
+void VideoRendererImpl::SetRect(const gfx::Rect& rect) {}
+
+// This method is always called on the renderer's thread.
+void VideoRendererImpl::Paint(SkCanvas* 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_)
+ VLOG(1) << "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(SkCanvas* 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) {
+ SkDevice* device = canvas->getDevice();
+ const SkBitmap::Config config = device->config();
+
+ if (config == SkBitmap::kARGB_8888_Config && device->isOpaque()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame,
+ SkCanvas* 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_.notifyPixelsChanged();
+ 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() != natural_size_.width() ||
+ dest_rect.height() != natural_size_.height()) {
+ matrix.preScale(SkIntToScalar(dest_rect.width()) /
+ SkIntToScalar(natural_size_.width()),
+ SkIntToScalar(dest_rect.height()) /
+ SkIntToScalar(natural_size_.height()));
+ }
+ SkPaint paint;
+ paint.setFlags(SkPaint::kFilterBitmap_Flag);
+ canvas->drawBitmapMatrix(bitmap_, matrix, &paint);
+}
+
+void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame,
+ SkCanvas* 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();
+ }
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/video_renderer_impl.h b/webkit/media/video_renderer_impl.h
new file mode 100644
index 0000000..af67208
--- /dev/null
+++ b/webkit/media/video_renderer_impl.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 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_MEDIA_VIDEO_RENDERER_IMPL_H_
+#define WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_
+
+#include "media/base/buffers.h"
+#include "media/base/filters.h"
+#include "media/filters/video_renderer_base.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h"
+#include "ui/gfx/size.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "webkit/media/web_video_renderer.h"
+
+namespace webkit_media {
+
+// 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.
+class VideoRendererImpl : public WebVideoRenderer {
+ public:
+ explicit VideoRendererImpl(bool pts_logging);
+ virtual ~VideoRendererImpl();
+
+ // WebVideoRenderer implementation.
+ virtual void SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) OVERRIDE;
+ virtual void SetRect(const gfx::Rect& rect) OVERRIDE;
+ virtual void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) OVERRIDE;
+
+ protected:
+ // VideoRendererBase implementation.
+ virtual bool OnInitialize(media::VideoDecoder* decoder) OVERRIDE;
+ virtual void OnStop(const base::Closure& callback) OVERRIDE;
+ virtual void OnFrameAvailable() OVERRIDE;
+
+ private:
+ // Determine the conditions to perform fast paint. Returns true if we can do
+ // fast paint otherwise false.
+ bool CanFastPaint(SkCanvas* 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,
+ SkCanvas* 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,
+ SkCanvas* canvas,
+ const gfx::Rect& dest_rect);
+
+ // Pointer to our parent object that is called to request repaints.
+ scoped_refptr<WebMediaPlayerProxy> 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 natural size of the video.
+ gfx::Size natural_size_;
+
+ // Whether we're logging video presentation timestamps (PTS).
+ bool pts_logging_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoRendererImpl);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_
diff --git a/webkit/media/web_data_source.cc b/webkit/media/web_data_source.cc
new file mode 100644
index 0000000..4795caa
--- /dev/null
+++ b/webkit/media/web_data_source.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/filters.h"
+#include "webkit/media/web_data_source.h"
+
+namespace webkit_media {
+
+WebDataSource::WebDataSource()
+ : media::DataSource() {
+}
+
+WebDataSource::~WebDataSource() {
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/web_data_source.h b/webkit/media/web_data_source.h
new file mode 100644
index 0000000..5cb27c4
--- /dev/null
+++ b/webkit/media/web_data_source.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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_MEDIA_WEB_DATA_SOURCE_H_
+#define WEBKIT_MEDIA_WEB_DATA_SOURCE_H_
+
+#include "base/callback.h"
+#include "media/base/filters.h"
+#include "media/base/pipeline_status.h"
+
+namespace webkit_media {
+
+// An interface that allows WebMediaPlayerImpl::Proxy to communicate with the
+// DataSource in the pipeline.
+class WebDataSource : public media::DataSource {
+ public:
+ WebDataSource();
+ virtual ~WebDataSource();
+
+ // Initialize this object using |url|. This object calls |callback| when
+ // initialization has completed.
+ virtual void Initialize(const std::string& url,
+ const media::PipelineStatusCB& callback) = 0;
+
+ // Called to cancel initialization. The callback passed in Initialize() will
+ // be destroyed and will not be called after this method returns. Once this
+ // method returns, the object will be in an uninitialized state and
+ // Initialize() cannot be called again. The caller is expected to release
+ // its handle to this object and never call it again.
+ virtual void CancelInitialize() = 0;
+
+ // Returns true if the media resource has a single origin, false otherwise.
+ //
+ // Method called on the render thread.
+ virtual bool HasSingleOrigin() = 0;
+
+ // This method is used to unblock any read calls that would cause the
+ // media pipeline to stall.
+ //
+ // Method called on the render thread.
+ virtual void Abort() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebDataSource);
+};
+
+// Temporary hack to allow WebMediaPlayerImpl::Proxy::AddDataSource() to
+// be called when WebDataSource objects are created. This can be removed
+// once WebMediaPlayerImpl::Proxy is fixed so it doesn't have to track
+// WebDataSources. Proxy only has to track WebDataSources so it can call Abort()
+// on them at shutdown. Once cancellation is added to DataSource and pause
+// support in Demuxers cancel pending reads, Proxy shouldn't have to keep
+// a WebDataSource list or call Abort().
+typedef base::Callback<void(WebDataSource*)> WebDataSourceBuildObserverHack;
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEB_DATA_SOURCE_H_
diff --git a/webkit/media/web_data_source_factory.cc b/webkit/media/web_data_source_factory.cc
new file mode 100644
index 0000000..4df0dcf
--- /dev/null
+++ b/webkit/media/web_data_source_factory.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2011 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/media/web_data_source_factory.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "media/base/media_log.h"
+
+namespace webkit_media {
+
+class WebDataSourceFactory::BuildRequest
+ : public media::AsyncDataSourceFactoryBase::BuildRequest {
+ public:
+ BuildRequest(const std::string& url, const BuildCallback& callback,
+ WebDataSource* data_source,
+ const WebDataSourceBuildObserverHack& build_observer);
+ virtual ~BuildRequest();
+
+ protected:
+ // AsyncDataSourceFactoryBase::BuildRequest method.
+ virtual void DoStart();
+
+ private:
+ void InitDone(media::PipelineStatus status);
+
+ scoped_refptr<WebDataSource> data_source_;
+ WebDataSourceBuildObserverHack build_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(BuildRequest);
+};
+
+WebDataSourceFactory::WebDataSourceFactory(
+ MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ FactoryFunction factory_function,
+ const WebDataSourceBuildObserverHack& build_observer)
+ : render_loop_(render_loop),
+ frame_(frame),
+ media_log_(media_log),
+ factory_function_(factory_function),
+ build_observer_(build_observer) {
+ DCHECK(render_loop_);
+ DCHECK(frame_);
+ DCHECK(media_log_);
+ DCHECK(factory_function_);
+}
+
+WebDataSourceFactory::~WebDataSourceFactory() {}
+
+media::DataSourceFactory* WebDataSourceFactory::Clone() const {
+ return new WebDataSourceFactory(render_loop_, frame_, media_log_,
+ factory_function_, build_observer_);
+}
+
+bool WebDataSourceFactory::AllowRequests() const {
+ return true;
+}
+
+media::AsyncDataSourceFactoryBase::BuildRequest*
+WebDataSourceFactory::CreateRequest(const std::string& url,
+ const BuildCallback& callback) {
+ WebDataSource* data_source = factory_function_(render_loop_, frame_,
+ media_log_);
+
+ return new WebDataSourceFactory::BuildRequest(url, callback, data_source,
+ build_observer_);
+}
+
+WebDataSourceFactory::BuildRequest::BuildRequest(
+ const std::string& url,
+ const BuildCallback& callback,
+ WebDataSource* data_source,
+ const WebDataSourceBuildObserverHack& build_observer)
+ : AsyncDataSourceFactoryBase::BuildRequest(url, callback),
+ data_source_(data_source),
+ build_observer_(build_observer) {
+}
+
+WebDataSourceFactory::BuildRequest::~BuildRequest() {
+ if (data_source_.get()) {
+ data_source_->CancelInitialize();
+ data_source_ = NULL;
+ }
+}
+
+void WebDataSourceFactory::BuildRequest::DoStart() {
+ data_source_->Initialize(url(), base::Bind(&BuildRequest::InitDone,
+ base::Unretained(this)));
+}
+
+void WebDataSourceFactory::BuildRequest::InitDone(
+ media::PipelineStatus status) {
+ scoped_refptr<WebDataSource> data_source;
+
+ data_source = (status == media::PIPELINE_OK) ? data_source_ : NULL;
+ data_source_ = NULL;
+
+ if (!build_observer_.is_null() && data_source.get()) {
+ build_observer_.Run(data_source.get());
+ }
+
+ RequestComplete(status, data_source);
+ // Don't do anything after this line. This object is deleted by
+ // RequestComplete().
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/web_data_source_factory.h b/webkit/media/web_data_source_factory.h
new file mode 100644
index 0000000..2b0dd65
--- /dev/null
+++ b/webkit/media/web_data_source_factory.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_
+#define WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_
+
+#include "base/memory/ref_counted.h"
+#include "media/base/async_filter_factory_base.h"
+#include "webkit/media/web_data_source.h"
+
+class MessageLoop;
+
+namespace media {
+class MediaLog;
+}
+
+namespace WebKit {
+class WebFrame;
+}
+
+namespace webkit_media {
+
+class WebDataSourceFactory : public media::AsyncDataSourceFactoryBase {
+ public:
+ typedef WebDataSource* (*FactoryFunction)(MessageLoop* render_loop,
+ WebKit::WebFrame* frame,
+ media::MediaLog* media_log);
+
+ WebDataSourceFactory(MessageLoop* render_loop, WebKit::WebFrame* frame,
+ media::MediaLog* media_log,
+ FactoryFunction factory_function,
+ const WebDataSourceBuildObserverHack& build_observer);
+ virtual ~WebDataSourceFactory();
+
+ // DataSourceFactory method.
+ virtual media::DataSourceFactory* Clone() const OVERRIDE;
+
+ protected:
+ // AsyncDataSourceFactoryBase methods.
+ virtual bool AllowRequests() const OVERRIDE;
+ virtual AsyncDataSourceFactoryBase::BuildRequest* CreateRequest(
+ const std::string& url, const BuildCallback& callback) OVERRIDE;
+
+ private:
+ class BuildRequest;
+
+ MessageLoop* render_loop_;
+ WebKit::WebFrame* frame_;
+ scoped_refptr<media::MediaLog> media_log_;
+ FactoryFunction factory_function_;
+ WebDataSourceBuildObserverHack build_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebDataSourceFactory);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_
diff --git a/webkit/media/web_video_renderer.h b/webkit/media/web_video_renderer.h
new file mode 100644
index 0000000..a1770fd
--- /dev/null
+++ b/webkit/media/web_video_renderer.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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_MEDIA_WEB_VIDEO_RENDERER_H_
+#define WEBKIT_MEDIA_WEB_VIDEO_RENDERER_H_
+
+#include "media/base/video_frame.h"
+#include "media/filters/video_renderer_base.h"
+#include "ui/gfx/rect.h"
+
+class SkCanvas;
+
+namespace webkit_media {
+
+class WebMediaPlayerProxy;
+
+// A specialized version of a VideoRenderer designed to be used inside WebKit.
+class WebVideoRenderer : public media::VideoRendererBase {
+ public:
+ WebVideoRenderer() : media::VideoRendererBase() {}
+ virtual ~WebVideoRenderer() {}
+
+ // Saves the reference to WebMediaPlayerProxy.
+ virtual void SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) = 0;
+
+ // 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(SkCanvas* canvas, const gfx::Rect& dest_rect) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebVideoRenderer);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEB_VIDEO_RENDERER_H_
diff --git a/webkit/media/webkit_media.gypi b/webkit/media/webkit_media.gypi
new file mode 100644
index 0000000..38327d8
--- /dev/null
+++ b/webkit/media/webkit_media.gypi
@@ -0,0 +1,50 @@
+# Copyright (c) 2011 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'webkit_media',
+ 'type': 'static_library',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/media/media.gyp:yuv_convert',
+ '<(DEPTH)/skia/skia.gyp:skia',
+ ],
+ 'sources': [
+ 'audio_decoder.cc',
+ 'audio_decoder.h',
+ 'buffered_data_source.cc',
+ 'buffered_data_source.h',
+ 'buffered_resource_loader.cc',
+ 'buffered_resource_loader.h',
+ 'media_stream_client.h',
+ 'simple_data_source.cc',
+ 'simple_data_source.h',
+ 'video_renderer_impl.cc',
+ 'video_renderer_impl.h',
+ 'web_data_source.cc',
+ 'web_data_source_factory.cc',
+ 'web_data_source_factory.h',
+ 'web_data_source.h',
+ 'web_video_renderer.h',
+ 'webmediaplayer_delegate.h',
+ 'webmediaplayer_impl.cc',
+ 'webmediaplayer_impl.h',
+ 'webmediaplayer_proxy.cc',
+ 'webmediaplayer_proxy.h',
+ 'webvideoframe_impl.cc',
+ 'webvideoframe_impl.h',
+ ],
+ 'conditions': [
+ ['inside_chromium_build==0', {
+ 'dependencies': [
+ '<(DEPTH)/webkit/support/setup_third_party.gyp:third_party_headers',
+ ],
+ }],
+ ],
+ },
+ ],
+}
diff --git a/webkit/media/webmediaplayer_delegate.h b/webkit/media/webmediaplayer_delegate.h
new file mode 100644
index 0000000..7fa3409
--- /dev/null
+++ b/webkit/media/webmediaplayer_delegate.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011 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_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_
+#define WEBKIT_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_
+
+namespace webkit_media {
+
+class WebMediaPlayerImpl;
+
+// An interface to allow a WebMediaPlayerImpl to communicate changes of state
+// to objects that need to know.
+class WebMediaPlayerDelegate {
+ public:
+ WebMediaPlayerDelegate() {}
+ virtual ~WebMediaPlayerDelegate() {}
+
+ // The specified player started playing media.
+ virtual void DidPlay(WebMediaPlayerImpl* player) {}
+
+ // The specified player stopped playing media.
+ virtual void DidPause(WebMediaPlayerImpl* player) {}
+
+ // The specified player was destroyed. Do not call any methods on it.
+ virtual void PlayerGone(WebMediaPlayerImpl* player) {}
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_
diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc
new file mode 100644
index 0000000..a4fe3e9
--- /dev/null
+++ b/webkit/media/webmediaplayer_impl.cc
@@ -0,0 +1,917 @@
+// Copyright (c) 2011 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/media/webmediaplayer_impl.h"
+
+#include <limits>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "media/base/composite_data_source_factory.h"
+#include "media/base/filter_collection.h"
+#include "media/base/limits.h"
+#include "media/base/media_log.h"
+#include "media/base/media_switches.h"
+#include "media/base/pipeline_impl.h"
+#include "media/base/video_frame.h"
+#include "media/filters/chunk_demuxer_factory.h"
+#include "media/filters/dummy_demuxer_factory.h"
+#include "media/filters/ffmpeg_audio_decoder.h"
+#include "media/filters/ffmpeg_demuxer_factory.h"
+#include "media/filters/ffmpeg_video_decoder.h"
+#include "media/filters/null_audio_renderer.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "v8/include/v8.h"
+#include "webkit/media/buffered_data_source.h"
+#include "webkit/media/media_stream_client.h"
+#include "webkit/media/simple_data_source.h"
+#include "webkit/media/video_renderer_impl.h"
+#include "webkit/media/web_video_renderer.h"
+#include "webkit/media/webmediaplayer_delegate.h"
+#include "webkit/media/webmediaplayer_proxy.h"
+#include "webkit/media/webvideoframe_impl.h"
+
+using WebKit::WebCanvas;
+using WebKit::WebRect;
+using WebKit::WebSize;
+using media::PipelineStatus;
+
+namespace {
+
+// Amount of extra memory used by each player instance reported to V8.
+// It is not exact number -- first, it differs on different platforms,
+// and second, it is very hard to calculate. Instead, use some arbitrary
+// value that will cause garbage collection from time to time. We don't want
+// it to happen on every allocation, but don't want 5k players to sit in memory
+// either. Looks that chosen constant achieves both goals, at least for audio
+// objects. (Do not worry about video objects yet, JS programs do not create
+// thousands of them...)
+const int kPlayerExtraMemory = 1024 * 1024;
+
+// Limits the range of playback rate.
+//
+// TODO(kylep): Revisit these.
+//
+// Vista has substantially lower performance than XP or Windows7. If you speed
+// up a video too much, it can't keep up, and rendering stops updating except on
+// the time bar. For really high speeds, audio becomes a bottleneck and we just
+// use up the data we have, which may not achieve the speed requested, but will
+// not crash the tab.
+//
+// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems
+// like a busy loop). It gets unresponsive, although its not completely dead.
+//
+// Also our timers are not very accurate (especially for ogg), which becomes
+// evident at low speeds and on Vista. Since other speeds are risky and outside
+// the norms, we think 1/16x to 16x is a safe and useful range for now.
+const float kMinRate = 0.0625f;
+const float kMaxRate = 16.0f;
+
+// Platform independent method for converting and rounding floating point
+// seconds to an int64 timestamp.
+//
+// Refer to https://bugs.webkit.org/show_bug.cgi?id=52697 for details.
+base::TimeDelta ConvertSecondsToTimestamp(float seconds) {
+ float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
+ float integer = ceilf(microseconds);
+ float difference = integer - microseconds;
+
+ // Round down if difference is large enough.
+ if ((microseconds > 0 && difference > 0.5f) ||
+ (microseconds <= 0 && difference >= 0.5f)) {
+ integer -= 1.0f;
+ }
+
+ // Now we can safely cast to int64 microseconds.
+ return base::TimeDelta::FromMicroseconds(static_cast<int64>(integer));
+}
+
+} // namespace
+
+namespace webkit_media {
+
+WebMediaPlayerImpl::WebMediaPlayerImpl(
+ WebKit::WebMediaPlayerClient* client,
+ base::WeakPtr<WebMediaPlayerDelegate> delegate,
+ media::FilterCollection* collection,
+ media::MessageLoopFactory* message_loop_factory,
+ MediaStreamClient* media_stream_client,
+ media::MediaLog* media_log)
+ : network_state_(WebKit::WebMediaPlayer::Empty),
+ ready_state_(WebKit::WebMediaPlayer::HaveNothing),
+ main_loop_(NULL),
+ filter_collection_(collection),
+ pipeline_(NULL),
+ message_loop_factory_(message_loop_factory),
+ paused_(true),
+ seeking_(false),
+ playback_rate_(0.0f),
+ pending_seek_(false),
+ client_(client),
+ proxy_(NULL),
+ delegate_(delegate),
+ media_stream_client_(media_stream_client),
+ media_log_(media_log),
+ is_accelerated_compositing_active_(false),
+ incremented_externally_allocated_memory_(false) {
+ // Saves the current message loop.
+ DCHECK(!main_loop_);
+ main_loop_ = MessageLoop::current();
+ media_log_->AddEvent(
+ media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED));
+}
+
+bool WebMediaPlayerImpl::Initialize(
+ WebKit::WebFrame* frame,
+ bool use_simple_data_source,
+ scoped_refptr<WebVideoRenderer> web_video_renderer) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ MessageLoop* pipeline_message_loop =
+ message_loop_factory_->GetMessageLoop("PipelineThread");
+ if (!pipeline_message_loop) {
+ NOTREACHED() << "Could not start PipelineThread";
+ return false;
+ }
+
+ // Let V8 know we started new thread if we did not did it yet.
+ // Made separate task to avoid deletion of player currently being created.
+ // Also, delaying GC until after player starts gets rid of starting lag --
+ // collection happens in parallel with playing.
+ // TODO(enal): remove when we get rid of per-audio-stream thread.
+ if (!incremented_externally_allocated_memory_) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebMediaPlayerImpl::IncrementExternallyAllocatedMemory,
+ AsWeakPtr()));
+ }
+
+ is_accelerated_compositing_active_ =
+ frame->view()->isAcceleratedCompositingActive();
+
+ pipeline_ = new media::PipelineImpl(pipeline_message_loop, media_log_);
+
+ // Also we want to be notified of |main_loop_| destruction.
+ main_loop_->AddDestructionObserver(this);
+
+ // Creates the proxy.
+ proxy_ = new WebMediaPlayerProxy(main_loop_, this);
+ web_video_renderer->SetWebMediaPlayerProxy(proxy_);
+ proxy_->SetVideoRenderer(web_video_renderer);
+
+ // Set our pipeline callbacks.
+ pipeline_->Init(
+ base::Bind(&WebMediaPlayerProxy::PipelineEndedCallback,
+ proxy_.get()),
+ base::Bind(&WebMediaPlayerProxy::PipelineErrorCallback,
+ proxy_.get()),
+ base::Bind(&WebMediaPlayerProxy::NetworkEventCallback,
+ proxy_.get()));
+
+ // A simple data source that keeps all data in memory.
+ scoped_ptr<media::DataSourceFactory> simple_data_source_factory(
+ SimpleDataSource::CreateFactory(MessageLoop::current(), frame,
+ media_log_,
+ proxy_->GetBuildObserver()));
+
+ // A sophisticated data source that does memory caching.
+ scoped_ptr<media::DataSourceFactory> buffered_data_source_factory(
+ BufferedDataSource::CreateFactory(MessageLoop::current(), frame,
+ media_log_,
+ proxy_->GetBuildObserver()));
+
+ scoped_ptr<media::CompositeDataSourceFactory> data_source_factory(
+ new media::CompositeDataSourceFactory());
+
+ if (use_simple_data_source) {
+ data_source_factory->AddFactory(simple_data_source_factory.release());
+ data_source_factory->AddFactory(buffered_data_source_factory.release());
+ } else {
+ data_source_factory->AddFactory(buffered_data_source_factory.release());
+ data_source_factory->AddFactory(simple_data_source_factory.release());
+ }
+
+ scoped_ptr<media::DemuxerFactory> demuxer_factory(
+ new media::FFmpegDemuxerFactory(data_source_factory.release(),
+ pipeline_message_loop));
+
+ std::string source_url = GetClient()->sourceURL().spec();
+
+ if (!source_url.empty()) {
+ demuxer_factory.reset(
+ new media::ChunkDemuxerFactory(source_url,
+ demuxer_factory.release(),
+ proxy_));
+ }
+ filter_collection_->SetDemuxerFactory(demuxer_factory.release());
+
+ // Add in the default filter factories.
+ filter_collection_->AddAudioDecoder(new media::FFmpegAudioDecoder(
+ message_loop_factory_->GetMessageLoop("AudioDecoderThread")));
+ filter_collection_->AddVideoDecoder(new media::FFmpegVideoDecoder(
+ message_loop_factory_->GetMessageLoop("VideoDecoderThread")));
+ filter_collection_->AddAudioRenderer(new media::NullAudioRenderer());
+
+ return true;
+}
+
+WebMediaPlayerImpl::~WebMediaPlayerImpl() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ Destroy();
+ media_log_->AddEvent(
+ media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED));
+
+ if (delegate_)
+ delegate_->PlayerGone(this);
+
+ // Finally tell the |main_loop_| we don't want to be notified of destruction
+ // event.
+ if (main_loop_) {
+ main_loop_->RemoveDestructionObserver(this);
+ }
+}
+
+namespace {
+
+// Helper enum for reporting scheme histograms.
+enum URLSchemeForHistogram {
+ kUnknownURLScheme,
+ kMissingURLScheme,
+ kHttpURLScheme,
+ kHttpsURLScheme,
+ kFtpURLScheme,
+ kChromeExtensionURLScheme,
+ kJavascriptURLScheme,
+ kFileURLScheme,
+ kBlobURLScheme,
+ kDataURLScheme,
+ kMaxURLScheme = kDataURLScheme // Must be equal to highest enum value.
+};
+
+URLSchemeForHistogram URLScheme(const GURL& url) {
+ if (!url.has_scheme()) return kMissingURLScheme;
+ if (url.SchemeIs("http")) return kHttpURLScheme;
+ if (url.SchemeIs("https")) return kHttpsURLScheme;
+ if (url.SchemeIs("ftp")) return kFtpURLScheme;
+ if (url.SchemeIs("chrome-extension")) return kChromeExtensionURLScheme;
+ if (url.SchemeIs("javascript")) return kJavascriptURLScheme;
+ if (url.SchemeIs("file")) return kFileURLScheme;
+ if (url.SchemeIs("blob")) return kBlobURLScheme;
+ if (url.SchemeIs("data")) return kDataURLScheme;
+ return kUnknownURLScheme;
+}
+
+} // anonymous namespace
+
+void WebMediaPlayerImpl::load(const WebKit::WebURL& url) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ DCHECK(proxy_);
+
+ UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme);
+
+ if (media_stream_client_) {
+ bool has_video = false;
+ bool has_audio = false;
+ scoped_refptr<media::VideoDecoder> new_decoder =
+ media_stream_client_->GetVideoDecoder(url, message_loop_factory_.get());
+ if (new_decoder.get()) {
+ // Remove the default decoder.
+ scoped_refptr<media::VideoDecoder> old_videodecoder;
+ filter_collection_->SelectVideoDecoder(&old_videodecoder);
+ filter_collection_->AddVideoDecoder(new_decoder.get());
+ has_video = true;
+ }
+
+ // TODO(wjia): add audio decoder handling when it's available.
+ if (has_video || has_audio)
+ filter_collection_->SetDemuxerFactory(
+ new media::DummyDemuxerFactory(has_video, has_audio));
+ }
+
+ // Handle any volume changes that occured before load().
+ setVolume(GetClient()->volume());
+ // Get the preload value.
+ setPreload(GetClient()->preload());
+
+ // Initialize the pipeline.
+ SetNetworkState(WebKit::WebMediaPlayer::Loading);
+ SetReadyState(WebKit::WebMediaPlayer::HaveNothing);
+ pipeline_->Start(
+ filter_collection_.release(),
+ url.spec(),
+ base::Bind(&WebMediaPlayerProxy::PipelineInitializationCallback,
+ proxy_.get()));
+
+ media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec()));
+}
+
+void WebMediaPlayerImpl::cancelLoad() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+}
+
+void WebMediaPlayerImpl::play() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ paused_ = false;
+ pipeline_->SetPlaybackRate(playback_rate_);
+
+ media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY));
+
+ if (delegate_)
+ delegate_->DidPlay(this);
+}
+
+void WebMediaPlayerImpl::pause() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ paused_ = true;
+ pipeline_->SetPlaybackRate(0.0f);
+ paused_time_ = pipeline_->GetCurrentTime();
+
+ media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE));
+
+ if (delegate_)
+ delegate_->DidPause(this);
+}
+
+bool WebMediaPlayerImpl::supportsFullscreen() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ return true;
+}
+
+bool WebMediaPlayerImpl::supportsSave() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ return true;
+}
+
+void WebMediaPlayerImpl::seek(float seconds) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // WebKit fires a seek(0) at the very start, however pipeline already does a
+ // seek(0) internally. Avoid doing seek(0) the second time because this will
+ // cause extra pre-rolling and will break servers without range request
+ // support.
+ //
+ // We still have to notify WebKit that time has changed otherwise
+ // HTMLMediaElement gets into an inconsistent state.
+ if (pipeline_->GetCurrentTime().ToInternalValue() == 0 && seconds == 0) {
+ GetClient()->timeChanged();
+ return;
+ }
+
+ if (seeking_) {
+ pending_seek_ = true;
+ pending_seek_seconds_ = seconds;
+ return;
+ }
+
+ media_log_->AddEvent(media_log_->CreateSeekEvent(seconds));
+
+ base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds);
+
+ // Update our paused time.
+ if (paused_) {
+ paused_time_ = seek_time;
+ }
+
+ seeking_ = true;
+
+ proxy_->DemuxerFlush();
+
+ // Kick off the asynchronous seek!
+ pipeline_->Seek(
+ seek_time,
+ base::Bind(&WebMediaPlayerProxy::PipelineSeekCallback,
+ proxy_.get()));
+}
+
+void WebMediaPlayerImpl::setEndTime(float seconds) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // TODO(hclam): add method call when it has been implemented.
+ return;
+}
+
+void WebMediaPlayerImpl::setRate(float rate) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // TODO(kylep): Remove when support for negatives is added. Also, modify the
+ // following checks so rewind uses reasonable values also.
+ if (rate < 0.0f)
+ return;
+
+ // Limit rates to reasonable values by clamping.
+ if (rate != 0.0f) {
+ if (rate < kMinRate)
+ rate = kMinRate;
+ else if (rate > kMaxRate)
+ rate = kMaxRate;
+ }
+
+ playback_rate_ = rate;
+ if (!paused_) {
+ pipeline_->SetPlaybackRate(rate);
+ }
+}
+
+void WebMediaPlayerImpl::setVolume(float volume) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ pipeline_->SetVolume(volume);
+}
+
+void WebMediaPlayerImpl::setVisible(bool visible) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // TODO(hclam): add appropriate method call when pipeline has it implemented.
+ return;
+}
+
+#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
+ COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::webkit_name) == \
+ static_cast<int>(media::chromium_name), \
+ mismatching_enums)
+COMPILE_ASSERT_MATCHING_ENUM(None, NONE);
+COMPILE_ASSERT_MATCHING_ENUM(MetaData, METADATA);
+COMPILE_ASSERT_MATCHING_ENUM(Auto, AUTO);
+
+void WebMediaPlayerImpl::setPreload(WebKit::WebMediaPlayer::Preload preload) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ pipeline_->SetPreload(static_cast<media::Preload>(preload));
+}
+
+bool WebMediaPlayerImpl::totalBytesKnown() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->GetTotalBytes() != 0;
+}
+
+bool WebMediaPlayerImpl::hasVideo() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->HasVideo();
+}
+
+bool WebMediaPlayerImpl::hasAudio() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->HasAudio();
+}
+
+WebKit::WebSize WebMediaPlayerImpl::naturalSize() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ gfx::Size size;
+ pipeline_->GetNaturalVideoSize(&size);
+ return WebKit::WebSize(size);
+}
+
+bool WebMediaPlayerImpl::paused() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->GetPlaybackRate() == 0.0f;
+}
+
+bool WebMediaPlayerImpl::seeking() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ if (ready_state_ == WebKit::WebMediaPlayer::HaveNothing)
+ return false;
+
+ return seeking_;
+}
+
+float WebMediaPlayerImpl::duration() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ base::TimeDelta duration = pipeline_->GetMediaDuration();
+ if (duration.InMicroseconds() == media::Limits::kMaxTimeInMicroseconds)
+ return std::numeric_limits<float>::infinity();
+ return static_cast<float>(duration.InSecondsF());
+}
+
+float WebMediaPlayerImpl::currentTime() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ if (paused_) {
+ return static_cast<float>(paused_time_.InSecondsF());
+ }
+ return static_cast<float>(pipeline_->GetCurrentTime().InSecondsF());
+}
+
+int WebMediaPlayerImpl::dataRate() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // TODO(hclam): Add this method call if pipeline has it in the interface.
+ return 0;
+}
+
+WebKit::WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const {
+ return network_state_;
+}
+
+WebKit::WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const {
+ return ready_state_;
+}
+
+const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // Update buffered_ with the most recent buffered time.
+ if (buffered_.size() > 0) {
+ float buffered_time = static_cast<float>(
+ pipeline_->GetBufferedTime().InSecondsF());
+ if (buffered_time >= buffered_[0].start)
+ buffered_[0].end = buffered_time;
+ }
+
+ return buffered_;
+}
+
+float WebMediaPlayerImpl::maxTimeSeekable() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // If we are performing streaming, we report that we cannot seek at all.
+ // We are using this flag to indicate if the data source supports seeking
+ // or not. We should be able to seek even if we are performing streaming.
+ // TODO(hclam): We need to update this when we have better caching.
+ if (pipeline_->IsStreaming())
+ return 0.0f;
+ return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF());
+}
+
+unsigned long long WebMediaPlayerImpl::bytesLoaded() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->GetBufferedBytes();
+}
+
+unsigned long long WebMediaPlayerImpl::totalBytes() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ return pipeline_->GetTotalBytes();
+}
+
+void WebMediaPlayerImpl::setSize(const WebSize& size) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ DCHECK(proxy_);
+
+ proxy_->SetSize(gfx::Rect(0, 0, size.width, size.height));
+}
+
+void WebMediaPlayerImpl::paint(WebCanvas* canvas,
+ const WebRect& rect) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ DCHECK(proxy_);
+
+#if WEBKIT_USING_SKIA
+ proxy_->Paint(canvas, rect);
+#elif WEBKIT_USING_CG
+ // Get the current scaling in X and Y.
+ CGAffineTransform mat = CGContextGetCTM(canvas);
+ float scale_x = sqrt(mat.a * mat.a + mat.b * mat.b);
+ float scale_y = sqrt(mat.c * mat.c + mat.d * mat.d);
+ float inverse_scale_x = SkScalarNearlyZero(scale_x) ? 0.0f : 1.0f / scale_x;
+ float inverse_scale_y = SkScalarNearlyZero(scale_y) ? 0.0f : 1.0f / scale_y;
+ int scaled_width = static_cast<int>(rect.width * fabs(scale_x));
+ int scaled_height = static_cast<int>(rect.height * fabs(scale_y));
+
+ // Make sure we don't create a huge canvas.
+ // TODO(hclam): Respect the aspect ratio.
+ if (scaled_width > static_cast<int>(media::Limits::kMaxCanvas))
+ scaled_width = media::Limits::kMaxCanvas;
+ if (scaled_height > static_cast<int>(media::Limits::kMaxCanvas))
+ scaled_height = media::Limits::kMaxCanvas;
+
+ // If there is no preexisting platform canvas, or if the size has
+ // changed, recreate the canvas. This is to avoid recreating the bitmap
+ // buffer over and over for each frame of video.
+ if (!skia_canvas_.get() ||
+ skia_canvas_->getDevice()->width() != scaled_width ||
+ skia_canvas_->getDevice()->height() != scaled_height) {
+ skia_canvas_.reset(
+ new skia::PlatformCanvas(scaled_width, scaled_height, true));
+ }
+
+ // Draw to our temporary skia canvas.
+ gfx::Rect normalized_rect(scaled_width, scaled_height);
+ proxy_->Paint(skia_canvas_.get(), normalized_rect);
+
+ // The mac coordinate system is flipped vertical from the normal skia
+ // coordinates. During painting of the frame, flip the coordinates
+ // system and, for simplicity, also translate the clip rectangle to
+ // start at 0,0.
+ CGContextSaveGState(canvas);
+ CGContextTranslateCTM(canvas, rect.x, rect.height + rect.y);
+ CGContextScaleCTM(canvas, inverse_scale_x, -inverse_scale_y);
+
+ // We need a local variable CGRect version for DrawToContext.
+ CGRect normalized_cgrect =
+ CGRectMake(normalized_rect.x(), normalized_rect.y(),
+ normalized_rect.width(), normalized_rect.height());
+
+ // Copy the frame rendered to our temporary skia canvas onto the passed in
+ // canvas.
+ skia::DrawToNativeContext(skia_canvas_.get(), canvas, 0, 0,
+ &normalized_cgrect);
+
+ CGContextRestoreGState(canvas);
+#else
+ NOTIMPLEMENTED() << "We only support rendering to skia or CG";
+#endif
+}
+
+bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const {
+ if (proxy_)
+ return proxy_->HasSingleOrigin();
+ return true;
+}
+
+WebKit::WebMediaPlayer::MovieLoadType
+ WebMediaPlayerImpl::movieLoadType() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // TODO(hclam): If the pipeline is performing streaming, we say that this is
+ // a live stream. But instead it should be a StoredStream if we have proper
+ // caching.
+ if (pipeline_->IsStreaming())
+ return WebKit::WebMediaPlayer::LiveStream;
+ return WebKit::WebMediaPlayer::Unknown;
+}
+
+float WebMediaPlayerImpl::mediaTimeForTimeValue(float timeValue) const {
+ return ConvertSecondsToTimestamp(timeValue).InSecondsF();
+}
+
+unsigned WebMediaPlayerImpl::decodedFrameCount() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ media::PipelineStatistics stats = pipeline_->GetStatistics();
+ return stats.video_frames_decoded;
+}
+
+unsigned WebMediaPlayerImpl::droppedFrameCount() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ media::PipelineStatistics stats = pipeline_->GetStatistics();
+ return stats.video_frames_dropped;
+}
+
+unsigned WebMediaPlayerImpl::audioDecodedByteCount() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ media::PipelineStatistics stats = pipeline_->GetStatistics();
+ return stats.audio_bytes_decoded;
+}
+
+unsigned WebMediaPlayerImpl::videoDecodedByteCount() const {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ media::PipelineStatistics stats = pipeline_->GetStatistics();
+ return stats.video_bytes_decoded;
+}
+
+WebKit::WebVideoFrame* WebMediaPlayerImpl::getCurrentFrame() {
+ scoped_refptr<media::VideoFrame> video_frame;
+ proxy_->GetCurrentFrame(&video_frame);
+ if (video_frame.get())
+ return new WebVideoFrameImpl(video_frame);
+ return NULL;
+}
+
+void WebMediaPlayerImpl::putCurrentFrame(
+ WebKit::WebVideoFrame* web_video_frame) {
+ if (web_video_frame) {
+ scoped_refptr<media::VideoFrame> video_frame(
+ WebVideoFrameImpl::toVideoFrame(web_video_frame));
+ proxy_->PutCurrentFrame(video_frame);
+ delete web_video_frame;
+ }
+}
+
+bool WebMediaPlayerImpl::sourceAppend(const unsigned char* data,
+ unsigned length) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ return proxy_->DemuxerAppend(data, length);
+}
+
+void WebMediaPlayerImpl::sourceEndOfStream(
+ WebKit::WebMediaPlayer::EndOfStreamStatus status) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ media::PipelineStatus pipeline_status = media::PIPELINE_OK;
+
+ switch(status) {
+ case WebKit::WebMediaPlayer::EosNoError:
+ break;
+ case WebKit::WebMediaPlayer::EosNetworkError:
+ pipeline_status = media::PIPELINE_ERROR_NETWORK;
+ break;
+ case WebKit::WebMediaPlayer::EosDecodeError:
+ pipeline_status = media::PIPELINE_ERROR_DECODE;
+ break;
+ default:
+ NOTIMPLEMENTED();
+ }
+
+ proxy_->DemuxerEndOfStream(pipeline_status);
+}
+
+void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() {
+ Destroy();
+ main_loop_ = NULL;
+}
+
+void WebMediaPlayerImpl::Repaint() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ GetClient()->repaint();
+}
+
+void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ if (status == media::PIPELINE_OK) {
+ // Only keep one time range starting from 0.
+ WebKit::WebTimeRanges new_buffered(static_cast<size_t>(1));
+ new_buffered[0].start = 0.0f;
+ new_buffered[0].end =
+ static_cast<float>(pipeline_->GetMediaDuration().InSecondsF());
+ buffered_.swap(new_buffered);
+
+ if (hasVideo()) {
+ UMA_HISTOGRAM_BOOLEAN("Media.AcceleratedCompositingActive",
+ is_accelerated_compositing_active_);
+ }
+
+ if (pipeline_->IsLoaded()) {
+ SetNetworkState(WebKit::WebMediaPlayer::Loaded);
+ }
+
+ // Since we have initialized the pipeline, say we have everything otherwise
+ // we'll remain either loading/idle.
+ // TODO(hclam): change this to report the correct status.
+ SetReadyState(WebKit::WebMediaPlayer::HaveMetadata);
+ SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData);
+ } else {
+ // TODO(hclam): should use |status| to determine the state
+ // properly and reports error using MediaError.
+ // WebKit uses FormatError to indicate an error for bogus URL or bad file.
+ // Since we are at the initialization stage we can safely treat every error
+ // as format error. Should post a task to call to |webmediaplayer_|.
+ SetNetworkState(WebKit::WebMediaPlayer::FormatError);
+ }
+
+ // Repaint to trigger UI update.
+ Repaint();
+}
+
+void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ seeking_ = false;
+ if (pending_seek_) {
+ pending_seek_ = false;
+ seek(pending_seek_seconds_);
+ return;
+ }
+
+ if (status == media::PIPELINE_OK) {
+ // Update our paused time.
+ if (paused_) {
+ paused_time_ = pipeline_->GetCurrentTime();
+ }
+
+ SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData);
+ GetClient()->timeChanged();
+ }
+}
+
+void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ if (status == media::PIPELINE_OK) {
+ GetClient()->timeChanged();
+ }
+}
+
+void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ switch (error) {
+ case media::PIPELINE_OK:
+ LOG(DFATAL) << "PIPELINE_OK isn't an error!";
+ break;
+
+ case media::PIPELINE_ERROR_NETWORK:
+ SetNetworkState(WebMediaPlayer::NetworkError);
+ break;
+
+ case media::PIPELINE_ERROR_INITIALIZATION_FAILED:
+ case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING:
+ case media::PIPELINE_ERROR_COULD_NOT_RENDER:
+ case media::PIPELINE_ERROR_URL_NOT_FOUND:
+ case media::PIPELINE_ERROR_READ:
+ case media::DEMUXER_ERROR_COULD_NOT_OPEN:
+ case media::DEMUXER_ERROR_COULD_NOT_PARSE:
+ case media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS:
+ case media::DEMUXER_ERROR_COULD_NOT_CREATE_THREAD:
+ case media::DECODER_ERROR_NOT_SUPPORTED:
+ case media::DATASOURCE_ERROR_URL_NOT_SUPPORTED:
+ // Format error.
+ SetNetworkState(WebMediaPlayer::FormatError);
+ break;
+
+ case media::PIPELINE_ERROR_DECODE:
+ case media::PIPELINE_ERROR_ABORT:
+ case media::PIPELINE_ERROR_OUT_OF_MEMORY:
+ case media::PIPELINE_ERROR_AUDIO_HARDWARE:
+ case media::PIPELINE_ERROR_OPERATION_PENDING:
+ case media::PIPELINE_ERROR_INVALID_STATE:
+ // Decode error.
+ SetNetworkState(WebMediaPlayer::DecodeError);
+ break;
+ }
+
+ // Repaint to trigger UI update.
+ Repaint();
+}
+
+void WebMediaPlayerImpl::OnNetworkEvent(bool is_downloading_data) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ if (is_downloading_data)
+ SetNetworkState(WebKit::WebMediaPlayer::Loading);
+ else
+ SetNetworkState(WebKit::WebMediaPlayer::Idle);
+}
+
+void WebMediaPlayerImpl::OnDemuxerOpened() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ GetClient()->sourceOpened();
+}
+
+void WebMediaPlayerImpl::SetNetworkState(
+ WebKit::WebMediaPlayer::NetworkState state) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ // Always notify to ensure client has the latest value.
+ network_state_ = state;
+ GetClient()->networkStateChanged();
+}
+
+void WebMediaPlayerImpl::SetReadyState(
+ WebKit::WebMediaPlayer::ReadyState state) {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ // Always notify to ensure client has the latest value.
+ ready_state_ = state;
+ GetClient()->readyStateChanged();
+}
+
+void WebMediaPlayerImpl::Destroy() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+
+ // Tell the data source to abort any pending reads so that the pipeline is
+ // not blocked when issuing stop commands to the other filters.
+ if (proxy_) {
+ proxy_->AbortDataSources();
+ proxy_->DemuxerShutdown();
+ }
+
+ // Make sure to kill the pipeline so there's no more media threads running.
+ // Note: stopping the pipeline might block for a long time.
+ if (pipeline_) {
+ media::PipelineStatusNotification note;
+ pipeline_->Stop(note.Callback());
+ note.Wait();
+
+ // Let V8 know we are not using extra resources anymore.
+ if (incremented_externally_allocated_memory_) {
+ v8::V8::AdjustAmountOfExternalAllocatedMemory(-kPlayerExtraMemory);
+ incremented_externally_allocated_memory_ = false;
+ }
+ }
+
+ message_loop_factory_.reset();
+
+ // And then detach the proxy, it may live on the render thread for a little
+ // longer until all the tasks are finished.
+ if (proxy_) {
+ proxy_->Detach();
+ proxy_ = NULL;
+ }
+}
+
+WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ DCHECK(client_);
+ return client_;
+}
+
+void WebMediaPlayerImpl::IncrementExternallyAllocatedMemory() {
+ DCHECK_EQ(main_loop_, MessageLoop::current());
+ incremented_externally_allocated_memory_ = true;
+ v8::V8::AdjustAmountOfExternalAllocatedMemory(kPlayerExtraMemory);
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/webmediaplayer_impl.h b/webkit/media/webmediaplayer_impl.h
new file mode 100644
index 0000000..d7e7296
--- /dev/null
+++ b/webkit/media/webmediaplayer_impl.h
@@ -0,0 +1,276 @@
+// Copyright (c) 2011 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.
+
+// Delegate calls from WebCore::MediaPlayerPrivate to Chrome's video player.
+// It contains PipelineImpl which is the actual media player pipeline, it glues
+// the media player pipeline, data source, audio renderer and renderer.
+// PipelineImpl would creates multiple threads and access some public methods
+// of this class, so we need to be extra careful about concurrent access of
+// methods and members.
+//
+// WebMediaPlayerImpl works with multiple objects, the most important ones are:
+//
+// media::PipelineImpl
+// The media playback pipeline.
+//
+// WebVideoRenderer
+// Video renderer object.
+//
+// WebKit::WebMediaPlayerClient
+// WebKit client of this media player object.
+//
+// The following diagram shows the relationship of these objects:
+// (note: ref-counted reference is marked by a "r".)
+//
+// WebMediaPlayerClient (WebKit object)
+// ^
+// |
+// WebMediaPlayerImpl ---> PipelineImpl
+// | ^ |
+// | | v r
+// | | WebVideoRenderer
+// | | | ^ r
+// | r | v r |
+// '---> WebMediaPlayerProxy --'
+//
+// Notice that WebMediaPlayerProxy and WebVideoRenderer are referencing each
+// other. This interdependency has to be treated carefully.
+//
+// Other issues:
+// During tear down of the whole browser or a tab, the DOM tree may not be
+// destructed nicely, and there will be some dangling media threads trying to
+// the main thread, so we need this class to listen to destruction event of the
+// main thread and cleanup the media threads when the even is received. Also
+// at destruction of this class we will need to unhook it from destruction event
+// list of the main thread.
+
+#ifndef WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_
+#define WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "media/base/filters.h"
+#include "media/base/message_loop_factory.h"
+#include "media/base/pipeline.h"
+#include "skia/ext/platform_canvas.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerClient.h"
+
+namespace WebKit {
+class WebFrame;
+}
+
+namespace media {
+class MediaLog;
+}
+
+namespace webkit_media {
+
+class MediaStreamClient;
+class WebMediaPlayerDelegate;
+class WebMediaPlayerProxy;
+class WebVideoRenderer;
+
+class WebMediaPlayerImpl
+ : public WebKit::WebMediaPlayer,
+ public MessageLoop::DestructionObserver,
+ public base::SupportsWeakPtr<WebMediaPlayerImpl> {
+ public:
+ // Construct a WebMediaPlayerImpl with reference to the client, and media
+ // filter collection. By providing the filter collection the implementor can
+ // provide more specific media filters that does resource loading and
+ // rendering. |collection| should contain filter factories for:
+ // 1. Data source
+ // 2. Audio renderer
+ // 3. Video renderer (optional)
+ //
+ // There are some default filters provided by this method:
+ // 1. FFmpeg demuxer
+ // 2. FFmpeg audio decoder
+ // 3. FFmpeg video decoder
+ // 4. Video renderer
+ // 5. Null audio renderer
+ // The video renderer provided by this class is using the graphics context
+ // provided by WebKit to perform renderering. The simple data source does
+ // resource loading by loading the whole resource object into memory. Null
+ // audio renderer is a fake audio device that plays silence. Provider of the
+ // |collection| can override the default filters by adding extra filters to
+ // |collection| before calling this method.
+ //
+ // Callers must call |Initialize()| before they can use the object.
+ WebMediaPlayerImpl(WebKit::WebMediaPlayerClient* client,
+ base::WeakPtr<WebMediaPlayerDelegate> delegate,
+ media::FilterCollection* collection,
+ media::MessageLoopFactory* message_loop_factory,
+ MediaStreamClient* media_stream_client,
+ media::MediaLog* media_log);
+ virtual ~WebMediaPlayerImpl();
+
+ // Finalizes initialization of the object.
+ bool Initialize(
+ WebKit::WebFrame* frame,
+ bool use_simple_data_source,
+ scoped_refptr<WebVideoRenderer> web_video_renderer);
+
+ virtual void load(const WebKit::WebURL& url);
+ virtual void cancelLoad();
+
+ // Playback controls.
+ virtual void play();
+ virtual void pause();
+ virtual bool supportsFullscreen() const;
+ virtual bool supportsSave() const;
+ virtual void seek(float seconds);
+ virtual void setEndTime(float seconds);
+ virtual void setRate(float rate);
+ virtual void setVolume(float volume);
+ virtual void setVisible(bool visible);
+ virtual void setPreload(WebKit::WebMediaPlayer::Preload preload);
+ virtual bool totalBytesKnown();
+ virtual const WebKit::WebTimeRanges& buffered();
+ virtual float maxTimeSeekable() const;
+
+ // Methods for painting.
+ virtual void setSize(const WebKit::WebSize& size);
+
+ virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect);
+
+ // True if the loaded media has a playable video/audio track.
+ virtual bool hasVideo() const;
+ virtual bool hasAudio() const;
+
+ // Dimensions of the video.
+ virtual WebKit::WebSize naturalSize() const;
+
+ // Getters of playback state.
+ virtual bool paused() const;
+ virtual bool seeking() const;
+ virtual float duration() const;
+ virtual float currentTime() const;
+
+ // Get rate of loading the resource.
+ virtual int32 dataRate() const;
+
+ // Internal states of loading and network.
+ // TODO(hclam): Ask the pipeline about the state rather than having reading
+ // them from members which would cause race conditions.
+ virtual WebKit::WebMediaPlayer::NetworkState networkState() const;
+ virtual WebKit::WebMediaPlayer::ReadyState readyState() const;
+
+ virtual unsigned long long bytesLoaded() const;
+ virtual unsigned long long totalBytes() const;
+
+ virtual bool hasSingleSecurityOrigin() const;
+ virtual WebKit::WebMediaPlayer::MovieLoadType movieLoadType() const;
+
+ virtual float mediaTimeForTimeValue(float timeValue) const;
+
+ virtual unsigned decodedFrameCount() const;
+ virtual unsigned droppedFrameCount() const;
+ virtual unsigned audioDecodedByteCount() const;
+ virtual unsigned videoDecodedByteCount() const;
+
+ virtual WebKit::WebVideoFrame* getCurrentFrame();
+ virtual void putCurrentFrame(WebKit::WebVideoFrame* web_video_frame);
+
+ virtual bool sourceAppend(const unsigned char* data, unsigned length);
+ virtual void sourceEndOfStream(EndOfStreamStatus status);
+
+ // As we are closing the tab or even the browser, |main_loop_| is destroyed
+ // even before this object gets destructed, so we need to know when
+ // |main_loop_| is being destroyed and we can stop posting repaint task
+ // to it.
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
+
+ void Repaint();
+
+ void OnPipelineInitialize(media::PipelineStatus status);
+ void OnPipelineSeek(media::PipelineStatus status);
+ void OnPipelineEnded(media::PipelineStatus status);
+ void OnPipelineError(media::PipelineStatus error);
+ void OnNetworkEvent(bool is_downloading_data);
+ void OnDemuxerOpened();
+
+ private:
+ // Helpers that set the network/ready state and notifies the client if
+ // they've changed.
+ void SetNetworkState(WebKit::WebMediaPlayer::NetworkState state);
+ void SetReadyState(WebKit::WebMediaPlayer::ReadyState state);
+
+ // Destroy resources held.
+ void Destroy();
+
+ // Getter method to |client_|.
+ WebKit::WebMediaPlayerClient* GetClient();
+
+ // Lets V8 know that player uses extra resources not managed by V8.
+ void IncrementExternallyAllocatedMemory();
+
+ // TODO(hclam): get rid of these members and read from the pipeline directly.
+ WebKit::WebMediaPlayer::NetworkState network_state_;
+ WebKit::WebMediaPlayer::ReadyState ready_state_;
+
+ // Keep a list of buffered time ranges.
+ WebKit::WebTimeRanges buffered_;
+
+ // Message loops for posting tasks between Chrome's main thread. Also used
+ // for DCHECKs so methods calls won't execute in the wrong thread.
+ MessageLoop* main_loop_;
+
+ // A collection of filters.
+ scoped_ptr<media::FilterCollection> filter_collection_;
+
+ // The actual pipeline and the thread it runs on.
+ scoped_refptr<media::Pipeline> pipeline_;
+
+ scoped_ptr<media::MessageLoopFactory> message_loop_factory_;
+
+ // Playback state.
+ //
+ // TODO(scherkus): we have these because Pipeline favours the simplicity of a
+ // single "playback rate" over worrying about paused/stopped etc... It forces
+ // all clients to manage the pause+playback rate externally, but is that
+ // really a bad thing?
+ //
+ // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't want
+ // to hang the render thread during pause(), we record the time at the same
+ // time we pause and then return that value in currentTime(). Otherwise our
+ // clock can creep forward a little bit while the asynchronous
+ // SetPlaybackRate(0) is being executed.
+ bool paused_;
+ bool seeking_;
+ float playback_rate_;
+ base::TimeDelta paused_time_;
+
+ // Seek gets pending if another seek is in progress. Only last pending seek
+ // will have effect.
+ bool pending_seek_;
+ float pending_seek_seconds_;
+
+ WebKit::WebMediaPlayerClient* client_;
+
+ scoped_refptr<WebMediaPlayerProxy> proxy_;
+
+ base::WeakPtr<WebMediaPlayerDelegate> delegate_;
+
+ MediaStreamClient* media_stream_client_;
+
+#if WEBKIT_USING_CG
+ scoped_ptr<skia::PlatformCanvas> skia_canvas_;
+#endif
+
+ scoped_refptr<media::MediaLog> media_log_;
+
+ bool is_accelerated_compositing_active_;
+
+ bool incremented_externally_allocated_memory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_
diff --git a/webkit/media/webmediaplayer_proxy.cc b/webkit/media/webmediaplayer_proxy.cc
new file mode 100644
index 0000000..a596ed8
--- /dev/null
+++ b/webkit/media/webmediaplayer_proxy.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2011 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/media/webmediaplayer_proxy.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "media/base/pipeline_status.h"
+#include "media/filters/chunk_demuxer.h"
+#include "webkit/media/web_video_renderer.h"
+#include "webkit/media/webmediaplayer_impl.h"
+
+using media::PipelineStatus;
+
+namespace webkit_media {
+
+// Limits the maximum outstanding repaints posted on render thread.
+// This number of 50 is a guess, it does not take too much memory on the task
+// queue but gives up a pretty good latency on repaint.
+static const int kMaxOutstandingRepaints = 50;
+
+WebMediaPlayerProxy::WebMediaPlayerProxy(MessageLoop* render_loop,
+ WebMediaPlayerImpl* webmediaplayer)
+ : render_loop_(render_loop),
+ webmediaplayer_(webmediaplayer),
+ outstanding_repaints_(0) {
+ DCHECK(render_loop_);
+ DCHECK(webmediaplayer_);
+}
+
+WebMediaPlayerProxy::~WebMediaPlayerProxy() {
+ Detach();
+}
+
+void WebMediaPlayerProxy::Repaint() {
+ base::AutoLock auto_lock(lock_);
+ if (outstanding_repaints_ < kMaxOutstandingRepaints) {
+ ++outstanding_repaints_;
+
+ render_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &WebMediaPlayerProxy::RepaintTask));
+ }
+}
+
+void WebMediaPlayerProxy::SetVideoRenderer(
+ scoped_refptr<WebVideoRenderer> video_renderer) {
+ video_renderer_ = video_renderer;
+}
+
+WebDataSourceBuildObserverHack WebMediaPlayerProxy::GetBuildObserver() {
+ if (build_observer_.is_null())
+ build_observer_ = base::Bind(&WebMediaPlayerProxy::AddDataSource, this);
+ return build_observer_;
+}
+
+void WebMediaPlayerProxy::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (video_renderer_) {
+ video_renderer_->Paint(canvas, dest_rect);
+ }
+}
+
+void WebMediaPlayerProxy::SetSize(const gfx::Rect& rect) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (video_renderer_) {
+ video_renderer_->SetRect(rect);
+ }
+}
+
+bool WebMediaPlayerProxy::HasSingleOrigin() {
+ DCHECK(MessageLoop::current() == render_loop_);
+
+ base::AutoLock auto_lock(data_sources_lock_);
+
+ for (DataSourceList::iterator itr = data_sources_.begin();
+ itr != data_sources_.end();
+ itr++) {
+ if (!(*itr)->HasSingleOrigin())
+ return false;
+ }
+ return true;
+}
+
+void WebMediaPlayerProxy::AbortDataSources() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ base::AutoLock auto_lock(data_sources_lock_);
+
+ for (DataSourceList::iterator itr = data_sources_.begin();
+ itr != data_sources_.end();
+ itr++) {
+ (*itr)->Abort();
+ }
+}
+
+void WebMediaPlayerProxy::Detach() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ webmediaplayer_ = NULL;
+ video_renderer_ = NULL;
+
+ {
+ base::AutoLock auto_lock(data_sources_lock_);
+ data_sources_.clear();
+ }
+}
+
+void WebMediaPlayerProxy::PipelineInitializationCallback(
+ PipelineStatus status) {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::PipelineInitializationTask, status));
+}
+
+void WebMediaPlayerProxy::PipelineSeekCallback(PipelineStatus status) {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::PipelineSeekTask, status));
+}
+
+void WebMediaPlayerProxy::PipelineEndedCallback(PipelineStatus status) {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::PipelineEndedTask, status));
+}
+
+void WebMediaPlayerProxy::PipelineErrorCallback(PipelineStatus error) {
+ DCHECK_NE(error, media::PIPELINE_OK);
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::PipelineErrorTask, error));
+}
+
+void WebMediaPlayerProxy::NetworkEventCallback(bool is_downloading_data) {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::NetworkEventTask, is_downloading_data));
+}
+
+void WebMediaPlayerProxy::AddDataSource(WebDataSource* data_source) {
+ base::AutoLock auto_lock(data_sources_lock_);
+ data_sources_.push_back(make_scoped_refptr(data_source));
+}
+
+void WebMediaPlayerProxy::RepaintTask() {
+ DCHECK(MessageLoop::current() == render_loop_);
+ {
+ base::AutoLock auto_lock(lock_);
+ --outstanding_repaints_;
+ DCHECK_GE(outstanding_repaints_, 0);
+ }
+ if (webmediaplayer_) {
+ webmediaplayer_->Repaint();
+ }
+}
+
+void WebMediaPlayerProxy::PipelineInitializationTask(PipelineStatus status) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (webmediaplayer_)
+ webmediaplayer_->OnPipelineInitialize(status);
+}
+
+void WebMediaPlayerProxy::PipelineSeekTask(PipelineStatus status) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (webmediaplayer_)
+ webmediaplayer_->OnPipelineSeek(status);
+}
+
+void WebMediaPlayerProxy::PipelineEndedTask(PipelineStatus status) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (webmediaplayer_)
+ webmediaplayer_->OnPipelineEnded(status);
+}
+
+void WebMediaPlayerProxy::PipelineErrorTask(PipelineStatus error) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (webmediaplayer_)
+ webmediaplayer_->OnPipelineError(error);
+}
+
+void WebMediaPlayerProxy::NetworkEventTask(bool is_downloading_data) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ if (webmediaplayer_)
+ webmediaplayer_->OnNetworkEvent(is_downloading_data);
+}
+
+void WebMediaPlayerProxy::GetCurrentFrame(
+ scoped_refptr<media::VideoFrame>* frame_out) {
+ if (video_renderer_)
+ video_renderer_->GetCurrentFrame(frame_out);
+}
+
+void WebMediaPlayerProxy::PutCurrentFrame(
+ scoped_refptr<media::VideoFrame> frame) {
+ if (video_renderer_)
+ video_renderer_->PutCurrentFrame(frame);
+}
+
+void WebMediaPlayerProxy::DemuxerOpened(media::ChunkDemuxer* demuxer) {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::DemuxerOpenedTask,
+ scoped_refptr<media::ChunkDemuxer>(demuxer)));
+}
+
+void WebMediaPlayerProxy::DemuxerClosed() {
+ render_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &WebMediaPlayerProxy::DemuxerClosedTask));
+}
+
+void WebMediaPlayerProxy::DemuxerFlush() {
+ if (chunk_demuxer_.get())
+ chunk_demuxer_->FlushData();
+}
+
+bool WebMediaPlayerProxy::DemuxerAppend(const uint8* data, size_t length) {
+ if (chunk_demuxer_.get())
+ return chunk_demuxer_->AppendData(data, length);
+ return false;
+}
+
+void WebMediaPlayerProxy::DemuxerEndOfStream(media::PipelineStatus status) {
+ if (chunk_demuxer_.get())
+ chunk_demuxer_->EndOfStream(status);
+}
+
+void WebMediaPlayerProxy::DemuxerShutdown() {
+ if (chunk_demuxer_.get())
+ chunk_demuxer_->Shutdown();
+}
+
+void WebMediaPlayerProxy::DemuxerOpenedTask(
+ const scoped_refptr<media::ChunkDemuxer>& demuxer) {
+ DCHECK(MessageLoop::current() == render_loop_);
+ chunk_demuxer_ = demuxer;
+ if (webmediaplayer_)
+ webmediaplayer_->OnDemuxerOpened();
+}
+
+void WebMediaPlayerProxy::DemuxerClosedTask() {
+ chunk_demuxer_ = NULL;
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/webmediaplayer_proxy.h b/webkit/media/webmediaplayer_proxy.h
new file mode 100644
index 0000000..dc96111
--- /dev/null
+++ b/webkit/media/webmediaplayer_proxy.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2011 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_MEDIA_WEBMEDIAPLAYER_PROXY_H_
+#define WEBKIT_MEDIA_WEBMEDIAPLAYER_PROXY_H_
+
+#include <list>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "media/filters/chunk_demuxer_client.h"
+#include "webkit/media/web_data_source.h"
+
+class MessageLoop;
+class SkCanvas;
+
+namespace gfx {
+class Rect;
+}
+
+namespace webkit_media {
+
+class WebMediaPlayerImpl;
+class WebVideoRenderer;
+
+// Acts as a thread proxy between the various threads used for multimedia and
+// the render thread that WebMediaPlayerImpl is running on.
+class WebMediaPlayerProxy
+ : public base::RefCountedThreadSafe<WebMediaPlayerProxy>,
+ public media::ChunkDemuxerClient {
+ public:
+ WebMediaPlayerProxy(MessageLoop* render_loop,
+ WebMediaPlayerImpl* webmediaplayer);
+
+ // Methods for Filter -> WebMediaPlayerImpl communication.
+ void Repaint();
+ void SetVideoRenderer(scoped_refptr<WebVideoRenderer> video_renderer);
+ WebDataSourceBuildObserverHack GetBuildObserver();
+
+ // Methods for WebMediaPlayerImpl -> Filter communication.
+ void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect);
+ void SetSize(const gfx::Rect& rect);
+ void Detach();
+ void GetCurrentFrame(scoped_refptr<media::VideoFrame>* frame_out);
+ void PutCurrentFrame(scoped_refptr<media::VideoFrame> frame);
+ bool HasSingleOrigin();
+ void AbortDataSources();
+
+ // Methods for PipelineImpl -> WebMediaPlayerImpl communication.
+ void PipelineInitializationCallback(media::PipelineStatus status);
+ void PipelineSeekCallback(media::PipelineStatus status);
+ void PipelineEndedCallback(media::PipelineStatus status);
+ void PipelineErrorCallback(media::PipelineStatus error);
+ void NetworkEventCallback(bool network_activity);
+
+ // ChunkDemuxerClient implementation.
+ virtual void DemuxerOpened(media::ChunkDemuxer* demuxer) OVERRIDE;
+ virtual void DemuxerClosed() OVERRIDE;
+
+ // Methods for Demuxer communication.
+ void DemuxerFlush();
+ bool DemuxerAppend(const uint8* data, size_t length);
+ void DemuxerEndOfStream(media::PipelineStatus status);
+ void DemuxerShutdown();
+
+ void DemuxerOpenedTask(const scoped_refptr<media::ChunkDemuxer>& demuxer);
+ void DemuxerClosedTask();
+
+ // Returns the message loop used by the proxy.
+ MessageLoop* message_loop() { return render_loop_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<WebMediaPlayerProxy>;
+ virtual ~WebMediaPlayerProxy();
+
+ // Adds a data source to data_sources_.
+ void AddDataSource(WebDataSource* data_source);
+
+ // Invoke |webmediaplayer_| to perform a repaint.
+ void RepaintTask();
+
+ // Notify |webmediaplayer_| that initialization has finished.
+ void PipelineInitializationTask(media::PipelineStatus status);
+
+ // Notify |webmediaplayer_| that a seek has finished.
+ void PipelineSeekTask(media::PipelineStatus status);
+
+ // Notify |webmediaplayer_| that the media has ended.
+ void PipelineEndedTask(media::PipelineStatus status);
+
+ // Notify |webmediaplayer_| that a pipeline error has occurred during
+ // playback.
+ void PipelineErrorTask(media::PipelineStatus error);
+
+ // Notify |webmediaplayer_| that there's a network event.
+ void NetworkEventTask(bool network_activity);
+
+ // The render message loop where WebKit lives.
+ MessageLoop* render_loop_;
+ WebMediaPlayerImpl* webmediaplayer_;
+
+ base::Lock data_sources_lock_;
+ typedef std::list<scoped_refptr<WebDataSource> > DataSourceList;
+ DataSourceList data_sources_;
+ WebDataSourceBuildObserverHack build_observer_;
+
+ scoped_refptr<WebVideoRenderer> video_renderer_;
+
+ base::Lock lock_;
+ int outstanding_repaints_;
+
+ scoped_refptr<media::ChunkDemuxer> chunk_demuxer_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebMediaPlayerProxy);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_PROXY_H_
diff --git a/webkit/media/webvideoframe_impl.cc b/webkit/media/webvideoframe_impl.cc
new file mode 100644
index 0000000..5a5b182
--- /dev/null
+++ b/webkit/media/webvideoframe_impl.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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/media/webvideoframe_impl.h"
+
+#include "base/logging.h"
+#include "media/base/video_frame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h"
+
+using WebKit::WebVideoFrame;
+
+namespace webkit_media {
+
+media::VideoFrame* WebVideoFrameImpl::toVideoFrame(
+ WebVideoFrame* web_video_frame) {
+ WebVideoFrameImpl* wrapped_frame =
+ static_cast<WebVideoFrameImpl*>(web_video_frame);
+ if (wrapped_frame)
+ return wrapped_frame->video_frame_.get();
+ return NULL;
+}
+
+WebVideoFrameImpl::WebVideoFrameImpl(
+ scoped_refptr<media::VideoFrame> video_frame)
+ : video_frame_(video_frame) {
+}
+
+WebVideoFrameImpl::~WebVideoFrameImpl() {}
+
+#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \
+ COMPILE_ASSERT(int(WebKit::WebVideoFrame::webkit_name) == \
+ int(media::VideoFrame::chromium_name), \
+ mismatching_enums)
+COMPILE_ASSERT_MATCHING_ENUM(FormatInvalid, INVALID);
+COMPILE_ASSERT_MATCHING_ENUM(FormatRGB555, RGB555);
+COMPILE_ASSERT_MATCHING_ENUM(FormatRGB565, RGB565);
+COMPILE_ASSERT_MATCHING_ENUM(FormatRGB24, RGB24);
+COMPILE_ASSERT_MATCHING_ENUM(FormatRGB32, RGB32);
+COMPILE_ASSERT_MATCHING_ENUM(FormatRGBA, RGBA);
+COMPILE_ASSERT_MATCHING_ENUM(FormatYV12, YV12);
+COMPILE_ASSERT_MATCHING_ENUM(FormatYV16, YV16);
+COMPILE_ASSERT_MATCHING_ENUM(FormatNV12, NV12);
+COMPILE_ASSERT_MATCHING_ENUM(FormatEmpty, EMPTY);
+COMPILE_ASSERT_MATCHING_ENUM(FormatASCII, ASCII);
+
+WebVideoFrame::Format WebVideoFrameImpl::format() const {
+ if (video_frame_.get())
+ return static_cast<WebVideoFrame::Format>(video_frame_->format());
+ return WebVideoFrame::FormatInvalid;
+}
+
+unsigned WebVideoFrameImpl::width() const {
+ if (video_frame_.get())
+ return video_frame_->width();
+ return 0;
+}
+
+unsigned WebVideoFrameImpl::height() const {
+ if (video_frame_.get())
+ return video_frame_->height();
+ return 0;
+}
+
+unsigned WebVideoFrameImpl::planes() const {
+ if (video_frame_.get())
+ return video_frame_->planes();
+ return 0;
+}
+
+int WebVideoFrameImpl::stride(unsigned plane) const {
+ if (video_frame_.get())
+ return static_cast<int>(video_frame_->stride(plane));
+ return 0;
+}
+
+const void* WebVideoFrameImpl::data(unsigned plane) const {
+ if (video_frame_.get())
+ return static_cast<const void*>(video_frame_->data(plane));
+ return NULL;
+}
+
+} // namespace webkit_media
diff --git a/webkit/media/webvideoframe_impl.h b/webkit/media/webvideoframe_impl.h
new file mode 100644
index 0000000..f82c4d7
--- /dev/null
+++ b/webkit/media/webvideoframe_impl.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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_MEDIA_WEBVIDEOFRAME_IMPL_H_
+#define WEBKIT_MEDIA_WEBVIDEOFRAME_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "media/base/video_frame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h"
+
+namespace webkit_media {
+
+class WebVideoFrameImpl : public WebKit::WebVideoFrame {
+ public:
+ // This converts a WebKit::WebVideoFrame to a media::VideoFrame.
+ static media::VideoFrame* toVideoFrame(
+ WebKit::WebVideoFrame* web_video_frame);
+
+ WebVideoFrameImpl(scoped_refptr<media::VideoFrame> video_frame);
+ virtual ~WebVideoFrameImpl();
+ virtual WebVideoFrame::Format format() const OVERRIDE;
+ virtual unsigned width() const OVERRIDE;
+ virtual unsigned height() const OVERRIDE;
+ virtual unsigned planes() const OVERRIDE;
+ virtual int stride(unsigned plane) const OVERRIDE;
+ virtual const void* data(unsigned plane) const OVERRIDE;
+
+ private:
+ scoped_refptr<media::VideoFrame> video_frame_;
+ DISALLOW_COPY_AND_ASSIGN(WebVideoFrameImpl);
+};
+
+} // namespace webkit_media
+
+#endif // WEBKIT_MEDIA_WEBVIDEOFRAME_IMPL_H_