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
13using WebKit::WebFrame;
14
15namespace {
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.
20const int kInitialReadBufferSize = 32768;
21
22// Number of cache misses we allow for a single Read() before signaling an
23// error.
24const int kNumCacheMissRetries = 3;
25
26}  // namespace
27
28namespace content {
29
30class 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
59BufferedDataSource::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
70BufferedDataSource::ReadOperation::~ReadOperation() {
71  DCHECK(callback_.is_null());
72}
73
74// static
75void BufferedDataSource::ReadOperation::Run(
76    scoped_ptr<ReadOperation> read_op, int result) {
77  base::ResetAndReturn(&read_op->callback_).Run(result);
78}
79
80BufferedDataSource::BufferedDataSource(
81    const scoped_refptr<base::MessageLoopProxy>& render_loop,
82    WebFrame* frame,
83    media::MediaLog* media_log,
84    const DownloadingCB& downloading_cb)
85    : weak_factory_(this),
86      weak_this_(weak_factory_.GetWeakPtr()),
87      cors_mode_(BufferedResourceLoader::kUnspecified),
88      total_bytes_(kPositionNotSpecified),
89      assume_fully_buffered_(false),
90      streaming_(false),
91      frame_(frame),
92      intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
93      intermediate_read_buffer_size_(kInitialReadBufferSize),
94      render_loop_(render_loop),
95      stop_signal_received_(false),
96      media_has_played_(false),
97      preload_(AUTO),
98      bitrate_(0),
99      playback_rate_(0.0),
100      media_log_(media_log),
101      downloading_cb_(downloading_cb) {
102  DCHECK(!downloading_cb_.is_null());
103}
104
105BufferedDataSource::~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.
110BufferedResourceLoader* 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
128void 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
137void 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
169void BufferedDataSource::SetPreload(Preload preload) {
170  DCHECK(render_loop_->BelongsToCurrentThread());
171  preload_ = preload;
172}
173
174bool 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
181bool BufferedDataSource::DidPassCORSAccessCheck() const {
182  return loader_.get() && loader_->DidPassCORSAccessCheck();
183}
184
185void 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/////////////////////////////////////////////////////////////////////////////
196// media::DataSource implementation.
197void BufferedDataSource::Stop(const base::Closure& closure) {
198  {
199    base::AutoLock auto_lock(lock_);
200    StopInternal_Locked();
201  }
202  closure.Run();
203
204  render_loop_->PostTask(FROM_HERE,
205      base::Bind(&BufferedDataSource::StopLoader, weak_this_));
206}
207
208void BufferedDataSource::SetPlaybackRate(float playback_rate) {
209  render_loop_->PostTask(FROM_HERE, base::Bind(
210      &BufferedDataSource::SetPlaybackRateTask, weak_this_, playback_rate));
211}
212
213void BufferedDataSource::SetBitrate(int bitrate) {
214  render_loop_->PostTask(FROM_HERE, base::Bind(
215      &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
216}
217
218void BufferedDataSource::Read(
219    int64 position, int size, uint8* data,
220    const media::DataSource::ReadCB& read_cb) {
221  DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
222  DCHECK(!read_cb.is_null());
223
224  {
225    base::AutoLock auto_lock(lock_);
226    DCHECK(!read_op_);
227
228    if (stop_signal_received_) {
229      read_cb.Run(kReadError);
230      return;
231    }
232
233    read_op_.reset(new ReadOperation(position, size, data, read_cb));
234  }
235
236  render_loop_->PostTask(FROM_HERE, base::Bind(
237      &BufferedDataSource::ReadTask, weak_this_));
238}
239
240bool BufferedDataSource::GetSize(int64* size_out) {
241  if (total_bytes_ != kPositionNotSpecified) {
242    *size_out = total_bytes_;
243    return true;
244  }
245  *size_out = 0;
246  return false;
247}
248
249bool BufferedDataSource::IsStreaming() {
250  return streaming_;
251}
252
253/////////////////////////////////////////////////////////////////////////////
254// Render thread tasks.
255void BufferedDataSource::ReadTask() {
256  DCHECK(render_loop_->BelongsToCurrentThread());
257  ReadInternal();
258}
259
260void BufferedDataSource::StopInternal_Locked() {
261  lock_.AssertAcquired();
262  if (stop_signal_received_)
263    return;
264
265  stop_signal_received_ = true;
266
267  // Initialize() isn't part of the DataSource interface so don't call it in
268  // response to Stop().
269  init_cb_.Reset();
270
271  if (read_op_)
272    ReadOperation::Run(read_op_.Pass(), kReadError);
273}
274
275void BufferedDataSource::StopLoader() {
276  DCHECK(render_loop_->BelongsToCurrentThread());
277
278  if (loader_)
279    loader_->Stop();
280}
281
282void BufferedDataSource::SetPlaybackRateTask(float playback_rate) {
283  DCHECK(render_loop_->BelongsToCurrentThread());
284  DCHECK(loader_.get());
285
286  if (playback_rate != 0)
287    media_has_played_ = true;
288
289  playback_rate_ = playback_rate;
290  loader_->SetPlaybackRate(playback_rate);
291
292  if (!loader_->range_supported()) {
293    // 200 responses end up not being reused to satisfy future range requests,
294    // and we don't want to get too far ahead of the read-head (and thus require
295    // a restart), so keep to the thresholds.
296    loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
297  } else if (media_has_played_ && playback_rate == 0) {
298    // If the playback has started (at which point the preload value is ignored)
299    // and we're paused, then try to load as much as possible (the loader will
300    // fall back to kCapacityDefer if it knows the current response won't be
301    // useful from the cache in the future).
302    loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
303  } else {
304    // If media is currently playing or the page indicated preload=auto,
305    // use threshold strategy to enable/disable deferring when the buffer
306    // is full/depleted.
307    loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
308  }
309}
310
311void BufferedDataSource::SetBitrateTask(int bitrate) {
312  DCHECK(render_loop_->BelongsToCurrentThread());
313  DCHECK(loader_.get());
314
315  bitrate_ = bitrate;
316  loader_->SetBitrate(bitrate);
317}
318
319// This method is the place where actual read happens, |loader_| must be valid
320// prior to make this method call.
321void BufferedDataSource::ReadInternal() {
322  DCHECK(render_loop_->BelongsToCurrentThread());
323  int64 position = 0;
324  int size = 0;
325  {
326    base::AutoLock auto_lock(lock_);
327    if (stop_signal_received_)
328      return;
329
330    position = read_op_->position();
331    size = read_op_->size();
332  }
333
334  // First we prepare the intermediate read buffer for BufferedResourceLoader
335  // to write to.
336  if (size > intermediate_read_buffer_size_) {
337    intermediate_read_buffer_.reset(new uint8[size]);
338  }
339
340  // Perform the actual read with BufferedResourceLoader.
341  loader_->Read(
342      position, size, intermediate_read_buffer_.get(),
343      base::Bind(&BufferedDataSource::ReadCallback, weak_this_));
344}
345
346
347/////////////////////////////////////////////////////////////////////////////
348// BufferedResourceLoader callback methods.
349void BufferedDataSource::StartCallback(
350    BufferedResourceLoader::Status status) {
351  DCHECK(render_loop_->BelongsToCurrentThread());
352  DCHECK(loader_.get());
353
354  bool init_cb_is_null = false;
355  {
356    base::AutoLock auto_lock(lock_);
357    init_cb_is_null = init_cb_.is_null();
358  }
359  if (init_cb_is_null) {
360    loader_->Stop();
361    return;
362  }
363
364  // All responses must be successful. Resources that are assumed to be fully
365  // buffered must have a known content length.
366  bool success = status == BufferedResourceLoader::kOk &&
367      (!assume_fully_buffered_ ||
368       loader_->instance_size() != kPositionNotSpecified);
369
370  if (success) {
371    total_bytes_ = loader_->instance_size();
372    streaming_ = !assume_fully_buffered_ &&
373        (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
374  } else {
375    loader_->Stop();
376  }
377
378  // TODO(scherkus): we shouldn't have to lock to signal host(), see
379  // http://crbug.com/113712 for details.
380  base::AutoLock auto_lock(lock_);
381  if (stop_signal_received_)
382    return;
383
384  if (success)
385    UpdateHostState_Locked();
386
387  base::ResetAndReturn(&init_cb_).Run(success);
388}
389
390void BufferedDataSource::PartialReadStartCallback(
391    BufferedResourceLoader::Status status) {
392  DCHECK(render_loop_->BelongsToCurrentThread());
393  DCHECK(loader_.get());
394
395  if (status == BufferedResourceLoader::kOk) {
396    // Once the request has started successfully, we can proceed with
397    // reading from it.
398    ReadInternal();
399    return;
400  }
401
402  // Stop the resource loader since we have received an error.
403  loader_->Stop();
404
405  // TODO(scherkus): we shouldn't have to lock to signal host(), see
406  // http://crbug.com/113712 for details.
407  base::AutoLock auto_lock(lock_);
408  if (stop_signal_received_)
409    return;
410  ReadOperation::Run(read_op_.Pass(), kReadError);
411}
412
413void BufferedDataSource::ReadCallback(
414    BufferedResourceLoader::Status status,
415    int bytes_read) {
416  DCHECK(render_loop_->BelongsToCurrentThread());
417
418  // TODO(scherkus): we shouldn't have to lock to signal host(), see
419  // http://crbug.com/113712 for details.
420  base::AutoLock auto_lock(lock_);
421  if (stop_signal_received_)
422    return;
423
424  if (status != BufferedResourceLoader::kOk) {
425    // Stop the resource load if it failed.
426    loader_->Stop();
427
428    if (status == BufferedResourceLoader::kCacheMiss &&
429        read_op_->retries() < kNumCacheMissRetries) {
430      read_op_->IncrementRetries();
431
432      // Recreate a loader starting from where we last left off until the
433      // end of the resource.
434      loader_.reset(CreateResourceLoader(
435          read_op_->position(), kPositionNotSpecified));
436      loader_->Start(
437          base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this_),
438          base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
439                     weak_this_),
440          base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
441          frame_);
442      return;
443    }
444
445    ReadOperation::Run(read_op_.Pass(), kReadError);
446    return;
447  }
448
449  if (bytes_read > 0) {
450    memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
451  } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
452    // We've reached the end of the file and we didn't know the total size
453    // before. Update the total size so Read()s past the end of the file will
454    // fail like they would if we had known the file size at the beginning.
455    total_bytes_ = loader_->instance_size();
456
457    if (host() && total_bytes_ != kPositionNotSpecified) {
458      host()->SetTotalBytes(total_bytes_);
459      host()->AddBufferedByteRange(loader_->first_byte_position(),
460                                   total_bytes_);
461    }
462  }
463  ReadOperation::Run(read_op_.Pass(), bytes_read);
464}
465
466void BufferedDataSource::LoadingStateChangedCallback(
467    BufferedResourceLoader::LoadingState state) {
468  DCHECK(render_loop_->BelongsToCurrentThread());
469
470  if (assume_fully_buffered_)
471    return;
472
473  bool is_downloading_data;
474  switch (state) {
475    case BufferedResourceLoader::kLoading:
476      is_downloading_data = true;
477      break;
478    case BufferedResourceLoader::kLoadingDeferred:
479    case BufferedResourceLoader::kLoadingFinished:
480      is_downloading_data = false;
481      break;
482
483    // TODO(scherkus): we don't signal network activity changes when loads
484    // fail to preserve existing behaviour when deferring is toggled, however
485    // we should consider changing DownloadingCB to also propagate loading
486    // state. For example there isn't any signal today to notify the client that
487    // loading has failed (we only get errors on subsequent reads).
488    case BufferedResourceLoader::kLoadingFailed:
489      return;
490  }
491
492  downloading_cb_.Run(is_downloading_data);
493}
494
495void BufferedDataSource::ProgressCallback(int64 position) {
496  DCHECK(render_loop_->BelongsToCurrentThread());
497
498  if (assume_fully_buffered_)
499    return;
500
501  // TODO(scherkus): we shouldn't have to lock to signal host(), see
502  // http://crbug.com/113712 for details.
503  base::AutoLock auto_lock(lock_);
504  if (stop_signal_received_)
505    return;
506
507  ReportOrQueueBufferedBytes(loader_->first_byte_position(), position);
508}
509
510void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) {
511  if (host())
512    host()->AddBufferedByteRange(start, end);
513  else
514    queued_buffered_byte_ranges_.Add(start, end);
515}
516
517void BufferedDataSource::UpdateHostState_Locked() {
518  lock_.AssertAcquired();
519
520  if (!host())
521    return;
522
523  for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) {
524    host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i),
525                                 queued_buffered_byte_ranges_.end(i));
526  }
527  queued_buffered_byte_ranges_.clear();
528
529  if (total_bytes_ == kPositionNotSpecified)
530    return;
531
532  host()->SetTotalBytes(total_bytes_);
533
534  if (assume_fully_buffered_)
535    host()->AddBufferedByteRange(0, total_bytes_);
536}
537
538}  // namespace content
539