summaryrefslogtreecommitdiffstats
path: root/media/blink/buffered_resource_loader.cc
blob: 01427a1c681e22a93738737254d0dff4e84c6f92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
// 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 <stddef.h>
#include <stdint.h>
#include <limits>
#include <utility>

#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(double 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 double 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.0) {
    backward_playback = true;
    playback_rate *= -1.0;
  }

  playback_rate = std::max(playback_rate, 1.0);
  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_t first_byte_position,
                                               int64_t last_byte_position,
                                               DeferStrategy strategy,
                                               int bitrate,
                                               double 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),
      cancel_upon_deferral_(false) {
  // 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 = std::move(test_loader_);
  } 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(std::move(loader)));
  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_t position,
                                  int read_size,
                                  uint8_t* 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_ + std::numeric_limits<int32_t>::max() ||
      read_position_ < offset_ + std::numeric_limits<int32_t>::min()) {
    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_t BufferedResourceLoader::content_length() {
  return content_length_;
}

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

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

/////////////////////////////////////////////////////////////////////////////
// blink::WebURLLoaderClient implementation.
void BufferedResourceLoader::willFollowRedirect(
    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::HTTPVersion_0_9
                   ? "0.9"
                   : response.httpVersion() == WebURLResponse::HTTPVersion_1_0
                         ? "1.0"
                         : response.httpVersion() ==
                                   WebURLResponse::HTTPVersion_1_1
                               ? "1.1"
                               : response.httpVersion() ==
                                         WebURLResponse::HTTPVersion_2_0
                                     ? "2.0"
                                     : "Unknown")
           << " " << response.httpStatusCode();
  DCHECK(active_loader_.get());
  response_original_url_ = response.wasFetchedViaServiceWorker()
                               ? response.originalURLViaServiceWorker()
                               : response.url();

  // 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_t 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 {
        MEDIA_LOG(ERROR, media_log_)
            << "Failed loading buffered resource using range request due to "
               "invalid server response. HTTP status code="
            << response.httpStatusCode();
        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".
        MEDIA_LOG(ERROR, media_log_)
            << "Failed loading buffered resource due to invalid server "
               "response. HTTP status code=" << response.httpStatusCode();
        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_t*>(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());
  MEDIA_LOG(ERROR, media_log_)
      << "Failed loading buffered resource. Error code=" << error.reason;

  // 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 = std::move(active_loader_);
  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 {
  // Until Start() is done we don't know, assume no until we know.
  if (!start_cb_.is_null())
    return false;
  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(double 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;

  // Abort any cancellations in progress if playback starts.
  if (playback_rate_ > 0 && cancel_upon_deferral_)
    cancel_upon_deferral_ = false;

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

  if (deferred && cancel_upon_deferral_)
    CancelUponDeferral();
}

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_t BufferedResourceLoader::first_byte_position() const {
  return first_byte_position_;
}

// static
bool BufferedResourceLoader::ParseContentRange(
    const std::string& content_range_str,
    int64_t* first_byte_position,
    int64_t* last_byte_position,
    int64_t* 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;
}

void BufferedResourceLoader::CancelUponDeferral() {
  cancel_upon_deferral_ = true;
  if (active_loader_ && active_loader_->deferred())
    active_loader_.reset();
}

int64_t BufferedResourceLoader::GetMemoryUsage() const {
  return buffer_.forward_bytes() + buffer_.backward_bytes();
}

bool BufferedResourceLoader::VerifyPartialResponse(
    const WebURLResponse& response) {
  int64_t 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