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(
82 const scoped_refptr<base::MessageLoopProxy>& render_loop,
84 media::MediaLog* media_log,
85 BufferedDataSourceHost* host,
86 const DownloadingCB& downloading_cb)
87 : cors_mode_(BufferedResourceLoader::kUnspecified),
88 total_bytes_(kPositionNotSpecified),
89 assume_fully_buffered_(false),
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),
100 media_log_(media_log),
102 downloading_cb_(downloading_cb),
103 weak_factory_(this) {
105 DCHECK(!downloading_cb_.is_null());
108 BufferedDataSource::~BufferedDataSource() {}
110 // A factory method to create BufferedResourceLoader using the read parameters.
111 // This method can be overridden to inject mock BufferedResourceLoader object
112 // for testing purpose.
113 BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
114 int64 first_byte_position, int64 last_byte_position) {
115 DCHECK(render_loop_->BelongsToCurrentThread());
117 BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
118 BufferedResourceLoader::kReadThenDefer :
119 BufferedResourceLoader::kCapacityDefer;
121 return new BufferedResourceLoader(url_,
131 void BufferedDataSource::Initialize(
133 BufferedResourceLoader::CORSMode cors_mode,
134 const InitializeCB& init_cb) {
135 DCHECK(render_loop_->BelongsToCurrentThread());
136 DCHECK(!init_cb.is_null());
137 DCHECK(!loader_.get());
139 cors_mode_ = cors_mode;
143 if (url_.SchemeIs(url::kHttpScheme) || url_.SchemeIs(url::kHttpsScheme)) {
144 // Do an unbounded range request starting at the beginning. If the server
145 // responds with 200 instead of 206 we'll fall back into a streaming mode.
146 loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
148 // For all other protocols, assume they support range request. We fetch
149 // the full range of the resource to obtain the instance size because
150 // we won't be served HTTP headers.
151 loader_.reset(CreateResourceLoader(kPositionNotSpecified,
152 kPositionNotSpecified));
153 assume_fully_buffered_ = true;
156 base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
158 base::Bind(&BufferedDataSource::StartCallback, weak_this),
159 base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this),
160 base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
164 void BufferedDataSource::SetPreload(Preload preload) {
165 DCHECK(render_loop_->BelongsToCurrentThread());
169 bool BufferedDataSource::HasSingleOrigin() {
170 DCHECK(render_loop_->BelongsToCurrentThread());
171 DCHECK(init_cb_.is_null() && loader_.get())
172 << "Initialize() must complete before calling HasSingleOrigin()";
173 return loader_->HasSingleOrigin();
176 bool BufferedDataSource::DidPassCORSAccessCheck() const {
177 return loader_.get() && loader_->DidPassCORSAccessCheck();
180 void BufferedDataSource::Abort() {
181 DCHECK(render_loop_->BelongsToCurrentThread());
183 base::AutoLock auto_lock(lock_);
184 StopInternal_Locked();
190 void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) {
191 DCHECK(render_loop_->BelongsToCurrentThread());
192 DCHECK(loader_.get());
194 if (playback_rate < 0.0f)
197 playback_rate_ = playback_rate;
198 loader_->SetPlaybackRate(playback_rate);
201 void BufferedDataSource::MediaIsPlaying() {
202 DCHECK(render_loop_->BelongsToCurrentThread());
203 media_has_played_ = true;
204 UpdateDeferStrategy(false);
207 void BufferedDataSource::MediaIsPaused() {
208 DCHECK(render_loop_->BelongsToCurrentThread());
209 UpdateDeferStrategy(true);
212 /////////////////////////////////////////////////////////////////////////////
213 // media::DataSource implementation.
214 void BufferedDataSource::Stop(const base::Closure& closure) {
216 base::AutoLock auto_lock(lock_);
217 StopInternal_Locked();
221 render_loop_->PostTask(
223 base::Bind(&BufferedDataSource::StopLoader, weak_factory_.GetWeakPtr()));
226 void BufferedDataSource::SetBitrate(int bitrate) {
227 render_loop_->PostTask(FROM_HERE,
228 base::Bind(&BufferedDataSource::SetBitrateTask,
229 weak_factory_.GetWeakPtr(),
233 void BufferedDataSource::Read(
234 int64 position, int size, uint8* data,
235 const media::DataSource::ReadCB& read_cb) {
236 DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
237 DCHECK(!read_cb.is_null());
240 base::AutoLock auto_lock(lock_);
243 if (stop_signal_received_) {
244 read_cb.Run(kReadError);
248 read_op_.reset(new ReadOperation(position, size, data, read_cb));
251 render_loop_->PostTask(
253 base::Bind(&BufferedDataSource::ReadTask, weak_factory_.GetWeakPtr()));
256 bool BufferedDataSource::GetSize(int64* size_out) {
257 if (total_bytes_ != kPositionNotSpecified) {
258 *size_out = total_bytes_;
265 bool BufferedDataSource::IsStreaming() {
269 /////////////////////////////////////////////////////////////////////////////
270 // Render thread tasks.
271 void BufferedDataSource::ReadTask() {
272 DCHECK(render_loop_->BelongsToCurrentThread());
276 void BufferedDataSource::StopInternal_Locked() {
277 lock_.AssertAcquired();
278 if (stop_signal_received_)
281 stop_signal_received_ = true;
283 // Initialize() isn't part of the DataSource interface so don't call it in
284 // response to Stop().
288 ReadOperation::Run(read_op_.Pass(), kReadError);
291 void BufferedDataSource::StopLoader() {
292 DCHECK(render_loop_->BelongsToCurrentThread());
298 void BufferedDataSource::SetBitrateTask(int bitrate) {
299 DCHECK(render_loop_->BelongsToCurrentThread());
300 DCHECK(loader_.get());
303 loader_->SetBitrate(bitrate);
306 // This method is the place where actual read happens, |loader_| must be valid
307 // prior to make this method call.
308 void BufferedDataSource::ReadInternal() {
309 DCHECK(render_loop_->BelongsToCurrentThread());
313 base::AutoLock auto_lock(lock_);
314 if (stop_signal_received_)
317 position = read_op_->position();
318 size = read_op_->size();
321 // First we prepare the intermediate read buffer for BufferedResourceLoader
323 if (size > intermediate_read_buffer_size_) {
324 intermediate_read_buffer_.reset(new uint8[size]);
327 // Perform the actual read with BufferedResourceLoader.
328 loader_->Read(position,
330 intermediate_read_buffer_.get(),
331 base::Bind(&BufferedDataSource::ReadCallback,
332 weak_factory_.GetWeakPtr()));
336 /////////////////////////////////////////////////////////////////////////////
337 // BufferedResourceLoader callback methods.
338 void BufferedDataSource::StartCallback(
339 BufferedResourceLoader::Status status) {
340 DCHECK(render_loop_->BelongsToCurrentThread());
341 DCHECK(loader_.get());
343 bool init_cb_is_null = false;
345 base::AutoLock auto_lock(lock_);
346 init_cb_is_null = init_cb_.is_null();
348 if (init_cb_is_null) {
353 // All responses must be successful. Resources that are assumed to be fully
354 // buffered must have a known content length.
355 bool success = status == BufferedResourceLoader::kOk &&
356 (!assume_fully_buffered_ ||
357 loader_->instance_size() != kPositionNotSpecified);
360 total_bytes_ = loader_->instance_size();
361 streaming_ = !assume_fully_buffered_ &&
362 (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
364 media_log_->SetDoubleProperty("total_bytes",
365 static_cast<double>(total_bytes_));
366 media_log_->SetBooleanProperty("streaming", streaming_);
371 // TODO(scherkus): we shouldn't have to lock to signal host(), see
372 // http://crbug.com/113712 for details.
373 base::AutoLock auto_lock(lock_);
374 if (stop_signal_received_)
378 if (total_bytes_ != kPositionNotSpecified) {
379 host_->SetTotalBytes(total_bytes_);
380 if (assume_fully_buffered_)
381 host_->AddBufferedByteRange(0, total_bytes_);
384 media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin());
385 media_log_->SetBooleanProperty("passed_cors_access_check",
386 loader_->DidPassCORSAccessCheck());
387 media_log_->SetBooleanProperty("range_header_supported",
388 loader_->range_supported());
391 base::ResetAndReturn(&init_cb_).Run(success);
394 void BufferedDataSource::PartialReadStartCallback(
395 BufferedResourceLoader::Status status) {
396 DCHECK(render_loop_->BelongsToCurrentThread());
397 DCHECK(loader_.get());
399 if (status == BufferedResourceLoader::kOk) {
400 // Once the request has started successfully, we can proceed with
406 // Stop the resource loader since we have received an error.
409 // TODO(scherkus): we shouldn't have to lock to signal host(), see
410 // http://crbug.com/113712 for details.
411 base::AutoLock auto_lock(lock_);
412 if (stop_signal_received_)
414 ReadOperation::Run(read_op_.Pass(), kReadError);
417 void BufferedDataSource::ReadCallback(
418 BufferedResourceLoader::Status status,
420 DCHECK(render_loop_->BelongsToCurrentThread());
422 // TODO(scherkus): we shouldn't have to lock to signal host(), see
423 // http://crbug.com/113712 for details.
424 base::AutoLock auto_lock(lock_);
425 if (stop_signal_received_)
428 if (status != BufferedResourceLoader::kOk) {
429 // Stop the resource load if it failed.
432 if (status == BufferedResourceLoader::kCacheMiss &&
433 read_op_->retries() < kNumCacheMissRetries) {
434 read_op_->IncrementRetries();
436 // Recreate a loader starting from where we last left off until the
437 // end of the resource.
438 loader_.reset(CreateResourceLoader(
439 read_op_->position(), kPositionNotSpecified));
441 base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr();
443 base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this),
444 base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
446 base::Bind(&BufferedDataSource::ProgressCallback, weak_this),
451 ReadOperation::Run(read_op_.Pass(), kReadError);
455 if (bytes_read > 0) {
456 memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
457 } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
458 // We've reached the end of the file and we didn't know the total size
459 // before. Update the total size so Read()s past the end of the file will
460 // fail like they would if we had known the file size at the beginning.
461 total_bytes_ = loader_->instance_size();
463 if (total_bytes_ != kPositionNotSpecified) {
464 host_->SetTotalBytes(total_bytes_);
465 host_->AddBufferedByteRange(loader_->first_byte_position(),
469 ReadOperation::Run(read_op_.Pass(), bytes_read);
472 void BufferedDataSource::LoadingStateChangedCallback(
473 BufferedResourceLoader::LoadingState state) {
474 DCHECK(render_loop_->BelongsToCurrentThread());
476 if (assume_fully_buffered_)
479 bool is_downloading_data;
481 case BufferedResourceLoader::kLoading:
482 is_downloading_data = true;
484 case BufferedResourceLoader::kLoadingDeferred:
485 case BufferedResourceLoader::kLoadingFinished:
486 is_downloading_data = false;
489 // TODO(scherkus): we don't signal network activity changes when loads
490 // fail to preserve existing behaviour when deferring is toggled, however
491 // we should consider changing DownloadingCB to also propagate loading
492 // state. For example there isn't any signal today to notify the client that
493 // loading has failed (we only get errors on subsequent reads).
494 case BufferedResourceLoader::kLoadingFailed:
498 downloading_cb_.Run(is_downloading_data);
501 void BufferedDataSource::ProgressCallback(int64 position) {
502 DCHECK(render_loop_->BelongsToCurrentThread());
504 if (assume_fully_buffered_)
507 // TODO(scherkus): we shouldn't have to lock to signal host(), see
508 // http://crbug.com/113712 for details.
509 base::AutoLock auto_lock(lock_);
510 if (stop_signal_received_)
513 host_->AddBufferedByteRange(loader_->first_byte_position(), position);
516 void BufferedDataSource::UpdateDeferStrategy(bool paused) {
517 // 200 responses end up not being reused to satisfy future range requests,
518 // and we don't want to get too far ahead of the read-head (and thus require
519 // a restart), so keep to the thresholds.
520 if (!loader_->range_supported()) {
521 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
525 // If the playback has started (at which point the preload value is ignored)
526 // and we're paused, then try to load as much as possible (the loader will
527 // fall back to kCapacityDefer if it knows the current response won't be
528 // useful from the cache in the future).
529 if (media_has_played_ && paused) {
530 loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
534 // If media is currently playing or the page indicated preload=auto,
535 // use threshold strategy to enable/disable deferring when the buffer
537 loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
540 } // namespace content