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.
5 #include "content/renderer/media/buffered_data_source.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"
14 using blink::WebFrame;
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
21 const int kInitialReadBufferSize = 32768;
23 // Number of cache misses we allow for a single Read() before signaling an
25 const int kNumCacheMissRetries = 3;
31 class BufferedDataSource::ReadOperation {
33 ReadOperation(int64 position, int size, uint8* data,
34 const media::DataSource::ReadCB& callback);
37 // Runs |callback_| with the given |result|, deleting the operation
39 static void Run(scoped_ptr<ReadOperation> read_op, int result);
41 // State for the number of times this read operation has been retried.
42 int retries() { return retries_; }
43 void IncrementRetries() { ++retries_; }
45 int64 position() { return position_; }
46 int size() { return size_; }
47 uint8* data() { return data_; }
52 const int64 position_;
55 media::DataSource::ReadCB callback_;
57 DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation);
60 BufferedDataSource::ReadOperation::ReadOperation(
61 int64 position, int size, uint8* data,
62 const media::DataSource::ReadCB& callback)
68 DCHECK(!callback_.is_null());
71 BufferedDataSource::ReadOperation::~ReadOperation() {
72 DCHECK(callback_.is_null());
76 void BufferedDataSource::ReadOperation::Run(
77 scoped_ptr<ReadOperation> read_op, int result) {
78 base::ResetAndReturn(&read_op->callback_).Run(result);
81 BufferedDataSource::BufferedDataSource(
83 BufferedResourceLoader::CORSMode cors_mode,
84 const scoped_refptr<base::MessageLoopProxy>& render_loop,
86 media::MediaLog* media_log,
87 BufferedDataSourceHost* host,
88 const DownloadingCB& downloading_cb)
90 cors_mode_(cors_mode),
91 total_bytes_(kPositionNotSpecified),
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),
102 media_log_(media_log),
104 downloading_cb_(downloading_cb),
105 weak_factory_(this) {
107 DCHECK(!downloading_cb_.is_null());
110 BufferedDataSource::~BufferedDataSource() {}
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.
115 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
116 int64 first_byte_position, int64 last_byte_position) {
117 DCHECK(render_loop_->BelongsToCurrentThread());
119 BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
120 BufferedResourceLoader::kReadThenDefer :
121 BufferedResourceLoader::kCapacityDefer;
123 return new BufferedResourceLoader(url_,
133 void BufferedDataSource::Initialize(const InitializeCB& init_cb) {
134 DCHECK(render_loop_->BelongsToCurrentThread());
135 DCHECK(!init_cb.is_null());
136 DCHECK(!loader_.get());
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));
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));
152 base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
154 base::Bind(&BufferedDataSource::StartCallback, weak_this),
155 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this),
156 base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
160 void BufferedDataSource::SetPreload(Preload preload) {
161 DCHECK(render_loop_->BelongsToCurrentThread());
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();
172 bool BufferedDataSource::DidPassCORSAccessCheck() const {
173 return loader_.get() && loader_->DidPassCORSAccessCheck();
176 void BufferedDataSource::Abort() {
177 DCHECK(render_loop_->BelongsToCurrentThread());
179 base::AutoLock auto_lock(lock_);
180 StopInternal_Locked();
186 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) {
187 DCHECK(render_loop_->BelongsToCurrentThread());
188 DCHECK(loader_.get());
190 if (playback_rate < 0.0f)
193 playback_rate_ = playback_rate;
194 loader_->SetPlaybackRate(playback_rate);
197 void BufferedDataSource::MediaIsPlaying() {
198 DCHECK(render_loop_->BelongsToCurrentThread());
199 media_has_played_ = true;
200 UpdateDeferStrategy(false);
203 void BufferedDataSource::MediaIsPaused() {
204 DCHECK(render_loop_->BelongsToCurrentThread());
205 UpdateDeferStrategy(true);
208 /////////////////////////////////////////////////////////////////////////////
209 // media::DataSource implementation.
210 void BufferedDataSource::Stop() {
212 base::AutoLock auto_lock(lock_);
213 StopInternal_Locked();
216 render_loop_->PostTask(
218 base::Bind(&BufferedDataSource::StopLoader, weak_factory_.GetWeakPtr()));
221 void BufferedDataSource::SetBitrate(int bitrate) {
222 render_loop_->PostTask(FROM_HERE,
223 base::Bind(&BufferedDataSource::SetBitrateTask,
224 weak_factory_.GetWeakPtr(),
228 void BufferedDataSource::Read(
229 int64 position, int size, uint8* data,
230 const media::DataSource::ReadCB& read_cb) {
231 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
232 DCHECK(!read_cb.is_null());
235 base::AutoLock auto_lock(lock_);
238 if (stop_signal_received_) {
239 read_cb.Run(kReadError);
243 read_op_.reset(new ReadOperation(position, size, data, read_cb));
246 render_loop_->PostTask(
248 base::Bind(&BufferedDataSource::ReadTask, weak_factory_.GetWeakPtr()));
251 bool BufferedDataSource::GetSize(int64* size_out) {
252 if (total_bytes_ != kPositionNotSpecified) {
253 *size_out = total_bytes_;
260 bool BufferedDataSource::IsStreaming() {
264 /////////////////////////////////////////////////////////////////////////////
265 // Render thread tasks.
266 void BufferedDataSource::ReadTask() {
267 DCHECK(render_loop_->BelongsToCurrentThread());
271 void BufferedDataSource::StopInternal_Locked() {
272 lock_.AssertAcquired();
273 if (stop_signal_received_)
276 stop_signal_received_ = true;
278 // Initialize() isn't part of the DataSource interface so don't call it in
279 // response to Stop().
283 ReadOperation::Run(read_op_.Pass(), kReadError);
286 void BufferedDataSource::StopLoader() {
287 DCHECK(render_loop_->BelongsToCurrentThread());
293 void BufferedDataSource::SetBitrateTask(int bitrate) {
294 DCHECK(render_loop_->BelongsToCurrentThread());
295 DCHECK(loader_.get());
298 loader_->SetBitrate(bitrate);
301 // This method is the place where actual read happens, |loader_| must be valid
302 // prior to make this method call.
303 void BufferedDataSource::ReadInternal() {
304 DCHECK(render_loop_->BelongsToCurrentThread());
308 base::AutoLock auto_lock(lock_);
309 if (stop_signal_received_)
312 position = read_op_->position();
313 size = read_op_->size();
316 // First we prepare the intermediate read buffer for BufferedResourceLoader
318 if (size > intermediate_read_buffer_size_) {
319 intermediate_read_buffer_.reset(new uint8[size]);
322 // Perform the actual read with BufferedResourceLoader.
323 loader_->Read(position,
325 intermediate_read_buffer_.get(),
326 base::Bind(&BufferedDataSource::ReadCallback,
327 weak_factory_.GetWeakPtr()));
331 /////////////////////////////////////////////////////////////////////////////
332 // BufferedResourceLoader callback methods.
333 void BufferedDataSource::StartCallback(
334 BufferedResourceLoader::Status status) {
335 DCHECK(render_loop_->BelongsToCurrentThread());
336 DCHECK(loader_.get());
338 bool init_cb_is_null = false;
340 base::AutoLock auto_lock(lock_);
341 init_cb_is_null = init_cb_.is_null();
343 if (init_cb_is_null) {
348 // All responses must be successful. Resources that are assumed to be fully
349 // buffered must have a known content length.
350 bool success = status == BufferedResourceLoader::kOk &&
351 (!assume_fully_buffered() ||
352 loader_->instance_size() != kPositionNotSpecified);
355 total_bytes_ = loader_->instance_size();
357 !assume_fully_buffered() &&
358 (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
360 media_log_->SetDoubleProperty("total_bytes",
361 static_cast<double>(total_bytes_));
362 media_log_->SetBooleanProperty("streaming", streaming_);
367 // TODO(scherkus): we shouldn't have to lock to signal host(), see
368 // http://crbug.com/113712 for details.
369 base::AutoLock auto_lock(lock_);
370 if (stop_signal_received_)
374 if (total_bytes_ != kPositionNotSpecified) {
375 host_->SetTotalBytes(total_bytes_);
376 if (assume_fully_buffered())
377 host_->AddBufferedByteRange(0, total_bytes_);
380 media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin());
381 media_log_->SetBooleanProperty("passed_cors_access_check",
382 loader_->DidPassCORSAccessCheck());
383 media_log_->SetBooleanProperty("range_header_supported",
384 loader_->range_supported());
387 base::ResetAndReturn(&init_cb_).Run(success);
390 void BufferedDataSource::PartialReadStartCallback(
391 BufferedResourceLoader::Status status) {
392 DCHECK(render_loop_->BelongsToCurrentThread());
393 DCHECK(loader_.get());
395 if (status == BufferedResourceLoader::kOk) {
396 // Once the request has started successfully, we can proceed with
402 // Stop the resource loader since we have received an error.
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_)
410 ReadOperation::Run(read_op_.Pass(), kReadError);
413 void BufferedDataSource::ReadCallback(
414 BufferedResourceLoader::Status status,
416 DCHECK(render_loop_->BelongsToCurrentThread());
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_)
424 if (status != BufferedResourceLoader::kOk) {
425 // Stop the resource load if it failed.
428 if (status == BufferedResourceLoader::kCacheMiss &&
429 read_op_->retries() < kNumCacheMissRetries) {
430 read_op_->IncrementRetries();
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));
437 base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
439 base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this),
440 base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
442 base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
447 ReadOperation::Run(read_op_.Pass(), kReadError);
451 if (bytes_read > 0) {
452 memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
453 } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
454 // We've reached the end of the file and we didn't know the total size
455 // before. Update the total size so Read()s past the end of the file will
456 // fail like they would if we had known the file size at the beginning.
457 total_bytes_ = loader_->instance_size();
459 if (total_bytes_ != kPositionNotSpecified) {
460 host_->SetTotalBytes(total_bytes_);
461 host_->AddBufferedByteRange(loader_->first_byte_position(),
465 ReadOperation::Run(read_op_.Pass(), bytes_read);
468 void BufferedDataSource::LoadingStateChangedCallback(
469 BufferedResourceLoader::LoadingState state) {
470 DCHECK(render_loop_->BelongsToCurrentThread());
472 if (assume_fully_buffered())
475 bool is_downloading_data;
477 case BufferedResourceLoader::kLoading:
478 is_downloading_data = true;
480 case BufferedResourceLoader::kLoadingDeferred:
481 case BufferedResourceLoader::kLoadingFinished:
482 is_downloading_data = false;
485 // TODO(scherkus): we don't signal network activity changes when loads
486 // fail to preserve existing behaviour when deferring is toggled, however
487 // we should consider changing DownloadingCB to also propagate loading
488 // state. For example there isn't any signal today to notify the client that
489 // loading has failed (we only get errors on subsequent reads).
490 case BufferedResourceLoader::kLoadingFailed:
494 downloading_cb_.Run(is_downloading_data);
497 void BufferedDataSource::ProgressCallback(int64 position) {
498 DCHECK(render_loop_->BelongsToCurrentThread());
500 if (assume_fully_buffered())
503 // TODO(scherkus): we shouldn't have to lock to signal host(), see
504 // http://crbug.com/113712 for details.
505 base::AutoLock auto_lock(lock_);
506 if (stop_signal_received_)
509 host_->AddBufferedByteRange(loader_->first_byte_position(), position);
512 void BufferedDataSource::UpdateDeferStrategy(bool paused) {
513 // No need to aggressively buffer when we are assuming the resource is fully
515 if (assume_fully_buffered()) {
516 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
520 // If the playback has started (at which point the preload value is ignored)
521 // and we're paused, then try to load as much as possible (the loader will
522 // fall back to kCapacityDefer if it knows the current response won't be
523 // useful from the cache in the future).
524 if (media_has_played_ && paused && loader_->range_supported()) {
525 loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
529 // If media is currently playing or the page indicated preload=auto or the
530 // the server does not support the byte range request or we do not want to go
531 // too far ahead of the read head, use threshold strategy to enable/disable
532 // deferring when the buffer is full/depleted.
533 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
536 } // namespace content