This source file includes following definitions.
- ComputeTargetBufferWindow
- media_log_
- Start
- Stop
- Read
- content_length
- instance_size
- range_supported
- willSendRequest
- didSendData
- didReceiveResponse
- didReceiveData
- didDownloadData
- didReceiveCachedMetadata
- didFinishLoading
- didFail
- HasSingleOrigin
- DidPassCORSAccessCheck
- UpdateDeferStrategy
- SetPlaybackRate
- SetBitrate
- UpdateBufferWindow
- UpdateDeferBehavior
- SetDeferred
- ShouldDefer
- CanFulfillRead
- WillFulfillRead
- ReadInternal
- first_byte_position
- ParseContentRange
- VerifyPartialResponse
- DoneRead
- DoneStart
- IsRangeRequest
- Log
#include "content/renderer/media/buffered_resource_loader.h"
#include "base/bits.h"
#include "base/callback_helpers.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "content/renderer/media/cache_util.h"
#include "media/base/media_log.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_request_headers.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURLError.h"
#include "third_party/WebKit/public/platform/WebURLResponse.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
using blink::WebFrame;
using blink::WebString;
using blink::WebURLError;
using blink::WebURLLoader;
using blink::WebURLLoaderOptions;
using blink::WebURLRequest;
using blink::WebURLResponse;
namespace content {
static const int kHttpOK = 200;
static const int kHttpPartialContent = 206;
static const int kMegabyte = 1024 * 1024;
static const int kMinBufferCapacity = 2 * kMegabyte;
static const int kMaxBufferCapacity = 20 * kMegabyte;
static const int kForwardWaitThreshold = 2 * kMegabyte;
static void ComputeTargetBufferWindow(float playback_rate, int bitrate,
int* out_backward_capacity,
int* out_forward_capacity) {
static const int kDefaultBitrate = 200 * 1024 * 8;
static const int kMaxBitrate = 20 * kMegabyte * 8;
static const float kMaxPlaybackRate = 25.0;
static const int kTargetSecondsBufferedAhead = 10;
static const int kTargetSecondsBufferedBehind = 2;
if (bitrate <= 0)
bitrate = kDefaultBitrate;
bitrate = std::min(bitrate, kMaxBitrate);
bool backward_playback = false;
if (playback_rate < 0.0f) {
backward_playback = true;
playback_rate *= -1.0f;
}
playback_rate = std::max(playback_rate, 1.0f);
playback_rate = std::min(playback_rate, kMaxPlaybackRate);
int bytes_per_second = (bitrate / 8.0) * playback_rate;
*out_forward_capacity = std::max(
kTargetSecondsBufferedAhead * bytes_per_second, kMinBufferCapacity);
*out_backward_capacity = std::max(
kTargetSecondsBufferedBehind * bytes_per_second, kMinBufferCapacity);
*out_forward_capacity = std::min(*out_forward_capacity, kMaxBufferCapacity);
*out_backward_capacity = std::min(*out_backward_capacity, kMaxBufferCapacity);
if (backward_playback)
std::swap(*out_forward_capacity, *out_backward_capacity);
}
BufferedResourceLoader::BufferedResourceLoader(
const GURL& url,
CORSMode cors_mode,
int64 first_byte_position,
int64 last_byte_position,
DeferStrategy strategy,
int bitrate,
float playback_rate,
media::MediaLog* media_log)
: buffer_(kMinBufferCapacity, kMinBufferCapacity),
loader_failed_(false),
defer_strategy_(strategy),
might_be_reused_from_cache_in_future_(true),
range_supported_(false),
saved_forward_capacity_(0),
url_(url),
cors_mode_(cors_mode),
first_byte_position_(first_byte_position),
last_byte_position_(last_byte_position),
single_origin_(true),
offset_(0),
content_length_(kPositionNotSpecified),
instance_size_(kPositionNotSpecified),
read_position_(0),
read_size_(0),
read_buffer_(NULL),
first_offset_(0),
last_offset_(0),
bitrate_(bitrate),
playback_rate_(playback_rate),
media_log_(media_log) {
UpdateBufferWindow();
}
BufferedResourceLoader::~BufferedResourceLoader() {}
void BufferedResourceLoader::Start(
const StartCB& start_cb,
const LoadingStateChangedCB& loading_cb,
const ProgressCB& progress_cb,
WebFrame* frame) {
DCHECK(start_cb_.is_null());
DCHECK(loading_cb_.is_null());
DCHECK(progress_cb_.is_null());
DCHECK(!start_cb.is_null());
DCHECK(!loading_cb.is_null());
DCHECK(!progress_cb.is_null());
CHECK(frame);
start_cb_ = start_cb;
loading_cb_ = loading_cb;
progress_cb_ = progress_cb;
if (first_byte_position_ != kPositionNotSpecified) {
offset_ = first_byte_position_;
}
WebURLRequest request(url_);
request.setTargetType(WebURLRequest::TargetIsMedia);
if (IsRangeRequest()) {
request.setHTTPHeaderField(
WebString::fromUTF8(net::HttpRequestHeaders::kRange),
WebString::fromUTF8(net::HttpByteRange::Bounded(
first_byte_position_, last_byte_position_).GetHeaderValue()));
}
frame->setReferrerForRequest(request, blink::WebURL());
request.setHTTPHeaderField(
WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
WebString::fromUTF8("identity;q=1, *;q=0"));
scoped_ptr<WebURLLoader> loader;
if (test_loader_) {
loader = test_loader_.Pass();
} else {
WebURLLoaderOptions options;
if (cors_mode_ == kUnspecified) {
options.allowCredentials = true;
options.crossOriginRequestPolicy =
WebURLLoaderOptions::CrossOriginRequestPolicyAllow;
} else {
options.exposeAllResponseHeaders = true;
options.preflightPolicy = WebURLLoaderOptions::PreventPreflight;
options.crossOriginRequestPolicy =
WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
if (cors_mode_ == kUseCredentials)
options.allowCredentials = true;
}
loader.reset(frame->createAssociatedURLLoader(options));
}
loader->loadAsynchronously(request, this);
active_loader_.reset(new ActiveLoader(loader.Pass()));
loading_cb_.Run(kLoading);
}
void BufferedResourceLoader::Stop() {
start_cb_.Reset();
loading_cb_.Reset();
progress_cb_.Reset();
read_cb_.Reset();
active_loader_.reset();
}
void BufferedResourceLoader::Read(
int64 position,
int read_size,
uint8* buffer,
const ReadCB& read_cb) {
DCHECK(start_cb_.is_null());
DCHECK(read_cb_.is_null());
DCHECK(!read_cb.is_null());
DCHECK(buffer);
DCHECK_GT(read_size, 0);
read_cb_ = read_cb;
read_position_ = position;
read_size_ = read_size;
read_buffer_ = buffer;
if (loader_failed_) {
DoneRead(kFailed, 0);
return;
}
if (instance_size_ != kPositionNotSpecified &&
instance_size_ <= read_position_) {
DVLOG(1) << "Appear to have seeked beyond EOS; returning 0.";
DoneRead(kOk, 0);
return;
}
if (read_position_ > offset_ + kint32max ||
read_position_ < offset_ + kint32min) {
DoneRead(kCacheMiss, 0);
return;
}
if (read_size_ > kMaxBufferCapacity) {
DoneRead(kFailed, 0);
return;
}
first_offset_ = read_position_ - offset_;
last_offset_ = first_offset_ + read_size_;
if (CanFulfillRead()) {
ReadInternal();
UpdateDeferBehavior();
return;
}
if (WillFulfillRead()) {
int advance = std::min(first_offset_, buffer_.forward_bytes());
bool ret = buffer_.Seek(advance);
DCHECK(ret);
offset_ += advance;
first_offset_ -= advance;
last_offset_ -= advance;
if (last_offset_ > buffer_.forward_capacity()) {
saved_forward_capacity_ = buffer_.forward_capacity();
buffer_.set_forward_capacity(last_offset_);
}
DCHECK(!ShouldDefer())
<< "Capacity was not adjusted properly to prevent deferring.";
UpdateDeferBehavior();
return;
}
DoneRead(kCacheMiss, 0);
}
int64 BufferedResourceLoader::content_length() {
return content_length_;
}
int64 BufferedResourceLoader::instance_size() {
return instance_size_;
}
bool BufferedResourceLoader::range_supported() {
return range_supported_;
}
void BufferedResourceLoader::willSendRequest(
WebURLLoader* loader,
WebURLRequest& newRequest,
const WebURLResponse& redirectResponse) {
if (start_cb_.is_null()) {
newRequest.setURL(blink::WebURL());
return;
}
if (single_origin_)
single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();
url_ = newRequest.url();
}
void BufferedResourceLoader::didSendData(
WebURLLoader* loader,
unsigned long long bytes_sent,
unsigned long long total_bytes_to_be_sent) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didReceiveResponse(
WebURLLoader* loader,
const WebURLResponse& response) {
DVLOG(1) << "didReceiveResponse: HTTP/"
<< (response.httpVersion() == WebURLResponse::HTTP_0_9 ? "0.9" :
response.httpVersion() == WebURLResponse::HTTP_1_0 ? "1.0" :
response.httpVersion() == WebURLResponse::HTTP_1_1 ? "1.1" :
"Unknown")
<< " " << response.httpStatusCode();
DCHECK(active_loader_.get());
if (start_cb_.is_null())
return;
uint32 reasons = GetReasonsForUncacheability(response);
might_be_reused_from_cache_in_future_ = 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);
if (reasons & 0x1)
UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift, max_enum);
reasons >>= 1;
++shift;
}
content_length_ = response.expectedContentLength();
if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
bool ok_response = (response.httpStatusCode() == kHttpOK);
if (IsRangeRequest()) {
std::string accept_ranges =
response.httpHeaderField("Accept-Ranges").utf8();
range_supported_ = (accept_ranges.find("bytes") != std::string::npos);
if (partial_response && VerifyPartialResponse(response)) {
range_supported_ = true;
} else if (ok_response && first_byte_position_ == 0 &&
last_byte_position_ == kPositionNotSpecified) {
instance_size_ = content_length_;
} else {
DoneStart(kFailed);
return;
}
} else {
instance_size_ = content_length_;
if (response.httpStatusCode() != kHttpOK) {
DoneStart(kFailed);
return;
}
}
} else {
CHECK_EQ(instance_size_, kPositionNotSpecified);
if (content_length_ != kPositionNotSpecified) {
if (first_byte_position_ == kPositionNotSpecified)
instance_size_ = content_length_;
else if (last_byte_position_ == kPositionNotSpecified)
instance_size_ = content_length_ + first_byte_position_;
}
}
DoneStart(kOk);
}
void BufferedResourceLoader::didReceiveData(
WebURLLoader* loader,
const char* data,
int data_length,
int encoded_data_length) {
DVLOG(1) << "didReceiveData: " << data_length << " bytes";
DCHECK(active_loader_.get());
DCHECK_GT(data_length, 0);
buffer_.Append(reinterpret_cast<const uint8*>(data), data_length);
if (HasPendingRead() && CanFulfillRead())
ReadInternal();
UpdateDeferBehavior();
if (buffer_.forward_bytes() > buffer_.forward_capacity()) {
int excess = buffer_.forward_bytes() - buffer_.forward_capacity();
bool success = buffer_.Seek(excess);
DCHECK(success);
offset_ += first_offset_ + excess;
}
progress_cb_.Run(offset_ + buffer_.forward_bytes() - 1);
Log();
}
void BufferedResourceLoader::didDownloadData(
blink::WebURLLoader* loader,
int dataLength,
int encoded_data_length) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didReceiveCachedMetadata(
WebURLLoader* loader,
const char* data,
int data_length) {
NOTIMPLEMENTED();
}
void BufferedResourceLoader::didFinishLoading(
WebURLLoader* loader,
double finishTime,
int64_t total_encoded_data_length) {
DVLOG(1) << "didFinishLoading";
DCHECK(active_loader_.get());
active_loader_.reset();
loading_cb_.Run(kLoadingFinished);
if (instance_size_ == kPositionNotSpecified) {
instance_size_ = offset_ + buffer_.forward_bytes();
}
if (!start_cb_.is_null()) {
DCHECK(read_cb_.is_null())
<< "Shouldn't have a read callback during start";
DoneStart(kOk);
return;
}
if (HasPendingRead()) {
if (CanFulfillRead())
ReadInternal();
else
DoneRead(kCacheMiss, 0);
}
}
void BufferedResourceLoader::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());
scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass();
loader_failed_ = true;
loading_cb_.Run(kLoadingFailed);
if (!start_cb_.is_null()) {
DCHECK(read_cb_.is_null())
<< "Shouldn't have a read callback during start";
DoneStart(kFailed);
return;
}
if (HasPendingRead()) {
DoneRead(kFailed, 0);
}
}
bool BufferedResourceLoader::HasSingleOrigin() const {
DCHECK(start_cb_.is_null())
<< "Start() must complete before calling HasSingleOrigin()";
return single_origin_;
}
bool BufferedResourceLoader::DidPassCORSAccessCheck() const {
DCHECK(start_cb_.is_null())
<< "Start() must complete before calling DidPassCORSAccessCheck()";
return !loader_failed_ && cors_mode_ != kUnspecified;
}
void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) {
if (!might_be_reused_from_cache_in_future_ && strategy == kNeverDefer)
strategy = kCapacityDefer;
defer_strategy_ = strategy;
UpdateDeferBehavior();
}
void BufferedResourceLoader::SetPlaybackRate(float playback_rate) {
playback_rate_ = playback_rate;
if (playback_rate_ == 0.0)
return;
UpdateBufferWindow();
}
void BufferedResourceLoader::SetBitrate(int bitrate) {
DCHECK(bitrate >= 0);
bitrate_ = bitrate;
UpdateBufferWindow();
}
void BufferedResourceLoader::UpdateBufferWindow() {
int backward_capacity;
int forward_capacity;
ComputeTargetBufferWindow(
playback_rate_, bitrate_, &backward_capacity, &forward_capacity);
buffer_.set_backward_capacity(backward_capacity);
buffer_.set_forward_capacity(forward_capacity);
}
void BufferedResourceLoader::UpdateDeferBehavior() {
if (!active_loader_)
return;
SetDeferred(ShouldDefer());
}
void BufferedResourceLoader::SetDeferred(bool deferred) {
if (active_loader_->deferred() == deferred)
return;
active_loader_->SetDeferred(deferred);
loading_cb_.Run(deferred ? kLoadingDeferred : kLoading);
}
bool BufferedResourceLoader::ShouldDefer() const {
switch(defer_strategy_) {
case kNeverDefer:
return false;
case kReadThenDefer:
DCHECK(read_cb_.is_null() || last_offset_ > buffer_.forward_bytes())
<< "We shouldn't stop deferring if we can fulfill the read";
return read_cb_.is_null();
case kCapacityDefer:
return buffer_.forward_bytes() >= buffer_.forward_capacity();
}
NOTREACHED();
return false;
}
bool BufferedResourceLoader::CanFulfillRead() const {
if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
return false;
if (first_offset_ >= buffer_.forward_bytes())
return false;
if (!active_loader_)
return true;
if (last_offset_ > buffer_.forward_bytes())
return false;
return true;
}
bool BufferedResourceLoader::WillFulfillRead() const {
if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
return false;
if ((first_offset_ - buffer_.forward_bytes()) >= kForwardWaitThreshold)
return false;
if (!active_loader_)
return false;
return true;
}
void BufferedResourceLoader::ReadInternal() {
bool ret = buffer_.Seek(first_offset_);
DCHECK(ret);
int read = buffer_.Read(read_buffer_, read_size_);
offset_ += first_offset_ + read;
DoneRead(kOk, read);
}
int64 BufferedResourceLoader::first_byte_position() const {
return first_byte_position_;
}
bool BufferedResourceLoader::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;
}
bool BufferedResourceLoader::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 (instance_size != kPositionNotSpecified) {
instance_size_ = instance_size;
}
if (first_byte_position_ != kPositionNotSpecified &&
first_byte_position_ != first_byte_position) {
return false;
}
return true;
}
void BufferedResourceLoader::DoneRead(Status status, int bytes_read) {
if (saved_forward_capacity_) {
buffer_.set_forward_capacity(saved_forward_capacity_);
saved_forward_capacity_ = 0;
}
read_position_ = 0;
read_size_ = 0;
read_buffer_ = NULL;
first_offset_ = 0;
last_offset_ = 0;
Log();
base::ResetAndReturn(&read_cb_).Run(status, bytes_read);
}
void BufferedResourceLoader::DoneStart(Status status) {
base::ResetAndReturn(&start_cb_).Run(status);
}
bool BufferedResourceLoader::IsRangeRequest() const {
return first_byte_position_ != kPositionNotSpecified;
}
void BufferedResourceLoader::Log() {
media_log_->AddEvent(
media_log_->CreateBufferedExtentsChangedEvent(
offset_ - buffer_.backward_bytes(),
offset_,
offset_ + buffer_.forward_bytes()));
}
}