Home | History | Annotate | Download | only in media
      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.
      4 
      5 #include "content/renderer/media/buffered_data_source.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/callback_helpers.h"
      9 #include "base/message_loop/message_loop_proxy.h"
     10 #include "media/base/media_log.h"
     11 #include "net/base/net_errors.h"
     12 
     13 using blink::WebFrame;
     14 
     15 namespace {
     16 
     17 // BufferedDataSource has an intermediate buffer, this value governs the initial
     18 // size of that buffer. It is set to 32KB because this is a typical read size
     19 // of FFmpeg.
     20 const int kInitialReadBufferSize = 32768;
     21 
     22 // Number of cache misses we allow for a single Read() before signaling an
     23 // error.
     24 const int kNumCacheMissRetries = 3;
     25 
     26 }  // namespace
     27 
     28 namespace content {
     29 
     30 class BufferedDataSource::ReadOperation {
     31  public:
     32   ReadOperation(int64 position, int size, uint8* data,
     33                 const media::DataSource::ReadCB& callback);
     34   ~ReadOperation();
     35 
     36   // Runs |callback_| with the given |result|, deleting the operation
     37   // afterwards.
     38   static void Run(scoped_ptr<ReadOperation> read_op, int result);
     39 
     40   // State for the number of times this read operation has been retried.
     41   int retries() { return retries_; }
     42   void IncrementRetries() { ++retries_; }
     43 
     44   int64 position() { return position_; }
     45   int size() { return size_; }
     46   uint8* data() { return data_; }
     47 
     48  private:
     49   int retries_;
     50 
     51   const int64 position_;
     52   const int size_;
     53   uint8* data_;
     54   media::DataSource::ReadCB callback_;
     55 
     56   DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation);
     57 };
     58 
     59 BufferedDataSource::ReadOperation::ReadOperation(
     60     int64 position, int size, uint8* data,
     61     const media::DataSource::ReadCB& callback)
     62     : retries_(0),
     63       position_(position),
     64       size_(size),
     65       data_(data),
     66       callback_(callback) {
     67   DCHECK(!callback_.is_null());
     68 }
     69 
     70 BufferedDataSource::ReadOperation::~ReadOperation() {
     71   DCHECK(callback_.is_null());
     72 }
     73 
     74 // static
     75 void BufferedDataSource::ReadOperation::Run(
     76     scoped_ptr<ReadOperation> read_op, int result) {
     77   base::ResetAndReturn(&read_op->callback_).Run(result);
     78 }
     79 
     80 BufferedDataSource::BufferedDataSource(
     81     const scoped_refptr<base::MessageLoopProxy>& render_loop,
     82     WebFrame* frame,
     83     media::MediaLog* media_log,
     84     const DownloadingCB& downloading_cb)
     85     : cors_mode_(BufferedResourceLoader::kUnspecified),
     86       total_bytes_(kPositionNotSpecified),
     87       assume_fully_buffered_(false),
     88       streaming_(false),
     89       frame_(frame),
     90       intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
     91       intermediate_read_buffer_size_(kInitialReadBufferSize),
     92       render_loop_(render_loop),
     93       stop_signal_received_(false),
     94       media_has_played_(false),
     95       preload_(AUTO),
     96       bitrate_(0),
     97       playback_rate_(0.0),
     98       media_log_(media_log),
     99       downloading_cb_(downloading_cb),
    100       weak_factory_(this) {
    101   DCHECK(!downloading_cb_.is_null());
    102   weak_this_ = weak_factory_.GetWeakPtr();
    103 }
    104 
    105 BufferedDataSource::~BufferedDataSource() {}
    106 
    107 // A factory method to create BufferedResourceLoader using the read parameters.
    108 // This method can be overridden to inject mock BufferedResourceLoader object
    109 // for testing purpose.
    110 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
    111     int64 first_byte_position, int64 last_byte_position) {
    112   DCHECK(render_loop_->BelongsToCurrentThread());
    113 
    114   BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
    115       BufferedResourceLoader::kReadThenDefer :
    116       BufferedResourceLoader::kCapacityDefer;
    117 
    118   return new BufferedResourceLoader(url_,
    119                                     cors_mode_,
    120                                     first_byte_position,
    121                                     last_byte_position,
    122                                     strategy,
    123                                     bitrate_,
    124                                     playback_rate_,
    125                                     media_log_.get());
    126 }
    127 
    128 void BufferedDataSource::set_host(media::DataSourceHost* host) {
    129   DataSource::set_host(host);
    130 
    131   if (loader_) {
    132     base::AutoLock auto_lock(lock_);
    133     UpdateHostState_Locked();
    134   }
    135 }
    136 
    137 void BufferedDataSource::Initialize(
    138     const GURL& url,
    139     BufferedResourceLoader::CORSMode cors_mode,
    140     const InitializeCB& init_cb) {
    141   DCHECK(render_loop_->BelongsToCurrentThread());
    142   DCHECK(!init_cb.is_null());
    143   DCHECK(!loader_.get());
    144   url_ = url;
    145   cors_mode_ = cors_mode;
    146 
    147   init_cb_ = init_cb;
    148 
    149   if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
    150     // Do an unbounded range request starting at the beginning.  If the server
    151     // responds with 200 instead of 206 we'll fall back into a streaming mode.
    152     loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
    153   } else {
    154     // For all other protocols, assume they support range request. We fetch
    155     // the full range of the resource to obtain the instance size because
    156     // we won't be served HTTP headers.
    157     loader_.reset(CreateResourceLoader(kPositionNotSpecified,
    158                                        kPositionNotSpecified));
    159     assume_fully_buffered_ = true;
    160   }
    161 
    162   loader_->Start(
    163       base::Bind(&BufferedDataSource::StartCallback, weak_this_),
    164       base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this_),
    165       base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
    166       frame_);
    167 }
    168 
    169 void BufferedDataSource::SetPreload(Preload preload) {
    170   DCHECK(render_loop_->BelongsToCurrentThread());
    171   preload_ = preload;
    172 }
    173 
    174 bool BufferedDataSource::HasSingleOrigin() {
    175   DCHECK(render_loop_->BelongsToCurrentThread());
    176   DCHECK(init_cb_.is_null() && loader_.get())
    177       << "Initialize() must complete before calling HasSingleOrigin()";
    178   return loader_->HasSingleOrigin();
    179 }
    180 
    181 bool BufferedDataSource::DidPassCORSAccessCheck() const {
    182   return loader_.get() && loader_->DidPassCORSAccessCheck();
    183 }
    184 
    185 void BufferedDataSource::Abort() {
    186   DCHECK(render_loop_->BelongsToCurrentThread());
    187   {
    188     base::AutoLock auto_lock(lock_);
    189     StopInternal_Locked();
    190   }
    191   StopLoader();
    192   frame_ = NULL;
    193 }
    194 
    195 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) {
    196   DCHECK(render_loop_->BelongsToCurrentThread());
    197   DCHECK(loader_.get());
    198 
    199   if (playback_rate < 0.0f)
    200     return;
    201 
    202   playback_rate_ = playback_rate;
    203   loader_->SetPlaybackRate(playback_rate);
    204 }
    205 
    206 void BufferedDataSource::MediaIsPlaying() {
    207   DCHECK(render_loop_->BelongsToCurrentThread());
    208   media_has_played_ = true;
    209   UpdateDeferStrategy(false);
    210 }
    211 
    212 void BufferedDataSource::MediaIsPaused() {
    213   DCHECK(render_loop_->BelongsToCurrentThread());
    214   UpdateDeferStrategy(true);
    215 }
    216 
    217 /////////////////////////////////////////////////////////////////////////////
    218 // media::DataSource implementation.
    219 void BufferedDataSource::Stop(const base::Closure& closure) {
    220   {
    221     base::AutoLock auto_lock(lock_);
    222     StopInternal_Locked();
    223   }
    224   closure.Run();
    225 
    226   render_loop_->PostTask(FROM_HERE,
    227       base::Bind(&BufferedDataSource::StopLoader, weak_this_));
    228 }
    229 
    230 void BufferedDataSource::SetBitrate(int bitrate) {
    231   render_loop_->PostTask(FROM_HERE, base::Bind(
    232       &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
    233 }
    234 
    235 void BufferedDataSource::Read(
    236     int64 position, int size, uint8* data,
    237     const media::DataSource::ReadCB& read_cb) {
    238   DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
    239   DCHECK(!read_cb.is_null());
    240 
    241   {
    242     base::AutoLock auto_lock(lock_);
    243     DCHECK(!read_op_);
    244 
    245     if (stop_signal_received_) {
    246       read_cb.Run(kReadError);
    247       return;
    248     }
    249 
    250     read_op_.reset(new ReadOperation(position, size, data, read_cb));
    251   }
    252 
    253   render_loop_->PostTask(FROM_HERE, base::Bind(
    254       &BufferedDataSource::ReadTask, weak_this_));
    255 }
    256 
    257 bool BufferedDataSource::GetSize(int64* size_out) {
    258   if (total_bytes_ != kPositionNotSpecified) {
    259     *size_out = total_bytes_;
    260     return true;
    261   }
    262   *size_out = 0;
    263   return false;
    264 }
    265 
    266 bool BufferedDataSource::IsStreaming() {
    267   return streaming_;
    268 }
    269 
    270 /////////////////////////////////////////////////////////////////////////////
    271 // Render thread tasks.
    272 void BufferedDataSource::ReadTask() {
    273   DCHECK(render_loop_->BelongsToCurrentThread());
    274   ReadInternal();
    275 }
    276 
    277 void BufferedDataSource::StopInternal_Locked() {
    278   lock_.AssertAcquired();
    279   if (stop_signal_received_)
    280     return;
    281 
    282   stop_signal_received_ = true;
    283 
    284   // Initialize() isn't part of the DataSource interface so don't call it in
    285   // response to Stop().
    286   init_cb_.Reset();
    287 
    288   if (read_op_)
    289     ReadOperation::Run(read_op_.Pass(), kReadError);
    290 }
    291 
    292 void BufferedDataSource::StopLoader() {
    293   DCHECK(render_loop_->BelongsToCurrentThread());
    294 
    295   if (loader_)
    296     loader_->Stop();
    297 }
    298 
    299 void BufferedDataSource::SetBitrateTask(int bitrate) {
    300   DCHECK(render_loop_->BelongsToCurrentThread());
    301   DCHECK(loader_.get());
    302 
    303   bitrate_ = bitrate;
    304   loader_->SetBitrate(bitrate);
    305 }
    306 
    307 // This method is the place where actual read happens, |loader_| must be valid
    308 // prior to make this method call.
    309 void BufferedDataSource::ReadInternal() {
    310   DCHECK(render_loop_->BelongsToCurrentThread());
    311   int64 position = 0;
    312   int size = 0;
    313   {
    314     base::AutoLock auto_lock(lock_);
    315     if (stop_signal_received_)
    316       return;
    317 
    318     position = read_op_->position();
    319     size = read_op_->size();
    320   }
    321 
    322   // First we prepare the intermediate read buffer for BufferedResourceLoader
    323   // to write to.
    324   if (size > intermediate_read_buffer_size_) {
    325     intermediate_read_buffer_.reset(new uint8[size]);
    326   }
    327 
    328   // Perform the actual read with BufferedResourceLoader.
    329   loader_->Read(
    330       position, size, intermediate_read_buffer_.get(),
    331       base::Bind(&BufferedDataSource::ReadCallback, weak_this_));
    332 }
    333 
    334 
    335 /////////////////////////////////////////////////////////////////////////////
    336 // BufferedResourceLoader callback methods.
    337 void BufferedDataSource::StartCallback(
    338     BufferedResourceLoader::Status status) {
    339   DCHECK(render_loop_->BelongsToCurrentThread());
    340   DCHECK(loader_.get());
    341 
    342   bool init_cb_is_null = false;
    343   {
    344     base::AutoLock auto_lock(lock_);
    345     init_cb_is_null = init_cb_.is_null();
    346   }
    347   if (init_cb_is_null) {
    348     loader_->Stop();
    349     return;
    350   }
    351 
    352   // All responses must be successful. Resources that are assumed to be fully
    353   // buffered must have a known content length.
    354   bool success = status == BufferedResourceLoader::kOk &&
    355       (!assume_fully_buffered_ ||
    356        loader_->instance_size() != kPositionNotSpecified);
    357 
    358   if (success) {
    359     total_bytes_ = loader_->instance_size();
    360     streaming_ = !assume_fully_buffered_ &&
    361         (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
    362 
    363     media_log_->SetDoubleProperty("total_bytes",
    364                                   static_cast<double>(total_bytes_));
    365     media_log_->SetBooleanProperty("streaming", streaming_);
    366   } else {
    367     loader_->Stop();
    368   }
    369 
    370   // TODO(scherkus): we shouldn't have to lock to signal host(), see
    371   // http://crbug.com/113712 for details.
    372   base::AutoLock auto_lock(lock_);
    373   if (stop_signal_received_)
    374     return;
    375 
    376   if (success) {
    377     UpdateHostState_Locked();
    378     media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin());
    379     media_log_->SetBooleanProperty("passed_cors_access_check",
    380                                    loader_->DidPassCORSAccessCheck());
    381     media_log_->SetBooleanProperty("range_header_supported",
    382                                    loader_->range_supported());
    383   }
    384 
    385   base::ResetAndReturn(&init_cb_).Run(success);
    386 }
    387 
    388 void BufferedDataSource::PartialReadStartCallback(
    389     BufferedResourceLoader::Status status) {
    390   DCHECK(render_loop_->BelongsToCurrentThread());
    391   DCHECK(loader_.get());
    392 
    393   if (status == BufferedResourceLoader::kOk) {
    394     // Once the request has started successfully, we can proceed with
    395     // reading from it.
    396     ReadInternal();
    397     return;
    398   }
    399 
    400   // Stop the resource loader since we have received an error.
    401   loader_->Stop();
    402 
    403   // TODO(scherkus): we shouldn't have to lock to signal host(), see
    404   // http://crbug.com/113712 for details.
    405   base::AutoLock auto_lock(lock_);
    406   if (stop_signal_received_)
    407     return;
    408   ReadOperation::Run(read_op_.Pass(), kReadError);
    409 }
    410 
    411 void BufferedDataSource::ReadCallback(
    412     BufferedResourceLoader::Status status,
    413     int bytes_read) {
    414   DCHECK(render_loop_->BelongsToCurrentThread());
    415 
    416   // TODO(scherkus): we shouldn't have to lock to signal host(), see
    417   // http://crbug.com/113712 for details.
    418   base::AutoLock auto_lock(lock_);
    419   if (stop_signal_received_)
    420     return;
    421 
    422   if (status != BufferedResourceLoader::kOk) {
    423     // Stop the resource load if it failed.
    424     loader_->Stop();
    425 
    426     if (status == BufferedResourceLoader::kCacheMiss &&
    427         read_op_->retries() < kNumCacheMissRetries) {
    428       read_op_->IncrementRetries();
    429 
    430       // Recreate a loader starting from where we last left off until the
    431       // end of the resource.
    432       loader_.reset(CreateResourceLoader(
    433           read_op_->position(), kPositionNotSpecified));
    434       loader_->Start(
    435           base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this_),
    436           base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
    437                      weak_this_),
    438           base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
    439           frame_);
    440       return;
    441     }
    442 
    443     ReadOperation::Run(read_op_.Pass(), kReadError);
    444     return;
    445   }
    446 
    447   if (bytes_read > 0) {
    448     memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
    449   } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
    450     // We've reached the end of the file and we didn't know the total size
    451     // before. Update the total size so Read()s past the end of the file will
    452     // fail like they would if we had known the file size at the beginning.
    453     total_bytes_ = loader_->instance_size();
    454 
    455     if (host() && total_bytes_ != kPositionNotSpecified) {
    456       host()->SetTotalBytes(total_bytes_);
    457       host()->AddBufferedByteRange(loader_->first_byte_position(),
    458                                    total_bytes_);
    459     }
    460   }
    461   ReadOperation::Run(read_op_.Pass(), bytes_read);
    462 }
    463 
    464 void BufferedDataSource::LoadingStateChangedCallback(
    465     BufferedResourceLoader::LoadingState state) {
    466   DCHECK(render_loop_->BelongsToCurrentThread());
    467 
    468   if (assume_fully_buffered_)
    469     return;
    470 
    471   bool is_downloading_data;
    472   switch (state) {
    473     case BufferedResourceLoader::kLoading:
    474       is_downloading_data = true;
    475       break;
    476     case BufferedResourceLoader::kLoadingDeferred:
    477     case BufferedResourceLoader::kLoadingFinished:
    478       is_downloading_data = false;
    479       break;
    480 
    481     // TODO(scherkus): we don't signal network activity changes when loads
    482     // fail to preserve existing behaviour when deferring is toggled, however
    483     // we should consider changing DownloadingCB to also propagate loading
    484     // state. For example there isn't any signal today to notify the client that
    485     // loading has failed (we only get errors on subsequent reads).
    486     case BufferedResourceLoader::kLoadingFailed:
    487       return;
    488   }
    489 
    490   downloading_cb_.Run(is_downloading_data);
    491 }
    492 
    493 void BufferedDataSource::ProgressCallback(int64 position) {
    494   DCHECK(render_loop_->BelongsToCurrentThread());
    495 
    496   if (assume_fully_buffered_)
    497     return;
    498 
    499   // TODO(scherkus): we shouldn't have to lock to signal host(), see
    500   // http://crbug.com/113712 for details.
    501   base::AutoLock auto_lock(lock_);
    502   if (stop_signal_received_)
    503     return;
    504 
    505   ReportOrQueueBufferedBytes(loader_->first_byte_position(), position);
    506 }
    507 
    508 void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) {
    509   if (host())
    510     host()->AddBufferedByteRange(start, end);
    511   else
    512     queued_buffered_byte_ranges_.Add(start, end);
    513 }
    514 
    515 void BufferedDataSource::UpdateHostState_Locked() {
    516   lock_.AssertAcquired();
    517 
    518   if (!host())
    519     return;
    520 
    521   for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) {
    522     host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i),
    523                                  queued_buffered_byte_ranges_.end(i));
    524   }
    525   queued_buffered_byte_ranges_.clear();
    526 
    527   if (total_bytes_ == kPositionNotSpecified)
    528     return;
    529 
    530   host()->SetTotalBytes(total_bytes_);
    531 
    532   if (assume_fully_buffered_)
    533     host()->AddBufferedByteRange(0, total_bytes_);
    534 }
    535 
    536 void BufferedDataSource::UpdateDeferStrategy(bool paused) {
    537   // 200 responses end up not being reused to satisfy future range requests,
    538   // and we don't want to get too far ahead of the read-head (and thus require
    539   // a restart), so keep to the thresholds.
    540   if (!loader_->range_supported()) {
    541     loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
    542     return;
    543   }
    544 
    545   // If the playback has started (at which point the preload value is ignored)
    546   // and we're paused, then try to load as much as possible (the loader will
    547   // fall back to kCapacityDefer if it knows the current response won't be
    548   // useful from the cache in the future).
    549   if (media_has_played_ && paused) {
    550     loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
    551     return;
    552   }
    553 
    554   // If media is currently playing or the page indicated preload=auto,
    555   // use threshold strategy to enable/disable deferring when the buffer
    556   // is full/depleted.
    557   loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
    558 }
    559 
    560 }  // namespace content
    561