root/content/renderer/media/buffered_resource_loader.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ComputeTargetBufferWindow
  2. media_log_
  3. Start
  4. Stop
  5. Read
  6. content_length
  7. instance_size
  8. range_supported
  9. willSendRequest
  10. didSendData
  11. didReceiveResponse
  12. didReceiveData
  13. didDownloadData
  14. didReceiveCachedMetadata
  15. didFinishLoading
  16. didFail
  17. HasSingleOrigin
  18. DidPassCORSAccessCheck
  19. UpdateDeferStrategy
  20. SetPlaybackRate
  21. SetBitrate
  22. UpdateBufferWindow
  23. UpdateDeferBehavior
  24. SetDeferred
  25. ShouldDefer
  26. CanFulfillRead
  27. WillFulfillRead
  28. ReadInternal
  29. first_byte_position
  30. ParseContentRange
  31. VerifyPartialResponse
  32. DoneRead
  33. DoneStart
  34. IsRangeRequest
  35. Log

// Copyright 2013 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 "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;

// Define the number of bytes in a megabyte.
static const int kMegabyte = 1024 * 1024;

// Minimum capacity of the buffer in forward or backward direction.
//
// 2MB is an arbitrary limit; it just seems to be "good enough" in practice.
static const int kMinBufferCapacity = 2 * kMegabyte;

// Maximum capacity of the buffer in forward or backward direction. This is
// effectively the largest single read the code path can handle.
// 20MB is an arbitrary limit; it just seems to be "good enough" in practice.
static const int kMaxBufferCapacity = 20 * kMegabyte;

// Maximum number of bytes outside the buffer we will wait for in order to
// fulfill a read. If a read starts more than 2MB away from the data we
// currently have in the buffer, we will not wait for buffer to reach the read's
// location and will instead reset the request.
static const int kForwardWaitThreshold = 2 * kMegabyte;

// Computes the suggested backward and forward capacity for the buffer
// if one wants to play at |playback_rate| * the natural playback speed.
// Use a value of 0 for |bitrate| if it is unknown.
static void ComputeTargetBufferWindow(float playback_rate, int bitrate,
                                      int* out_backward_capacity,
                                      int* out_forward_capacity) {
  static const int kDefaultBitrate = 200 * 1024 * 8;  // 200 Kbps.
  static const int kMaxBitrate = 20 * kMegabyte * 8;  // 20 Mbps.
  static const float kMaxPlaybackRate = 25.0;
  static const int kTargetSecondsBufferedAhead = 10;
  static const int kTargetSecondsBufferedBehind = 2;

  // Use a default bit rate if unknown and clamp to prevent overflow.
  if (bitrate <= 0)
    bitrate = kDefaultBitrate;
  bitrate = std::min(bitrate, kMaxBitrate);

  // Only scale the buffer window for playback rates greater than 1.0 in
  // magnitude and clamp to prevent overflow.
  bool backward_playback = false;
  if (playback_rate < 0.0f) {
    backward_playback = true;
    playback_rate *= -1.0f;
  }

  playback_rate = std::max(playback_rate, 1.0f);
  playback_rate = std::min(playback_rate, kMaxPlaybackRate);

  int bytes_per_second = (bitrate / 8.0) * playback_rate;

  // Clamp between kMinBufferCapacity and kMaxBufferCapacity.
  *out_forward_capacity = std::max(
      kTargetSecondsBufferedAhead * bytes_per_second, kMinBufferCapacity);
  *out_backward_capacity = std::max(
      kTargetSecondsBufferedBehind * bytes_per_second, kMinBufferCapacity);

  *out_forward_capacity = std::min(*out_forward_capacity, kMaxBufferCapacity);
  *out_backward_capacity = std::min(*out_backward_capacity, kMaxBufferCapacity);

  if (backward_playback)
    std::swap(*out_forward_capacity, *out_backward_capacity);
}

BufferedResourceLoader::BufferedResourceLoader(
    const GURL& url,
    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) {

  // Set the initial capacity of |buffer_| based on |bitrate_| and
  // |playback_rate_|.
  UpdateBufferWindow();
}

BufferedResourceLoader::~BufferedResourceLoader() {}

void BufferedResourceLoader::Start(
    const StartCB& start_cb,
    const LoadingStateChangedCB& loading_cb,
    const ProgressCB& progress_cb,
    WebFrame* frame) {
  // Make sure we have not started.
  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) {
    // TODO(hclam): server may not support range request so |offset_| may not
    // equal to |first_byte_position_|.
    offset_ = first_byte_position_;
  }

  // Prepare the request.
  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());

  // 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 (cors_mode_ == kUnspecified) {
      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 (cors_mode_ == kUseCredentials)
        options.allowCredentials = true;
    }
    loader.reset(frame->createAssociatedURLLoader(options));
  }

  // Start the resource loading.
  loader->loadAsynchronously(request, this);
  active_loader_.reset(new ActiveLoader(loader.Pass()));
  loading_cb_.Run(kLoading);
}

