• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
retries()41   int retries() { return retries_; }
IncrementRetries()42   void IncrementRetries() { ++retries_; }
43 
position()44   int64 position() { return position_; }
size()45   int size() { return size_; }
data()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 
ReadOperation(int64 position,int size,uint8 * data,const media::DataSource::ReadCB & callback)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 
~ReadOperation()70 BufferedDataSource::ReadOperation::~ReadOperation() {
71   DCHECK(callback_.is_null());
72 }
73 
74 // static
Run(scoped_ptr<ReadOperation> read_op,int result)75 void BufferedDataSource::ReadOperation::Run(
76     scoped_ptr<ReadOperation> read_op, int result) {
77   base::ResetAndReturn(&read_op->callback_).Run(result);
78 }
79 
BufferedDataSource(const scoped_refptr<base::MessageLoopProxy> & render_loop,WebFrame * frame,media::MediaLog * media_log,const DownloadingCB & downloading_cb)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 
~BufferedDataSource()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.
CreateResourceLoader(int64 first_byte_position,int64 last_byte_position)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 
set_host(media::DataSourceHost * host)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 
Initialize(const GURL & url,BufferedResourceLoader::CORSMode cors_mode,const InitializeCB & init_cb)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 
SetPreload(Preload preload)169 void BufferedDataSource::SetPreload(Preload preload) {
170   DCHECK(render_loop_->BelongsToCurrentThread());
171   preload_ = preload;
172 }
173 
HasSingleOrigin()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 
DidPassCORSAccessCheck() const181 bool BufferedDataSource::DidPassCORSAccessCheck() const {
182   return loader_.get() && loader_->DidPassCORSAccessCheck();
183 }
184 
Abort()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 
MediaPlaybackRateChanged(float playback_rate)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 
MediaIsPlaying()206 void BufferedDataSource::MediaIsPlaying() {
207   DCHECK(render_loop_->BelongsToCurrentThread());
208   media_has_played_ = true;
209   UpdateDeferStrategy(false);
210 }
211 
MediaIsPaused()212 void BufferedDataSource::MediaIsPaused() {
213   DCHECK(render_loop_->BelongsToCurrentThread());
214   UpdateDeferStrategy(true);
215 }
216 
217 /////////////////////////////////////////////////////////////////////////////
218 // media::DataSource implementation.
Stop(const base::Closure & closure)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 
SetBitrate(int bitrate)230 void BufferedDataSource::SetBitrate(int bitrate) {
231   render_loop_->PostTask(FROM_HERE, base::Bind(
232       &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
233 }
234 
Read(int64 position,int size,uint8 * data,const media::DataSource::ReadCB & read_cb)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 
GetSize(int64 * size_out)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 
IsStreaming()266 bool BufferedDataSource::IsStreaming() {
267   return streaming_;
268 }
269 
270 /////////////////////////////////////////////////////////////////////////////
271 // Render thread tasks.
ReadTask()272 void BufferedDataSource::ReadTask() {
273   DCHECK(render_loop_->BelongsToCurrentThread());
274   ReadInternal();
275 }
276 
StopInternal_Locked()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 
StopLoader()292 void BufferedDataSource::StopLoader() {
293   DCHECK(render_loop_->BelongsToCurrentThread());
294 
295   if (loader_)
296     loader_->Stop();
297 }
298 
SetBitrateTask(int bitrate)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.
ReadInternal()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.
StartCallback(BufferedResourceLoader::Status status)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 
PartialReadStartCallback(BufferedResourceLoader::Status status)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 
ReadCallback(BufferedResourceLoader::Status status,int bytes_read)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 
LoadingStateChangedCallback(BufferedResourceLoader::LoadingState state)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 
ProgressCallback(int64 position)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 
ReportOrQueueBufferedBytes(int64 start,int64 end)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 
UpdateHostState_Locked()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 
UpdateDeferStrategy(bool paused)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