diff options
Diffstat (limited to 'webkit/glue/media')
-rw-r--r-- | webkit/glue/media/buffered_data_source.cc | 283 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source.h | 42 | ||||
-rw-r--r-- | webkit/glue/media/buffered_data_source_unittest.cc | 60 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader.cc | 175 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader.h | 58 | ||||
-rw-r--r-- | webkit/glue/media/buffered_resource_loader_unittest.cc | 216 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.cc | 245 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source.h | 26 | ||||
-rw-r--r-- | webkit/glue/media/simple_data_source_unittest.cc | 44 | ||||
-rw-r--r-- | webkit/glue/media/video_renderer_impl.cc | 10 | ||||
-rw-r--r-- | webkit/glue/media/video_renderer_impl.h | 10 | ||||
-rw-r--r-- | webkit/glue/media/web_data_source.h | 24 | ||||
-rw-r--r-- | webkit/glue/media/web_data_source_factory.cc | 103 | ||||
-rw-r--r-- | webkit/glue/media/web_data_source_factory.h | 51 | ||||
-rw-r--r-- | webkit/glue/media/web_video_renderer.h | 4 |
15 files changed, 933 insertions, 418 deletions
diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc index 7902221..8aafd26 100644 --- a/webkit/glue/media/buffered_data_source.cc +++ b/webkit/glue/media/buffered_data_source.cc @@ -6,6 +6,7 @@ #include "media/base/filter_host.h" #include "net/base/net_errors.h" +#include "webkit/glue/media/web_data_source_factory.h" #include "webkit/glue/webkit_glue.h" using WebKit::WebFrame; @@ -27,10 +28,25 @@ static const int kReadTrials = 3; // of FFmpeg. static const int kInitialReadBufferSize = 32768; +static WebDataSource* NewBufferedDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame) { + return new BufferedDataSource(render_loop, frame); +} + +// static +media::DataSourceFactory* BufferedDataSource::CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + WebDataSourceBuildObserverHack* build_observer) { + return new WebDataSourceFactory(render_loop, frame, &NewBufferedDataSource, + build_observer); +} + BufferedDataSource::BufferedDataSource( MessageLoop* render_loop, WebFrame* frame) : total_bytes_(kPositionNotSpecified), + buffered_bytes_(0), loaded_(false), streaming_(false), frame_(frame), @@ -48,6 +64,8 @@ BufferedDataSource::BufferedDataSource( stop_signal_received_(false), stopped_on_render_loop_(false), media_is_paused_(true), + media_has_played_(false), + preload_(media::METADATA), using_range_request_(true) { } @@ -73,18 +91,25 @@ base::TimeDelta BufferedDataSource::GetTimeoutMilliseconds() { return base::TimeDelta::FromMilliseconds(kTimeoutMilliseconds); } -///////////////////////////////////////////////////////////////////////////// -// media::Filter implementation. +void BufferedDataSource::set_host(media::FilterHost* host) { + DataSource::set_host(host); + + if (loader_.get()) + UpdateHostState(); +} + void BufferedDataSource::Initialize(const std::string& url, - media::FilterCallback* callback) { + media::PipelineStatusCallback* callback) { // Saves the url. url_ = GURL(url); - if (!IsProtocolSupportedForMedia(url_)) { - // This method is called on the thread where host() lives so it is safe - // to make this call. - host()->SetError(media::PIPELINE_ERROR_NETWORK); - callback->Run(); + // This data source doesn't support data:// protocol so reject it. + if (url_.SchemeIs(kDataScheme)) { + callback->Run(media::DATASOURCE_ERROR_URL_NOT_SUPPORTED); + delete callback; + return; + } else if (!IsProtocolSupportedForMedia(url_)) { + callback->Run(media::PIPELINE_ERROR_NETWORK); delete callback; return; } @@ -92,8 +117,6 @@ void BufferedDataSource::Initialize(const std::string& url, DCHECK(callback); initialize_callback_.reset(callback); - media_format_.SetAsString(media::MediaFormat::kMimeType, - media::mime_type::kApplicationOctetStream); media_format_.SetAsString(media::MediaFormat::kURL, url); // Post a task to complete the initialization task. @@ -101,13 +124,18 @@ void BufferedDataSource::Initialize(const std::string& url, NewRunnableMethod(this, &BufferedDataSource::InitializeTask)); } -bool BufferedDataSource::IsUrlSupported(const std::string& url) { - GURL gurl(url); +void BufferedDataSource::CancelInitialize() { + base::AutoLock auto_lock(lock_); + DCHECK(initialize_callback_.get()); - // This data source doesn't support data:// protocol so reject it. - return IsProtocolSupportedForMedia(gurl) && !gurl.SchemeIs(kDataScheme); + initialize_callback_.reset(); + + render_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &BufferedDataSource::CleanupTask)); } +///////////////////////////////////////////////////////////////////////////// +// media::Filter implementation. void BufferedDataSource::Stop(media::FilterCallback* callback) { { base::AutoLock auto_lock(lock_); @@ -128,6 +156,12 @@ void BufferedDataSource::SetPlaybackRate(float playback_rate) { playback_rate)); } +void BufferedDataSource::SetPreload(media::Preload preload) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::SetPreloadTask, + preload)); +} + ///////////////////////////////////////////////////////////////////////////// // media::DataSource implementation. void BufferedDataSource::Read(int64 position, size_t size, uint8* data, @@ -174,15 +208,6 @@ bool BufferedDataSource::HasSingleOrigin() { void BufferedDataSource::Abort() { DCHECK(MessageLoop::current() == render_loop_); - { - base::AutoLock auto_lock(lock_); - - // If we are told to abort, immediately return from any pending read - // with an error. - if (read_callback_.get()) - DoneRead_Locked(net::ERR_FAILED); - } - CleanupTask(); frame_ = NULL; } @@ -192,7 +217,7 @@ void BufferedDataSource::Abort() { void BufferedDataSource::InitializeTask() { DCHECK(MessageLoop::current() == render_loop_); DCHECK(!loader_.get()); - if (stopped_on_render_loop_) + if (stopped_on_render_loop_ || !initialize_callback_.get()) return; // Kick starts the watch dog task that will handle connection timeout. @@ -256,7 +281,13 @@ void BufferedDataSource::CleanupTask() { if (stopped_on_render_loop_) return; - read_callback_.reset(); + // 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_.get()) + DoneRead_Locked(net::ERR_FAILED); } // Stop the watch dog. @@ -272,9 +303,6 @@ void BufferedDataSource::CleanupTask() { read_buffer_ = 0; read_submitted_time_ = base::Time(); read_attempts_ = 0; - - // Signal that stop task has finished execution. - stopped_on_render_loop_ = true; } void BufferedDataSource::RestartLoadingTask() { @@ -290,7 +318,8 @@ void BufferedDataSource::RestartLoadingTask() { } loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified); - loader_->SetAllowDefer(!media_is_paused_); + BufferedResourceLoader::DeferStrategy strategy = ChooseDeferStrategy(); + loader_->UpdateDeferStrategy(strategy); loader_->Start( NewCallback(this, &BufferedDataSource::PartialReadStartCallback), NewCallback(this, &BufferedDataSource::NetworkEventCallback), @@ -326,7 +355,8 @@ void BufferedDataSource::WatchDogTask() { // retry the request. loader_->Stop(); loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified); - loader_->SetAllowDefer(!media_is_paused_); + BufferedResourceLoader::DeferStrategy strategy = ChooseDeferStrategy(); + loader_->UpdateDeferStrategy(strategy); loader_->Start( NewCallback(this, &BufferedDataSource::PartialReadStartCallback), NewCallback(this, &BufferedDataSource::NetworkEventCallback), @@ -340,13 +370,37 @@ void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { bool previously_paused = media_is_paused_; media_is_paused_ = (playback_rate == 0.0); - // Disallow deferring data when we are pausing, allow deferring data - // when we resume playing. - if (previously_paused && !media_is_paused_) { - loader_->SetAllowDefer(true); - } else if (!previously_paused && media_is_paused_) { - loader_->SetAllowDefer(false); + 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; +} + +BufferedResourceLoader::DeferStrategy +BufferedDataSource::ChooseDeferStrategy() { + // If the user indicates preload=metadata, then just load exactly + // what is needed for starting the pipeline and prerolling frames. + if (preload_ == media::METADATA && !media_has_played_) + return BufferedResourceLoader::kReadThenDefer; + + // In general, we want to try to buffer the entire video when the video + // is paused. But we don't want to do this if the video hasn't played yet + // and preload!=auto. + if (media_is_paused_ && + (preload_ == media::AUTO || media_has_played_)) { + return BufferedResourceLoader::kNeverDefer; } + + // When the video is playing, regardless of preload state, we buffer up + // to a hard limit and enable/disable deferring when the buffer is + // depleted/full. + return BufferedResourceLoader::kThresholdDefer; } // This method is the place where actual read happens, |loader_| must be valid @@ -386,13 +440,15 @@ void BufferedDataSource::DoneRead_Locked(int error) { read_buffer_ = 0; } -void BufferedDataSource::DoneInitialization_Locked() { +void BufferedDataSource::DoneInitialization_Locked( + media::PipelineStatus status) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(initialize_callback_.get()); lock_.AssertAcquired(); - initialize_callback_->Run(); - initialize_callback_.reset(); + scoped_ptr<media::PipelineStatusCallback> initialize_callback( + initialize_callback_.release()); + initialize_callback->Run(status); } ///////////////////////////////////////////////////////////////////////////// @@ -402,15 +458,20 @@ void BufferedDataSource::HttpInitialStartCallback(int error) { DCHECK(loader_.get()); int64 instance_size = loader_->instance_size(); - bool partial_response = loader_->partial_response(); bool success = error == net::OK; + if (!initialize_callback_.get()) { + 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) || !partial_response; + streaming_ = (instance_size == kPositionNotSpecified) || + !loader_->range_supported(); } else { // TODO(hclam): In case of failure, we can retry several times. loader_->Stop(); @@ -429,85 +490,90 @@ void BufferedDataSource::HttpInitialStartCallback(int error) { 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. 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; + // Reference to prevent destruction while inside the |initialize_callback_| + // 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) { - host()->SetError(media::PIPELINE_ERROR_NETWORK); - DoneInitialization_Locked(); - return; - } + if (!success) { + DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK); + return; + } - if (streaming_) { - // If the server didn't reply with an instance size, it is likely this - // is a streaming response. - host()->SetStreaming(true); - } else { - // This value governs the range that we can seek to. - // TODO(hclam): Report the correct value of buffered bytes. - host()->SetTotalBytes(total_bytes_); - host()->SetBufferedBytes(0); + UpdateHostState(); + DoneInitialization_Locked(media::PIPELINE_OK); } - - // Currently, only files can be used reliably w/o a network. - host()->SetLoaded(false); - DoneInitialization_Locked(); } void BufferedDataSource::NonHttpInitialStartCallback(int error) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); + if (!initialize_callback_.get()) { + 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(); } - // 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; + // Reference to prevent destruction while inside the |initialize_callback_| + // 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_callback_.get()) + return; - if (success) { - host()->SetTotalBytes(total_bytes_); - host()->SetBufferedBytes(total_bytes_); - host()->SetLoaded(loaded_); - } else { - host()->SetError(media::PIPELINE_ERROR_NETWORK); + if (!success) { + DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK); + return; + } + + UpdateHostState(); + DoneInitialization_Locked(media::PIPELINE_OK); } - DoneInitialization_Locked(); } void BufferedDataSource::PartialReadStartCallback(int error) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); - // This callback method is invoked after we have verified the server has - // range request capability, so as a safety guard verify again the response - // is partial. - if (error == net::OK && loader_->partial_response()) { - // Once the range request has started successfully, we can proceed with + if (error == net::OK) { + // Once the request has started successfully, we can proceed with // reading from it. ReadInternal(); return; @@ -562,6 +628,14 @@ void BufferedDataSource::ReadCallback(int error) { // 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); } @@ -596,9 +670,28 @@ void BufferedDataSource::NetworkEventCallback() { if (network_activity != network_activity_) { network_activity_ = network_activity; - host()->SetNetworkActivity(network_activity); + if (host()) + host()->SetNetworkActivity(network_activity); + } + + buffered_bytes_ = buffered_position + 1; + if (host()) + host()->SetBufferedBytes(buffered_bytes_); +} + +void BufferedDataSource::UpdateHostState() { + 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_); } - host()->SetBufferedBytes(buffered_position + 1); } } // namespace webkit_glue diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h index 895b259..933ffb9 100644 --- a/webkit/glue/media/buffered_data_source.h +++ b/webkit/glue/media/buffered_data_source.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -8,23 +8,29 @@ #include <string> #include "base/callback.h" -#include "base/scoped_ptr.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/glue/media/buffered_resource_loader.h" namespace webkit_glue { class BufferedDataSource : public WebDataSource { public: + // Creates a DataSourceFactory for building BufferedDataSource objects. + static media::DataSourceFactory* CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + WebDataSourceBuildObserverHack* build_observer); + BufferedDataSource(MessageLoop* render_loop, WebKit::WebFrame* frame); virtual ~BufferedDataSource(); // media::Filter implementation. - virtual void Initialize(const std::string& url, - media::FilterCallback* callback); - virtual bool IsUrlSupported(const std::string& url); + virtual void set_host(media::FilterHost* host); virtual void Stop(media::FilterCallback* callback); virtual void SetPlaybackRate(float playback_rate); @@ -35,12 +41,16 @@ class BufferedDataSource : public WebDataSource { media::DataSource::ReadCallback* read_callback); virtual bool GetSize(int64* size_out); virtual bool IsStreaming(); + virtual void SetPreload(media::Preload preload); const media::MediaFormat& media_format() { return media_format_; } // webkit_glue::WebDataSource implementation. + virtual void Initialize(const std::string& url, + media::PipelineStatusCallback* callback); + virtual void CancelInitialize(); virtual bool HasSingleOrigin(); virtual void Abort(); @@ -82,6 +92,13 @@ class BufferedDataSource : public WebDataSource { // 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); + + // 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(); @@ -90,7 +107,7 @@ class BufferedDataSource : public WebDataSource { void DoneRead_Locked(int error); // Calls |initialize_callback_| and reset it. - void DoneInitialization_Locked(); + 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 @@ -116,6 +133,8 @@ class BufferedDataSource : public WebDataSource { // Callback method when a network event is received. void NetworkEventCallback(); + void UpdateHostState(); + media::MediaFormat media_format_; // URL of the resource requested. @@ -126,6 +145,7 @@ class BufferedDataSource : public WebDataSource { // 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_; @@ -144,7 +164,7 @@ class BufferedDataSource : public WebDataSource { bool network_activity_; // Callback method from the pipeline for initialization. - scoped_ptr<media::FilterCallback> initialize_callback_; + scoped_ptr<media::PipelineStatusCallback> initialize_callback_; // Read parameters received from the Read() method call. scoped_ptr<media::DataSource::ReadCallback> read_callback_; @@ -185,6 +205,14 @@ class BufferedDataSource : public WebDataSource { // 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_; + // This timer is to run the WatchDogTask repeatedly. We use a timer instead // of doing PostDelayedTask() reduce the extra reference held by the message // loop. The RepeatingTimer does PostDelayedTask() internally, by using it diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc index 41f37d6..528a66f 100644 --- a/webkit/glue/media/buffered_data_source_unittest.cc +++ b/webkit/glue/media/buffered_data_source_unittest.cc @@ -76,7 +76,7 @@ class MockBufferedResourceLoader : public BufferedResourceLoader { net::CompletionCallback* callback)); MOCK_METHOD0(content_length, int64()); MOCK_METHOD0(instance_size, int64()); - MOCK_METHOD0(partial_response, bool()); + MOCK_METHOD0(range_supported, bool()); MOCK_METHOD0(network_activity, bool()); MOCK_METHOD0(url, const GURL&()); MOCK_METHOD0(GetBufferedFirstBytePosition, int64()); @@ -164,10 +164,14 @@ class BufferedDataSourceTest : public testing::Test { ON_CALL(*loader_, instance_size()) .WillByDefault(Return(instance_size)); - ON_CALL(*loader_, partial_response()) + + // 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)); @@ -183,12 +187,13 @@ class BufferedDataSourceTest : public testing::Test { EXPECT_CALL(host_, SetStreaming(true)); } } else { - EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); + expected_init_status = media::PIPELINE_ERROR_NETWORK; EXPECT_CALL(*loader_, Stop()); } // Actual initialization of the data source. - data_source_->Initialize(url, media::NewExpectedCallback()); + data_source_->Initialize(url, + media::NewExpectedStatusCallback(expected_init_status)); message_loop_->RunAllPending(); if (initialized_ok) { @@ -275,7 +280,7 @@ class BufferedDataSourceTest : public testing::Test { loader_ = NULL; } - void ReadDataSourceMiss(int64 position, int size) { + void ReadDataSourceMiss(int64 position, int size, int start_error) { EXPECT_TRUE(loader_); // 1. Reply with a cache miss for the read. @@ -296,19 +301,26 @@ class BufferedDataSourceTest : public testing::Test { // 3. Then the new loader will be started. EXPECT_CALL(*new_loader, Start(NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll(Assign(&error_, net::OK), + .WillOnce(DoAll(Assign(&error_, start_error), Invoke(this, &BufferedDataSourceTest::InvokeStartCallback))); - EXPECT_CALL(*new_loader, partial_response()) - .WillRepeatedly(Return(loader_->partial_response())); - // 4. Then again a read request is made to the new loader. - EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) - .WillOnce(DoAll(Assign(&error_, size), - Invoke(this, - &BufferedDataSourceTest::InvokeReadCallback))); + if (start_error == net::OK) { + EXPECT_CALL(*new_loader, range_supported()) + .WillRepeatedly(Return(loader_->range_supported())); - EXPECT_CALL(*this, ReadCallback(size)); + // 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_, @@ -316,7 +328,8 @@ class BufferedDataSourceTest : public testing::Test { message_loop_->RunAllPending(); // Make sure data is correct. - EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); + if (start_error == net::OK) + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); loader_ = new_loader; } @@ -365,8 +378,8 @@ class BufferedDataSourceTest : public testing::Test { .WillOnce(DoAll(Assign(&error_, net::OK), Invoke(this, &BufferedDataSourceTest::InvokeStartCallback))); - EXPECT_CALL(*new_loader, partial_response()) - .WillRepeatedly(Return(loader_->partial_response())); + EXPECT_CALL(*new_loader, range_supported()) + .WillRepeatedly(Return(loader_->range_supported())); // 4. Then again a read request is made to the new loader. EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) @@ -457,8 +470,17 @@ TEST_F(BufferedDataSourceTest, ReadCacheHit) { TEST_F(BufferedDataSourceTest, ReadCacheMiss) { InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); - ReadDataSourceMiss(1000, 10); - ReadDataSourceMiss(20, 10); + 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(); } diff --git a/webkit/glue/media/buffered_resource_loader.cc b/webkit/glue/media/buffered_resource_loader.cc index 4a3ae8b..8d7f1dd 100644 --- a/webkit/glue/media/buffered_resource_loader.cc +++ b/webkit/glue/media/buffered_resource_loader.cc @@ -50,10 +50,10 @@ BufferedResourceLoader::BufferedResourceLoader( int64 last_byte_position) : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), deferred_(false), - defer_allowed_(true), + defer_strategy_(kReadThenDefer), completed_(false), range_requested_(false), - partial_response_(false), + range_supported_(false), url_(url), first_byte_position_(first_byte_position), last_byte_position_(last_byte_position), @@ -90,7 +90,6 @@ void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, event_callback_.reset(event_callback); if (first_byte_position_ != kPositionNotSpecified) { - range_requested_ = true; // TODO(hclam): server may not support range request so |offset_| may not // equal to |first_byte_position_|. offset_ = first_byte_position_; @@ -103,10 +102,14 @@ void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, // Prepare the request. WebURLRequest request(url_); request.setTargetType(WebURLRequest::TargetIsMedia); - request.setHTTPHeaderField(WebString::fromUTF8("Range"), - WebString::fromUTF8(GenerateHeaders( - first_byte_position_, - last_byte_position_))); + + if (IsRangeRequest()) { + range_requested_ = true; + request.setHTTPHeaderField(WebString::fromUTF8("Range"), + WebString::fromUTF8(GenerateHeaders( + first_byte_position_, + last_byte_position_))); + } frame->setReferrerForRequest(request, WebKit::WebURL()); // This flag is for unittests as we don't want to reset |url_loader| @@ -180,10 +183,15 @@ void BufferedResourceLoader::Read(int64 position, // If we can serve the request now, do the actual read. if (CanFulfillRead()) { ReadInternal(); - DisableDeferIfNeeded(); + UpdateDeferBehavior(); return; } + // If you're deferred and you can't fulfill the read because you don't have + // enough data, you will never fulfill the read. + // Update defer behavior to re-enable deferring if need be. + UpdateDeferBehavior(); + // If we expected the read request to be fulfilled later, returns // immediately and let more data to flow in. if (WillFulfillRead()) @@ -199,11 +207,6 @@ int64 BufferedResourceLoader::GetBufferedPosition() { return kPositionNotSpecified; } -void BufferedResourceLoader::SetAllowDefer(bool is_allowed) { - defer_allowed_ = is_allowed; - DisableDeferIfNeeded(); -} - int64 BufferedResourceLoader::content_length() { return content_length_; } @@ -212,8 +215,8 @@ int64 BufferedResourceLoader::instance_size() { return instance_size_; } -bool BufferedResourceLoader::partial_response() { - return partial_response_; +bool BufferedResourceLoader::range_supported() { + return range_supported_; } bool BufferedResourceLoader::network_activity() { @@ -244,7 +247,7 @@ void BufferedResourceLoader::willSendRequest( return; } - // Only allow |single_origin_| if we haven't seen a different origin yet. + // Only allow |single_origin_| if we haven't seen a different origin yet. if (single_origin_) single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin(); @@ -274,6 +277,8 @@ void BufferedResourceLoader::didReceiveResponse( 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 @@ -281,13 +286,20 @@ void BufferedResourceLoader::didReceiveResponse( if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { int error = net::OK; - if (response.httpStatusCode() == kHttpPartialContent) - partial_response_ = true; + // 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_ && partial_response_) { + if (range_requested_) { // If we have verified the partial response and it is correct, we will - // return net::OK. - if (!VerifyPartialResponse(response)) + // 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". @@ -302,7 +314,7 @@ void BufferedResourceLoader::didReceiveResponse( } else { // For any protocol other than HTTP and HTTPS, assume range request is // always fulfilled. - partial_response_ = range_requested_; + partial_response = range_requested_; } // Expected content length can be |kPositionNotSpecified|, in that case @@ -311,7 +323,7 @@ void BufferedResourceLoader::didReceiveResponse( // If we have not requested a range, then the size of the instance is equal // to the content length. - if (!partial_response_) + if (!partial_response) instance_size_ = content_length_; // Calls with a successful response. @@ -321,7 +333,8 @@ void BufferedResourceLoader::didReceiveResponse( void BufferedResourceLoader::didReceiveData( WebURLLoader* loader, const char* data, - int data_length) { + int data_length, + int encoded_data_length) { DCHECK(!completed_); DCHECK_GT(data_length, 0); @@ -334,21 +347,19 @@ void BufferedResourceLoader::didReceiveData( buffer_->Append(reinterpret_cast<const uint8*>(data), data_length); // If there is an active read request, try to fulfill the request. - if (HasPendingRead() && CanFulfillRead()) { + if (HasPendingRead() && CanFulfillRead()) ReadInternal(); - } else if (!defer_allowed_) { - // If we're not allowed to defer, slide the buffer window forward instead - // of deferring. - if (buffer_->forward_bytes() > buffer_->forward_capacity()) { - size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity(); - bool success = buffer_->Seek(excess); - DCHECK(success); - offset_ += first_offset_ + excess; - } - } // At last see if the buffer is full and we need to defer the downloading. - EnableDeferIfNeeded(); + 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(); @@ -373,6 +384,11 @@ void BufferedResourceLoader::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); @@ -431,32 +447,83 @@ bool BufferedResourceLoader::HasSingleOrigin() const { ///////////////////////////////////////////////////////////////////////////// // Helper methods. -void BufferedResourceLoader::EnableDeferIfNeeded() { - if (!defer_allowed_) +void BufferedResourceLoader::UpdateDeferBehavior() { + if (!url_loader_.get() || !buffer_.get()) return; - if (!deferred_ && - buffer_->forward_bytes() >= buffer_->forward_capacity()) { - deferred_ = true; + if ((deferred_ && ShouldDisableDefer()) || + (!deferred_ && ShouldEnableDefer())) { + bool eventOccurred = ToggleDeferring(); + if (eventOccurred) + NotifyNetworkEvent(); + } +} - if (url_loader_.get()) - url_loader_->setDefersLoading(true); +void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) { + defer_strategy_ = strategy; + UpdateDeferBehavior(); +} - 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; } -void BufferedResourceLoader::DisableDeferIfNeeded() { - if (deferred_ && - (!defer_allowed_ || - buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) { - deferred_ = false; +bool BufferedResourceLoader::ShouldDisableDefer() { + // If we're not deferring, then disabling makes no sense. + if (!deferred_) + return false; - if (url_loader_.get()) - url_loader_->setDefersLoading(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: { + size_t amount_buffered = buffer_->forward_bytes(); + size_t amount_to_read = static_cast<size_t>(read_size_); + return read_callback_.get() && amount_buffered < amount_to_read; + } - NotifyNetworkEvent(); + // 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::ToggleDeferring() { + deferred_ = !deferred_; + if (url_loader_.get()) { + url_loader_->setDefersLoading(deferred_); + return true; + } + return false; } bool BufferedResourceLoader::CanFulfillRead() { @@ -579,4 +646,8 @@ void BufferedResourceLoader::NotifyNetworkEvent() { event_callback_->Run(); } +bool BufferedResourceLoader::IsRangeRequest() const { + return first_byte_position_ != kPositionNotSpecified; +} + } // namespace webkit_glue diff --git a/webkit/glue/media/buffered_resource_loader.h b/webkit/glue/media/buffered_resource_loader.h index fe01fb4..39e89d8 100644 --- a/webkit/glue/media/buffered_resource_loader.h +++ b/webkit/glue/media/buffered_resource_loader.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -8,7 +8,7 @@ #include <string> #include "base/callback.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/timer.h" #include "googleurl/src/gurl.h" #include "media/base/seekable_buffer.h" @@ -36,6 +36,15 @@ 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, + }; + typedef Callback0::Type NetworkEventCallback; // |url| - URL for the resource to be loaded. @@ -85,9 +94,6 @@ class BufferedResourceLoader : // |kPositionNotSpecified| if such value is not available. virtual int64 GetBufferedPosition(); - // Sets whether deferring data is allowed or disallowed. - virtual void SetAllowDefer(bool is_allowed); - // Gets the content length in bytes of the instance after this loader has been // started. If this value is |kPositionNotSpecified|, then content length is // unknown. @@ -97,9 +103,8 @@ class BufferedResourceLoader : // |kPositionNotSpecified|, then the size is unknown. virtual int64 instance_size(); - // Returns true if the response for this loader is a partial response. - // It means a 206 response in HTTP/HTTPS protocol. - virtual bool partial_response(); + // Returns true if the server supports byte range requests. + virtual bool range_supported(); // Returns true if network is currently active. virtual bool network_activity(); @@ -124,11 +129,12 @@ class BufferedResourceLoader : const WebKit::WebURLResponse& response); virtual void didDownloadData( WebKit::WebURLLoader* loader, - int dataLength); + int data_length); virtual void didReceiveData( WebKit::WebURLLoader* loader, const char* data, - int dataLength); + int data_length, + int encoded_data_length); virtual void didReceiveCachedMetadata( WebKit::WebURLLoader* loader, const char* data, int dataLength); @@ -141,19 +147,30 @@ class BufferedResourceLoader : bool HasSingleOrigin() const; + // Sets the defer strategy to the given value. + void UpdateDeferStrategy(DeferStrategy strategy); + protected: friend class base::RefCountedThreadSafe<BufferedResourceLoader>; - virtual ~BufferedResourceLoader(); private: friend class BufferedResourceLoaderTest; - // Defer the resource loading if the buffer is full. - void EnableDeferIfNeeded(); + // Toggles whether the resource loading is deferred or not. + // Returns true if a network event was fired. + bool ToggleDeferring(); - // Disable defer loading if we are under-buffered. - void DisableDeferIfNeeded(); + // 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(); // Returns true if the current read request can be fulfilled by what is in // the buffer. @@ -190,14 +207,17 @@ class BufferedResourceLoader : bool HasPendingRead() { return read_callback_.get() != NULL; } + // Helper function that returns true if a range request was specified. + bool IsRangeRequest() const; + // A sliding window of buffer. scoped_ptr<media::SeekableBuffer> buffer_; // True if resource loading was deferred. bool deferred_; - // True if resource loader is allowed to defer, false otherwise. - bool defer_allowed_; + // Current buffering algorithm in place for resource loading. + DeferStrategy defer_strategy_; // True if resource loading has completed. bool completed_; @@ -205,8 +225,8 @@ class BufferedResourceLoader : // True if a range request was made. bool range_requested_; - // True if response data received is a partial range. - bool partial_response_; + // True if Range header is supported. + bool range_supported_; // Does the work of loading and sends data back to this client. scoped_ptr<WebKit::WebURLLoader> url_loader_; diff --git a/webkit/glue/media/buffered_resource_loader_unittest.cc b/webkit/glue/media/buffered_resource_loader_unittest.cc index aef44ca..03fd360 100644 --- a/webkit/glue/media/buffered_resource_loader_unittest.cc +++ b/webkit/glue/media/buffered_resource_loader_unittest.cc @@ -103,7 +103,15 @@ class BufferedResourceLoaderTest : public testing::Test { } void FullResponse(int64 instance_size) { - EXPECT_CALL(*this, StartCallback(net::OK)); + 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"), @@ -112,15 +120,23 @@ class BufferedResourceLoaderTest : public testing::Test { response.setExpectedContentLength(instance_size); response.setHTTPStatusCode(kHttpOK); loader_->didReceiveResponse(url_loader_, response); - EXPECT_EQ(instance_size, loader_->content_length()); - EXPECT_EQ(instance_size, loader_->instance_size()); - EXPECT_FALSE(loader_->partial_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)); - int64 content_length = last_position - first_position + 1; WebURLResponse response(gurl_); response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), @@ -129,12 +145,36 @@ class BufferedResourceLoaderTest : public testing::Test { 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()); - EXPECT_TRUE(loader_->partial_response()); + + // A valid partial response should always result in this being true. + EXPECT_TRUE(loader_->range_supported()); } void Redirect(const char* url) { @@ -160,7 +200,9 @@ class BufferedResourceLoaderTest : public testing::Test { EXPECT_CALL(*this, NetworkCallback()) .RetiresOnSaturation(); loader_->didReceiveData(url_loader_, - reinterpret_cast<char*>(data_ + position), size); + reinterpret_cast<char*>(data_ + position), + size, + size); } // Helper method to read from |loader_|. @@ -174,18 +216,8 @@ class BufferedResourceLoaderTest : public testing::Test { EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); } - // Helper method to disallow deferring in |loader_|. - void DisallowLoaderDefer() { - if (loader_->deferred_) { - EXPECT_CALL(*url_loader_, setDefersLoading(false)); - EXPECT_CALL(*this, NetworkCallback()); - } - loader_->SetAllowDefer(false); - } - - // Helper method to allow deferring in |loader_|. - void AllowLoaderDefer() { - loader_->SetAllowDefer(true); + void ConfirmLoaderDeferredState(bool expectedVal) { + EXPECT_EQ(loader_->deferred_, expectedVal); } MOCK_METHOD1(StartCallback, void(int error)); @@ -232,8 +264,7 @@ TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { Initialize(kHttpUrl, 100, -1); Start(); - FullResponse(1024); - StopWhenLoad(); + FullResponse(1024, net::ERR_INVALID_RESPONSE); } // Tests that a 200 response is received. @@ -252,6 +283,27 @@ TEST_F(BufferedResourceLoaderTest, PartialResponse) { 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); @@ -273,6 +325,7 @@ TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { // 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); @@ -323,6 +376,7 @@ TEST_F(BufferedResourceLoaderTest, BufferAndRead) { TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { Initialize(kHttpUrl, 10, 0x00FFFFFF); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer); Start(); PartialResponse(10, 0x00FFFFFF, 0x01000000); @@ -366,128 +420,86 @@ TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { loader_->didFail(url_loader_, error); } -// Tests the logic of caching data to disk when media is paused. -TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - // Start in undeferred state, then disallow defer, then allow defer - // without receiving data in between. - DisallowLoaderDefer(); - AllowLoaderDefer(); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) { +// 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]; - // Start in undeferred state, disallow defer, receive data but don't shift - // buffer window, then allow defer and read. - DisallowLoaderDefer(); - WriteLoader(10, 10); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(10, 10, buffer); - VerifyBuffer(buffer, 10, 10); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); - - uint8 buffer[10]; - - // Not deferred, disallow defer, received data and shift buffer window, - // allow defer, then read in area outside of buffer window. - DisallowLoaderDefer(); + // Read past the buffer size; should not defer regardless. WriteLoader(10, 10); WriteLoader(20, 50); - AllowLoaderDefer(); + ConfirmLoaderDeferredState(false); + // Should move past window. EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); ReadLoader(10, 10, buffer); + StopWhenLoad(); } -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) { +// 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]; - // Start in deferred state, then disallow defer, receive no data, and - // allow defer and read. - EXPECT_CALL(*url_loader_, setDefersLoading(true)); + // Make an outstanding read request. + // We should disable deferring after the read request, so expect + // a network event. EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 40); - - DisallowLoaderDefer(); - AllowLoaderDefer(); - - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(20, 10, buffer); - VerifyBuffer(buffer, 20, 10); - StopWhenLoad(); -} - -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) { - Initialize(kHttpUrl, 10, 99); - SetLoaderBuffer(10, 20); - Start(); - PartialResponse(10, 99, 100); + ReadLoader(10, 10, buffer); - uint8 buffer[10]; + // Receive almost enough data to cover, shouldn't defer. + WriteLoader(10, 9); + ConfirmLoaderDeferredState(false); - // Start in deferred state, disallow defer, receive data and shift buffer - // window, allow defer, and read in a place that's still in the window. - EXPECT_CALL(*url_loader_, setDefersLoading(true)); + // As soon as we have received enough data to fulfill the read, defer. EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 30); + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(19, 1); - DisallowLoaderDefer(); - WriteLoader(40, 5); - AllowLoaderDefer(); + ConfirmLoaderDeferredState(true); + VerifyBuffer(buffer, 10, 10); - EXPECT_CALL(*this, ReadCallback(10)); - ReadLoader(20, 10, buffer); - VerifyBuffer(buffer, 20, 10); StopWhenLoad(); } -TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) { +// Tests the data buffering logic of ThresholdDefer strategy. +TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) { Initialize(kHttpUrl, 10, 99); SetLoaderBuffer(10, 20); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer); Start(); PartialResponse(10, 99, 100); uint8 buffer[10]; - // Start in deferred state, disallow defer, receive data and shift buffer - // window, allow defer, and read outside of the buffer window. - EXPECT_CALL(*url_loader_, setDefersLoading(true)); + WriteLoader(10, 5); + // Haven't reached threshold, don't defer. + ConfirmLoaderDeferredState(false); + + // We're at the threshold now, let's defer. EXPECT_CALL(*this, NetworkCallback()); - WriteLoader(10, 40); + WriteLoader(15, 5); + ConfirmLoaderDeferredState(true); - DisallowLoaderDefer(); - WriteLoader(50, 20); - WriteLoader(70, 40); - AllowLoaderDefer(); + // Now we've read over half of the buffer, disable deferring. + EXPECT_CALL(*this, ReadCallback(6)); + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(10, 6, buffer); + + ConfirmLoaderDeferredState(false); + VerifyBuffer(buffer, 10, 6); - EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); - ReadLoader(20, 5, buffer); StopWhenLoad(); } diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc index 5c3eb18..6647e52 100644 --- a/webkit/glue/media/simple_data_source.cc +++ b/webkit/glue/media/simple_data_source.cc @@ -12,12 +12,27 @@ #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/WebKitClient.h" +#include "webkit/glue/media/web_data_source_factory.h" #include "webkit/glue/webkit_glue.h" namespace webkit_glue { static const char kDataScheme[] = "data"; +static WebDataSource* NewSimpleDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame) { + return new SimpleDataSource(render_loop, frame); +} + +// static +media::DataSourceFactory* SimpleDataSource::CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + WebDataSourceBuildObserverHack* build_observer) { + return new WebDataSourceFactory(render_loop, frame, &NewSimpleDataSource, + build_observer); +} + SimpleDataSource::SimpleDataSource( MessageLoop* render_loop, WebKit::WebFrame* frame) @@ -35,6 +50,15 @@ SimpleDataSource::~SimpleDataSource() { 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(media::FilterCallback* callback) { base::AutoLock auto_lock(lock_); state_ = STOPPED; @@ -48,26 +72,42 @@ void SimpleDataSource::Stop(media::FilterCallback* callback) { NewRunnableMethod(this, &SimpleDataSource::CancelTask)); } -void SimpleDataSource::Initialize(const std::string& url, - media::FilterCallback* callback) { - base::AutoLock auto_lock(lock_); - DCHECK_EQ(state_, UNINITIALIZED); - DCHECK(callback); - state_ = INITIALIZING; - initialize_callback_.reset(callback); - - // Validate the URL. - SetURL(GURL(url)); - if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) { - host()->SetError(media::PIPELINE_ERROR_NETWORK); - initialize_callback_->Run(); - initialize_callback_.reset(); - return; +void SimpleDataSource::Initialize( + const std::string& url, + media::PipelineStatusCallback* callback) { + // Reference to prevent destruction while inside the |initialize_callback_| + // 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); + state_ = INITIALIZING; + initialize_callback_.reset(callback); + + // Validate the URL. + SetURL(GURL(url)); + if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) { + DoneInitialization_Locked(false); + return; + } + + // Post a task to the render thread to start loading the resource. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SimpleDataSource::StartTask)); } +} - // Post a task to the render thread to start loading the resource. +void SimpleDataSource::CancelInitialize() { + base::AutoLock auto_lock(lock_); + DCHECK(initialize_callback_.get()); + state_ = STOPPED; + initialize_callback_.reset(); + + // Post a task to the render thread to cancel loading the resource. render_loop_->PostTask(FROM_HERE, - NewRunnableMethod(this, &SimpleDataSource::StartTask)); + NewRunnableMethod(this, &SimpleDataSource::CancelTask)); } const media::MediaFormat& SimpleDataSource::media_format() { @@ -99,6 +139,8 @@ bool SimpleDataSource::IsStreaming() { return false; } +void SimpleDataSource::SetPreload(media::Preload preload) {} + void SimpleDataSource::SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader) { url_loader_.reset(mock_loader); keep_test_loader_ = true; @@ -141,7 +183,8 @@ void SimpleDataSource::didDownloadData( void SimpleDataSource::didReceiveData( WebKit::WebURLLoader* loader, const char* data, - int data_length) { + int data_length, + int encoded_data_length) { DCHECK(MessageLoop::current() == render_loop_); data_.append(data, data_length); } @@ -157,44 +200,56 @@ void SimpleDataSource::didFinishLoading( WebKit::WebURLLoader* loader, double finishTime) { DCHECK(MessageLoop::current() == render_loop_); - 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); + // Reference to prevent destruction while inside the |initialize_callback_| + // 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_); - 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); + // Reference to prevent destruction while inside the |initialize_callback_| + // 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() { @@ -210,44 +265,48 @@ void SimpleDataSource::Abort() { void SimpleDataSource::SetURL(const GURL& url) { url_ = url; media_format_.Clear(); - media_format_.SetAsString(media::MediaFormat::kMimeType, - media::mime_type::kApplicationOctetStream); media_format_.SetAsString(media::MediaFormat::kURL, url.spec()); } void SimpleDataSource::StartTask() { DCHECK(MessageLoop::current() == render_loop_); - 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()); - - // This flag is for unittests as we don't want to reset |url_loader| - if (!keep_test_loader_) - url_loader_.reset(frame_->createAssociatedURLLoader()); - - // Start the resource loading. - url_loader_->loadAsynchronously(request, this); + // Reference to prevent destruction while inside the |initialize_callback_| + // 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()); + + // This flag is for unittests as we don't want to reset |url_loader| + if (!keep_test_loader_) + url_loader_.reset(frame_->createAssociatedURLLoader()); + + // Start the resource loading. + url_loader_->loadAsynchronously(request, this); + } } } @@ -265,17 +324,29 @@ void SimpleDataSource::CancelTask() { 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(); + } + + scoped_ptr<media::PipelineStatusCallback> initialize_callback( + initialize_callback_.release()); + initialize_callback->Run(status); +} + +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)); - } else { - host()->SetError(media::PIPELINE_ERROR_NETWORK); } - initialize_callback_->Run(); - initialize_callback_.reset(); } } // namespace webkit_glue diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h index 10d93ff..2c4075a 100644 --- a/webkit/glue/media/simple_data_source.h +++ b/webkit/glue/media/simple_data_source.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -13,8 +13,9 @@ #include <algorithm> #include <string> +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.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" @@ -31,20 +32,26 @@ namespace webkit_glue { 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, + WebDataSourceBuildObserverHack* build_observer); + SimpleDataSource(MessageLoop* render_loop, WebKit::WebFrame* frame); virtual ~SimpleDataSource(); // media::Filter implementation. + virtual void set_host(media::FilterHost* host); virtual void Stop(media::FilterCallback* callback); // media::DataSource implementation. - virtual void Initialize(const std::string& url, - media::FilterCallback* callback); virtual const media::MediaFormat& media_format(); virtual void Read(int64 position, size_t size, uint8* data, ReadCallback* read_callback); virtual bool GetSize(int64* size_out); virtual bool IsStreaming(); + virtual void SetPreload(media::Preload preload); // Used to inject a mock used for unittests. virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader); @@ -67,7 +74,8 @@ class SimpleDataSource : public WebDataSource, virtual void didReceiveData( WebKit::WebURLLoader* loader, const char* data, - int dataLength); + int dataLength, + int encodedDataLength); virtual void didReceiveCachedMetadata( WebKit::WebURLLoader* loader, const char* data, int dataLength); @@ -79,6 +87,9 @@ class SimpleDataSource : public WebDataSource, const WebKit::WebURLError&); // webkit_glue::WebDataSource implementation. + virtual void Initialize(const std::string& url, + media::PipelineStatusCallback* callback); + virtual void CancelInitialize(); virtual bool HasSingleOrigin(); virtual void Abort(); @@ -95,6 +106,9 @@ class SimpleDataSource : public WebDataSource, // 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_; @@ -123,7 +137,7 @@ class SimpleDataSource : public WebDataSource, base::Lock lock_; // Filter callbacks. - scoped_ptr<media::FilterCallback> initialize_callback_; + scoped_ptr<media::PipelineStatusCallback> initialize_callback_; // Used to ensure mocks for unittests are used instead of reset in Start(). bool keep_test_loader_; diff --git a/webkit/glue/media/simple_data_source_unittest.cc b/webkit/glue/media/simple_data_source_unittest.cc index 977807b..61e41f2 100644 --- a/webkit/glue/media/simple_data_source_unittest.cc +++ b/webkit/glue/media/simple_data_source_unittest.cc @@ -59,7 +59,7 @@ class SimpleDataSourceTest : public testing::Test { } void InitializeDataSource(const char* url, - media::MockCallback* callback) { + media::MockStatusCallback* callback) { gurl_ = GURL(url); frame_.reset(new NiceMock<MockWebFrame>()); @@ -86,7 +86,7 @@ class SimpleDataSourceTest : public testing::Test { EXPECT_EQ(kDataSize, size); for (int i = 0; i < kDataSize; ++i) { - data_source_->didReceiveData(NULL, data_ + i, 1); + data_source_->didReceiveData(NULL, data_ + i, 1, 1); } EXPECT_CALL(host_, SetLoaded(is_loaded)); @@ -103,7 +103,6 @@ class SimpleDataSourceTest : public testing::Test { void RequestFailed() { InSequence s; - EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); WebURLError error; error.reason = net::ERR_FAILED; @@ -158,19 +157,22 @@ class SimpleDataSourceTest : public testing::Test { }; TEST_F(SimpleDataSourceTest, InitializeHTTP) { - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); RequestSucceeded(false); DestroyDataSource(); } TEST_F(SimpleDataSourceTest, InitializeHTTPS) { - InitializeDataSource(kHttpsUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpsUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); RequestSucceeded(false); DestroyDataSource(); } TEST_F(SimpleDataSourceTest, InitializeFile) { - InitializeDataSource(kFileUrl, media::NewExpectedCallback()); + InitializeDataSource(kFileUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); RequestSucceeded(true); DestroyDataSource(); } @@ -181,8 +183,6 @@ TEST_F(SimpleDataSourceTest, InitializeData) { data_source_ = new SimpleDataSource(MessageLoop::current(), frame_.get()); - EXPECT_TRUE(data_source_->IsUrlSupported(kDataUrl)); - // There is no need to provide a message loop to data source. data_source_->set_host(&host_); data_source_->SetURLLoaderForTest(url_loader_); @@ -191,14 +191,16 @@ TEST_F(SimpleDataSourceTest, InitializeData) { EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded))); EXPECT_CALL(host_, SetBufferedBytes(sizeof(kDataUrlDecoded))); - data_source_->Initialize(kDataUrl, media::NewExpectedCallback()); + data_source_->Initialize(kDataUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); MessageLoop::current()->RunAllPending(); DestroyDataSource(); } TEST_F(SimpleDataSourceTest, RequestFailed) { - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_ERROR_NETWORK)); RequestFailed(); DestroyDataSource(); } @@ -206,8 +208,8 @@ TEST_F(SimpleDataSourceTest, RequestFailed) { TEST_F(SimpleDataSourceTest, StopWhenDownloading) { // The callback should be deleted, but not executed. // TODO(scherkus): should this really be the behaviour? Seems strange... - StrictMock<media::MockCallback>* callback = - new StrictMock<media::MockCallback>(); + StrictMock<media::MockStatusCallback>* callback = + new StrictMock<media::MockStatusCallback>(); EXPECT_CALL(*callback, Destructor()); InitializeDataSource(kHttpUrl, callback); @@ -217,7 +219,8 @@ TEST_F(SimpleDataSourceTest, StopWhenDownloading) { } TEST_F(SimpleDataSourceTest, AsyncRead) { - InitializeDataSource(kFileUrl, media::NewExpectedCallback()); + InitializeDataSource(kFileUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); RequestSucceeded(true); AsyncRead(); DestroyDataSource(); @@ -228,20 +231,23 @@ TEST_F(SimpleDataSourceTest, AsyncRead) { // is fixed. TEST_F(SimpleDataSourceTest, HasSingleOrigin) { // Make sure no redirect case works as expected. - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); RequestSucceeded(false); EXPECT_TRUE(data_source_->HasSingleOrigin()); DestroyDataSource(); // Test redirect to the same domain. - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); Redirect(kHttpRedirectToSameDomainUrl1); RequestSucceeded(false); EXPECT_TRUE(data_source_->HasSingleOrigin()); DestroyDataSource(); // Test redirect twice to the same domain. - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); Redirect(kHttpRedirectToSameDomainUrl1); Redirect(kHttpRedirectToSameDomainUrl2); RequestSucceeded(false); @@ -249,14 +255,16 @@ TEST_F(SimpleDataSourceTest, HasSingleOrigin) { DestroyDataSource(); // Test redirect to a different domain. - InitializeDataSource(kHttpUrl, media::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(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::NewExpectedCallback()); + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCallback(media::PIPELINE_OK)); Redirect(kHttpRedirectToSameDomainUrl1); Redirect(kHttpRedirectToDifferentDomainUrl1); RequestSucceeded(false); diff --git a/webkit/glue/media/video_renderer_impl.cc b/webkit/glue/media/video_renderer_impl.cc index 3bbd626..5e159fe 100644 --- a/webkit/glue/media/video_renderer_impl.cc +++ b/webkit/glue/media/video_renderer_impl.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -49,7 +49,7 @@ void VideoRendererImpl::SetRect(const gfx::Rect& rect) { } // This method is always called on the renderer's thread. -void VideoRendererImpl::Paint(skia::PlatformCanvas* canvas, +void VideoRendererImpl::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) { scoped_refptr<media::VideoFrame> video_frame; GetCurrentFrame(&video_frame); @@ -98,7 +98,7 @@ void VideoRendererImpl::PutCurrentFrame( // 4. Canvas is opaque. // TODO(hclam): The fast paint method should support flipping and mirroring. // Disable the flipping and mirroring checks once we have it. -bool VideoRendererImpl::CanFastPaint(skia::PlatformCanvas* canvas, +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 @@ -152,7 +152,7 @@ bool VideoRendererImpl::CanFastPaint(skia::PlatformCanvas* canvas, } void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame, - skia::PlatformCanvas* canvas, + SkCanvas* canvas, const gfx::Rect& dest_rect) { // 1. Convert YUV frame to RGB. base::TimeDelta timestamp = video_frame->GetTimestamp(); @@ -199,7 +199,7 @@ void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame, } void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame, - skia::PlatformCanvas* canvas, + SkCanvas* canvas, const gfx::Rect& dest_rect) { DCHECK(video_frame->format() == media::VideoFrame::YV12 || video_frame->format() == media::VideoFrame::YV16); diff --git a/webkit/glue/media/video_renderer_impl.h b/webkit/glue/media/video_renderer_impl.h index b73c099..b14038b 100644 --- a/webkit/glue/media/video_renderer_impl.h +++ b/webkit/glue/media/video_renderer_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. // @@ -30,7 +30,7 @@ class VideoRendererImpl : public WebVideoRenderer { // WebVideoRenderer implementation. virtual void SetWebMediaPlayerImplProxy(WebMediaPlayerImpl::Proxy* proxy); virtual void SetRect(const gfx::Rect& rect); - virtual void Paint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect); + virtual void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect); virtual void GetCurrentFrame(scoped_refptr<media::VideoFrame>* frame_out); virtual void PutCurrentFrame(scoped_refptr<media::VideoFrame> frame); @@ -47,18 +47,18 @@ class VideoRendererImpl : public WebVideoRenderer { private: // Determine the conditions to perform fast paint. Returns true if we can do // fast paint otherwise false. - bool CanFastPaint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect); + 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, - skia::PlatformCanvas* canvas, + 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, - skia::PlatformCanvas* canvas, + SkCanvas* canvas, const gfx::Rect& dest_rect); void TransformToSkIRect(const SkMatrix& matrix, const gfx::Rect& src_rect, diff --git a/webkit/glue/media/web_data_source.h b/webkit/glue/media/web_data_source.h index 956ac9e..25f1258 100644 --- a/webkit/glue/media/web_data_source.h +++ b/webkit/glue/media/web_data_source.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -6,6 +6,7 @@ #define WEBKIT_GLUE_MEDIA_WEB_DATA_SOURCE_H_ #include "media/base/filters.h" +#include "media/base/pipeline_status.h" namespace webkit_glue { @@ -16,6 +17,18 @@ class WebDataSource : public media::DataSource { WebDataSource(); virtual ~WebDataSource(); + // Initialize this object using |url|. This object calls |callback| when + // initialization has completed. + virtual void Initialize(const std::string& url, + media::PipelineStatusCallback* 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. @@ -31,6 +44,15 @@ class WebDataSource : public media::DataSource { 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 Callback1<WebDataSource*>::Type WebDataSourceBuildObserverHack; + } // namespace webkit_glue #endif // WEBKIT_GLUE_MEDIA_WEB_DATA_SOURCE_H_ diff --git a/webkit/glue/media/web_data_source_factory.cc b/webkit/glue/media/web_data_source_factory.cc new file mode 100644 index 0000000..d98e2da --- /dev/null +++ b/webkit/glue/media/web_data_source_factory.cc @@ -0,0 +1,103 @@ +// 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/glue/media/web_data_source_factory.h" + +#include "base/logging.h" + +namespace webkit_glue { + +class WebDataSourceFactory::BuildRequest + : public media::AsyncDataSourceFactoryBase::BuildRequest { + public: + BuildRequest(const std::string& url, BuildCallback* callback, + WebDataSource* data_source, + 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, + FactoryFunction factory_function, + WebDataSourceBuildObserverHack* build_observer) + : render_loop_(render_loop), + frame_(frame), + factory_function_(factory_function), + build_observer_(build_observer) { + DCHECK(render_loop_); + DCHECK(frame_); + DCHECK(factory_function_); +} + +WebDataSourceFactory::~WebDataSourceFactory() {} + +media::DataSourceFactory* WebDataSourceFactory::Clone() const { + return new WebDataSourceFactory(render_loop_, frame_, factory_function_, + build_observer_); +} + +bool WebDataSourceFactory::AllowRequests() const { + return true; +} + +media::AsyncDataSourceFactoryBase::BuildRequest* +WebDataSourceFactory::CreateRequest(const std::string& url, + BuildCallback* callback) { + WebDataSource* data_source = factory_function_(render_loop_, frame_); + + return new WebDataSourceFactory::BuildRequest(url, callback, data_source, + build_observer_); +} + +WebDataSourceFactory::BuildRequest::BuildRequest( + const std::string& url, + BuildCallback* callback, + WebDataSource* data_source, + 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(), NewCallback(this, &BuildRequest::InitDone)); +} + +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_ && 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_glue diff --git a/webkit/glue/media/web_data_source_factory.h b/webkit/glue/media/web_data_source_factory.h new file mode 100644 index 0000000..7163ef4 --- /dev/null +++ b/webkit/glue/media/web_data_source_factory.h @@ -0,0 +1,51 @@ +// 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_GLUE_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ +#define WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ + +#include "media/base/async_filter_factory_base.h" +#include "webkit/glue/media/web_data_source.h" + +class MessageLoop; + +namespace WebKit { +class WebFrame; +} + +namespace webkit_glue { + +class WebDataSourceFactory : public media::AsyncDataSourceFactoryBase { + public: + typedef WebDataSource* (*FactoryFunction)(MessageLoop* render_loop, + WebKit::WebFrame* frame); + + WebDataSourceFactory(MessageLoop* render_loop, WebKit::WebFrame* frame, + FactoryFunction factory_function, + WebDataSourceBuildObserverHack* build_observer); + virtual ~WebDataSourceFactory(); + + // DataSourceFactory method. + virtual media::DataSourceFactory* Clone() const; + + protected: + // AsyncDataSourceFactoryBase methods. + virtual bool AllowRequests() const; + virtual AsyncDataSourceFactoryBase::BuildRequest* CreateRequest( + const std::string& url, BuildCallback* callback); + + private: + class BuildRequest; + + MessageLoop* render_loop_; + WebKit::WebFrame* frame_; + FactoryFunction factory_function_; + WebDataSourceBuildObserverHack* build_observer_; + + DISALLOW_COPY_AND_ASSIGN(WebDataSourceFactory); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ diff --git a/webkit/glue/media/web_video_renderer.h b/webkit/glue/media/web_video_renderer.h index efd3109..9e1eed5 100644 --- a/webkit/glue/media/web_video_renderer.h +++ b/webkit/glue/media/web_video_renderer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -32,7 +32,7 @@ class WebVideoRenderer : public media::VideoRendererBase { // |dest_rect|. // // Method called on the render thread. - virtual void Paint(skia::PlatformCanvas* canvas, + virtual void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) = 0; // Clients of this class (painter/compositor) should use GetCurrentFrame() |