void BufferedResourceLoader::Stop() {
  // Reset callbacks.
  start_cb_.Reset();
  loading_cb_.Reset();
  progress_cb_.Reset();
  read_cb_.Reset();

  // Cancel and reset any active loaders.
  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);

  // Save the parameter of reading.
  read_cb_ = read_cb;
  read_position_ = position;
  read_size_ = read_size;
  read_buffer_ = buffer;

  // Reads should immediately fail if the loader also failed.
  if (loader_failed_) {
    DoneRead(kFailed, 0);
    return;
  }

  // If we're attempting to read past the end of the file, return a zero
  // indicating EOF.
  //
  // This can happen with callees that read in fixed-sized amounts for parsing
  // or at the end of chunked 200 responses when we discover the actual length
  // of the file.
  if (instance_size_ != kPositionNotSpecified &&
      instance_size_ <= read_position_) {
    DVLOG(1) << "Appear to have seeked beyond EOS; returning 0.";
    DoneRead(kOk, 0);
    return;
  }

  // Make sure |offset_| and |read_position_| does not differ by a large
  // amount.
  if (read_position_ > offset_ + kint32max ||
      read_position_ < offset_ + kint32min) {
    DoneRead(kCacheMiss, 0);
    return;
  }

  // Make sure |read_size_| is not too large for the buffer to ever be able to
  // fulfill the read request.
  if (read_size_ > kMaxBufferCapacity) {
    DoneRead(kFailed, 0);
    return;
  }

  // Prepare the parameters.
  first_offset_ = read_position_ - offset_;
  last_offset_ = first_offset_ + read_size_;

  // If we can serve the request now, do the actual read.
  if (CanFulfillRead()) {
    ReadInternal();
    UpdateDeferBehavior();
    return;
  }

  // If we expect the read request to be fulfilled later, expand capacity as
  // necessary and disable deferring.
  if (WillFulfillRead()) {
    // Advance offset as much as possible to create additional capacity.
    int advance = std::min(first_offset_, buffer_.forward_bytes());
    bool ret = buffer_.Seek(advance);
    DCHECK(ret);

    offset_ += advance;
    first_offset_ -= advance;
    last_offset_ -= advance;

    // Expand capacity to accomodate a read that extends past the normal
    // capacity.
    //
    // This can happen when reading in a large seek index or when the
    // first byte of a read request falls within kForwardWaitThreshold.
    if (last_offset_ > buffer_.forward_capacity()) {
      saved_forward_capacity_ = buffer_.forward_capacity();
      buffer_.set_forward_capacity(last_offset_);
    }

    // Make sure we stop deferring now that there's additional capacity.
    DCHECK(!ShouldDefer())
        << "Capacity was not adjusted properly to prevent deferring.";
    UpdateDeferBehavior();

    return;
  }

  // Make a callback to report failure.
  DoneRead(kCacheMiss, 0);
}

int64 BufferedResourceLoader::content_length() {
  return content_length_;
}

int64 BufferedResourceLoader::instance_size() {
  return instance_size_;
}

bool BufferedResourceLoader::range_supported() {
  return range_supported_;
}

