| Index: media/blink/resource_multibuffer_data_provider.cc
|
| diff --git a/media/blink/resource_multibuffer_data_provider.cc b/media/blink/resource_multibuffer_data_provider.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..78bd2a0c22e48a26324acbca1022310514b50664
|
| --- /dev/null
|
| +++ b/media/blink/resource_multibuffer_data_provider.cc
|
| @@ -0,0 +1,480 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "media/blink/resource_multibuffer_data_provider.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bits.h"
|
| +#include "base/callback_helpers.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/metrics/histogram.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "media/blink/active_loader.h"
|
| +#include "media/blink/cache_util.h"
|
| +#include "media/blink/media_blink_export.h"
|
| +#include "media/blink/url_index.h"
|
| +#include "net/http/http_byte_range.h"
|
| +#include "net/http/http_request_headers.h"
|
| +#include "third_party/WebKit/public/platform/WebURLError.h"
|
| +#include "third_party/WebKit/public/platform/WebURLResponse.h"
|
| +
|
| +using blink::WebFrame;
|
| +using blink::WebString;
|
| +using blink::WebURLError;
|
| +using blink::WebURLLoader;
|
| +using blink::WebURLLoaderOptions;
|
| +using blink::WebURLRequest;
|
| +using blink::WebURLResponse;
|
| +
|
| +namespace media {
|
| +
|
| +// The number of milliseconds to wait before retrying a failed load.
|
| +const int kLoaderFailedRetryDelayMs = 250;
|
| +
|
| +const int kHttpOK = 200;
|
| +const int kHttpPartialContent = 206;
|
| +const int kHttpRangeNotSatisfiable = 416;
|
| +const int kMaxRetries = 3;
|
| +
|
| +ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
|
| + UrlData* url_data,
|
| + MultiBufferBlockId pos)
|
| + : pos_(pos),
|
| + url_data_(url_data),
|
| + retries_(0),
|
| + cors_mode_(url_data->cors_mode()),
|
| + origin_(url_data->url().GetOrigin()),
|
| + weak_factory_(this) {
|
| + DCHECK(url_data_) << " pos = " << pos;
|
| + DCHECK_GE(pos, 0);
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::Start() {
|
| + // In the case of a re-start, throw away any half-finished blocks.
|
| + fifo_.clear();
|
| + // Prepare the request.
|
| + WebURLRequest request(url_data_->url());
|
| + // TODO(mkwst): Split this into video/audio.
|
| + request.setRequestContext(WebURLRequest::RequestContextVideo);
|
| +
|
| + request.setHTTPHeaderField(
|
| + WebString::fromUTF8(net::HttpRequestHeaders::kRange),
|
| + WebString::fromUTF8(
|
| + net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue()));
|
| +
|
| + url_data_->frame()->setReferrerForRequest(request, blink::WebURL());
|
| +
|
| + // Disable compression, compression for audio/video doesn't make sense...
|
| + request.setHTTPHeaderField(
|
| + WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
|
| + WebString::fromUTF8("identity;q=1, *;q=0"));
|
| +
|
| + // Check for our test WebURLLoader.
|
| + scoped_ptr<WebURLLoader> loader;
|
| + if (test_loader_) {
|
| + loader = test_loader_.Pass();
|
| + } else {
|
| + WebURLLoaderOptions options;
|
| + if (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) {
|
| + options.allowCredentials = true;
|
| + options.crossOriginRequestPolicy =
|
| + WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
|
| + } else {
|
| + options.exposeAllResponseHeaders = true;
|
| + // The author header set is empty, no preflight should go ahead.
|
| + options.preflightPolicy = WebURLLoaderOptions::PreventPreflight;
|
| + options.crossOriginRequestPolicy =
|
| + WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
|
| + if (url_data_->cors_mode() == UrlData::CORS_USE_CREDENTIALS)
|
| + options.allowCredentials = true;
|
| + }
|
| + loader.reset(url_data_->frame()->createAssociatedURLLoader(options));
|
| + }
|
| +
|
| + // Start the resource loading.
|
| + loader->loadAsynchronously(request, this);
|
| + active_loader_.reset(new ActiveLoader(loader.Pass()));
|
| +}
|
| +
|
| +ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() {}
|
| +
|
| +/////////////////////////////////////////////////////////////////////////////
|
| +// MultiBuffer::DataProvider implementation.
|
| +MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const {
|
| + return pos_;
|
| +}
|
| +
|
| +bool ResourceMultiBufferDataProvider::Available() const {
|
| + if (fifo_.empty())
|
| + return false;
|
| + if (fifo_.back()->end_of_stream())
|
| + return true;
|
| + if (fifo_.front()->data_size() == block_size())
|
| + return true;
|
| + return false;
|
| +}
|
| +
|
| +scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() {
|
| + DCHECK(Available());
|
| + scoped_refptr<DataBuffer> ret = fifo_.front();
|
| + fifo_.pop_front();
|
| + ++pos_;
|
| + return ret;
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) {
|
| + if (!active_loader_ || active_loader_->deferred() == deferred)
|
| + return;
|
| + active_loader_->SetDeferred(deferred);
|
| +}
|
| +
|
| +/////////////////////////////////////////////////////////////////////////////
|
| +// WebURLLoaderClient implementation.
|
| +
|
| +void ResourceMultiBufferDataProvider::willFollowRedirect(
|
| + WebURLLoader* loader,
|
| + WebURLRequest& newRequest,
|
| + const WebURLResponse& redirectResponse) {
|
| + redirects_to_ = newRequest.url();
|
| + url_data_->set_valid_until(base::Time::Now() +
|
| + GetCacheValidUntil(redirectResponse));
|
| +
|
| + // This test is vital for security!
|
| + if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
|
| + if (origin_ != redirects_to_.GetOrigin()) {
|
| + url_data_->Fail();
|
| + }
|
| + }
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didSendData(
|
| + WebURLLoader* loader,
|
| + unsigned long long bytes_sent,
|
| + unsigned long long total_bytes_to_be_sent) {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didReceiveResponse(
|
| + WebURLLoader* loader,
|
| + const WebURLResponse& response) {
|
| +#if ENABLE_DLOG
|
| + string version;
|
| + switch (response.httpVersion()) {
|
| + case WebURLResponse::HTTPVersion_0_9:
|
| + version = "0.9";
|
| + break;
|
| + case WebURLResponse::HTTPVersion_1_0:
|
| + version = "1.0";
|
| + break;
|
| + case WebURLResponse::HTTPVersion_1_1:
|
| + version = "1.1";
|
| + break;
|
| + case WebURLResponse::HTTPVersion_2_0:
|
| + version = "2.1";
|
| + break;
|
| + }
|
| + DVLOG(1) << "didReceiveResponse: HTTP/" << version << " "
|
| + << response.httpStatusCode();
|
| +#endif
|
| + DCHECK(active_loader_);
|
| +
|
| + scoped_refptr<UrlData> destination_url_data(url_data_);
|
| +
|
| + UrlIndex* url_index = url_data_->url_index();
|
| +
|
| + if (!redirects_to_.is_empty()) {
|
| + if (!url_index) {
|
| + // We've been disconnected from the url index.
|
| + // That means the url_index_ has been destroyed, which means we do not
|
| + // need to do anything clever.
|
| + return;
|
| + }
|
| + destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_);
|
| + redirects_to_ = GURL();
|
| + }
|
| +
|
| + base::Time last_modified;
|
| + if (base::Time::FromString(
|
| + response.httpHeaderField("Last-Modified").utf8().data(),
|
| + &last_modified)) {
|
| + destination_url_data->set_last_modified(last_modified);
|
| + }
|
| +
|
| + destination_url_data->set_valid_until(base::Time::Now() +
|
| + GetCacheValidUntil(response));
|
| +
|
| + uint32 reasons = GetReasonsForUncacheability(response);
|
| + destination_url_data->set_cacheable(reasons == 0);
|
| + UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0);
|
| + int shift = 0;
|
| + int max_enum = base::bits::Log2Ceiling(kMaxReason);
|
| + while (reasons) {
|
| + DCHECK_LT(shift, max_enum); // Sanity check.
|
| + if (reasons & 0x1) {
|
| + UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift,
|
| + max_enum); // PRESUBMIT_IGNORE_UMA_MAX
|
| + }
|
| +
|
| + reasons >>= 1;
|
| + ++shift;
|
| + }
|
| +
|
| + // Expected content length can be |kPositionNotSpecified|, in that case
|
| + // |content_length_| is not specified and this is a streaming response.
|
| + int64 content_length = response.expectedContentLength();
|
| +
|
| + // We make a strong assumption that when we reach here we have either
|
| + // received a response from HTTP/HTTPS protocol or the request was
|
| + // successful (in particular range request). So we only verify the partial
|
| + // response for HTTP and HTTPS protocol.
|
| + if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) {
|
| + bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
|
| + bool ok_response = (response.httpStatusCode() == kHttpOK);
|
| +
|
| + // Check to see whether the server supports byte ranges.
|
| + std::string accept_ranges =
|
| + response.httpHeaderField("Accept-Ranges").utf8();
|
| + if (accept_ranges.find("bytes") != std::string::npos)
|
| + destination_url_data->set_range_supported();
|
| +
|
| + // If we have verified the partial response and it is correct.
|
| + // It's also possible for a server to support range requests
|
| + // without advertising "Accept-Ranges: bytes".
|
| + if (partial_response && VerifyPartialResponse(response)) {
|
| + destination_url_data->set_range_supported();
|
| + } else if (ok_response && pos_ == 0) {
|
| + // We accept a 200 response for a Range:0- request, trusting the
|
| + // Accept-Ranges header, because Apache thinks that's a reasonable thing
|
| + // to return.
|
| + destination_url_data->set_length(content_length);
|
| + } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) {
|
| + // Really, we should never request a range that doesn't exist, but
|
| + // if we do, let's handle it in a sane way.
|
| + // Unsatisfiable range
|
| + fifo_.push_back(DataBuffer::CreateEOSBuffer());
|
| + destination_url_data->multibuffer()->OnDataProviderEvent(this);
|
| + return;
|
| + } else {
|
| + destination_url_data->Fail();
|
| + return;
|
| + }
|
| + } else {
|
| + destination_url_data->set_range_supported();
|
| + if (content_length != kPositionNotSpecified) {
|
| + destination_url_data->set_length(content_length + byte_pos());
|
| + }
|
| + }
|
| +
|
| + if (url_index) {
|
| + destination_url_data = url_index->TryInsert(destination_url_data);
|
| + }
|
| +
|
| + if (destination_url_data != url_data_) {
|
| + // At this point, we've encountered a redirect, or found a better url data
|
| + // instance for the data that we're about to download.
|
| +
|
| + // First, let's take a ref on the current url data.
|
| + scoped_refptr<UrlData> old_url_data(url_data_);
|
| + destination_url_data->Use();
|
| +
|
| + // Take ownership of ourselves. (From the multibuffer)
|
| + scoped_ptr<DataProvider> self(
|
| + url_data_->multibuffer()->RemoveProvider(this));
|
| + url_data_ = destination_url_data.get();
|
| + // Give the ownership to our new owner.
|
| + url_data_->multibuffer()->AddProvider(self.Pass());
|
| +
|
| + // Call callback to let upstream users know about the transfer.
|
| + // This will merge the data from the two multibuffers and
|
| + // cause clients to start using the new UrlData.
|
| + old_url_data->RedirectTo(destination_url_data);
|
| + }
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader,
|
| + const char* data,
|
| + int data_length,
|
| + int encoded_data_length) {
|
| + DVLOG(1) << "didReceiveData: " << data_length << " bytes";
|
| + DCHECK(!Available());
|
| + DCHECK(active_loader_);
|
| + DCHECK_GT(data_length, 0);
|
| +
|
| + // When we receive data, we allow more retries.
|
| + retries_ = 0;
|
| +
|
| + while (data_length) {
|
| + if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
|
| + fifo_.push_back(new DataBuffer(block_size()));
|
| + fifo_.back()->set_data_size(0);
|
| + }
|
| + int last_block_size = fifo_.back()->data_size();
|
| + int to_append = std::min<int>(data_length, block_size() - last_block_size);
|
| + DCHECK_GT(to_append, 0);
|
| + memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append);
|
| + data += to_append;
|
| + fifo_.back()->set_data_size(last_block_size + to_append);
|
| + data_length -= to_append;
|
| + }
|
| +
|
| + if (Available())
|
| + url_data_->multibuffer()->OnDataProviderEvent(this);
|
| +
|
| + // Beware, this object might be deleted here.
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader,
|
| + int dataLength,
|
| + int encoded_data_length) {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didReceiveCachedMetadata(
|
| + WebURLLoader* loader,
|
| + const char* data,
|
| + int data_length) {
|
| + NOTIMPLEMENTED();
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didFinishLoading(
|
| + WebURLLoader* loader,
|
| + double finishTime,
|
| + int64_t total_encoded_data_length) {
|
| + DVLOG(1) << "didFinishLoading";
|
| + DCHECK(active_loader_.get());
|
| + DCHECK(!Available());
|
| +
|
| + // We're done with the loader.
|
| + active_loader_.reset();
|
| +
|
| + // If we didn't know the |instance_size_| we do now.
|
| + int64_t size = byte_pos();
|
| + if (!fifo_.empty())
|
| + size += fifo_.back()->data_size();
|
| +
|
| + // This request reports something smaller than what we've seen in the past,
|
| + // Maybe it's transient error?
|
| + if (url_data_->length() != kPositionNotSpecified &&
|
| + size < url_data_->length()) {
|
| + if (retries_ < kMaxRetries) {
|
| + fifo_.clear();
|
| + retries_++;
|
| + base::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
|
| + weak_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
|
| + return;
|
| + } else {
|
| + scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
|
| + url_data_->Fail();
|
| + return;
|
| + }
|
| + }
|
| +
|
| + url_data_->set_length(size);
|
| + fifo_.push_back(DataBuffer::CreateEOSBuffer());
|
| +
|
| + DCHECK(Available());
|
| + url_data_->multibuffer()->OnDataProviderEvent(this);
|
| +
|
| + // Beware, this object might be deleted here.
|
| +}
|
| +
|
| +void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader,
|
| + const WebURLError& error) {
|
| + DVLOG(1) << "didFail: reason=" << error.reason
|
| + << ", isCancellation=" << error.isCancellation
|
| + << ", domain=" << error.domain.utf8().data()
|
| + << ", localizedDescription="
|
| + << error.localizedDescription.utf8().data();
|
| + DCHECK(active_loader_.get());
|
| +
|
| + if (retries_ < kMaxRetries) {
|
| + retries_++;
|
| + base::MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start,
|
| + weak_factory_.GetWeakPtr()),
|
| + base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs));
|
| + } else {
|
| + // We don't need to continue loading after failure.
|
| + //
|
| + // Keep it alive until we exit this method so that |error| remains valid.
|
| + scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
|
| + url_data_->Fail();
|
| + }
|
| +}
|
| +
|
| +bool ResourceMultiBufferDataProvider::ParseContentRange(
|
| + const std::string& content_range_str,
|
| + int64* first_byte_position,
|
| + int64* last_byte_position,
|
| + int64* instance_size) {
|
| + const std::string kUpThroughBytesUnit = "bytes ";
|
| + if (content_range_str.find(kUpThroughBytesUnit) != 0)
|
| + return false;
|
| + std::string range_spec =
|
| + content_range_str.substr(kUpThroughBytesUnit.length());
|
| + size_t dash_offset = range_spec.find("-");
|
| + size_t slash_offset = range_spec.find("/");
|
| +
|
| + if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
|
| + slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
|
| + return false;
|
| + }
|
| + if (!base::StringToInt64(range_spec.substr(0, dash_offset),
|
| + first_byte_position) ||
|
| + !base::StringToInt64(
|
| + range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
|
| + last_byte_position)) {
|
| + return false;
|
| + }
|
| + if (slash_offset == range_spec.length() - 2 &&
|
| + range_spec[slash_offset + 1] == '*') {
|
| + *instance_size = kPositionNotSpecified;
|
| + } else {
|
| + if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
|
| + instance_size)) {
|
| + return false;
|
| + }
|
| + }
|
| + if (*last_byte_position < *first_byte_position ||
|
| + (*instance_size != kPositionNotSpecified &&
|
| + *last_byte_position >= *instance_size)) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +int64_t ResourceMultiBufferDataProvider::byte_pos() const {
|
| + int64_t ret = pos_;
|
| + return ret << url_data_->multibuffer()->block_size_shift();
|
| +}
|
| +
|
| +int64_t ResourceMultiBufferDataProvider::block_size() const {
|
| + int64_t ret = 1;
|
| + return ret << url_data_->multibuffer()->block_size_shift();
|
| +}
|
| +
|
| +bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
|
| + const WebURLResponse& response) {
|
| + int64 first_byte_position, last_byte_position, instance_size;
|
| + if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(),
|
| + &first_byte_position, &last_byte_position,
|
| + &instance_size)) {
|
| + return false;
|
| + }
|
| +
|
| + if (url_data_->length() == kPositionNotSpecified) {
|
| + url_data_->set_length(instance_size);
|
| + }
|
| +
|
| + if (byte_pos() != first_byte_position) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace media
|
|
|