summaryrefslogtreecommitdiffstats
path: root/content/renderer/media/buffered_data_source.cc
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-10 19:37:21 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-10 19:37:21 +0000
commit5e35a8ddc99982a51bf75359822321e11f3b7793 (patch)
treec811aa3e620c0823dbc303fac48f575fbc6c714d /content/renderer/media/buffered_data_source.cc
parentc1c8e6f114be1f18e344a0eb3ac6ac3a9a039aa5 (diff)
downloadchromium_src-5e35a8ddc99982a51bf75359822321e11f3b7793.zip
chromium_src-5e35a8ddc99982a51bf75359822321e11f3b7793.tar.gz
chromium_src-5e35a8ddc99982a51bf75359822321e11f3b7793.tar.bz2
Migrate webkit/renderer/media/ to content/renderer/media/.
This moves everything except the crypto bits. BUG=251306 R=jam@chromium.org Review URL: https://codereview.chromium.org/18261007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@210912 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/renderer/media/buffered_data_source.cc')
-rw-r--r--content/renderer/media/buffered_data_source.cc538
1 files changed, 538 insertions, 0 deletions
diff --git a/content/renderer/media/buffered_data_source.cc b/content/renderer/media/buffered_data_source.cc
new file mode 100644
index 0000000..1d9b33f
--- /dev/null
+++ b/content/renderer/media/buffered_data_source.cc
@@ -0,0 +1,538 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/media/buffered_data_source.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "media/base/media_log.h"
+#include "net/base/net_errors.h"
+
+using WebKit::WebFrame;
+
+namespace {
+
+// BufferedDataSource has an intermediate buffer, this value governs the initial
+// size of that buffer. It is set to 32KB because this is a typical read size
+// of FFmpeg.
+const int kInitialReadBufferSize = 32768;
+
+// Number of cache misses we allow for a single Read() before signaling an
+// error.
+const int kNumCacheMissRetries = 3;
+
+} // namespace
+
+namespace content {
+
+class BufferedDataSource::ReadOperation {
+ public:
+ ReadOperation(int64 position, int size, uint8* data,
+ const media::DataSource::ReadCB& callback);
+ ~ReadOperation();
+
+ // Runs |callback_| with the given |result|, deleting the operation
+ // afterwards.
+ static void Run(scoped_ptr<ReadOperation> read_op, int result);
+
+ // State for the number of times this read operation has been retried.
+ int retries() { return retries_; }
+ void IncrementRetries() { ++retries_; }
+
+ int64 position() { return position_; }
+ int size() { return size_; }
+ uint8* data() { return data_; }
+
+ private:
+ int retries_;
+
+ const int64 position_;
+ const int size_;
+ uint8* data_;
+ media::DataSource::ReadCB callback_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation);
+};
+
+BufferedDataSource::ReadOperation::ReadOperation(
+ int64 position, int size, uint8* data,
+ const media::DataSource::ReadCB& callback)
+ : retries_(0),
+ position_(position),
+ size_(size),
+ data_(data),
+ callback_(callback) {
+ DCHECK(!callback_.is_null());
+}
+
+BufferedDataSource::ReadOperation::~ReadOperation() {
+ DCHECK(callback_.is_null());
+}
+
+// static
+void BufferedDataSource::ReadOperation::Run(
+ scoped_ptr<ReadOperation> read_op, int result) {
+ base::ResetAndReturn(&read_op->callback_).Run(result);
+}
+
+BufferedDataSource::BufferedDataSource(
+ const scoped_refptr<base::MessageLoopProxy>& render_loop,
+ WebFrame* frame,
+ media::MediaLog* media_log,
+ const DownloadingCB& downloading_cb)
+ : weak_factory_(this),
+ weak_this_(weak_factory_.GetWeakPtr()),
+ cors_mode_(BufferedResourceLoader::kUnspecified),
+ total_bytes_(kPositionNotSpecified),
+ assume_fully_buffered_(false),
+ streaming_(false),
+ frame_(frame),
+ intermediate_read_buffer_(new uint8[kInitialReadBufferSize]),
+ intermediate_read_buffer_size_(kInitialReadBufferSize),
+ render_loop_(render_loop),
+ stop_signal_received_(false),
+ media_has_played_(false),
+ preload_(AUTO),
+ bitrate_(0),
+ playback_rate_(0.0),
+ media_log_(media_log),
+ downloading_cb_(downloading_cb) {
+ DCHECK(!downloading_cb_.is_null());
+}
+
+BufferedDataSource::~BufferedDataSource() {}
+
+// A factory method to create BufferedResourceLoader using the read parameters.
+// This method can be overridden to inject mock BufferedResourceLoader object
+// for testing purpose.
+BufferedResourceLoader* BufferedDataSource::CreateResourceLoader(
+ int64 first_byte_position, int64 last_byte_position) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+
+ BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ?
+ BufferedResourceLoader::kReadThenDefer :
+ BufferedResourceLoader::kCapacityDefer;
+
+ return new BufferedResourceLoader(url_,
+ cors_mode_,
+ first_byte_position,
+ last_byte_position,
+ strategy,
+ bitrate_,
+ playback_rate_,
+ media_log_.get());
+}
+
+void BufferedDataSource::set_host(media::DataSourceHost* host) {
+ DataSource::set_host(host);
+
+ if (loader_) {
+ base::AutoLock auto_lock(lock_);
+ UpdateHostState_Locked();
+ }
+}
+
+void BufferedDataSource::Initialize(
+ const GURL& url,
+ BufferedResourceLoader::CORSMode cors_mode,
+ const InitializeCB& init_cb) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(!init_cb.is_null());
+ DCHECK(!loader_.get());
+ url_ = url;
+ cors_mode_ = cors_mode;
+
+ init_cb_ = init_cb;
+
+ if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
+ // Do an unbounded range request starting at the beginning. If the server
+ // responds with 200 instead of 206 we'll fall back into a streaming mode.
+ loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
+ } else {
+ // For all other protocols, assume they support range request. We fetch
+ // the full range of the resource to obtain the instance size because
+ // we won't be served HTTP headers.
+ loader_.reset(CreateResourceLoader(kPositionNotSpecified,
+ kPositionNotSpecified));
+ assume_fully_buffered_ = true;
+ }
+
+ loader_->Start(
+ base::Bind(&BufferedDataSource::StartCallback, weak_this_),
+ base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this_),
+ base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
+ frame_);
+}
+
+void BufferedDataSource::SetPreload(Preload preload) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ preload_ = preload;
+}
+
+bool BufferedDataSource::HasSingleOrigin() {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(init_cb_.is_null() && loader_.get())
+ << "Initialize() must complete before calling HasSingleOrigin()";
+ return loader_->HasSingleOrigin();
+}
+
+bool BufferedDataSource::DidPassCORSAccessCheck() const {
+ return loader_.get() && loader_->DidPassCORSAccessCheck();
+}
+
+void BufferedDataSource::Abort() {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ {
+ base::AutoLock auto_lock(lock_);
+ StopInternal_Locked();
+ }
+ StopLoader();
+ frame_ = NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// media::DataSource implementation.
+void BufferedDataSource::Stop(const base::Closure& closure) {
+ {
+ base::AutoLock auto_lock(lock_);
+ StopInternal_Locked();
+ }
+ closure.Run();
+
+ render_loop_->PostTask(FROM_HERE,
+ base::Bind(&BufferedDataSource::StopLoader, weak_this_));
+}
+
+void BufferedDataSource::SetPlaybackRate(float playback_rate) {
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::SetPlaybackRateTask, weak_this_, playback_rate));
+}
+
+void BufferedDataSource::SetBitrate(int bitrate) {
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::SetBitrateTask, weak_this_, bitrate));
+}
+
+void BufferedDataSource::Read(
+ int64 position, int size, uint8* data,
+ const media::DataSource::ReadCB& read_cb) {
+ DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
+ DCHECK(!read_cb.is_null());
+
+ {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(!read_op_);
+
+ if (stop_signal_received_) {
+ read_cb.Run(kReadError);
+ return;
+ }
+
+ read_op_.reset(new ReadOperation(position, size, data, read_cb));
+ }
+
+ render_loop_->PostTask(FROM_HERE, base::Bind(
+ &BufferedDataSource::ReadTask, weak_this_));
+}
+
+bool BufferedDataSource::GetSize(int64* size_out) {
+ if (total_bytes_ != kPositionNotSpecified) {
+ *size_out = total_bytes_;
+ return true;
+ }
+ *size_out = 0;
+ return false;
+}
+
+bool BufferedDataSource::IsStreaming() {
+ return streaming_;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Render thread tasks.
+void BufferedDataSource::ReadTask() {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ ReadInternal();
+}
+
+void BufferedDataSource::StopInternal_Locked() {
+ lock_.AssertAcquired();
+ if (stop_signal_received_)
+ return;
+
+ stop_signal_received_ = true;
+
+ // Initialize() isn't part of the DataSource interface so don't call it in
+ // response to Stop().
+ init_cb_.Reset();
+
+ if (read_op_)
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+}
+
+void BufferedDataSource::StopLoader() {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+
+ if (loader_)
+ loader_->Stop();
+}
+
+void BufferedDataSource::SetPlaybackRateTask(float playback_rate) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ if (playback_rate != 0)
+ media_has_played_ = true;
+
+ playback_rate_ = playback_rate;
+ loader_->SetPlaybackRate(playback_rate);
+
+ if (!loader_->range_supported()) {
+ // 200 responses end up not being reused to satisfy future range requests,
+ // and we don't want to get too far ahead of the read-head (and thus require
+ // a restart), so keep to the thresholds.
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
+ } else if (media_has_played_ && playback_rate == 0) {
+ // If the playback has started (at which point the preload value is ignored)
+ // and we're paused, then try to load as much as possible (the loader will
+ // fall back to kCapacityDefer if it knows the current response won't be
+ // useful from the cache in the future).
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer);
+ } else {
+ // If media is currently playing or the page indicated preload=auto,
+ // use threshold strategy to enable/disable deferring when the buffer
+ // is full/depleted.
+ loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer);
+ }
+}
+
+void BufferedDataSource::SetBitrateTask(int bitrate) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ bitrate_ = bitrate;
+ loader_->SetBitrate(bitrate);
+}
+
+// This method is the place where actual read happens, |loader_| must be valid
+// prior to make this method call.
+void BufferedDataSource::ReadInternal() {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ int64 position = 0;
+ int size = 0;
+ {
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ position = read_op_->position();
+ size = read_op_->size();
+ }
+
+ // First we prepare the intermediate read buffer for BufferedResourceLoader
+ // to write to.
+ if (size > intermediate_read_buffer_size_) {
+ intermediate_read_buffer_.reset(new uint8[size]);
+ }
+
+ // Perform the actual read with BufferedResourceLoader.
+ loader_->Read(
+ position, size, intermediate_read_buffer_.get(),
+ base::Bind(&BufferedDataSource::ReadCallback, weak_this_));
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// BufferedResourceLoader callback methods.
+void BufferedDataSource::StartCallback(
+ BufferedResourceLoader::Status status) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ bool init_cb_is_null = false;
+ {
+ base::AutoLock auto_lock(lock_);
+ init_cb_is_null = init_cb_.is_null();
+ }
+ if (init_cb_is_null) {
+ loader_->Stop();
+ return;
+ }
+
+ // All responses must be successful. Resources that are assumed to be fully
+ // buffered must have a known content length.
+ bool success = status == BufferedResourceLoader::kOk &&
+ (!assume_fully_buffered_ ||
+ loader_->instance_size() != kPositionNotSpecified);
+
+ if (success) {
+ total_bytes_ = loader_->instance_size();
+ streaming_ = !assume_fully_buffered_ &&
+ (total_bytes_ == kPositionNotSpecified || !loader_->range_supported());
+ } else {
+ loader_->Stop();
+ }
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (success)
+ UpdateHostState_Locked();
+
+ base::ResetAndReturn(&init_cb_).Run(success);
+}
+
+void BufferedDataSource::PartialReadStartCallback(
+ BufferedResourceLoader::Status status) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+ DCHECK(loader_.get());
+
+ if (status == BufferedResourceLoader::kOk) {
+ // Once the request has started successfully, we can proceed with
+ // reading from it.
+ ReadInternal();
+ return;
+ }
+
+ // Stop the resource loader since we have received an error.
+ loader_->Stop();
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+}
+
+void BufferedDataSource::ReadCallback(
+ BufferedResourceLoader::Status status,
+ int bytes_read) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ if (status != BufferedResourceLoader::kOk) {
+ // Stop the resource load if it failed.
+ loader_->Stop();
+
+ if (status == BufferedResourceLoader::kCacheMiss &&
+ read_op_->retries() < kNumCacheMissRetries) {
+ read_op_->IncrementRetries();
+
+ // Recreate a loader starting from where we last left off until the
+ // end of the resource.
+ loader_.reset(CreateResourceLoader(
+ read_op_->position(), kPositionNotSpecified));
+ loader_->Start(
+ base::Bind(&BufferedDataSource::PartialReadStartCallback, weak_this_),
+ base::Bind(&BufferedDataSource::LoadingStateChangedCallback,
+ weak_this_),
+ base::Bind(&BufferedDataSource::ProgressCallback, weak_this_),
+ frame_);
+ return;
+ }
+
+ ReadOperation::Run(read_op_.Pass(), kReadError);
+ return;
+ }
+
+ if (bytes_read > 0) {
+ memcpy(read_op_->data(), intermediate_read_buffer_.get(), bytes_read);
+ } else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
+ // We've reached the end of the file and we didn't know the total size
+ // before. Update the total size so Read()s past the end of the file will
+ // fail like they would if we had known the file size at the beginning.
+ total_bytes_ = loader_->instance_size();
+
+ if (host() && total_bytes_ != kPositionNotSpecified) {
+ host()->SetTotalBytes(total_bytes_);
+ host()->AddBufferedByteRange(loader_->first_byte_position(),
+ total_bytes_);
+ }
+ }
+ ReadOperation::Run(read_op_.Pass(), bytes_read);
+}
+
+void BufferedDataSource::LoadingStateChangedCallback(
+ BufferedResourceLoader::LoadingState state) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+
+ if (assume_fully_buffered_)
+ return;
+
+ bool is_downloading_data;
+ switch (state) {
+ case BufferedResourceLoader::kLoading:
+ is_downloading_data = true;
+ break;
+ case BufferedResourceLoader::kLoadingDeferred:
+ case BufferedResourceLoader::kLoadingFinished:
+ is_downloading_data = false;
+ break;
+
+ // TODO(scherkus): we don't signal network activity changes when loads
+ // fail to preserve existing behaviour when deferring is toggled, however
+ // we should consider changing DownloadingCB to also propagate loading
+ // state. For example there isn't any signal today to notify the client that
+ // loading has failed (we only get errors on subsequent reads).
+ case BufferedResourceLoader::kLoadingFailed:
+ return;
+ }
+
+ downloading_cb_.Run(is_downloading_data);
+}
+
+void BufferedDataSource::ProgressCallback(int64 position) {
+ DCHECK(render_loop_->BelongsToCurrentThread());
+
+ if (assume_fully_buffered_)
+ return;
+
+ // TODO(scherkus): we shouldn't have to lock to signal host(), see
+ // http://crbug.com/113712 for details.
+ base::AutoLock auto_lock(lock_);
+ if (stop_signal_received_)
+ return;
+
+ ReportOrQueueBufferedBytes(loader_->first_byte_position(), position);
+}
+
+void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) {
+ if (host())
+ host()->AddBufferedByteRange(start, end);
+ else
+ queued_buffered_byte_ranges_.Add(start, end);
+}
+
+void BufferedDataSource::UpdateHostState_Locked() {
+ lock_.AssertAcquired();
+
+ if (!host())
+ return;
+
+ for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) {
+ host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i),
+ queued_buffered_byte_ranges_.end(i));
+ }
+ queued_buffered_byte_ranges_.clear();
+
+ if (total_bytes_ == kPositionNotSpecified)
+ return;
+
+ host()->SetTotalBytes(total_bytes_);
+
+ if (assume_fully_buffered_)
+ host()->AddBufferedByteRange(0, total_bytes_);
+}
+
+} // namespace content