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 blink::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    : 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
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
195void 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
206void BufferedDataSource::MediaIsPlaying() {
207  DCHECK(render_loop_->BelongsToCurrentThread());
208  media_has_played_ = true;
209  UpdateDeferStrategy(false);
210}
211
212void BufferedDataSource::MediaIsPaused() {
213  DCHECK(render_loop_->BelongsToCurrentThread());
214  UpdateDeferStrategy(true);
215}
216
217/////////////////////////////////////////////////////////////////////////////
218// media::DataSource implementation.
219void 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
230void BufferedDataSource::SetBitrate(int bitrate) {
231  render_loop_->PostTask(FROM_HERE, base::Bind(
232      &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
233}
234
235void 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
257bool 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
266bool BufferedDataSource::IsStreaming() {
267  return streaming_;
268}
269
270/////////////////////////////////////////////////////////////////////////////
271// Render thread tasks.
272void BufferedDataSource::ReadTask() {
273  DCHECK(render_loop_->BelongsToCurrentThread());
274  ReadInternal();
275}
276
277void 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
292void BufferedDataSource::StopLoader() {
293  DCHECK(render_loop_->BelongsToCurrentThread());
294
295  if (loader_)
296    loader_->Stop();
297}
298
299void 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.
309void 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.
337void 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
388void 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
411void 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
464void 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
493void 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
508void 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
515void 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
536void 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