1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/renderer/media/buffered_resource_loader.h"
8 #include "base/callback_helpers.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "content/public/common/url_constants.h"
13 #include "content/renderer/media/cache_util.h"
14 #include "media/base/media_log.h"
15 #include "net/http/http_byte_range.h"
16 #include "net/http/http_request_headers.h"
17 #include "third_party/WebKit/public/platform/WebString.h"
18 #include "third_party/WebKit/public/platform/WebURLError.h"
19 #include "third_party/WebKit/public/platform/WebURLResponse.h"
20 #include "third_party/WebKit/public/web/WebKit.h"
21 #include "third_party/WebKit/public/web/WebURLLoaderOptions.h"
23 using blink::WebFrame
;
24 using blink::WebString
;
25 using blink::WebURLError
;
26 using blink::WebURLLoader
;
27 using blink::WebURLLoaderOptions
;
28 using blink::WebURLRequest
;
29 using blink::WebURLResponse
;
33 static const int kHttpOK
= 200;
34 static const int kHttpPartialContent
= 206;
36 // Define the number of bytes in a megabyte.
37 static const int kMegabyte
= 1024 * 1024;
39 // Minimum capacity of the buffer in forward or backward direction.
41 // 2MB is an arbitrary limit; it just seems to be "good enough" in practice.
42 static const int kMinBufferCapacity
= 2 * kMegabyte
;
44 // Maximum capacity of the buffer in forward or backward direction. This is
45 // effectively the largest single read the code path can handle.
46 // 20MB is an arbitrary limit; it just seems to be "good enough" in practice.
47 static const int kMaxBufferCapacity
= 20 * kMegabyte
;
49 // Maximum number of bytes outside the buffer we will wait for in order to
50 // fulfill a read. If a read starts more than 2MB away from the data we
51 // currently have in the buffer, we will not wait for buffer to reach the read's
52 // location and will instead reset the request.
53 static const int kForwardWaitThreshold
= 2 * kMegabyte
;
55 // Computes the suggested backward and forward capacity for the buffer
56 // if one wants to play at |playback_rate| * the natural playback speed.
57 // Use a value of 0 for |bitrate| if it is unknown.
58 static void ComputeTargetBufferWindow(float playback_rate
, int bitrate
,
59 int* out_backward_capacity
,
60 int* out_forward_capacity
) {
61 static const int kDefaultBitrate
= 200 * 1024 * 8; // 200 Kbps.
62 static const int kMaxBitrate
= 20 * kMegabyte
* 8; // 20 Mbps.
63 static const float kMaxPlaybackRate
= 25.0;
64 static const int kTargetSecondsBufferedAhead
= 10;
65 static const int kTargetSecondsBufferedBehind
= 2;
67 // Use a default bit rate if unknown and clamp to prevent overflow.
69 bitrate
= kDefaultBitrate
;
70 bitrate
= std::min(bitrate
, kMaxBitrate
);
72 // Only scale the buffer window for playback rates greater than 1.0 in
73 // magnitude and clamp to prevent overflow.
74 bool backward_playback
= false;
75 if (playback_rate
< 0.0f
) {
76 backward_playback
= true;
77 playback_rate
*= -1.0f
;
80 playback_rate
= std::max(playback_rate
, 1.0f
);
81 playback_rate
= std::min(playback_rate
, kMaxPlaybackRate
);
83 int bytes_per_second
= (bitrate
/ 8.0) * playback_rate
;
85 // Clamp between kMinBufferCapacity and kMaxBufferCapacity.
86 *out_forward_capacity
= std::max(
87 kTargetSecondsBufferedAhead
* bytes_per_second
, kMinBufferCapacity
);
88 *out_backward_capacity
= std::max(
89 kTargetSecondsBufferedBehind
* bytes_per_second
, kMinBufferCapacity
);
91 *out_forward_capacity
= std::min(*out_forward_capacity
, kMaxBufferCapacity
);
92 *out_backward_capacity
= std::min(*out_backward_capacity
, kMaxBufferCapacity
);
94 if (backward_playback
)
95 std::swap(*out_forward_capacity
, *out_backward_capacity
);
98 BufferedResourceLoader::BufferedResourceLoader(
101 int64 first_byte_position
,
102 int64 last_byte_position
,
103 DeferStrategy strategy
,
106 media::MediaLog
* media_log
)
107 : buffer_(kMinBufferCapacity
, kMinBufferCapacity
),
108 loader_failed_(false),
109 defer_strategy_(strategy
),
110 might_be_reused_from_cache_in_future_(true),
111 range_supported_(false),
112 saved_forward_capacity_(0),
114 cors_mode_(cors_mode
),
115 first_byte_position_(first_byte_position
),
116 last_byte_position_(last_byte_position
),
117 single_origin_(true),
119 content_length_(kPositionNotSpecified
),
120 instance_size_(kPositionNotSpecified
),
127 playback_rate_(playback_rate
),
128 media_log_(media_log
) {
130 // Set the initial capacity of |buffer_| based on |bitrate_| and
132 UpdateBufferWindow();
135 BufferedResourceLoader::~BufferedResourceLoader() {}
137 void BufferedResourceLoader::Start(
138 const StartCB
& start_cb
,
139 const LoadingStateChangedCB
& loading_cb
,
140 const ProgressCB
& progress_cb
,
142 // Make sure we have not started.
143 DCHECK(start_cb_
.is_null());
144 DCHECK(loading_cb_
.is_null());
145 DCHECK(progress_cb_
.is_null());
146 DCHECK(!start_cb
.is_null());
147 DCHECK(!loading_cb
.is_null());
148 DCHECK(!progress_cb
.is_null());
151 start_cb_
= start_cb
;
152 loading_cb_
= loading_cb
;
153 progress_cb_
= progress_cb
;
155 if (first_byte_position_
!= kPositionNotSpecified
) {
156 // TODO(hclam): server may not support range request so |offset_| may not
157 // equal to |first_byte_position_|.
158 offset_
= first_byte_position_
;
161 // Prepare the request.
162 WebURLRequest
request(url_
);
163 // TODO(mkwst): Split this into video/audio.
164 request
.setRequestContext(WebURLRequest::RequestContextVideo
);
166 if (IsRangeRequest()) {
167 request
.setHTTPHeaderField(
168 WebString::fromUTF8(net::HttpRequestHeaders::kRange
),
169 WebString::fromUTF8(net::HttpByteRange::Bounded(
170 first_byte_position_
, last_byte_position_
).GetHeaderValue()));
173 frame
->setReferrerForRequest(request
, blink::WebURL());
175 // Disable compression, compression for audio/video doesn't make sense...
176 request
.setHTTPHeaderField(
177 WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding
),
178 WebString::fromUTF8("identity;q=1, *;q=0"));
180 // Check for our test WebURLLoader.
181 scoped_ptr
<WebURLLoader
> loader
;
183 loader
= test_loader_
.Pass();
185 WebURLLoaderOptions options
;
186 if (cors_mode_
== kUnspecified
) {
187 options
.allowCredentials
= true;
188 options
.crossOriginRequestPolicy
=
189 WebURLLoaderOptions::CrossOriginRequestPolicyAllow
;
191 options
.exposeAllResponseHeaders
= true;
192 // The author header set is empty, no preflight should go ahead.
193 options
.preflightPolicy
= WebURLLoaderOptions::PreventPreflight
;
194 options
.crossOriginRequestPolicy
=
195 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl
;
196 if (cors_mode_
== kUseCredentials
)
197 options
.allowCredentials
= true;
199 loader
.reset(frame
->createAssociatedURLLoader(options
));
202 // Start the resource loading.
203 loader
->loadAsynchronously(request
, this);
204 active_loader_
.reset(new ActiveLoader(loader
.Pass()));
205 loading_cb_
.Run(kLoading
);
208 void BufferedResourceLoader::Stop() {
212 progress_cb_
.Reset();
215 // Cancel and reset any active loaders.
216 active_loader_
.reset();
219 void BufferedResourceLoader::Read(
223 const ReadCB
& read_cb
) {
224 DCHECK(start_cb_
.is_null());
225 DCHECK(read_cb_
.is_null());
226 DCHECK(!read_cb
.is_null());
228 DCHECK_GT(read_size
, 0);
230 // Save the parameter of reading.
232 read_position_
= position
;
233 read_size_
= read_size
;
234 read_buffer_
= buffer
;
236 // Reads should immediately fail if the loader also failed.
237 if (loader_failed_
) {
238 DoneRead(kFailed
, 0);
242 // If we're attempting to read past the end of the file, return a zero
245 // This can happen with callees that read in fixed-sized amounts for parsing
246 // or at the end of chunked 200 responses when we discover the actual length
248 if (instance_size_
!= kPositionNotSpecified
&&
249 instance_size_
<= read_position_
) {
250 DVLOG(1) << "Appear to have seeked beyond EOS; returning 0.";
255 // Make sure |offset_| and |read_position_| does not differ by a large
257 if (read_position_
> offset_
+ kint32max
||
258 read_position_
< offset_
+ kint32min
) {
259 DoneRead(kCacheMiss
, 0);
263 // Make sure |read_size_| is not too large for the buffer to ever be able to
264 // fulfill the read request.
265 if (read_size_
> kMaxBufferCapacity
) {
266 DoneRead(kFailed
, 0);
270 // Prepare the parameters.
271 first_offset_
= read_position_
- offset_
;
272 last_offset_
= first_offset_
+ read_size_
;
274 // If we can serve the request now, do the actual read.
275 if (CanFulfillRead()) {
277 UpdateDeferBehavior();
281 // If we expect the read request to be fulfilled later, expand capacity as
282 // necessary and disable deferring.
283 if (WillFulfillRead()) {
284 // Advance offset as much as possible to create additional capacity.
285 int advance
= std::min(first_offset_
, buffer_
.forward_bytes());
286 bool ret
= buffer_
.Seek(advance
);
290 first_offset_
-= advance
;
291 last_offset_
-= advance
;
293 // Expand capacity to accomodate a read that extends past the normal
296 // This can happen when reading in a large seek index or when the
297 // first byte of a read request falls within kForwardWaitThreshold.
298 if (last_offset_
> buffer_
.forward_capacity()) {
299 saved_forward_capacity_
= buffer_
.forward_capacity();
300 buffer_
.set_forward_capacity(last_offset_
);
303 // Make sure we stop deferring now that there's additional capacity.
304 DCHECK(!ShouldDefer())
305 << "Capacity was not adjusted properly to prevent deferring.";
306 UpdateDeferBehavior();
311 // Make a callback to report failure.
312 DoneRead(kCacheMiss
, 0);
315 int64
BufferedResourceLoader::content_length() {
316 return content_length_
;
319 int64
BufferedResourceLoader::instance_size() {
320 return instance_size_
;
323 bool BufferedResourceLoader::range_supported() {
324 return range_supported_
;
327 /////////////////////////////////////////////////////////////////////////////
328 // blink::WebURLLoaderClient implementation.
329 void BufferedResourceLoader::willSendRequest(
330 WebURLLoader
* loader
,
331 WebURLRequest
& newRequest
,
332 const WebURLResponse
& redirectResponse
) {
334 // The load may have been stopped and |start_cb| is destroyed.
335 // In this case we shouldn't do anything.
336 if (start_cb_
.is_null()) {
337 // Set the url in the request to an invalid value (empty url).
338 newRequest
.setURL(blink::WebURL());
342 // Only allow |single_origin_| if we haven't seen a different origin yet.
344 single_origin_
= url_
.GetOrigin() == GURL(newRequest
.url()).GetOrigin();
346 url_
= newRequest
.url();
349 void BufferedResourceLoader::didSendData(
350 WebURLLoader
* loader
,
351 unsigned long long bytes_sent
,
352 unsigned long long total_bytes_to_be_sent
) {
356 void BufferedResourceLoader::didReceiveResponse(
357 WebURLLoader
* loader
,
358 const WebURLResponse
& response
) {
359 DVLOG(1) << "didReceiveResponse: HTTP/"
360 << (response
.httpVersion() == WebURLResponse::HTTP_0_9
? "0.9" :
361 response
.httpVersion() == WebURLResponse::HTTP_1_0
? "1.0" :
362 response
.httpVersion() == WebURLResponse::HTTP_1_1
? "1.1" :
364 << " " << response
.httpStatusCode();
365 DCHECK(active_loader_
.get());
367 // The loader may have been stopped and |start_cb| is destroyed.
368 // In this case we shouldn't do anything.
369 if (start_cb_
.is_null())
372 uint32 reasons
= GetReasonsForUncacheability(response
);
373 might_be_reused_from_cache_in_future_
= reasons
== 0;
374 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons
== 0);
376 int max_enum
= base::bits::Log2Ceiling(kMaxReason
);
378 DCHECK_LT(shift
, max_enum
); // Sanity check.
380 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift
, max_enum
);
385 // Expected content length can be |kPositionNotSpecified|, in that case
386 // |content_length_| is not specified and this is a streaming response.
387 content_length_
= response
.expectedContentLength();
389 // We make a strong assumption that when we reach here we have either
390 // received a response from HTTP/HTTPS protocol or the request was
391 // successful (in particular range request). So we only verify the partial
392 // response for HTTP and HTTPS protocol.
393 if (url_
.SchemeIsHTTPOrHTTPS()) {
394 bool partial_response
= (response
.httpStatusCode() == kHttpPartialContent
);
395 bool ok_response
= (response
.httpStatusCode() == kHttpOK
);
397 if (IsRangeRequest()) {
398 // Check to see whether the server supports byte ranges.
399 std::string accept_ranges
=
400 response
.httpHeaderField("Accept-Ranges").utf8();
401 range_supported_
= (accept_ranges
.find("bytes") != std::string::npos
);
403 // If we have verified the partial response and it is correct, we will
404 // return kOk. It's also possible for a server to support range requests
405 // without advertising "Accept-Ranges: bytes".
406 if (partial_response
&& VerifyPartialResponse(response
)) {
407 range_supported_
= true;
408 } else if (ok_response
&& first_byte_position_
== 0 &&
409 last_byte_position_
== kPositionNotSpecified
) {
410 // We accept a 200 response for a Range:0- request, trusting the
411 // Accept-Ranges header, because Apache thinks that's a reasonable thing
413 instance_size_
= content_length_
;
419 instance_size_
= content_length_
;
420 if (response
.httpStatusCode() != kHttpOK
) {
421 // We didn't request a range but server didn't reply with "200 OK".
428 CHECK_EQ(instance_size_
, kPositionNotSpecified
);
429 if (content_length_
!= kPositionNotSpecified
) {
430 if (first_byte_position_
== kPositionNotSpecified
)
431 instance_size_
= content_length_
;
432 else if (last_byte_position_
== kPositionNotSpecified
)
433 instance_size_
= content_length_
+ first_byte_position_
;
437 // Calls with a successful response.
441 void BufferedResourceLoader::didReceiveData(
442 WebURLLoader
* loader
,
445 int encoded_data_length
) {
446 DVLOG(1) << "didReceiveData: " << data_length
<< " bytes";
447 DCHECK(active_loader_
.get());
448 DCHECK_GT(data_length
, 0);
450 buffer_
.Append(reinterpret_cast<const uint8
*>(data
), data_length
);
452 // If there is an active read request, try to fulfill the request.
453 if (HasPendingRead() && CanFulfillRead())
456 // At last see if the buffer is full and we need to defer the downloading.
457 UpdateDeferBehavior();
459 // Consume excess bytes from our in-memory buffer if necessary.
460 if (buffer_
.forward_bytes() > buffer_
.forward_capacity()) {
461 int excess
= buffer_
.forward_bytes() - buffer_
.forward_capacity();
462 bool success
= buffer_
.Seek(excess
);
464 offset_
+= first_offset_
+ excess
;
467 // Notify latest progress and buffered offset.
468 progress_cb_
.Run(offset_
+ buffer_
.forward_bytes() - 1);
472 void BufferedResourceLoader::didDownloadData(
473 blink::WebURLLoader
* loader
,
475 int encoded_data_length
) {
479 void BufferedResourceLoader::didReceiveCachedMetadata(
480 WebURLLoader
* loader
,
486 void BufferedResourceLoader::didFinishLoading(
487 WebURLLoader
* loader
,
489 int64_t total_encoded_data_length
) {
490 DVLOG(1) << "didFinishLoading";
491 DCHECK(active_loader_
.get());
493 // We're done with the loader.
494 active_loader_
.reset();
495 loading_cb_
.Run(kLoadingFinished
);
497 // If we didn't know the |instance_size_| we do now.
498 if (instance_size_
== kPositionNotSpecified
) {
499 instance_size_
= offset_
+ buffer_
.forward_bytes();
502 // If there is a start callback, run it.
503 if (!start_cb_
.is_null()) {
504 DCHECK(read_cb_
.is_null())
505 << "Shouldn't have a read callback during start";
510 // Don't leave read callbacks hanging around.
511 if (HasPendingRead()) {
512 // Try to fulfill with what is in the buffer.
513 if (CanFulfillRead())
516 DoneRead(kCacheMiss
, 0);
520 void BufferedResourceLoader::didFail(
521 WebURLLoader
* loader
,
522 const WebURLError
& error
) {
523 DVLOG(1) << "didFail: reason=" << error
.reason
524 << ", isCancellation=" << error
.isCancellation
525 << ", domain=" << error
.domain
.utf8().data()
526 << ", localizedDescription="
527 << error
.localizedDescription
.utf8().data();
528 DCHECK(active_loader_
.get());
530 // We don't need to continue loading after failure.
532 // Keep it alive until we exit this method so that |error| remains valid.
533 scoped_ptr
<ActiveLoader
> active_loader
= active_loader_
.Pass();
534 loader_failed_
= true;
535 loading_cb_
.Run(kLoadingFailed
);
537 // Don't leave start callbacks hanging around.
538 if (!start_cb_
.is_null()) {
539 DCHECK(read_cb_
.is_null())
540 << "Shouldn't have a read callback during start";
545 // Don't leave read callbacks hanging around.
546 if (HasPendingRead()) {
547 DoneRead(kFailed
, 0);
551 bool BufferedResourceLoader::HasSingleOrigin() const {
552 DCHECK(start_cb_
.is_null())
553 << "Start() must complete before calling HasSingleOrigin()";
554 return single_origin_
;
557 bool BufferedResourceLoader::DidPassCORSAccessCheck() const {
558 DCHECK(start_cb_
.is_null())
559 << "Start() must complete before calling DidPassCORSAccessCheck()";
560 return !loader_failed_
&& cors_mode_
!= kUnspecified
;
563 void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy
) {
564 if (!might_be_reused_from_cache_in_future_
&& strategy
== kNeverDefer
)
565 strategy
= kCapacityDefer
;
566 defer_strategy_
= strategy
;
567 UpdateDeferBehavior();
570 void BufferedResourceLoader::SetPlaybackRate(float playback_rate
) {
571 playback_rate_
= playback_rate
;
573 // This is a pause so don't bother updating the buffer window as we'll likely
574 // get unpaused in the future.
575 if (playback_rate_
== 0.0)
578 UpdateBufferWindow();
581 void BufferedResourceLoader::SetBitrate(int bitrate
) {
582 DCHECK(bitrate
>= 0);
584 UpdateBufferWindow();
587 /////////////////////////////////////////////////////////////////////////////
590 void BufferedResourceLoader::UpdateBufferWindow() {
591 int backward_capacity
;
592 int forward_capacity
;
593 ComputeTargetBufferWindow(
594 playback_rate_
, bitrate_
, &backward_capacity
, &forward_capacity
);
596 // This does not evict data from the buffer if the new capacities are less
597 // than the current capacities; the new limits will be enforced after the
598 // existing excess buffered data is consumed.
599 buffer_
.set_backward_capacity(backward_capacity
);
600 buffer_
.set_forward_capacity(forward_capacity
);
603 void BufferedResourceLoader::UpdateDeferBehavior() {
607 SetDeferred(ShouldDefer());
610 void BufferedResourceLoader::SetDeferred(bool deferred
) {
611 if (active_loader_
->deferred() == deferred
)
614 active_loader_
->SetDeferred(deferred
);
615 loading_cb_
.Run(deferred
? kLoadingDeferred
: kLoading
);
618 bool BufferedResourceLoader::ShouldDefer() const {
619 switch(defer_strategy_
) {
624 DCHECK(read_cb_
.is_null() || last_offset_
> buffer_
.forward_bytes())
625 << "We shouldn't stop deferring if we can fulfill the read";
626 return read_cb_
.is_null();
629 return buffer_
.forward_bytes() >= buffer_
.forward_capacity();
635 bool BufferedResourceLoader::CanFulfillRead() const {
636 // If we are reading too far in the backward direction.
637 if (first_offset_
< 0 && (first_offset_
+ buffer_
.backward_bytes()) < 0)
640 // If the start offset is too far ahead.
641 if (first_offset_
>= buffer_
.forward_bytes())
644 // At the point, we verified that first byte requested is within the buffer.
645 // If the request has completed, then just returns with what we have now.
649 // If the resource request is still active, make sure the whole requested
651 if (last_offset_
> buffer_
.forward_bytes())
657 bool BufferedResourceLoader::WillFulfillRead() const {
658 // Trying to read too far behind.
659 if (first_offset_
< 0 && (first_offset_
+ buffer_
.backward_bytes()) < 0)
662 // Trying to read too far ahead.
663 if ((first_offset_
- buffer_
.forward_bytes()) >= kForwardWaitThreshold
)
666 // The resource request has completed, there's no way we can fulfill the
674 void BufferedResourceLoader::ReadInternal() {
675 // Seek to the first byte requested.
676 bool ret
= buffer_
.Seek(first_offset_
);
680 int read
= buffer_
.Read(read_buffer_
, read_size_
);
681 offset_
+= first_offset_
+ read
;
683 // And report with what we have read.
687 int64
BufferedResourceLoader::first_byte_position() const {
688 return first_byte_position_
;
692 bool BufferedResourceLoader::ParseContentRange(
693 const std::string
& content_range_str
, int64
* first_byte_position
,
694 int64
* last_byte_position
, int64
* instance_size
) {
695 const std::string kUpThroughBytesUnit
= "bytes ";
696 if (content_range_str
.find(kUpThroughBytesUnit
) != 0)
698 std::string range_spec
=
699 content_range_str
.substr(kUpThroughBytesUnit
.length());
700 size_t dash_offset
= range_spec
.find("-");
701 size_t slash_offset
= range_spec
.find("/");
703 if (dash_offset
== std::string::npos
|| slash_offset
== std::string::npos
||
704 slash_offset
< dash_offset
|| slash_offset
+ 1 == range_spec
.length()) {
707 if (!base::StringToInt64(range_spec
.substr(0, dash_offset
),
708 first_byte_position
) ||
709 !base::StringToInt64(range_spec
.substr(dash_offset
+ 1,
710 slash_offset
- dash_offset
- 1),
711 last_byte_position
)) {
714 if (slash_offset
== range_spec
.length() - 2 &&
715 range_spec
[slash_offset
+ 1] == '*') {
716 *instance_size
= kPositionNotSpecified
;
718 if (!base::StringToInt64(range_spec
.substr(slash_offset
+ 1),
723 if (*last_byte_position
< *first_byte_position
||
724 (*instance_size
!= kPositionNotSpecified
&&
725 *last_byte_position
>= *instance_size
)) {
732 bool BufferedResourceLoader::VerifyPartialResponse(
733 const WebURLResponse
& response
) {
734 int64 first_byte_position
, last_byte_position
, instance_size
;
735 if (!ParseContentRange(response
.httpHeaderField("Content-Range").utf8(),
736 &first_byte_position
, &last_byte_position
,
741 if (instance_size
!= kPositionNotSpecified
) {
742 instance_size_
= instance_size
;
745 if (first_byte_position_
!= kPositionNotSpecified
&&
746 first_byte_position_
!= first_byte_position
) {
750 // TODO(hclam): I should also check |last_byte_position|, but since
751 // we will never make such a request that it is ok to leave it unimplemented.
755 void BufferedResourceLoader::DoneRead(Status status
, int bytes_read
) {
756 if (saved_forward_capacity_
) {
757 buffer_
.set_forward_capacity(saved_forward_capacity_
);
758 saved_forward_capacity_
= 0;
767 base::ResetAndReturn(&read_cb_
).Run(status
, bytes_read
);
771 void BufferedResourceLoader::DoneStart(Status status
) {
772 base::ResetAndReturn(&start_cb_
).Run(status
);
775 bool BufferedResourceLoader::IsRangeRequest() const {
776 return first_byte_position_
!= kPositionNotSpecified
;
779 void BufferedResourceLoader::Log() {
780 media_log_
->AddEvent(
781 media_log_
->CreateBufferedExtentsChangedEvent(
782 offset_
- buffer_
.backward_bytes(),
784 offset_
+ buffer_
.forward_bytes()));
787 } // namespace content