/////////////////////////////////////////////////////////////////////////////
// blink::WebURLLoaderClient implementation.
void BufferedResourceLoader::willSendRequest(
    WebURLLoader* loader,
    WebURLRequest& newRequest,
    const WebURLResponse& redirectResponse) {

  // The load may have been stopped and |start_cb| is destroyed.
  // In this case we shouldn't do anything.
  if (start_cb_.is_null()) {
    // Set the url in the request to an invalid value (empty url).
    newRequest.setURL(blink::WebURL());
    return;
  }

  // Only allow |single_origin_| if we haven't seen a different origin yet.
  if (single_origin_)
    single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();

  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());

  // The loader may have been stopped and |start_cb| is destroyed.
  // In this case we shouldn't do anything.
  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);  // Sanity check.
    if (reasons & 0x1)
      UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift, max_enum);
    reasons >>= 1;
    ++shift;
  }

  // Expected content length can be |kPositionNotSpecified|, in that case
  // |content_length_| is not specified and this is a streaming response.
  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 (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
    bool partial_response = (response.httpStatusCode() == kHttpPartialContent);
    bool ok_response = (response.httpStatusCode() == kHttpOK);

    if (IsRangeRequest()) {
      // 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);

      // If we have verified the partial response and it is correct, we will
      // return kOk. 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 if (ok_response && first_byte_position_ == 0 &&
                 last_byte_position_ == kPositionNotSpecified) {
        // 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.
        instance_size_ = content_length_;
      } else {
        DoneStart(kFailed);
        return;
      }
    } else {
      instance_size_ = content_length_;
      if (response.httpStatusCode() != kHttpOK) {
        // We didn't request a range but server didn't reply with "200 OK".
        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_;
    }
  }

  // Calls with a successful response.
  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 there is an active read request, try to fulfill the request.
  if (HasPendingRead() && CanFulfillRead())
    ReadInternal();

  // At last see if the buffer is full and we need to defer the downloading.
  UpdateDeferBehavior();

  // Consume excess bytes from our in-memory buffer if necessary.
  if (buffer_.forward_bytes() > buffer_.forward_capacity()) {
    int excess = buffer_.forward_bytes() - buffer_.forward_capacity();
    bool success = buffer_.Seek(excess);
    DCHECK(success);
    offset_ += first_offset_ + excess;
  }

  // Notify latest progress and buffered offset.
  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());

  // We're done with the loader.
  active_loader_.reset();
  loading_cb_.Run(kLoadingFinished);

  // 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, run it.
  if (!start_cb_.is_null()) {
    DCHECK(read_cb_.is_null())
        << "Shouldn't have a read callback during start";
    DoneStart(kOk);
    return;
  }

  // Don't leave read callbacks hanging around.
  if (HasPendingRead()) {
    // Try to fulfill with what is in the buffer.
    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());

  // 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();
  loader_failed_ = true;
  loading_cb_.Run(kLoadingFailed);

  // Don't leave start callbacks hanging around.
  if (!start_cb_.is_null()) {
    DCHECK(read_cb_.is_null())
        << "Shouldn't have a read callback during start";
    DoneStart(kFailed);
    return;
  }

  // Don't leave read callbacks hanging around.
  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;

  // This is a pause so don't bother updating the buffer window as we'll likely
  // get unpaused in the future.
  if (playback_rate_ == 0.0)
    return;

  UpdateBufferWindow();
}

void BufferedResourceLoader::SetBitrate(int bitrate) {
  DCHECK(bitrate >= 0);
  bitrate_ = bitrate;
  UpdateBufferWindow();
}

/////////////////////////////////////////////////////////////////////////////
// Helper methods.

void BufferedResourceLoader::UpdateBufferWindow() {
  int backward_capacity;
  int forward_capacity;
  ComputeTargetBufferWindow(
      playback_rate_, bitrate_, &backward_capacity, &forward_capacity);

  // This does not evict data from the buffer if the new capacities are less
  // than the current capacities; the new limits will be enforced after the
  // existing excess buffered data is consumed.
  buffer_.set_backward_capacity(backward_capacity);
  buffer_.set_forward_capacity(forward_capacity);
}

void BufferedResourceLoader::UpdateDeferBehavior() {
  if (!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 we are reading too far in the backward direction.
  if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
    return false;

  // If the start offset is too far ahead.
  if (first_offset_ >= buffer_.forward_bytes())
    return false;

  // At the point, we verified that first byte requested is within the buffer.
  // If the request has completed, then just returns with what we have now.
  if (!active_loader_)
    return true;

  // If the resource request is still active, make sure the whole requested
  // range is covered.
  if (last_offset_ > buffer_.forward_bytes())
    return false;

  return true;
}

bool BufferedResourceLoader::WillFulfillRead() const {
  // Trying to read too far behind.
  if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0)
    return false;

  // Trying to read too far ahead.
  if ((first_offset_ - buffer_.forward_bytes()) >= kForwardWaitThreshold)
    return false;

  // The resource request has completed, there's no way we can fulfill the
  // read request.
  if (!active_loader_)
    return false;

  return true;
}

void BufferedResourceLoader::ReadInternal() {
  // Seek to the first byte requested.
  bool ret = buffer_.Seek(first_offset_);
  DCHECK(ret);

  // Then do the read.
  int read = buffer_.Read(read_buffer_, read_size_);
  offset_ += first_offset_ + read;

  // And report with what we have read.
  DoneRead(kOk, read);
}

int64 BufferedResourceLoader::first_byte_position() const {
  return first_byte_position_;
}

// static
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;
  }

  // TODO(hclam): I should also check |last_byte_position|, but since
  // we will never make such a request that it is ok to leave it unimplemented.
  return true;
}

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()));
}

}  // namespace content

/* [<][>][^][v][top][bottom][index][help] */