| // 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 "media/blink/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 "media/base/media_log.h" |
| #include "media/blink/cache_util.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 media { |
| |
| 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, |
| 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_); |
| // TODO(mkwst): Split this into video/audio. |
| request.setRequestContext(WebURLRequest::RequestContextVideo); |
| |
| 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); // 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. |
| 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_.SchemeIsHTTPOrHTTPS()) { |
| 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 media |