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