Index: chrome/renderer/media/buffered_data_source.cc |
=================================================================== |
--- chrome/renderer/media/buffered_data_source.cc (revision 0) |
+++ chrome/renderer/media/buffered_data_source.cc (revision 0) |
@@ -0,0 +1,635 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
+// source code is governed by a BSD-style license that can be found in the |
+// LICENSE file. |
+ |
+#include "base/compiler_specific.h" |
+#include "base/message_loop.h" |
+#include "base/process_util.h" |
+#include "base/string_util.h" |
+#include "chrome/common/extensions/url_pattern.h" |
+#include "chrome/renderer/media/buffered_data_source.h" |
+#include "chrome/renderer/render_view.h" |
+#include "chrome/renderer/webmediaplayer_delegate_impl.h" |
+#include "chrome/renderer/render_thread.h" |
+#include "media/base/filter_host.h" |
+#include "net/base/load_flags.h" |
+#include "net/base/net_errors.h" |
+#include "net/http/http_response_headers.h" |
+#include "webkit/glue/webappcachecontext.h" |
+ |
+namespace { |
+ |
+const char kHttpScheme[] = "http"; |
+const char kHttpsScheme[] = "https"; |
+const int64 kPositionNotSpecified = -1; |
+const int kHttpOK = 200; |
+const int kHttpPartialContent = 206; |
+ |
+// A helper method that accepts only HTTP, HTTPS and FILE protocol. |
+bool IsSchemeSupported(const GURL& url) { |
+ return url.SchemeIs(kHttpScheme) || |
+ url.SchemeIs(kHttpsScheme) || |
+ url.SchemeIsFile(); |
+} |
+ |
+} // namespace |
+ |
+///////////////////////////////////////////////////////////////////////////// |
+// BufferedResourceLoader |
+BufferedResourceLoader::BufferedResourceLoader(int route_id, |
+ const GURL& url, |
+ int64 first_byte_position, |
+ int64 last_byte_position) |
+ : start_callback_(NULL), |
+ bridge_(NULL), |
+ offset_(0), |
+ content_length_(kPositionNotSpecified), |
+ buffered_bytes_(0), |
+ buffer_limit_(10240000), // By default 10MB. |
+ buffer_event_(false, false), |
+ deferred_(false), |
+ stopped_(false), |
+ completed_(false), |
+ range_requested_(false), |
+ async_start_(false), |
+ route_id_(route_id), |
+ url_(url), |
+ first_byte_position_(first_byte_position), |
+ last_byte_position_(last_byte_position), |
+ render_loop_(RenderThread::current()->message_loop()) { |
+} |
+ |
+BufferedResourceLoader::~BufferedResourceLoader() { |
+ for (size_t i = 0; i < buffers_.size(); ++i) |
+ delete buffers_[i]; |
+ buffers_.clear(); |
+} |
+ |
+int BufferedResourceLoader::Start(net::CompletionCallback* start_callback) { |
+ // Make sure we only start no more than once. |
+ DCHECK(!bridge_.get()); |
+ DCHECK(!start_callback_.get()); |
+ start_callback_.reset(start_callback); |
+ |
+ // Save the information that we are doing an asynchronous start since |
+ // start_callback_ may get reset, we can't rely on it. |
+ if (start_callback_.get()) |
+ async_start_ = true; |
+ |
+ std::string header; |
+ if (first_byte_position_ != kPositionNotSpecified && |
+ last_byte_position_ != kPositionNotSpecified) { |
+ header = StringPrintf("Range: bytes=%lld-%lld", |
+ first_byte_position_, |
+ last_byte_position_); |
+ range_requested_ = true; |
+ offset_ = first_byte_position_; |
+ } else if (first_byte_position_ != kPositionNotSpecified) { |
+ header = StringPrintf("Range: bytes=%lld-", first_byte_position_); |
+ range_requested_ = true; |
+ offset_ = first_byte_position_; |
+ } else if (last_byte_position_ != kPositionNotSpecified) { |
+ NOTIMPLEMENTED() << "Suffix length range request not implemented."; |
+ } |
+ |
+ bridge_.reset(RenderThread::current()->resource_dispatcher()->CreateBridge( |
+ "GET", |
+ GURL(url_), |
+ GURL(url_), |
+ GURL(), // TODO(hclam): provide referer here. |
+ "null", // TODO(abarth): provide frame_origin |
+ "null", // TODO(abarth): provide main_frame_origin |
+ header, |
+ net::LOAD_BYPASS_CACHE, |
+ base::GetCurrentProcId(), |
+ ResourceType::MEDIA, |
+ 0, |
+ // TODO(michaeln): delegate->mediaplayer->frame-> |
+ // app_cache_context()->context_id() |
+ // For now don't service media resource requests from the appcache. |
+ WebAppCacheContext::kNoAppCacheContextId, |
+ route_id_)); |
+ |
+ // We may receive stop signal while we are inside this method, it's because |
+ // Start() may get called on demuxer thread while Stop() is called on |
+ // pipeline thread, so we want to protect the posting of OnStart() task |
+ // with a lock. |
+ bool task_posted = false; |
+ { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ task_posted = true; |
+ render_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, &BufferedResourceLoader::OnStart)); |
+ } |
+ } |
+ |
+ // Wait for response to arrive we don't have an async start. |
+ if (task_posted && !async_start_) |
+ buffer_event_.Wait(); |
+ |
+ { |
+ AutoLock auto_lock(lock_); |
+ // We may have stopped because of a bad response from the server. |
+ if (stopped_) |
+ return net::ERR_ABORTED; |
+ else if (completed_) |
+ return net::ERR_FAILED; |
+ else if (async_start_) |
+ return net::ERR_IO_PENDING; |
+ return net::OK; |
+ } |
+} |
+ |
+void BufferedResourceLoader::Stop() { |
+ { |
+ AutoLock auto_lock(lock_); |
+ stopped_ = true; |
+ } |
+ |
+ // Wakes up the waiting thread so they can catch the stop signal. |
+ render_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, &BufferedResourceLoader::OnDestroy)); |
+ buffer_event_.Signal(); |
+} |
+ |
+size_t BufferedResourceLoader::Read(uint8* data, size_t size) { |
+ size_t taken = 0; |
+ while (taken < size) { |
+ Buffer* buffer = NULL; |
+ { |
+ AutoLock auto_lock(lock_); |
+ if (stopped_) |
+ break; |
+ if (!buffers_.empty()) |
+ buffer = buffers_.front(); |
+ else if (completed_) |
+ break; |
+ } |
+ if (buffer) { |
+ size_t copy = std::min(size - taken, buffer->size - buffer->taken); |
+ memcpy(data + taken, buffer->data.get() + buffer->taken, copy); |
+ taken += copy; |
+ buffer->taken += copy; |
+ if (buffer->taken == buffer->size) { |
+ // The buffer has been consumed, remove it. |
+ { |
+ AutoLock auto_lock(lock_); |
+ buffers_.pop_front(); |
+ } |
+ delete buffer; |
+ } |
+ } else { |
+ buffer_event_.Wait(); |
+ } |
+ } |
+ if (taken > 0) { |
+ offset_ += taken; |
+ { |
+ AutoLock auto_lock(lock_); |
+ buffered_bytes_ -= taken; |
+ DCHECK(buffered_bytes_ >= 0); |
+ } |
+ if (ShouldDisableDefer()) { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ render_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, |
+ &BufferedResourceLoader::OnDisableDeferLoading)); |
+ } |
+ } |
+ } |
+ return taken; |
+} |
+ |
+bool BufferedResourceLoader::SeekForward(int64 position) { |
+ // Use of |offset_| is safe without a lock, because it's modified only in |
+ // Read() and this method after Start(). Read() and SeekForward() happens |
+ // on the same thread. |
+ // Seeking backward. |
+ if (position < offset_) |
+ return false; |
+ // Done seeking forward. |
+ else if (position == offset_) |
+ return true; |
+ |
+ while(true) { |
+ { |
+ AutoLock auto_lock(lock_); |
+ // Loader has stopped. |
+ if (stopped_) |
+ return false; |
+ // Seek position exceeds bufferable range, buffer_limit_ can be changed. |
+ if (position >= offset_ + buffer_limit_) |
+ return false; |
+ // Response completed and seek position exceeds buffered range. |
+ if (completed_ && position >= offset_ + buffered_bytes_) |
+ return false; |
+ |
+ if (!buffers_.empty()) { |
+ Buffer* buffer = buffers_.front(); |
+ int64 bytes_to_take = position - offset_; |
+ if (!buffers_.empty()) { |
+ size_t available_bytes_in_buffer = buffer->size - buffer->taken; |
+ size_t taken = 0; |
+ if (available_bytes_in_buffer <= bytes_to_take) { |
+ taken = available_bytes_in_buffer; |
+ buffers_.pop_front(); |
+ delete buffer; |
+ } else { |
+ taken = static_cast<size_t>(bytes_to_take); |
+ buffer->taken += taken; |
+ } |
+ offset_ += taken; |
+ if (bytes_to_take == taken) |
+ return true; |
+ } |
+ continue; |
+ } |
+ } |
+ buffer_event_.Wait(); |
+ } |
+} |
+ |
+int64 BufferedResourceLoader::GetOffset() { |
+ AutoLock auto_lock(lock_); |
+ return offset_; |
+} |
+ |
+int64 BufferedResourceLoader::GetBufferLimit() { |
+ AutoLock auto_lock(lock_); |
+ return buffer_limit_; |
+} |
+ |
+void BufferedResourceLoader::SetBufferLimit(size_t buffer_limit) { |
+ { |
+ AutoLock auto_lock(lock_); |
+ buffer_limit_ = buffer_limit; |
+ } |
+ |
+ if (ShouldDisableDefer()) { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ render_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, |
+ &BufferedResourceLoader::OnDisableDeferLoading)); |
+ } |
+ } |
+ if (ShouldEnableDefer()) { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ render_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, |
+ &BufferedResourceLoader::OnEnableDeferLoading)); |
+ } |
+ } |
+} |
+ |
+size_t BufferedResourceLoader::GetTimeout() { |
+ // TODO(hclam): implement. |
+ return 0; |
+} |
+ |
+void BufferedResourceLoader::SetTimeout(size_t milliseconds) { |
+ // TODO(hclam): implement. |
+} |
+ |
+///////////////////////////////////////////////////////////////////////////// |
+// BufferedResourceLoader, |
+// webkit_glue::ResourceLoaderBridge::Peer implementations |
+void BufferedResourceLoader::OnReceivedRedirect(const GURL& new_url) { |
+ url_ = new_url; |
+ |
+ // If we got redirected to an unsupported protocol then stop. |
+ if (!IsSchemeSupported(new_url)) |
+ Stop(); |
+} |
+ |
+void BufferedResourceLoader::OnReceivedResponse( |
+ const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, |
+ bool content_filtered) { |
+ int64 first_byte_position = -1; |
+ int64 last_byte_position = -1; |
+ int64 instance_size = -1; |
+ |
+ // The file:// protocol should be able to serve any request we want, so we |
+ // take an exception for file protocol. |
+ if (!url_.SchemeIsFile()) { |
+ if (!info.headers) { |
+ // We expect to receive headers because this is a HTTP or HTTPS protocol, |
+ // if not report failure. |
+ InvokeAndResetStartCallback(net::ERR_INVALID_RESPONSE); |
+ Stop(); |
+ return; |
+ } else if (range_requested_) { |
+ if (info.headers->response_code() != kHttpPartialContent || |
+ !info.headers->GetContentRange(&first_byte_position, |
+ &last_byte_position, |
+ &instance_size)) { |
+ // We requested a range, but server didn't reply with partial content or |
+ // the "Content-Range" header is corrupted. |
+ InvokeAndResetStartCallback(net::ERR_INVALID_RESPONSE); |
+ Stop(); |
+ return; |
+ } |
+ } else if (info.headers->response_code() != kHttpOK) { |
+ // We didn't request a range but server didn't reply with "200 OK". |
+ InvokeAndResetStartCallback(net::ERR_FAILED); |
+ Stop(); |
+ return; |
+ } |
+ } |
+ |
+ { |
+ AutoLock auto_lock(lock_); |
+ // |info.content_length| can be -1, in that case |content_length_| is |
+ // not specified and this is a streaming response. |
+ content_length_ = info.content_length; |
+ // We only care about the first byte position if it's given by the server. |
+ if (first_byte_position != kPositionNotSpecified) |
+ offset_ = first_byte_position; |
+ } |
+ |
+ // If we have started asynchronously we just need to invoke the callback or |
+ // we need to signal the Start() method to wake up. |
+ if (async_start_) |
+ InvokeAndResetStartCallback(net::OK); |
+ else |
+ buffer_event_.Signal(); |
+} |
+ |
+void BufferedResourceLoader::OnReceivedData(const char* data, int len) { |
+ DCHECK(bridge_.get()); |
+ |
+ AppendToBuffer(reinterpret_cast<const uint8*>(data), len); |
+ if (ShouldEnableDefer()) |
+ bridge_->SetDefersLoading(true); |
+} |
+ |
+void BufferedResourceLoader::OnCompletedRequest( |
+ const URLRequestStatus& status, const std::string& security_info) { |
+ SignalComplete(); |
+ // After the response has completed, we don't need the bridge any more. |
+ bridge_.reset(); |
+ |
+ if (async_start_) |
+ InvokeAndResetStartCallback(status.os_error()); |
+} |
+ |
+///////////////////////////////////////////////////////////////////////////// |
+// BufferedResourceLoader, private |
+void BufferedResourceLoader::AppendToBuffer(const uint8* data, size_t size) { |
+ Buffer* buffer = new Buffer(size); |
+ memcpy(buffer->data.get(), data, size); |
+ { |
+ AutoLock auto_lock(lock_); |
+ buffers_.push_back(buffer); |
+ buffered_bytes_ += size; |
+ } |
+ buffer_event_.Signal(); |
+} |
+ |
+void BufferedResourceLoader::SignalComplete() { |
+ { |
+ AutoLock auto_lock(lock_); |
+ completed_ = true; |
+ } |
+ buffer_event_.Signal(); |
+} |
+ |
+bool BufferedResourceLoader::ShouldEnableDefer() { |
+ AutoLock auto_lock(lock_); |
+ if (deferred_) { |
+ return false; |
+ } else if (buffered_bytes_ >= buffer_limit_) { |
+ deferred_ = true; |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool BufferedResourceLoader::ShouldDisableDefer() { |
+ AutoLock auto_lock(lock_); |
+ if (deferred_ && buffered_bytes_ < buffer_limit_ / 2) |
+ return true; |
+ else |
+ return false; |
+} |
+ |
+void BufferedResourceLoader::OnStart() { |
+ DCHECK(MessageLoop::current() == render_loop_); |
+ DCHECK(bridge_.get()); |
+ bridge_->Start(this); |
+} |
+ |
+void BufferedResourceLoader::OnDestroy() { |
+ DCHECK(MessageLoop::current() == render_loop_); |
+ bridge_.reset(); |
+} |
+ |
+void BufferedResourceLoader::OnEnableDeferLoading() { |
+ DCHECK(MessageLoop::current() == render_loop_); |
+ // This message may arrive after the bridge is destroyed. |
+ if (bridge_.get()) |
+ bridge_->SetDefersLoading(true); |
+} |
+ |
+void BufferedResourceLoader::OnDisableDeferLoading() { |
+ DCHECK(MessageLoop::current() == render_loop_); |
+ // This message may arrive after the bridge is destroyed. |
+ if (bridge_.get()) |
+ bridge_->SetDefersLoading(false); |
+} |
+ |
+void BufferedResourceLoader::InvokeAndResetStartCallback(int error) { |
+ AutoLock auto_lock(lock_); |
+ if (start_callback_.get()) { |
+ start_callback_->Run(error); |
+ start_callback_.reset(); |
+ } |
+} |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+// BufferedDataSource |
+BufferedDataSource::BufferedDataSource(WebMediaPlayerDelegateImpl* delegate) |
+ : delegate_(delegate), |
+ stopped_(false), |
+ position_(0), |
+ total_bytes_(kPositionNotSpecified), |
+ buffered_resource_loader_(NULL), |
+ pipeline_loop_(MessageLoop::current()) { |
+} |
+ |
+BufferedDataSource::~BufferedDataSource() { |
+} |
+ |
+void BufferedDataSource::Stop() { |
+ scoped_refptr<BufferedResourceLoader> resource_loader = NULL; |
+ // Set the stop signal first. |
+ { |
+ AutoLock auto_lock(lock_); |
+ stopped_ = true; |
+ resource_loader = buffered_resource_loader_; |
+ // Release the reference to the resource loader. |
+ buffered_resource_loader_ = NULL; |
+ } |
+ // Tell the loader to stop. |
+ if (resource_loader) |
+ resource_loader->Stop(); |
+} |
+ |
+bool BufferedDataSource::Initialize(const std::string& url) { |
+ // Save the url. |
+ url_ = GURL(url); |
+ |
+ // Make sure we support the scheme of the URL. |
+ if (!IsSchemeSupported(url_)) { |
+ host_->Error(media::PIPELINE_ERROR_NETWORK); |
+ return false; |
+ } |
+ |
+ media_format_.SetAsString(media::MediaFormat::kMimeType, |
+ media::mime_type::kApplicationOctetStream); |
+ media_format_.SetAsString(media::MediaFormat::kURL, url); |
+ |
+ // Setup the BufferedResourceLoader here. |
+ scoped_refptr<BufferedResourceLoader> resource_loader = NULL; |
+ { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ buffered_resource_loader_ = new BufferedResourceLoader( |
+ delegate_->view()->routing_id(), |
+ url_, kPositionNotSpecified, kPositionNotSpecified); |
+ resource_loader = buffered_resource_loader_; |
+ } |
+ } |
+ |
+ // Use the local reference to start the request. |
+ if (resource_loader) { |
+ if (net::ERR_IO_PENDING != resource_loader->Start( |
+ NewCallback(this, &BufferedDataSource::InitialRequestStarted))) { |
+ host_->Error(media::PIPELINE_ERROR_NETWORK); |
+ return false; |
+ } |
+ return true; |
+ } |
+ host_->Error(media::PIPELINE_ERROR_NETWORK); |
+ return false; |
+} |
+ |
+size_t BufferedDataSource::Read(uint8* data, size_t size) { |
+ // We try two times here: |
+ // 1. Use the existing resource loader to seek forward and read from it. |
+ // 2. If any of the above operations failed, we create a new resource loader |
+ // starting with a new range. Goto 1. |
+ // TODO(hclam): change the logic here to do connection recovery and allow a |
+ // maximum of trials. |
+ for (int trials = 2; trials > 0; --trials) { |
+ scoped_refptr<BufferedResourceLoader> resource_loader = NULL; |
+ { |
+ AutoLock auto_lock(lock_); |
+ resource_loader = buffered_resource_loader_; |
+ } |
+ |
+ if (resource_loader && resource_loader->SeekForward(position_)) { |
+ size_t read = resource_loader->Read(data, size); |
+ if (read >= 0) { |
+ position_ += read; |
+ return read; |
+ } else { |
+ return DataSource::kReadError; |
+ } |
+ } else { |
+ // We enter here because the current resource loader cannot serve the |
+ // range requested, we will need to create a new request for doing it. |
+ scoped_refptr<BufferedResourceLoader> old_resource_loader = NULL; |
+ { |
+ AutoLock auto_lock(lock_); |
+ if (stopped_) |
+ return DataSource::kReadError; |
+ old_resource_loader = buffered_resource_loader_; |
+ buffered_resource_loader_ = |
+ new BufferedResourceLoader(delegate_->view()->routing_id(), |
+ url_, position_, |
+ kPositionNotSpecified); |
+ resource_loader = buffered_resource_loader_; |
+ } |
+ if (old_resource_loader) |
+ old_resource_loader->Stop(); |
+ if (resource_loader) { |
+ if (net::OK != resource_loader->Start(NULL)) { |
+ // We have started a new request but failed, report that. |
+ // TODO(hclam): should allow some retry mechanism here. |
+ HandleError(media::PIPELINE_ERROR_NETWORK); |
+ return DataSource::kReadError; |
+ } |
+ } |
+ } |
+ } |
+ return DataSource::kReadError; |
+} |
+ |
+bool BufferedDataSource::GetPosition(int64* position_out) { |
+ *position_out = position_; |
+ return true; |
+} |
+ |
+bool BufferedDataSource::SetPosition(int64 position) { |
+ position_ = position; |
+ return true; |
+} |
+ |
+bool BufferedDataSource::GetSize(int64* size_out) { |
+ if (total_bytes_ != kPositionNotSpecified) { |
+ *size_out = total_bytes_; |
+ return true; |
+ } |
+ *size_out = 0; |
+ return false; |
+} |
+ |
+bool BufferedDataSource::IsSeekable() { |
+ return total_bytes_ != kPositionNotSpecified; |
+} |
+ |
+void BufferedDataSource::HandleError(media::PipelineError error) { |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ host_->Error(error); |
+ } |
+} |
+ |
+void BufferedDataSource::InitialRequestStarted(int error) { |
+ // Don't take any lock and call to |host_| here, this method is called from |
+ // BufferedResourceLoader after the response has started or failed, it is |
+ // very likely we are called within a lock in BufferedResourceLoader. |
+ // Acquiring an additional lock here we might have a deadlock situation, |
+ // but one thing very sure is that pipeline thread is still alive, so we |
+ // just need to post a task on that thread. |
+ total_bytes_ = buffered_resource_loader_->content_length(); |
+ pipeline_loop_->PostTask(FROM_HERE, |
+ NewRunnableMethod(this, |
+ &BufferedDataSource::OnInitialRequestStarted, error)); |
+} |
+ |
+void BufferedDataSource::OnInitialRequestStarted(int error) { |
+ // Acquiring a lock should not be needed because stopped_ is only written |
+ // on pipeline thread and we are on pipeline thread but just to be safe. |
+ AutoLock auto_lock(lock_); |
+ if (!stopped_) { |
+ if (error == net::OK) { |
+ if (IsSeekable()) { |
+ host_->SetTotalBytes(total_bytes_); |
+ // TODO(hclam): report the amount of bytes buffered accurately. |
+ host_->SetBufferedBytes(total_bytes_); |
+ } |
+ host_->InitializationComplete(); |
+ } else { |
+ host_->Error(media::PIPELINE_ERROR_NETWORK); |
+ } |
+ } |
+} |
+ |
+const media::MediaFormat& BufferedDataSource::media_format() { |
+ return media_format_; |
+} |