diff options
author | acolwell <acolwell@chromium.org> | 2014-09-06 12:01:32 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-06 19:06:46 +0000 |
commit | 9e0840d0672da96350a1f33d684f2c64d2574f46 (patch) | |
tree | 6d72e3d50fcfd66f70a58c5d3583358055de4d9a /media | |
parent | 87a3ebac6b26918b151151efed3311d0ddc20d73 (diff) | |
download | chromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.zip chromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.tar.gz chromium_src-9e0840d0672da96350a1f33d684f2c64d2574f46.tar.bz2 |
Move WebMediaPlayerImpl and its dependencies to media/blink.
Moving WebMediaPlayerImpl and related classes in content/renderer/media to media/blink so that they can be reused by Mojo code.
BUG=408338
Review URL: https://codereview.chromium.org/495353003
Cr-Commit-Position: refs/heads/master@{#293628}
Diffstat (limited to 'media')
49 files changed, 7958 insertions, 4 deletions
@@ -11,4 +11,5 @@ include_rules = [ "+ui/gfx", "+ui/gl", "+ui/ozone", + "-media/blink", ] diff --git a/media/base/data_source.cc b/media/base/data_source.cc index c8ab446..b6999c2 100644 --- a/media/base/data_source.cc +++ b/media/base/data_source.cc @@ -8,9 +8,6 @@ namespace media { -// static -const int DataSource::kReadError = -1; - DataSource::DataSource() {} DataSource::~DataSource() {} diff --git a/media/base/data_source.h b/media/base/data_source.h index e0b7373..42566a9 100644 --- a/media/base/data_source.h +++ b/media/base/data_source.h @@ -15,7 +15,8 @@ class MEDIA_EXPORT DataSource { public: typedef base::Callback<void(int64, int64)> StatusCallback; typedef base::Callback<void(int)> ReadCB; - static const int kReadError; + + enum { kReadError = -1 }; DataSource(); virtual ~DataSource(); diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn new file mode 100644 index 0000000..ad52f62 --- /dev/null +++ b/media/blink/BUILD.gn @@ -0,0 +1,87 @@ +# Copyright 2014 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. +component("blink") { + output_name = "media_blink" + + deps = [ + "//base", + "//cc", + "//cc/blink", + "//media", + "//net", + "//third_party/WebKit/public:blink", + "//ui/gfx", + "//ui/gfx/geometry", + ] + + defines = [ "MEDIA_IMPLEMENTATION" ] + + sources = [ + "active_loader.cc", + "active_loader.h", + "buffered_data_source.cc", + "buffered_data_source.h", + "buffered_data_source_host_impl.cc", + "buffered_data_source_host_impl.h", + "buffered_resource_loader.cc", + "buffered_resource_loader.h", + "encrypted_media_player_support.cc", + "encrypted_media_player_support.h", + "cache_util.cc", + "cache_util.h", + "texttrack_impl.cc", + "texttrack_impl.h", + "video_frame_compositor.cc", + "video_frame_compositor.h", + "webaudiosourceprovider_impl.cc", + "webaudiosourceprovider_impl.h", + "webinbandtexttrack_impl.cc", + "webinbandtexttrack_impl.h", + "webmediaplayer_delegate.h", + "webmediaplayer_impl.cc", + "webmediaplayer_impl.h", + "webmediaplayer_params.cc", + "webmediaplayer_params.h", + "webmediaplayer_util.cc", + "webmediaplayer_util.h", + "webmediasource_impl.cc", + "webmediasource_impl.h", + "websourcebuffer_impl.cc", + "websourcebuffer_impl.h", + ] +} + +test("media_blink_unittests") { + deps = [ + ":blink", + "//base", + "//base/test:test_support", + "//cc", + "//cc/blink", + "//media", + "//media/base:test_support", + "//net", + "//testing/gmock", + "//testing/gtest", + "//third_party/WebKit/public:blink", + "//ui/gfx/geometry", + "//ui/gfx:test_support", + "//url", + ] + + sources = [ + "buffered_data_source_host_impl_unittest.cc", + "buffered_data_source_unittest.cc", + "buffered_resource_loader_unittest.cc", + "cache_util_unittest.cc", + "mock_webframeclient.h", + "mock_weburlloader.cc", + "mock_weburlloader.h", + "run_all_unittests.cc", + "test_response_generator.cc", + "test_response_generator.h", + "video_frame_compositor_unittest.cc", + "webaudiosourceprovider_impl_unittest.cc", + ] +} diff --git a/media/blink/DEPS b/media/blink/DEPS new file mode 100644 index 0000000..5c05816 --- /dev/null +++ b/media/blink/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+cc/layers/video_frame_provider.h", + "+cc/layers/video_layer.h", + "+cc/blink/web_layer_impl.h", + "+media", + "+net/base", + "+net/http", + "+third_party/WebKit/public/platform", + "+third_party/WebKit/public/web", +]
\ No newline at end of file diff --git a/media/blink/active_loader.cc b/media/blink/active_loader.cc new file mode 100644 index 0000000..2f1e084 --- /dev/null +++ b/media/blink/active_loader.cc @@ -0,0 +1,26 @@ +// 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 "media/blink/active_loader.h" + +#include "media/blink/buffered_resource_loader.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" + +namespace media { + +ActiveLoader::ActiveLoader(scoped_ptr<blink::WebURLLoader> loader) + : loader_(loader.Pass()), + deferred_(false) { +} + +ActiveLoader::~ActiveLoader() { + loader_->cancel(); +} + +void ActiveLoader::SetDeferred(bool deferred) { + deferred_ = deferred; + loader_->setDefersLoading(deferred); +} + +} // namespace media diff --git a/media/blink/active_loader.h b/media/blink/active_loader.h new file mode 100644 index 0000000..d8c60df --- /dev/null +++ b/media/blink/active_loader.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef MEDIA_BLINK_ACTIVE_LOADER_H_ +#define MEDIA_BLINK_ACTIVE_LOADER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" + +namespace blink { +class WebURLLoader; +} + +namespace media { + +// Wraps an active WebURLLoader with some additional state. +// +// Handles deferring and deletion of loaders. +class MEDIA_EXPORT ActiveLoader { + public: + // Creates an ActiveLoader with the given loader. It is assumed that the + // initial state of |loader| is loading and not deferred. + explicit ActiveLoader(scoped_ptr<blink::WebURLLoader> loader); + ~ActiveLoader(); + + // Starts or stops deferring the resource load. + void SetDeferred(bool deferred); + bool deferred() { return deferred_; } + + private: + friend class BufferedDataSourceTest; + + scoped_ptr<blink::WebURLLoader> loader_; + bool deferred_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ActiveLoader); +}; + +} // namespace media + +#endif // MEDIA_BLINK_ACTIVE_LOADER_H_ diff --git a/media/blink/buffered_data_source.cc b/media/blink/buffered_data_source.cc new file mode 100644 index 0000000..91ed1a2 --- /dev/null +++ b/media/blink/buffered_data_source.cc @@ -0,0 +1,535 @@ +// 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 "media/blink/buffered_data_source.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/single_thread_task_runner.h" +#include "media/base/media_log.h" +#include "net/base/net_errors.h" + +using blink::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 media { + +class BufferedDataSource::ReadOperation { + public: + ReadOperation(int64 position, int size, uint8* data, + const 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_; + DataSource::ReadCB callback_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation); +}; + +BufferedDataSource::ReadOperation::ReadOperation( + int64 position, int size, uint8* data, + const 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 GURL& url, + BufferedResourceLoader::CORSMode cors_mode, + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + WebFrame* frame, + MediaLog* media_log, + BufferedDataSourceHost* host, + const DownloadingCB& downloading_cb) + : url_(url), + cors_mode_(cors_mode), + total_bytes_(kPositionNotSpecified), + streaming_(false), + frame_(frame), + intermediate_read_buffer_(new uint8[kInitialReadBufferSize]), + intermediate_read_buffer_size_(kInitialReadBufferSize), + render_task_runner_(task_runner), + stop_signal_received_(false), + media_has_played_(false), + preload_(AUTO), + bitrate_(0), + playback_rate_(0.0), + media_log_(media_log), + host_(host), + downloading_cb_(downloading_cb), + weak_factory_(this) { + DCHECK(host_); + 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_task_runner_->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::Initialize(const InitializeCB& init_cb) { + DCHECK(render_task_runner_->BelongsToCurrentThread()); + DCHECK(!init_cb.is_null()); + DCHECK(!loader_.get()); + + init_cb_ = init_cb; + + if (url_.SchemeIsHTTPOrHTTPS()) { + // 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)); + } + + base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr(); + 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_task_runner_->BelongsToCurrentThread()); + preload_ = preload; +} + +bool BufferedDataSource::HasSingleOrigin() { + DCHECK(render_task_runner_->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_task_runner_->BelongsToCurrentThread()); + { + base::AutoLock auto_lock(lock_); + StopInternal_Locked(); + } + StopLoader(); + frame_ = NULL; +} + +void BufferedDataSource::MediaPlaybackRateChanged(float playback_rate) { + DCHECK(render_task_runner_->BelongsToCurrentThread()); + DCHECK(loader_.get()); + + if (playback_rate < 0.0f) + return; + + playback_rate_ = playback_rate; + loader_->SetPlaybackRate(playback_rate); +} + +void BufferedDataSource::MediaIsPlaying() { + DCHECK(render_task_runner_->BelongsToCurrentThread()); + media_has_played_ = true; + UpdateDeferStrategy(false); +} + +void BufferedDataSource::MediaIsPaused() { + DCHECK(render_task_runner_->BelongsToCurrentThread()); + UpdateDeferStrategy(true); +} + +///////////////////////////////////////////////////////////////////////////// +// DataSource implementation. +void BufferedDataSource::Stop() { + { + base::AutoLock auto_lock(lock_); + StopInternal_Locked(); + } + + render_task_runner_->PostTask( + FROM_HERE, + base::Bind(&BufferedDataSource::StopLoader, weak_factory_.GetWeakPtr())); +} + +void BufferedDataSource::SetBitrate(int bitrate) { + render_task_runner_->PostTask(FROM_HERE, + base::Bind(&BufferedDataSource::SetBitrateTask, + weak_factory_.GetWeakPtr(), + bitrate)); +} + +void BufferedDataSource::Read( + int64 position, int size, uint8* data, + const 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_task_runner_->PostTask( + FROM_HERE, + base::Bind(&BufferedDataSource::ReadTask, weak_factory_.GetWeakPtr())); +} + +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_task_runner_->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_task_runner_->BelongsToCurrentThread()); + + if (loader_) + loader_->Stop(); +} + +void BufferedDataSource::SetBitrateTask(int bitrate) { + DCHECK(render_task_runner_->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_task_runner_->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_factory_.GetWeakPtr())); +} + + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader callback methods. +void BufferedDataSource::StartCallback( + BufferedResourceLoader::Status status) { + DCHECK(render_task_runner_->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()); + + media_log_->SetDoubleProperty("total_bytes", + static_cast<double>(total_bytes_)); + media_log_->SetBooleanProperty("streaming", streaming_); + } 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) { + if (total_bytes_ != kPositionNotSpecified) { + host_->SetTotalBytes(total_bytes_); + if (assume_fully_buffered()) + host_->AddBufferedByteRange(0, total_bytes_); + } + + media_log_->SetBooleanProperty("single_origin", loader_->HasSingleOrigin()); + media_log_->SetBooleanProperty("passed_cors_access_check", + loader_->DidPassCORSAccessCheck()); + media_log_->SetBooleanProperty("range_header_supported", + loader_->range_supported()); + } + + base::ResetAndReturn(&init_cb_).Run(success); +} + +void BufferedDataSource::PartialReadStartCallback( + BufferedResourceLoader::Status status) { + DCHECK(render_task_runner_->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_task_runner_->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)); + + base::WeakPtr<BufferedDataSource> weak_this = weak_factory_.GetWeakPtr(); + 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 (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_task_runner_->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_task_runner_->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; + + host_->AddBufferedByteRange(loader_->first_byte_position(), position); +} + +void BufferedDataSource::UpdateDeferStrategy(bool paused) { + // No need to aggressively buffer when we are assuming the resource is fully + // buffered. + if (assume_fully_buffered()) { + loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); + return; + } + + // 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). + if (media_has_played_ && paused && loader_->range_supported()) { + loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer); + return; + } + + // If media is currently playing or the page indicated preload=auto or the + // the server does not support the byte range request or we do not want to go + // too far ahead of the read head, use threshold strategy to enable/disable + // deferring when the buffer is full/depleted. + loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); +} + +} // namespace media diff --git a/media/blink/buffered_data_source.h b/media/blink/buffered_data_source.h new file mode 100644 index 0000000..5ae7d93 --- /dev/null +++ b/media/blink/buffered_data_source.h @@ -0,0 +1,240 @@ +// 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. + +#ifndef MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_ +#define MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "media/base/data_source.h" +#include "media/base/media_export.h" +#include "media/base/ranges.h" +#include "media/blink/buffered_resource_loader.h" +#include "url/gurl.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { +class MediaLog; + +class MEDIA_EXPORT BufferedDataSourceHost { + public: + // Notify the host of the total size of the media file. + virtual void SetTotalBytes(int64 total_bytes) = 0; + + // Notify the host that byte range [start,end] has been buffered. + // TODO(fischman): remove this method when demuxing is push-based instead of + // pull-based. http://crbug.com/131444 + virtual void AddBufferedByteRange(int64 start, int64 end) = 0; + + protected: + virtual ~BufferedDataSourceHost() {}; +}; + +// A data source capable of loading URLs and buffering the data using an +// in-memory sliding window. +// +// BufferedDataSource must be created and initialized on the render thread +// before being passed to other threads. It may be deleted on any thread. +class MEDIA_EXPORT BufferedDataSource : public DataSource { + public: + // Used to specify video preload states. They are "hints" to the browser about + // how aggressively the browser should load and buffer data. + // Please see the HTML5 spec for the descriptions of these values: + // http://www.w3.org/TR/html5/video.html#attr-media-preload + // + // Enum values must match the values in blink::WebMediaPlayer::Preload and + // there will be assertions at compile time if they do not match. + enum Preload { + NONE, + METADATA, + AUTO, + }; + typedef base::Callback<void(bool)> DownloadingCB; + + // |url| and |cors_mode| are passed to the object. Buffered byte range changes + // will be reported to |host|. |downloading_cb| will be called whenever the + // downloading/paused state of the source changes. + BufferedDataSource( + const GURL& url, + BufferedResourceLoader::CORSMode cors_mode, + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + blink::WebFrame* frame, + MediaLog* media_log, + BufferedDataSourceHost* host, + const DownloadingCB& downloading_cb); + virtual ~BufferedDataSource(); + + // Executes |init_cb| with the result of initialization when it has completed. + // + // Method called on the render thread. + typedef base::Callback<void(bool)> InitializeCB; + void Initialize(const InitializeCB& init_cb); + + // Adjusts the buffering algorithm based on the given preload value. + void SetPreload(Preload preload); + + // Returns true if the media resource has a single origin, false otherwise. + // Only valid to call after Initialize() has completed. + // + // Method called on the render thread. + bool HasSingleOrigin(); + + // Returns true if the media resource passed a CORS access control check. + bool DidPassCORSAccessCheck() const; + + // Cancels initialization, any pending loaders, and any pending read calls + // from the demuxer. The caller is expected to release its reference to this + // object and never call it again. + // + // Method called on the render thread. + void Abort(); + + // Notifies changes in playback state for controlling media buffering + // behavior. + void MediaPlaybackRateChanged(float playback_rate); + void MediaIsPlaying(); + void MediaIsPaused(); + + // Returns true if the resource is local. + bool assume_fully_buffered() { return !url_.SchemeIsHTTPOrHTTPS(); } + + // DataSource implementation. + // Called from demuxer thread. + virtual void Stop() OVERRIDE; + + virtual void Read(int64 position, int size, uint8* data, + const DataSource::ReadCB& read_cb) OVERRIDE; + virtual bool GetSize(int64* size_out) OVERRIDE; + virtual bool IsStreaming() OVERRIDE; + virtual void SetBitrate(int bitrate) OVERRIDE; + + protected: + // A factory method to create a BufferedResourceLoader based on the read + // parameters. We can override this file to object a mock + // BufferedResourceLoader for testing. + virtual BufferedResourceLoader* CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position); + + private: + friend class BufferedDataSourceTest; + + // Task posted to perform actual reading on the render thread. + void ReadTask(); + + // Cancels oustanding callbacks and sets |stop_signal_received_|. Safe to call + // from any thread. + void StopInternal_Locked(); + + // Stops |loader_| if present. Used by Abort() and Stop(). + void StopLoader(); + + // Tells |loader_| the bitrate of the media. + void SetBitrateTask(int bitrate); + + // The method that performs actual read. This method can only be executed on + // the render thread. + void ReadInternal(); + + // BufferedResourceLoader::Start() callback for initial load. + void StartCallback(BufferedResourceLoader::Status status); + + // BufferedResourceLoader::Start() callback for subsequent loads (i.e., + // when accessing ranges that are outside initial buffered region). + void PartialReadStartCallback(BufferedResourceLoader::Status status); + + // BufferedResourceLoader callbacks. + void ReadCallback(BufferedResourceLoader::Status status, int bytes_read); + void LoadingStateChangedCallback(BufferedResourceLoader::LoadingState state); + void ProgressCallback(int64 position); + + // Update |loader_|'s deferring strategy in response to a play/pause, or + // change in playback rate. + void UpdateDeferStrategy(bool paused); + + // URL of the resource requested. + GURL url_; + // crossorigin attribute on the corresponding HTML media element, if any. + BufferedResourceLoader::CORSMode cors_mode_; + + // The total size of the resource. Set during StartCallback() if the size is + // known, otherwise it will remain kPositionNotSpecified until the size is + // determined by reaching EOF. + int64 total_bytes_; + + // This value will be true if this data source can only support streaming. + // i.e. range request is not supported. + bool streaming_; + + // A webframe for loading. + blink::WebFrame* frame_; + + // A resource loader for the media resource. + scoped_ptr<BufferedResourceLoader> loader_; + + // Callback method from the pipeline for initialization. + InitializeCB init_cb_; + + // Read parameters received from the Read() method call. Must be accessed + // under |lock_|. + class ReadOperation; + scoped_ptr<ReadOperation> read_op_; + + // This buffer is intermediate, we use it for BufferedResourceLoader to write + // to. And when read in BufferedResourceLoader is done, we copy data from + // this buffer to |read_buffer_|. The reason for an additional copy is that + // we don't own |read_buffer_|. But since the read operation is asynchronous, + // |read_buffer| can be destroyed at any time, so we only copy into + // |read_buffer| in the final step when it is safe. + // Memory is allocated for this member during initialization of this object + // because we want buffer to be passed into BufferedResourceLoader to be + // always non-null. And by initializing this member with a default size we can + // avoid creating zero-sized buffered if the first read has zero size. + scoped_ptr<uint8[]> intermediate_read_buffer_; + int intermediate_read_buffer_size_; + + // The task runner of the render thread. + const scoped_refptr<base::SingleThreadTaskRunner> render_task_runner_; + + // Protects |stop_signal_received_| and |read_op_|. + base::Lock lock_; + + // Whether we've been told to stop via Abort() or Stop(). + bool stop_signal_received_; + + // This variable is true when the user has requested the video to play at + // least once. + bool media_has_played_; + + // This variable holds the value of the preload attribute for the video + // element. + Preload preload_; + + // Bitrate of the content, 0 if unknown. + int bitrate_; + + // Current playback rate. + float playback_rate_; + + scoped_refptr<MediaLog> media_log_; + + // Host object to report buffered byte range changes to. + BufferedDataSourceHost* host_; + + DownloadingCB downloading_cb_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<BufferedDataSource> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSource); +}; + +} // namespace media + +#endif // MEDIA_BLINK_BUFFERED_DATA_SOURCE_H_ diff --git a/media/blink/buffered_data_source_host_impl.cc b/media/blink/buffered_data_source_host_impl.cc new file mode 100644 index 0000000..42f9822 --- /dev/null +++ b/media/blink/buffered_data_source_host_impl.cc @@ -0,0 +1,58 @@ +// Copyright 2014 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 "media/blink/buffered_data_source_host_impl.h" + +namespace media { + +BufferedDataSourceHostImpl::BufferedDataSourceHostImpl() + : total_bytes_(0), + did_loading_progress_(false) { } + +BufferedDataSourceHostImpl::~BufferedDataSourceHostImpl() { } + +void BufferedDataSourceHostImpl::SetTotalBytes(int64 total_bytes) { + total_bytes_ = total_bytes; +} + +void BufferedDataSourceHostImpl::AddBufferedByteRange(int64 start, int64 end) { + buffered_byte_ranges_.Add(start, end); + did_loading_progress_ = true; +} + +static base::TimeDelta TimeForByteOffset( + int64 byte_offset, int64 total_bytes, base::TimeDelta duration) { + double position = static_cast<double>(byte_offset) / total_bytes; + // Snap to the beginning/end where the approximation can look especially bad. + if (position < 0.01) + return base::TimeDelta(); + if (position > 0.99) + return duration; + return base::TimeDelta::FromMilliseconds( + static_cast<int64>(position * duration.InMilliseconds())); +} + +void BufferedDataSourceHostImpl::AddBufferedTimeRanges( + Ranges<base::TimeDelta>* buffered_time_ranges, + base::TimeDelta media_duration) const { + DCHECK(media_duration != kNoTimestamp()); + DCHECK(media_duration != kInfiniteDuration()); + if (total_bytes_ && buffered_byte_ranges_.size()) { + for (size_t i = 0; i < buffered_byte_ranges_.size(); ++i) { + int64 start = buffered_byte_ranges_.start(i); + int64 end = buffered_byte_ranges_.end(i); + buffered_time_ranges->Add( + TimeForByteOffset(start, total_bytes_, media_duration), + TimeForByteOffset(end, total_bytes_, media_duration)); + } + } +} + +bool BufferedDataSourceHostImpl::DidLoadingProgress() { + bool ret = did_loading_progress_; + did_loading_progress_ = false; + return ret; +} + +} // namespace media diff --git a/media/blink/buffered_data_source_host_impl.h b/media/blink/buffered_data_source_host_impl.h new file mode 100644 index 0000000..fde5bb2 --- /dev/null +++ b/media/blink/buffered_data_source_host_impl.h @@ -0,0 +1,51 @@ +// Copyright 2014 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. + +#ifndef MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_ +#define MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_ + +#include "base/time/time.h" +#include "media/base/media_export.h" +#include "media/base/ranges.h" +#include "media/blink/buffered_data_source.h" + +namespace media { + +// Provides an implementation of BufferedDataSourceHost that translates the +// buffered byte ranges into estimated time ranges. +class MEDIA_EXPORT BufferedDataSourceHostImpl + : public BufferedDataSourceHost { + public: + BufferedDataSourceHostImpl(); + virtual ~BufferedDataSourceHostImpl(); + + // BufferedDataSourceHost implementation. + virtual void SetTotalBytes(int64 total_bytes) OVERRIDE; + virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE; + + // Translate the byte ranges to time ranges and append them to the list. + // TODO(sandersd): This is a confusing name, find something better. + void AddBufferedTimeRanges( + Ranges<base::TimeDelta>* buffered_time_ranges, + base::TimeDelta media_duration) const; + + bool DidLoadingProgress(); + + private: + // Total size of the data source. + int64 total_bytes_; + + // List of buffered byte ranges for estimating buffered time. + Ranges<int64> buffered_byte_ranges_; + + // True when AddBufferedByteRange() has been called more recently than + // DidLoadingProgress(). + bool did_loading_progress_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceHostImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_BUFFERED_DATA_SOURCE_HOST_IMPL_H_ diff --git a/media/blink/buffered_data_source_host_impl_unittest.cc b/media/blink/buffered_data_source_host_impl_unittest.cc new file mode 100644 index 0000000..ef0a461 --- /dev/null +++ b/media/blink/buffered_data_source_host_impl_unittest.cc @@ -0,0 +1,75 @@ +// Copyright 2014 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 "media/blink/buffered_data_source_host_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class BufferedDataSourceHostImplTest : public testing::Test { + public: + BufferedDataSourceHostImplTest() {} + + void Add() { + host_.AddBufferedTimeRanges(&ranges_, base::TimeDelta::FromSeconds(10)); + } + + protected: + BufferedDataSourceHostImpl host_; + Ranges<base::TimeDelta> ranges_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceHostImplTest); +}; + +TEST_F(BufferedDataSourceHostImplTest, Empty) { + EXPECT_FALSE(host_.DidLoadingProgress()); + Add(); + EXPECT_EQ(0u, ranges_.size()); +} + +TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges) { + host_.AddBufferedByteRange(10, 20); + host_.SetTotalBytes(100); + Add(); + EXPECT_EQ(1u, ranges_.size()); + EXPECT_EQ(base::TimeDelta::FromSeconds(1), ranges_.start(0)); + EXPECT_EQ(base::TimeDelta::FromSeconds(2), ranges_.end(0)); +} + +TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges_Merges) { + ranges_.Add(base::TimeDelta::FromSeconds(0), base::TimeDelta::FromSeconds(1)); + host_.AddBufferedByteRange(10, 20); + host_.SetTotalBytes(100); + Add(); + EXPECT_EQ(1u, ranges_.size()); + EXPECT_EQ(base::TimeDelta::FromSeconds(0), ranges_.start(0)); + EXPECT_EQ(base::TimeDelta::FromSeconds(2), ranges_.end(0)); +} + +TEST_F(BufferedDataSourceHostImplTest, AddBufferedTimeRanges_Snaps) { + host_.AddBufferedByteRange(5, 995); + host_.SetTotalBytes(1000); + Add(); + EXPECT_EQ(1u, ranges_.size()); + EXPECT_EQ(base::TimeDelta::FromSeconds(0), ranges_.start(0)); + EXPECT_EQ(base::TimeDelta::FromSeconds(10), ranges_.end(0)); +} + +TEST_F(BufferedDataSourceHostImplTest, SetTotalBytes) { + host_.AddBufferedByteRange(10, 20); + Add(); + EXPECT_EQ(0u, ranges_.size()); + + host_.SetTotalBytes(100); + Add(); + EXPECT_EQ(1u, ranges_.size()); +} + +TEST_F(BufferedDataSourceHostImplTest, DidLoadingProgress) { + host_.AddBufferedByteRange(10, 20); + EXPECT_TRUE(host_.DidLoadingProgress()); + EXPECT_FALSE(host_.DidLoadingProgress()); +} + +} // namespace media diff --git a/media/blink/buffered_data_source_unittest.cc b/media/blink/buffered_data_source_unittest.cc new file mode 100644 index 0000000..a76beb0 --- /dev/null +++ b/media/blink/buffered_data_source_unittest.cc @@ -0,0 +1,779 @@ +// 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 "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "media/base/media_log.h" +#include "media/base/mock_filters.h" +#include "media/base/test_helpers.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/mock_webframeclient.h" +#include "media/blink/mock_weburlloader.h" +#include "media/blink/test_response_generator.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::NiceMock; +using ::testing::StrictMock; + +using blink::WebLocalFrame; +using blink::WebString; +using blink::WebURLLoader; +using blink::WebURLResponse; +using blink::WebView; + +namespace media { + +class MockBufferedDataSourceHost : public BufferedDataSourceHost { + public: + MockBufferedDataSourceHost() {} + virtual ~MockBufferedDataSourceHost() {} + + MOCK_METHOD1(SetTotalBytes, void(int64 total_bytes)); + MOCK_METHOD2(AddBufferedByteRange, void(int64 start, int64 end)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSourceHost); +}; + +// Overrides CreateResourceLoader() to permit injecting a MockWebURLLoader. +// Also keeps track of whether said MockWebURLLoader is actively loading. +class MockBufferedDataSource : public BufferedDataSource { + public: + MockBufferedDataSource( + const GURL& url, + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + WebLocalFrame* frame, + BufferedDataSourceHost* host) + : BufferedDataSource(url, + BufferedResourceLoader::kUnspecified, + task_runner, + frame, + new media::MediaLog(), + host, + base::Bind(&MockBufferedDataSource::set_downloading, + base::Unretained(this))), + downloading_(false), + loading_(false) {} + virtual ~MockBufferedDataSource() {} + + MOCK_METHOD2(CreateResourceLoader, BufferedResourceLoader*(int64, int64)); + BufferedResourceLoader* CreateMockResourceLoader(int64 first_byte_position, + int64 last_byte_position) { + CHECK(!loading_) << "Previous resource load wasn't cancelled"; + + BufferedResourceLoader* loader = + BufferedDataSource::CreateResourceLoader(first_byte_position, + last_byte_position); + + // Keep track of active loading state via loadAsynchronously() and cancel(). + NiceMock<MockWebURLLoader>* url_loader = new NiceMock<MockWebURLLoader>(); + ON_CALL(*url_loader, loadAsynchronously(_, _)) + .WillByDefault(Assign(&loading_, true)); + ON_CALL(*url_loader, cancel()) + .WillByDefault(Assign(&loading_, false)); + + // |test_loader_| will be used when Start() is called. + loader->test_loader_ = scoped_ptr<WebURLLoader>(url_loader); + return loader; + } + + bool loading() { return loading_; } + void set_loading(bool loading) { loading_ = loading; } + bool downloading() { return downloading_; } + void set_downloading(bool downloading) { downloading_ = downloading; } + + private: + // Whether the resource is downloading or deferred. + bool downloading_; + + // Whether the resource load has starting loading but yet to been cancelled. + bool loading_; + + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); +}; + +static const int64 kFileSize = 5000000; +static const int64 kFarReadPosition = 4000000; +static const int kDataSize = 1024; + +static const char kHttpUrl[] = "http://localhost/foo.webm"; +static const char kFileUrl[] = "file:///tmp/bar.webm"; + +class BufferedDataSourceTest : public testing::Test { + public: + BufferedDataSourceTest() + : view_(WebView::create(NULL)), + frame_(WebLocalFrame::create(&client_)), + preload_(BufferedDataSource::AUTO) { + view_->setMainFrame(frame_); + } + + virtual ~BufferedDataSourceTest() { + view_->close(); + frame_->close(); + } + + MOCK_METHOD1(OnInitialize, void(bool)); + + void Initialize(const char* url, bool expected) { + GURL gurl(url); + data_source_.reset( + new MockBufferedDataSource(gurl, + message_loop_.message_loop_proxy(), + view_->mainFrame()->toWebLocalFrame(), + &host_)); + data_source_->SetPreload(preload_); + + response_generator_.reset(new TestResponseGenerator(gurl, kFileSize)); + ExpectCreateResourceLoader(); + EXPECT_CALL(*this, OnInitialize(expected)); + data_source_->Initialize(base::Bind(&BufferedDataSourceTest::OnInitialize, + base::Unretained(this))); + message_loop_.RunUntilIdle(); + + bool is_http = gurl.SchemeIsHTTPOrHTTPS(); + EXPECT_EQ(data_source_->downloading(), is_http); + } + + // Helper to initialize tests with a valid 200 response. + void InitializeWith200Response() { + Initialize(kHttpUrl, true); + + EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); + Respond(response_generator_->Generate200()); + } + + // Helper to initialize tests with a valid 206 response. + void InitializeWith206Response() { + Initialize(kHttpUrl, true); + + EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); + Respond(response_generator_->Generate206(0)); + } + + // Helper to initialize tests with a valid file:// response. + void InitializeWithFileResponse() { + Initialize(kFileUrl, true); + + EXPECT_CALL(host_, SetTotalBytes(kFileSize)); + EXPECT_CALL(host_, AddBufferedByteRange(0, kFileSize)); + Respond(response_generator_->GenerateFileResponse(0)); + } + + // Stops any active loaders and shuts down the data source. + // + // This typically happens when the page is closed and for our purposes is + // appropriate to do when tearing down a test. + void Stop() { + if (data_source_->loading()) { + loader()->didFail(url_loader(), response_generator_->GenerateError()); + message_loop_.RunUntilIdle(); + } + + data_source_->Stop(); + message_loop_.RunUntilIdle(); + } + + void ExpectCreateResourceLoader() { + EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) + .WillOnce(Invoke(data_source_.get(), + &MockBufferedDataSource::CreateMockResourceLoader)); + message_loop_.RunUntilIdle(); + } + + void Respond(const WebURLResponse& response) { + loader()->didReceiveResponse(url_loader(), response); + message_loop_.RunUntilIdle(); + } + + void ReceiveData(int size) { + scoped_ptr<char[]> data(new char[size]); + memset(data.get(), 0xA5, size); // Arbitrary non-zero value. + + loader()->didReceiveData(url_loader(), data.get(), size, size); + message_loop_.RunUntilIdle(); + } + + void FinishLoading() { + data_source_->set_loading(false); + loader()->didFinishLoading(url_loader(), 0, -1); + message_loop_.RunUntilIdle(); + } + + MOCK_METHOD1(ReadCallback, void(int size)); + + void ReadAt(int64 position) { + data_source_->Read(position, kDataSize, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + message_loop_.RunUntilIdle(); + } + + // Accessors for private variables on |data_source_|. + BufferedResourceLoader* loader() { + return data_source_->loader_.get(); + } + WebURLLoader* url_loader() { + return loader()->active_loader_->loader_.get(); + } + + BufferedDataSource::Preload preload() { return data_source_->preload_; } + void set_preload(BufferedDataSource::Preload preload) { preload_ = preload; } + BufferedResourceLoader::DeferStrategy defer_strategy() { + return loader()->defer_strategy_; + } + int data_source_bitrate() { return data_source_->bitrate_; } + int data_source_playback_rate() { return data_source_->playback_rate_; } + int loader_bitrate() { return loader()->bitrate_; } + int loader_playback_rate() { return loader()->playback_rate_; } + bool is_local_source() { return data_source_->assume_fully_buffered(); } + void set_might_be_reused_from_cache_in_future(bool value) { + loader()->might_be_reused_from_cache_in_future_ = value; + } + + scoped_ptr<MockBufferedDataSource> data_source_; + + scoped_ptr<TestResponseGenerator> response_generator_; + MockWebFrameClient client_; + WebView* view_; + WebLocalFrame* frame_; + + StrictMock<MockBufferedDataSourceHost> host_; + base::MessageLoop message_loop_; + + private: + // Used for calling BufferedDataSource::Read(). + uint8 buffer_[kDataSize]; + + BufferedDataSource::Preload preload_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest); +}; + +TEST_F(BufferedDataSourceTest, Range_Supported) { + InitializeWith206Response(); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_FALSE(data_source_->IsStreaming()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_InstanceSizeUnknown) { + Initialize(kHttpUrl, true); + + Respond(response_generator_->Generate206( + 0, TestResponseGenerator::kNoContentRangeInstanceSize)); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_TRUE(data_source_->IsStreaming()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_NotFound) { + Initialize(kHttpUrl, false); + Respond(response_generator_->Generate404()); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_NotSupported) { + InitializeWith200Response(); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_TRUE(data_source_->IsStreaming()); + Stop(); +} + +// Special carve-out for Apache versions that choose to return a 200 for +// Range:0- ("because it's more efficient" than a 206) +TEST_F(BufferedDataSourceTest, Range_SupportedButReturned200) { + Initialize(kHttpUrl, true); + EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); + WebURLResponse response = response_generator_->Generate200(); + response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), + WebString::fromUTF8("bytes")); + Respond(response); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_FALSE(data_source_->IsStreaming()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_MissingContentRange) { + Initialize(kHttpUrl, false); + Respond(response_generator_->Generate206( + 0, TestResponseGenerator::kNoContentRange)); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_MissingContentLength) { + Initialize(kHttpUrl, true); + + // It'll manage without a Content-Length response. + EXPECT_CALL(host_, SetTotalBytes(response_generator_->content_length())); + Respond(response_generator_->Generate206( + 0, TestResponseGenerator::kNoContentLength)); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_FALSE(data_source_->IsStreaming()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Range_WrongContentRange) { + Initialize(kHttpUrl, false); + + // Now it's done and will fail. + Respond(response_generator_->Generate206(1337)); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +// Test the case where the initial response from the server indicates that +// Range requests are supported, but a later request prove otherwise. +TEST_F(BufferedDataSourceTest, Range_ServerLied) { + InitializeWith206Response(); + + // Read causing a new request to be made -- we'll expect it to error. + ExpectCreateResourceLoader(); + ReadAt(kFarReadPosition); + + // Return a 200 in response to a range request. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + Respond(response_generator_->Generate200()); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_AbortWhileReading) { + InitializeWith206Response(); + + // Make sure there's a pending read -- we'll expect it to error. + ReadAt(0); + + // Abort!!! + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + data_source_->Abort(); + message_loop_.RunUntilIdle(); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_AbortWhileReading) { + InitializeWithFileResponse(); + + // Make sure there's a pending read -- we'll expect it to error. + ReadAt(0); + + // Abort!!! + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + data_source_->Abort(); + message_loop_.RunUntilIdle(); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_Retry) { + InitializeWith206Response(); + + // Read to advance our position. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1)); + ReadAt(0); + ReceiveData(kDataSize); + + // Issue a pending read but terminate the connection to force a retry. + ReadAt(kDataSize); + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->Generate206(kDataSize)); + + // Complete the read. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + EXPECT_CALL(host_, AddBufferedByteRange(kDataSize, (kDataSize * 2) - 1)); + ReceiveData(kDataSize); + + EXPECT_TRUE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_Retry) { + InitializeWithFileResponse(); + + // Read to advance our position. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + ReadAt(0); + ReceiveData(kDataSize); + + // Issue a pending read but terminate the connection to force a retry. + ReadAt(kDataSize); + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->GenerateFileResponse(kDataSize)); + + // Complete the read. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + ReceiveData(kDataSize); + + EXPECT_TRUE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_TooManyRetries) { + InitializeWith206Response(); + + // Make sure there's a pending read -- we'll expect it to error. + ReadAt(0); + + // It'll try three times. + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->Generate206(0)); + + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->Generate206(0)); + + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->Generate206(0)); + + // It'll error after this. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + FinishLoading(); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_TooManyRetries) { + InitializeWithFileResponse(); + + // Make sure there's a pending read -- we'll expect it to error. + ReadAt(0); + + // It'll try three times. + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->GenerateFileResponse(0)); + + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->GenerateFileResponse(0)); + + ExpectCreateResourceLoader(); + FinishLoading(); + Respond(response_generator_->GenerateFileResponse(0)); + + // It'll error after this. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + FinishLoading(); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_InstanceSizeUnknown) { + Initialize(kFileUrl, false); + EXPECT_FALSE(data_source_->downloading()); + + Respond(response_generator_->GenerateFileResponse(-1)); + + EXPECT_FALSE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_Successful) { + InitializeWithFileResponse(); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_FALSE(data_source_->IsStreaming()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, StopDuringRead) { + InitializeWith206Response(); + + uint8 buffer[256]; + data_source_->Read(0, arraysize(buffer), buffer, base::Bind( + &BufferedDataSourceTest::ReadCallback, base::Unretained(this))); + + // The outstanding read should fail before the stop callback runs. + { + InSequence s; + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + data_source_->Stop(); + } + message_loop_.RunUntilIdle(); +} + +TEST_F(BufferedDataSourceTest, DefaultValues) { + InitializeWith206Response(); + + // Ensure we have sane values for default loading scenario. + EXPECT_EQ(BufferedDataSource::AUTO, preload()); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + EXPECT_EQ(0, data_source_bitrate()); + EXPECT_EQ(0.0f, data_source_playback_rate()); + EXPECT_EQ(0, loader_bitrate()); + EXPECT_EQ(0.0f, loader_playback_rate()); + + EXPECT_TRUE(data_source_->loading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, SetBitrate) { + InitializeWith206Response(); + + data_source_->SetBitrate(1234); + message_loop_.RunUntilIdle(); + EXPECT_EQ(1234, data_source_bitrate()); + EXPECT_EQ(1234, loader_bitrate()); + + // Read so far ahead to cause the loader to get recreated. + BufferedResourceLoader* old_loader = loader(); + ExpectCreateResourceLoader(); + ReadAt(kFarReadPosition); + Respond(response_generator_->Generate206(kFarReadPosition)); + + // Verify loader changed but still has same bitrate. + EXPECT_NE(old_loader, loader()); + EXPECT_EQ(1234, loader_bitrate()); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + Stop(); +} + +TEST_F(BufferedDataSourceTest, MediaPlaybackRateChanged) { + InitializeWith206Response(); + + data_source_->MediaPlaybackRateChanged(2.0f); + message_loop_.RunUntilIdle(); + EXPECT_EQ(2.0f, data_source_playback_rate()); + EXPECT_EQ(2.0f, loader_playback_rate()); + + // Read so far ahead to cause the loader to get recreated. + BufferedResourceLoader* old_loader = loader(); + ExpectCreateResourceLoader(); + ReadAt(kFarReadPosition); + Respond(response_generator_->Generate206(kFarReadPosition)); + + // Verify loader changed but still has same playback rate. + EXPECT_NE(old_loader, loader()); + + EXPECT_TRUE(data_source_->loading()); + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_Read) { + InitializeWith206Response(); + + ReadAt(0); + + // Receive first half of the read. + EXPECT_CALL(host_, AddBufferedByteRange(0, (kDataSize / 2) - 1)); + ReceiveData(kDataSize / 2); + + // Receive last half of the read. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1)); + ReceiveData(kDataSize / 2); + + EXPECT_TRUE(data_source_->downloading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_Read_Seek) { + InitializeWith206Response(); + + // Read a bit from the beginning. + ReadAt(0); + EXPECT_CALL(*this, ReadCallback(kDataSize)); + EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize - 1)); + ReceiveData(kDataSize); + + // Simulate a seek by reading a bit beyond kDataSize. + ReadAt(kDataSize * 2); + + // We receive data leading up to but not including our read. + EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 2 - 1)); + ReceiveData(kDataSize); + + // We now receive the rest of the data for our read. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + EXPECT_CALL(host_, AddBufferedByteRange(0, kDataSize * 3 - 1)); + ReceiveData(kDataSize); + + EXPECT_TRUE(data_source_->downloading()); + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_Read) { + InitializeWithFileResponse(); + + ReadAt(0); + + // Receive first half of the read but no buffering update. + ReceiveData(kDataSize / 2); + + // Receive last half of the read but no buffering update. + EXPECT_CALL(*this, ReadCallback(kDataSize)); + ReceiveData(kDataSize / 2); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, Http_FinishLoading) { + InitializeWith206Response(); + + EXPECT_TRUE(data_source_->downloading()); + FinishLoading(); + EXPECT_FALSE(data_source_->downloading()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, File_FinishLoading) { + InitializeWithFileResponse(); + + EXPECT_FALSE(data_source_->downloading()); + FinishLoading(); + EXPECT_FALSE(data_source_->downloading()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, LocalResource_DeferStrategy) { + InitializeWithFileResponse(); + + EXPECT_EQ(BufferedDataSource::AUTO, preload()); + EXPECT_TRUE(is_local_source()); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, LocalResource_PreloadMetadata_DeferStrategy) { + set_preload(BufferedDataSource::METADATA); + InitializeWithFileResponse(); + + EXPECT_EQ(BufferedDataSource::METADATA, preload()); + EXPECT_TRUE(is_local_source()); + EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, ExternalResource_Reponse200_DeferStrategy) { + InitializeWith200Response(); + + EXPECT_EQ(BufferedDataSource::AUTO, preload()); + EXPECT_FALSE(is_local_source()); + EXPECT_FALSE(loader()->range_supported()); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, + ExternalResource_Response200_PreloadMetadata_DeferStrategy) { + set_preload(BufferedDataSource::METADATA); + InitializeWith200Response(); + + EXPECT_EQ(BufferedDataSource::METADATA, preload()); + EXPECT_FALSE(is_local_source()); + EXPECT_FALSE(loader()->range_supported()); + EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, ExternalResource_Reponse206_DeferStrategy) { + InitializeWith206Response(); + + EXPECT_EQ(BufferedDataSource::AUTO, preload()); + EXPECT_FALSE(is_local_source()); + EXPECT_TRUE(loader()->range_supported()); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + set_might_be_reused_from_cache_in_future(true); + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kNeverDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + set_might_be_reused_from_cache_in_future(false); + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +TEST_F(BufferedDataSourceTest, + ExternalResource_Response206_PreloadMetadata_DeferStrategy) { + set_preload(BufferedDataSource::METADATA); + InitializeWith206Response(); + + EXPECT_EQ(BufferedDataSource::METADATA, preload()); + EXPECT_FALSE(is_local_source()); + EXPECT_TRUE(loader()->range_supported()); + EXPECT_EQ(BufferedResourceLoader::kReadThenDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + set_might_be_reused_from_cache_in_future(true); + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kNeverDefer, defer_strategy()); + + data_source_->MediaIsPlaying(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + set_might_be_reused_from_cache_in_future(false); + data_source_->MediaIsPaused(); + EXPECT_EQ(BufferedResourceLoader::kCapacityDefer, defer_strategy()); + + Stop(); +} + +} // namespace media diff --git a/media/blink/buffered_resource_loader.cc b/media/blink/buffered_resource_loader.cc new file mode 100644 index 0000000..d88266d --- /dev/null +++ b/media/blink/buffered_resource_loader.cc @@ -0,0 +1,790 @@ +// 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 "media/blink/buffered_resource_loader.h" + +#include "base/bits.h" +#include "base/callback_helpers.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "media/base/media_log.h" +#include "media/blink/cache_util.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_request_headers.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" +#include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebURLLoaderOptions.h" + +using blink::WebFrame; +using blink::WebString; +using blink::WebURLError; +using blink::WebURLLoader; +using blink::WebURLLoaderOptions; +using blink::WebURLRequest; +using blink::WebURLResponse; + +namespace media { + +static const int kHttpOK = 200; +static const int kHttpPartialContent = 206; + +// Define the number of bytes in a megabyte. +static const int kMegabyte = 1024 * 1024; + +// Minimum capacity of the buffer in forward or backward direction. +// +// 2MB is an arbitrary limit; it just seems to be "good enough" in practice. +static const int kMinBufferCapacity = 2 * kMegabyte; + +// Maximum capacity of the buffer in forward or backward direction. This is +// effectively the largest single read the code path can handle. +// 20MB is an arbitrary limit; it just seems to be "good enough" in practice. +static const int kMaxBufferCapacity = 20 * kMegabyte; + +// Maximum number of bytes outside the buffer we will wait for in order to +// fulfill a read. If a read starts more than 2MB away from the data we +// currently have in the buffer, we will not wait for buffer to reach the read's +// location and will instead reset the request. +static const int kForwardWaitThreshold = 2 * kMegabyte; + +// Computes the suggested backward and forward capacity for the buffer +// if one wants to play at |playback_rate| * the natural playback speed. +// Use a value of 0 for |bitrate| if it is unknown. +static void ComputeTargetBufferWindow(float playback_rate, int bitrate, + int* out_backward_capacity, + int* out_forward_capacity) { + static const int kDefaultBitrate = 200 * 1024 * 8; // 200 Kbps. + static const int kMaxBitrate = 20 * kMegabyte * 8; // 20 Mbps. + static const float kMaxPlaybackRate = 25.0; + static const int kTargetSecondsBufferedAhead = 10; + static const int kTargetSecondsBufferedBehind = 2; + + // Use a default bit rate if unknown and clamp to prevent overflow. + if (bitrate <= 0) + bitrate = kDefaultBitrate; + bitrate = std::min(bitrate, kMaxBitrate); + + // Only scale the buffer window for playback rates greater than 1.0 in + // magnitude and clamp to prevent overflow. + bool backward_playback = false; + if (playback_rate < 0.0f) { + backward_playback = true; + playback_rate *= -1.0f; + } + + playback_rate = std::max(playback_rate, 1.0f); + playback_rate = std::min(playback_rate, kMaxPlaybackRate); + + int bytes_per_second = (bitrate / 8.0) * playback_rate; + + // Clamp between kMinBufferCapacity and kMaxBufferCapacity. + *out_forward_capacity = std::max( + kTargetSecondsBufferedAhead * bytes_per_second, kMinBufferCapacity); + *out_backward_capacity = std::max( + kTargetSecondsBufferedBehind * bytes_per_second, kMinBufferCapacity); + + *out_forward_capacity = std::min(*out_forward_capacity, kMaxBufferCapacity); + *out_backward_capacity = std::min(*out_backward_capacity, kMaxBufferCapacity); + + if (backward_playback) + std::swap(*out_forward_capacity, *out_backward_capacity); +} + +BufferedResourceLoader::BufferedResourceLoader( + const GURL& url, + CORSMode cors_mode, + int64 first_byte_position, + int64 last_byte_position, + DeferStrategy strategy, + int bitrate, + float playback_rate, + MediaLog* media_log) + : buffer_(kMinBufferCapacity, kMinBufferCapacity), + loader_failed_(false), + defer_strategy_(strategy), + might_be_reused_from_cache_in_future_(true), + range_supported_(false), + saved_forward_capacity_(0), + url_(url), + cors_mode_(cors_mode), + first_byte_position_(first_byte_position), + last_byte_position_(last_byte_position), + single_origin_(true), + offset_(0), + content_length_(kPositionNotSpecified), + instance_size_(kPositionNotSpecified), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + first_offset_(0), + last_offset_(0), + bitrate_(bitrate), + playback_rate_(playback_rate), + media_log_(media_log) { + + // Set the initial capacity of |buffer_| based on |bitrate_| and + // |playback_rate_|. + UpdateBufferWindow(); +} + +BufferedResourceLoader::~BufferedResourceLoader() {} + +void BufferedResourceLoader::Start( + const StartCB& start_cb, + const LoadingStateChangedCB& loading_cb, + const ProgressCB& progress_cb, + WebFrame* frame) { + // Make sure we have not started. + DCHECK(start_cb_.is_null()); + DCHECK(loading_cb_.is_null()); + DCHECK(progress_cb_.is_null()); + DCHECK(!start_cb.is_null()); + DCHECK(!loading_cb.is_null()); + DCHECK(!progress_cb.is_null()); + CHECK(frame); + + start_cb_ = start_cb; + loading_cb_ = loading_cb; + progress_cb_ = progress_cb; + + if (first_byte_position_ != kPositionNotSpecified) { + // TODO(hclam): server may not support range request so |offset_| may not + // equal to |first_byte_position_|. + offset_ = first_byte_position_; + } + + // Prepare the request. + WebURLRequest request(url_); + // TODO(mkwst): Split this into video/audio. + request.setRequestContext(WebURLRequest::RequestContextVideo); + + if (IsRangeRequest()) { + request.setHTTPHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kRange), + WebString::fromUTF8(net::HttpByteRange::Bounded( + first_byte_position_, last_byte_position_).GetHeaderValue())); + } + + frame->setReferrerForRequest(request, blink::WebURL()); + + // Disable compression, compression for audio/video doesn't make sense... + request.setHTTPHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding), + WebString::fromUTF8("identity;q=1, *;q=0")); + + // Check for our test WebURLLoader. + scoped_ptr<WebURLLoader> loader; + if (test_loader_) { + loader = test_loader_.Pass(); + } else { + WebURLLoaderOptions options; + if (cors_mode_ == kUnspecified) { + options.allowCredentials = true; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyAllow; + } else { + options.exposeAllResponseHeaders = true; + // The author header set is empty, no preflight should go ahead. + options.preflightPolicy = WebURLLoaderOptions::PreventPreflight; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; + if (cors_mode_ == kUseCredentials) + options.allowCredentials = true; + } + loader.reset(frame->createAssociatedURLLoader(options)); + } + + // Start the resource loading. + loader->loadAsynchronously(request, this); + active_loader_.reset(new ActiveLoader(loader.Pass())); + loading_cb_.Run(kLoading); +} + +void BufferedResourceLoader::Stop() { + // Reset callbacks. + start_cb_.Reset(); + loading_cb_.Reset(); + progress_cb_.Reset(); + read_cb_.Reset(); + + // Cancel and reset any active loaders. + active_loader_.reset(); +} + +void BufferedResourceLoader::Read( + int64 position, + int read_size, + uint8* buffer, + const ReadCB& read_cb) { + DCHECK(start_cb_.is_null()); + DCHECK(read_cb_.is_null()); + DCHECK(!read_cb.is_null()); + DCHECK(buffer); + DCHECK_GT(read_size, 0); + + // Save the parameter of reading. + read_cb_ = read_cb; + read_position_ = position; + read_size_ = read_size; + read_buffer_ = buffer; + + // Reads should immediately fail if the loader also failed. + if (loader_failed_) { + DoneRead(kFailed, 0); + return; + } + + // If we're attempting to read past the end of the file, return a zero + // indicating EOF. + // + // This can happen with callees that read in fixed-sized amounts for parsing + // or at the end of chunked 200 responses when we discover the actual length + // of the file. + if (instance_size_ != kPositionNotSpecified && + instance_size_ <= read_position_) { + DVLOG(1) << "Appear to have seeked beyond EOS; returning 0."; + DoneRead(kOk, 0); + return; + } + + // Make sure |offset_| and |read_position_| does not differ by a large + // amount. + if (read_position_ > offset_ + kint32max || + read_position_ < offset_ + kint32min) { + DoneRead(kCacheMiss, 0); + return; + } + + // Make sure |read_size_| is not too large for the buffer to ever be able to + // fulfill the read request. + if (read_size_ > kMaxBufferCapacity) { + DoneRead(kFailed, 0); + return; + } + + // Prepare the parameters. + first_offset_ = read_position_ - offset_; + last_offset_ = first_offset_ + read_size_; + + // If we can serve the request now, do the actual read. + if (CanFulfillRead()) { + ReadInternal(); + UpdateDeferBehavior(); + return; + } + + // If we expect the read request to be fulfilled later, expand capacity as + // necessary and disable deferring. + if (WillFulfillRead()) { + // Advance offset as much as possible to create additional capacity. + int advance = std::min(first_offset_, buffer_.forward_bytes()); + bool ret = buffer_.Seek(advance); + DCHECK(ret); + + offset_ += advance; + first_offset_ -= advance; + last_offset_ -= advance; + + // Expand capacity to accomodate a read that extends past the normal + // capacity. + // + // This can happen when reading in a large seek index or when the + // first byte of a read request falls within kForwardWaitThreshold. + if (last_offset_ > buffer_.forward_capacity()) { + saved_forward_capacity_ = buffer_.forward_capacity(); + buffer_.set_forward_capacity(last_offset_); + } + + // Make sure we stop deferring now that there's additional capacity. + DCHECK(!ShouldDefer()) + << "Capacity was not adjusted properly to prevent deferring."; + UpdateDeferBehavior(); + + return; + } + + // Make a callback to report failure. + DoneRead(kCacheMiss, 0); +} + +int64 BufferedResourceLoader::content_length() { + return content_length_; +} + +int64 BufferedResourceLoader::instance_size() { + return instance_size_; +} + +bool BufferedResourceLoader::range_supported() { + return range_supported_; +} + +///////////////////////////////////////////////////////////////////////////// +// blink::WebURLLoaderClient implementation. +void BufferedResourceLoader::willSendRequest( + WebURLLoader* loader, + WebURLRequest& newRequest, + const WebURLResponse& redirectResponse) { + + // The load may have been stopped and |start_cb| is destroyed. + // In this case we shouldn't do anything. + if (start_cb_.is_null()) { + // Set the url in the request to an invalid value (empty url). + newRequest.setURL(blink::WebURL()); + return; + } + + // Only allow |single_origin_| if we haven't seen a different origin yet. + if (single_origin_) + single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin(); + + url_ = newRequest.url(); +} + +void BufferedResourceLoader::didSendData( + WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + NOTIMPLEMENTED(); +} + +void BufferedResourceLoader::didReceiveResponse( + WebURLLoader* loader, + const WebURLResponse& response) { + DVLOG(1) << "didReceiveResponse: HTTP/" + << (response.httpVersion() == WebURLResponse::HTTP_0_9 ? "0.9" : + response.httpVersion() == WebURLResponse::HTTP_1_0 ? "1.0" : + response.httpVersion() == WebURLResponse::HTTP_1_1 ? "1.1" : + "Unknown") + << " " << response.httpStatusCode(); + DCHECK(active_loader_.get()); + + // The loader may have been stopped and |start_cb| is destroyed. + // In this case we shouldn't do anything. + if (start_cb_.is_null()) + return; + + uint32 reasons = GetReasonsForUncacheability(response); + might_be_reused_from_cache_in_future_ = reasons == 0; + UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0); + int shift = 0; + int max_enum = base::bits::Log2Ceiling(kMaxReason); + while (reasons) { + DCHECK_LT(shift, max_enum); // Sanity check. + if (reasons & 0x1) { + UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", + shift, + max_enum); // PRESUBMIT_IGNORE_UMA_MAX + } + + reasons >>= 1; + ++shift; + } + + // Expected content length can be |kPositionNotSpecified|, in that case + // |content_length_| is not specified and this is a streaming response. + content_length_ = response.expectedContentLength(); + + // We make a strong assumption that when we reach here we have either + // received a response from HTTP/HTTPS protocol or the request was + // successful (in particular range request). So we only verify the partial + // response for HTTP and HTTPS protocol. + if (url_.SchemeIsHTTPOrHTTPS()) { + bool partial_response = (response.httpStatusCode() == kHttpPartialContent); + bool ok_response = (response.httpStatusCode() == kHttpOK); + + if (IsRangeRequest()) { + // Check to see whether the server supports byte ranges. + std::string accept_ranges = + response.httpHeaderField("Accept-Ranges").utf8(); + range_supported_ = (accept_ranges.find("bytes") != std::string::npos); + + // If we have verified the partial response and it is correct, we will + // return kOk. It's also possible for a server to support range requests + // without advertising "Accept-Ranges: bytes". + if (partial_response && VerifyPartialResponse(response)) { + range_supported_ = true; + } else if (ok_response && first_byte_position_ == 0 && + last_byte_position_ == kPositionNotSpecified) { + // We accept a 200 response for a Range:0- request, trusting the + // Accept-Ranges header, because Apache thinks that's a reasonable thing + // to return. + instance_size_ = content_length_; + } else { + DoneStart(kFailed); + return; + } + } else { + instance_size_ = content_length_; + if (response.httpStatusCode() != kHttpOK) { + // We didn't request a range but server didn't reply with "200 OK". + DoneStart(kFailed); + return; + } + } + + } else { + CHECK_EQ(instance_size_, kPositionNotSpecified); + if (content_length_ != kPositionNotSpecified) { + if (first_byte_position_ == kPositionNotSpecified) + instance_size_ = content_length_; + else if (last_byte_position_ == kPositionNotSpecified) + instance_size_ = content_length_ + first_byte_position_; + } + } + + // Calls with a successful response. + DoneStart(kOk); +} + +void BufferedResourceLoader::didReceiveData( + WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length) { + DVLOG(1) << "didReceiveData: " << data_length << " bytes"; + DCHECK(active_loader_.get()); + DCHECK_GT(data_length, 0); + + buffer_.Append(reinterpret_cast<const uint8*>(data), data_length); + + // If there is an active read request, try to fulfill the request. + if (HasPendingRead() && CanFulfillRead()) + ReadInternal(); + + // At last see if the buffer is full and we need to defer the downloading. + UpdateDeferBehavior(); + + // Consume excess bytes from our in-memory buffer if necessary. + if (buffer_.forward_bytes() > buffer_.forward_capacity()) { + int excess = buffer_.forward_bytes() - buffer_.forward_capacity(); + bool success = buffer_.Seek(excess); + DCHECK(success); + offset_ += first_offset_ + excess; + } + + // Notify latest progress and buffered offset. + progress_cb_.Run(offset_ + buffer_.forward_bytes() - 1); + Log(); +} + +void BufferedResourceLoader::didDownloadData( + blink::WebURLLoader* loader, + int dataLength, + int encoded_data_length) { + NOTIMPLEMENTED(); +} + +void BufferedResourceLoader::didReceiveCachedMetadata( + WebURLLoader* loader, + const char* data, + int data_length) { + NOTIMPLEMENTED(); +} + +void BufferedResourceLoader::didFinishLoading( + WebURLLoader* loader, + double finishTime, + int64_t total_encoded_data_length) { + DVLOG(1) << "didFinishLoading"; + DCHECK(active_loader_.get()); + + // We're done with the loader. + active_loader_.reset(); + loading_cb_.Run(kLoadingFinished); + + // If we didn't know the |instance_size_| we do now. + if (instance_size_ == kPositionNotSpecified) { + instance_size_ = offset_ + buffer_.forward_bytes(); + } + + // If there is a start callback, run it. + if (!start_cb_.is_null()) { + DCHECK(read_cb_.is_null()) + << "Shouldn't have a read callback during start"; + DoneStart(kOk); + return; + } + + // Don't leave read callbacks hanging around. + if (HasPendingRead()) { + // Try to fulfill with what is in the buffer. + if (CanFulfillRead()) + ReadInternal(); + else + DoneRead(kCacheMiss, 0); + } +} + +void BufferedResourceLoader::didFail( + WebURLLoader* loader, + const WebURLError& error) { + DVLOG(1) << "didFail: reason=" << error.reason + << ", isCancellation=" << error.isCancellation + << ", domain=" << error.domain.utf8().data() + << ", localizedDescription=" + << error.localizedDescription.utf8().data(); + DCHECK(active_loader_.get()); + + // We don't need to continue loading after failure. + // + // Keep it alive until we exit this method so that |error| remains valid. + scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass(); + loader_failed_ = true; + loading_cb_.Run(kLoadingFailed); + + // Don't leave start callbacks hanging around. + if (!start_cb_.is_null()) { + DCHECK(read_cb_.is_null()) + << "Shouldn't have a read callback during start"; + DoneStart(kFailed); + return; + } + + // Don't leave read callbacks hanging around. + if (HasPendingRead()) { + DoneRead(kFailed, 0); + } +} + +bool BufferedResourceLoader::HasSingleOrigin() const { + DCHECK(start_cb_.is_null()) + << "Start() must complete before calling HasSingleOrigin()"; + return single_origin_; +} + +bool BufferedResourceLoader::DidPassCORSAccessCheck() const { + DCHECK(start_cb_.is_null()) + << "Start() must complete before calling DidPassCORSAccessCheck()"; + return !loader_failed_ && cors_mode_ != kUnspecified; +} + +void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) { + if (!might_be_reused_from_cache_in_future_ && strategy == kNeverDefer) + strategy = kCapacityDefer; + defer_strategy_ = strategy; + UpdateDeferBehavior(); +} + +void BufferedResourceLoader::SetPlaybackRate(float playback_rate) { + playback_rate_ = playback_rate; + + // This is a pause so don't bother updating the buffer window as we'll likely + // get unpaused in the future. + if (playback_rate_ == 0.0) + return; + + UpdateBufferWindow(); +} + +void BufferedResourceLoader::SetBitrate(int bitrate) { + DCHECK(bitrate >= 0); + bitrate_ = bitrate; + UpdateBufferWindow(); +} + +///////////////////////////////////////////////////////////////////////////// +// Helper methods. + +void BufferedResourceLoader::UpdateBufferWindow() { + int backward_capacity; + int forward_capacity; + ComputeTargetBufferWindow( + playback_rate_, bitrate_, &backward_capacity, &forward_capacity); + + // This does not evict data from the buffer if the new capacities are less + // than the current capacities; the new limits will be enforced after the + // existing excess buffered data is consumed. + buffer_.set_backward_capacity(backward_capacity); + buffer_.set_forward_capacity(forward_capacity); +} + +void BufferedResourceLoader::UpdateDeferBehavior() { + if (!active_loader_) + return; + + SetDeferred(ShouldDefer()); +} + +void BufferedResourceLoader::SetDeferred(bool deferred) { + if (active_loader_->deferred() == deferred) + return; + + active_loader_->SetDeferred(deferred); + loading_cb_.Run(deferred ? kLoadingDeferred : kLoading); +} + +bool BufferedResourceLoader::ShouldDefer() const { + switch(defer_strategy_) { + case kNeverDefer: + return false; + + case kReadThenDefer: + DCHECK(read_cb_.is_null() || last_offset_ > buffer_.forward_bytes()) + << "We shouldn't stop deferring if we can fulfill the read"; + return read_cb_.is_null(); + + case kCapacityDefer: + return buffer_.forward_bytes() >= buffer_.forward_capacity(); + } + NOTREACHED(); + return false; +} + +bool BufferedResourceLoader::CanFulfillRead() const { + // If we are reading too far in the backward direction. + if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0) + return false; + + // If the start offset is too far ahead. + if (first_offset_ >= buffer_.forward_bytes()) + return false; + + // At the point, we verified that first byte requested is within the buffer. + // If the request has completed, then just returns with what we have now. + if (!active_loader_) + return true; + + // If the resource request is still active, make sure the whole requested + // range is covered. + if (last_offset_ > buffer_.forward_bytes()) + return false; + + return true; +} + +bool BufferedResourceLoader::WillFulfillRead() const { + // Trying to read too far behind. + if (first_offset_ < 0 && (first_offset_ + buffer_.backward_bytes()) < 0) + return false; + + // Trying to read too far ahead. + if ((first_offset_ - buffer_.forward_bytes()) >= kForwardWaitThreshold) + return false; + + // The resource request has completed, there's no way we can fulfill the + // read request. + if (!active_loader_) + return false; + + return true; +} + +void BufferedResourceLoader::ReadInternal() { + // Seek to the first byte requested. + bool ret = buffer_.Seek(first_offset_); + DCHECK(ret); + + // Then do the read. + int read = buffer_.Read(read_buffer_, read_size_); + offset_ += first_offset_ + read; + + // And report with what we have read. + DoneRead(kOk, read); +} + +int64 BufferedResourceLoader::first_byte_position() const { + return first_byte_position_; +} + +// static +bool BufferedResourceLoader::ParseContentRange( + const std::string& content_range_str, int64* first_byte_position, + int64* last_byte_position, int64* instance_size) { + const std::string kUpThroughBytesUnit = "bytes "; + if (content_range_str.find(kUpThroughBytesUnit) != 0) + return false; + std::string range_spec = + content_range_str.substr(kUpThroughBytesUnit.length()); + size_t dash_offset = range_spec.find("-"); + size_t slash_offset = range_spec.find("/"); + + if (dash_offset == std::string::npos || slash_offset == std::string::npos || + slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) { + return false; + } + if (!base::StringToInt64(range_spec.substr(0, dash_offset), + first_byte_position) || + !base::StringToInt64(range_spec.substr(dash_offset + 1, + slash_offset - dash_offset - 1), + last_byte_position)) { + return false; + } + if (slash_offset == range_spec.length() - 2 && + range_spec[slash_offset + 1] == '*') { + *instance_size = kPositionNotSpecified; + } else { + if (!base::StringToInt64(range_spec.substr(slash_offset + 1), + instance_size)) { + return false; + } + } + if (*last_byte_position < *first_byte_position || + (*instance_size != kPositionNotSpecified && + *last_byte_position >= *instance_size)) { + return false; + } + + return true; +} + +bool BufferedResourceLoader::VerifyPartialResponse( + const WebURLResponse& response) { + int64 first_byte_position, last_byte_position, instance_size; + if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(), + &first_byte_position, &last_byte_position, + &instance_size)) { + return false; + } + + if (instance_size != kPositionNotSpecified) { + instance_size_ = instance_size; + } + + if (first_byte_position_ != kPositionNotSpecified && + first_byte_position_ != first_byte_position) { + return false; + } + + // TODO(hclam): I should also check |last_byte_position|, but since + // we will never make such a request that it is ok to leave it unimplemented. + return true; +} + +void BufferedResourceLoader::DoneRead(Status status, int bytes_read) { + if (saved_forward_capacity_) { + buffer_.set_forward_capacity(saved_forward_capacity_); + saved_forward_capacity_ = 0; + } + read_position_ = 0; + read_size_ = 0; + read_buffer_ = NULL; + first_offset_ = 0; + last_offset_ = 0; + Log(); + + base::ResetAndReturn(&read_cb_).Run(status, bytes_read); +} + + +void BufferedResourceLoader::DoneStart(Status status) { + base::ResetAndReturn(&start_cb_).Run(status); +} + +bool BufferedResourceLoader::IsRangeRequest() const { + return first_byte_position_ != kPositionNotSpecified; +} + +void BufferedResourceLoader::Log() { + media_log_->AddEvent( + media_log_->CreateBufferedExtentsChangedEvent( + offset_ - buffer_.backward_bytes(), + offset_, + offset_ + buffer_.forward_bytes())); +} + +} // namespace media diff --git a/media/blink/buffered_resource_loader.h b/media/blink/buffered_resource_loader.h new file mode 100644 index 0000000..fb633be --- /dev/null +++ b/media/blink/buffered_resource_loader.h @@ -0,0 +1,320 @@ +// 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. + +#ifndef MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_ +#define MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer/timer.h" +#include "media/base/media_export.h" +#include "media/base/seekable_buffer.h" +#include "media/blink/active_loader.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" +#include "third_party/WebKit/public/platform/WebURLLoaderClient.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "url/gurl.h" + +namespace media { +class MediaLog; +class SeekableBuffer; + +const int64 kPositionNotSpecified = -1; + +// BufferedResourceLoader is single threaded and must be accessed on the +// render thread. It wraps a WebURLLoader and does in-memory buffering, +// pausing resource loading when the in-memory buffer is full and resuming +// resource loading when there is available capacity. +class MEDIA_EXPORT BufferedResourceLoader + : NON_EXPORTED_BASE(public blink::WebURLLoaderClient) { + public: + // kNeverDefer - Aggresively buffer; never defer loading while paused. + // kReadThenDefer - Request only enough data to fulfill read requests. + // kCapacityDefer - Try to keep amount of buffered data at capacity. + enum DeferStrategy { + kNeverDefer, + kReadThenDefer, + kCapacityDefer, + }; + + // Status codes for start/read operations on BufferedResourceLoader. + enum Status { + // Everything went as planned. + kOk, + + // The operation failed, which may have been due to: + // - Page navigation + // - Server replied 4xx/5xx + // - The response was invalid + // - Connection was terminated + // + // At this point you should delete the loader. + kFailed, + + // The loader will never be able to satisfy the read request. Please stop, + // delete, create a new loader, and try again. + kCacheMiss, + }; + + // Keep in sync with WebMediaPlayer::CORSMode. + enum CORSMode { kUnspecified, kAnonymous, kUseCredentials }; + + enum LoadingState { + kLoading, // Actively attempting to download data. + kLoadingDeferred, // Loading intentionally deferred. + kLoadingFinished, // Loading finished normally; no more data will arrive. + kLoadingFailed, // Loading finished abnormally; no more data will arrive. + }; + + // |url| - URL for the resource to be loaded. + // |cors_mode| - HTML media element's crossorigin attribute. + // |first_byte_position| - First byte to start loading from, + // |kPositionNotSpecified| for not specified. + // |last_byte_position| - Last byte to be loaded, + // |kPositionNotSpecified| for not specified. + // |strategy| is the initial loading strategy to use. + // |bitrate| is the bitrate of the media, 0 if unknown. + // |playback_rate| is the current playback rate of the media. + BufferedResourceLoader( + const GURL& url, + CORSMode cors_mode, + int64 first_byte_position, + int64 last_byte_position, + DeferStrategy strategy, + int bitrate, + float playback_rate, + MediaLog* media_log); + virtual ~BufferedResourceLoader(); + + // Start the resource loading with the specified URL and range. + // + // |loading_cb| is executed when the loading state has changed. + // |progress_cb| is executed when additional data has arrived. + typedef base::Callback<void(Status)> StartCB; + typedef base::Callback<void(LoadingState)> LoadingStateChangedCB; + typedef base::Callback<void(int64)> ProgressCB; + void Start(const StartCB& start_cb, + const LoadingStateChangedCB& loading_cb, + const ProgressCB& progress_cb, + blink::WebFrame* frame); + + // Stops everything associated with this loader, including active URL loads + // and pending callbacks. + // + // It is safe to delete a BufferedResourceLoader after calling Stop(). + void Stop(); + + // Copies |read_size| bytes from |position| into |buffer|, executing |read_cb| + // when the operation has completed. + // + // The callback will contain the number of bytes read iff the status is kOk, + // zero otherwise. + // + // If necessary will temporarily increase forward capacity of buffer to + // accomodate an unusually large read. + typedef base::Callback<void(Status, int)> ReadCB; + void Read(int64 position, int read_size, + uint8* buffer, const ReadCB& read_cb); + + // Gets the content length in bytes of the instance after this loader has been + // started. If this value is |kPositionNotSpecified|, then content length is + // unknown. + int64 content_length(); + + // Gets the original size of the file requested. If this value is + // |kPositionNotSpecified|, then the size is unknown. + int64 instance_size(); + + // Returns true if the server supports byte range requests. + bool range_supported(); + + // blink::WebURLLoaderClient implementation. + virtual void willSendRequest( + blink::WebURLLoader* loader, + blink::WebURLRequest& newRequest, + const blink::WebURLResponse& redirectResponse); + virtual void didSendData( + blink::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse( + blink::WebURLLoader* loader, + const blink::WebURLResponse& response); + virtual void didDownloadData( + blink::WebURLLoader* loader, + int data_length, + int encoded_data_length); + virtual void didReceiveData( + blink::WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length); + virtual void didReceiveCachedMetadata( + blink::WebURLLoader* loader, + const char* data, int dataLength); + virtual void didFinishLoading( + blink::WebURLLoader* loader, + double finishTime, + int64_t total_encoded_data_length); + virtual void didFail( + blink::WebURLLoader* loader, + const blink::WebURLError&); + + // Returns true if the media resource has a single origin, false otherwise. + // Only valid to call after Start() has completed. + bool HasSingleOrigin() const; + + // Returns true if the media resource passed a CORS access control check. + // Only valid to call after Start() has completed. + bool DidPassCORSAccessCheck() const; + + // Sets the defer strategy to the given value unless it seems unwise. + // Specifically downgrade kNeverDefer to kCapacityDefer if we know the + // current response will not be used to satisfy future requests (the cache + // won't help us). + void UpdateDeferStrategy(DeferStrategy strategy); + + // Sets the playback rate to the given value and updates buffer window + // accordingly. + void SetPlaybackRate(float playback_rate); + + // Sets the bitrate to the given value and updates buffer window + // accordingly. + void SetBitrate(int bitrate); + + // Return the |first_byte_position| passed into the ctor. + int64 first_byte_position() const; + + // Parse a Content-Range header into its component pieces and return true if + // each of the expected elements was found & parsed correctly. + // |*instance_size| may be set to kPositionNotSpecified if the range ends in + // "/*". + // NOTE: only public for testing! This is an implementation detail of + // VerifyPartialResponse (a private method). + static bool ParseContentRange( + const std::string& content_range_str, int64* first_byte_position, + int64* last_byte_position, int64* instance_size); + + private: + friend class BufferedDataSourceTest; + friend class BufferedResourceLoaderTest; + friend class MockBufferedDataSource; + + // Updates the |buffer_|'s forward and backward capacities. + void UpdateBufferWindow(); + + // Updates deferring behavior based on current buffering scheme. + void UpdateDeferBehavior(); + + // Sets |active_loader_|'s defer state and fires |loading_cb_| if the state + // changed. + void SetDeferred(bool deferred); + + // Returns true if we should defer resource loading based on the current + // buffering scheme. + bool ShouldDefer() const; + + // Returns true if the current read request can be fulfilled by what is in + // the buffer. + bool CanFulfillRead() const; + + // Returns true if the current read request will be fulfilled in the future. + bool WillFulfillRead() const; + + // Method that does the actual read and calls the |read_cb_|, assuming the + // request range is in |buffer_|. + void ReadInternal(); + + // If we have made a range request, verify the response from the server. + bool VerifyPartialResponse(const blink::WebURLResponse& response); + + // Done with read. Invokes the read callback and reset parameters for the + // read request. + void DoneRead(Status status, int bytes_read); + + // Done with start. Invokes the start callback and reset it. + void DoneStart(Status status); + + bool HasPendingRead() { return !read_cb_.is_null(); } + + // Helper function that returns true if a range request was specified. + bool IsRangeRequest() const; + + // Log everything interesting to |media_log_|. + void Log(); + + // A sliding window of buffer. + SeekableBuffer buffer_; + + // Keeps track of an active WebURLLoader and associated state. + scoped_ptr<ActiveLoader> active_loader_; + + // Tracks if |active_loader_| failed. If so, then all calls to Read() will + // fail. + bool loader_failed_; + + // Current buffering algorithm in place for resource loading. + DeferStrategy defer_strategy_; + + // True if the currently-reading response might be used to satisfy a future + // request from the cache. + bool might_be_reused_from_cache_in_future_; + + // True if Range header is supported. + bool range_supported_; + + // Forward capacity to reset to after an extension. + int saved_forward_capacity_; + + GURL url_; + CORSMode cors_mode_; + const int64 first_byte_position_; + const int64 last_byte_position_; + bool single_origin_; + + // Executed whenever the state of resource loading has changed. + LoadingStateChangedCB loading_cb_; + + // Executed whenever additional data has been downloaded and reports the + // zero-indexed file offset of the furthest buffered byte. + ProgressCB progress_cb_; + + // Members used during request start. + StartCB start_cb_; + int64 offset_; + int64 content_length_; + int64 instance_size_; + + // Members used during a read operation. They should be reset after each + // read has completed or failed. + ReadCB read_cb_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + + // Offsets of the requested first byte and last byte in |buffer_|. They are + // written by Read(). + int first_offset_; + int last_offset_; + + // Injected WebURLLoader instance for testing purposes. + scoped_ptr<blink::WebURLLoader> test_loader_; + + // Bitrate of the media. Set to 0 if unknown. + int bitrate_; + + // Playback rate of the media. + float playback_rate_; + + scoped_refptr<MediaLog> media_log_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); +}; + +} // namespace media + +#endif // MEDIA_BLINK_BUFFERED_RESOURCE_LOADER_H_ diff --git a/media/blink/buffered_resource_loader_unittest.cc b/media/blink/buffered_resource_loader_unittest.cc new file mode 100644 index 0000000..3e7eaa8 --- /dev/null +++ b/media/blink/buffered_resource_loader_unittest.cc @@ -0,0 +1,1131 @@ +// 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 <algorithm> +#include <string> + +#include "base/bind.h" +#include "base/format_macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "media/base/media_log.h" +#include "media/base/seekable_buffer.h" +#include "media/blink/buffered_resource_loader.h" +#include "media/blink/mock_webframeclient.h" +#include "media/blink/mock_weburlloader.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_util.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::Truly; +using ::testing::NiceMock; + +using blink::WebLocalFrame; +using blink::WebString; +using blink::WebURLError; +using blink::WebURLResponse; +using blink::WebView; + +namespace media { + +static const char* kHttpUrl = "http://test"; +static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing"; +static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2"; +static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2"; + +static const int kDataSize = 1024; +static const int kHttpOK = 200; +static const int kHttpPartialContent = 206; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +// Predicate that tests that request disallows compressed data. +static bool CorrectAcceptEncoding(const blink::WebURLRequest &request) { + std::string value = request.httpHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding)).utf8(); + return (value.find("identity;q=1") != std::string::npos) && + (value.find("*;q=0") != std::string::npos); +} + +class BufferedResourceLoaderTest : public testing::Test { + public: + BufferedResourceLoaderTest() + : view_(WebView::create(NULL)), frame_(WebLocalFrame::create(&client_)) { + view_->setMainFrame(frame_); + + for (int i = 0; i < kDataSize; ++i) { + data_[i] = i; + } + } + + virtual ~BufferedResourceLoaderTest() { + view_->close(); + frame_->close(); + } + + void Initialize(const char* url, int first_position, int last_position) { + gurl_ = GURL(url); + first_position_ = first_position; + last_position_ = last_position; + + loader_.reset(new BufferedResourceLoader( + gurl_, BufferedResourceLoader::kUnspecified, + first_position_, last_position_, + BufferedResourceLoader::kCapacityDefer, 0, 0, + new MediaLog())); + + // |test_loader_| will be used when Start() is called. + url_loader_ = new NiceMock<MockWebURLLoader>(); + loader_->test_loader_ = scoped_ptr<blink::WebURLLoader>(url_loader_); + } + + void SetLoaderBuffer(int forward_capacity, int backward_capacity) { + loader_->buffer_.set_forward_capacity(forward_capacity); + loader_->buffer_.set_backward_capacity(backward_capacity); + loader_->buffer_.Clear(); + } + + void Start() { + InSequence s; + EXPECT_CALL(*url_loader_, loadAsynchronously(Truly(CorrectAcceptEncoding), + loader_.get())); + + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + loader_->Start( + base::Bind(&BufferedResourceLoaderTest::StartCallback, + base::Unretained(this)), + base::Bind(&BufferedResourceLoaderTest::LoadingCallback, + base::Unretained(this)), + base::Bind(&BufferedResourceLoaderTest::ProgressCallback, + base::Unretained(this)), + view_->mainFrame()); + } + + void FullResponse(int64 instance_size) { + FullResponse(instance_size, BufferedResourceLoader::kOk); + } + + void FullResponse(int64 instance_size, + BufferedResourceLoader::Status status) { + EXPECT_CALL(*this, StartCallback(status)); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8(base::StringPrintf("%" + PRId64, instance_size))); + response.setExpectedContentLength(instance_size); + response.setHTTPStatusCode(kHttpOK); + loader_->didReceiveResponse(url_loader_, response); + + if (status == BufferedResourceLoader::kOk) { + EXPECT_EQ(instance_size, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + } + + EXPECT_FALSE(loader_->range_supported()); + } + + void PartialResponse(int64 first_position, int64 last_position, + int64 instance_size) { + PartialResponse(first_position, last_position, instance_size, false, true); + } + + void PartialResponse(int64 first_position, int64 last_position, + int64 instance_size, bool chunked, bool accept_ranges) { + EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kOk)); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(base::StringPrintf("bytes " + "%" PRId64 "-%" PRId64 "/%" PRId64, + first_position, + last_position, + instance_size))); + + // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked. + int64 content_length = -1; + if (chunked) { + response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"), + WebString::fromUTF8("chunked")); + } else { + content_length = last_position - first_position + 1; + } + response.setExpectedContentLength(content_length); + + // A server isn't required to return Accept-Ranges even though it might. + if (accept_ranges) { + response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), + WebString::fromUTF8("bytes")); + } + + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); + + // XXX: what's the difference between these two? For example in the chunked + // range request case, Content-Length is unspecified (because it's chunked) + // but Content-Range: a-b/c can be returned, where c == Content-Length + // + // Can we eliminate one? + EXPECT_EQ(content_length, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + + // A valid partial response should always result in this being true. + EXPECT_TRUE(loader_->range_supported()); + } + + void Redirect(const char* url) { + GURL redirectUrl(url); + blink::WebURLRequest newRequest(redirectUrl); + blink::WebURLResponse redirectResponse(gurl_); + + loader_->willSendRequest(url_loader_, newRequest, redirectResponse); + + base::MessageLoop::current()->RunUntilIdle(); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*url_loader_, cancel()); + loader_->Stop(); + loader_.reset(); + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + EXPECT_CALL(*this, ProgressCallback(position + size - 1)); + loader_->didReceiveData(url_loader_, + reinterpret_cast<char*>(data_ + position), + size, + size); + } + + void WriteData(int size) { + EXPECT_CALL(*this, ProgressCallback(_)); + + scoped_ptr<char[]> data(new char[size]); + loader_->didReceiveData(url_loader_, data.get(), size, size); + } + + void WriteUntilThreshold() { + int buffered = loader_->buffer_.forward_bytes(); + int capacity = loader_->buffer_.forward_capacity(); + CHECK_LT(buffered, capacity); + + EXPECT_CALL(*this, LoadingCallback( + BufferedResourceLoader::kLoadingDeferred)); + WriteData(capacity - buffered); + } + + // Helper method to read from |loader_|. + void ReadLoader(int64 position, int size, uint8* buffer) { + loader_->Read(position, size, buffer, + base::Bind(&BufferedResourceLoaderTest::ReadCallback, + base::Unretained(this))); + } + + // Verifies that data in buffer[0...size] is equal to data_[pos...pos+size]. + void VerifyBuffer(uint8* buffer, int pos, int size) { + EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); + } + + void ConfirmLoaderOffsets(int64 expected_offset, + int expected_first_offset, + int expected_last_offset) { + EXPECT_EQ(loader_->offset_, expected_offset); + EXPECT_EQ(loader_->first_offset_, expected_first_offset); + EXPECT_EQ(loader_->last_offset_, expected_last_offset); + } + + void ConfirmBufferState(int backward_bytes, + int backward_capacity, + int forward_bytes, + int forward_capacity) { + EXPECT_EQ(backward_bytes, loader_->buffer_.backward_bytes()); + EXPECT_EQ(backward_capacity, loader_->buffer_.backward_capacity()); + EXPECT_EQ(forward_bytes, loader_->buffer_.forward_bytes()); + EXPECT_EQ(forward_capacity, loader_->buffer_.forward_capacity()); + } + + void ConfirmLoaderBufferBackwardCapacity(int expected_backward_capacity) { + EXPECT_EQ(loader_->buffer_.backward_capacity(), + expected_backward_capacity); + } + + void ConfirmLoaderBufferForwardCapacity(int expected_forward_capacity) { + EXPECT_EQ(loader_->buffer_.forward_capacity(), expected_forward_capacity); + } + + // Makes sure the |loader_| buffer window is in a reasonable range. + void CheckBufferWindowBounds() { + // Corresponds to value defined in buffered_resource_loader.cc. + static const int kMinBufferCapacity = 2 * 1024 * 1024; + EXPECT_GE(loader_->buffer_.forward_capacity(), kMinBufferCapacity); + EXPECT_GE(loader_->buffer_.backward_capacity(), kMinBufferCapacity); + + // Corresponds to value defined in buffered_resource_loader.cc. + static const int kMaxBufferCapacity = 20 * 1024 * 1024; + EXPECT_LE(loader_->buffer_.forward_capacity(), kMaxBufferCapacity); + EXPECT_LE(loader_->buffer_.backward_capacity(), kMaxBufferCapacity); + } + + MOCK_METHOD1(StartCallback, void(BufferedResourceLoader::Status)); + MOCK_METHOD2(ReadCallback, void(BufferedResourceLoader::Status, int)); + MOCK_METHOD1(LoadingCallback, void(BufferedResourceLoader::LoadingState)); + MOCK_METHOD1(ProgressCallback, void(int64)); + + protected: + GURL gurl_; + int64 first_position_; + int64 last_position_; + + scoped_ptr<BufferedResourceLoader> loader_; + NiceMock<MockWebURLLoader>* url_loader_; + + MockWebFrameClient client_; + WebView* view_; + WebLocalFrame* frame_; + + base::MessageLoop message_loop_; + + uint8 data_[kDataSize]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest); +}; + +TEST_F(BufferedResourceLoaderTest, StartStop) { + Initialize(kHttpUrl, -1, -1); + Start(); + StopWhenLoad(); +} + +// Tests that a bad HTTP response is recived, e.g. file not found. +TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kFailed)); + + WebURLResponse response(gurl_); + response.setHTTPStatusCode(404); + response.setHTTPStatusText("Not Found\n"); + loader_->didReceiveResponse(url_loader_, response); + StopWhenLoad(); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100, -1); + Start(); + FullResponse(1024, BufferedResourceLoader::kFailed); + StopWhenLoad(); +} + +// Tests that a 200 response is received. +TEST_F(BufferedResourceLoaderTest, FullResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a partial content response is received. +TEST_F(BufferedResourceLoaderTest, PartialResponse) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, PartialResponse_Chunked) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024, true, true); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, PartialResponse_NoAcceptRanges) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024, false, false); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, PartialResponse_ChunkedNoAcceptRanges) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024, true, false); + StopWhenLoad(); +} + +// Tests that an invalid partial response is received. +TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { + Initialize(kHttpUrl, 0, 10); + Start(); + + EXPECT_CALL(*this, StartCallback(BufferedResourceLoader::kFailed)); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(base::StringPrintf("bytes " + "%d-%d/%d", 1, 10, 1024))); + response.setExpectedContentLength(10); + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); + StopWhenLoad(); +} + +// Tests the logic of sliding window for data buffering and reading. +TEST_F(BufferedResourceLoaderTest, BufferAndRead) { + Initialize(kHttpUrl, 10, 29); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Writes 10 bytes and read them back. + WriteLoader(10, 10); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Writes 10 bytes and read 2 times. + WriteLoader(20, 10); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5)); + ReadLoader(20, 5, buffer); + VerifyBuffer(buffer, 20, 5); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5)); + ReadLoader(25, 5, buffer); + VerifyBuffer(buffer, 25, 5); + + // Read backward within buffer. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Read backward outside buffer. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(9, 10, buffer); + + // Response has completed. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFinished)); + loader_->didFinishLoading(url_loader_, 0, -1); + + // Try to read 10 from position 25 will just return with 5 bytes. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5)); + ReadLoader(25, 10, buffer); + VerifyBuffer(buffer, 25, 5); + + // Try to read outside buffered range after request has completed. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(5, 10, buffer); + + // Try to read beyond the instance size. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 0)); + ReadLoader(30, 10, buffer); +} + +// Tests the logic of expanding the data buffer for large reads. +TEST_F(BufferedResourceLoaderTest, ReadExtendBuffer) { + Initialize(kHttpUrl, 10, 0x014FFFFFF); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 0x014FFFFFF, 0x015000000); + + uint8 buffer[20]; + InSequence s; + + // Write more than forward capacity and read it back. Ensure forward capacity + // gets reset after reading. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + WriteLoader(10, 20); + + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 20)); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(10, 20, buffer); + + VerifyBuffer(buffer, 10, 20); + ConfirmLoaderBufferForwardCapacity(10); + + // Make and outstanding read request larger than forward capacity. Ensure + // forward capacity gets extended. + ReadLoader(30, 20, buffer); + ConfirmLoaderBufferForwardCapacity(20); + + // Fulfill outstanding request. Ensure forward capacity gets reset. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 20)); + WriteLoader(30, 20); + + VerifyBuffer(buffer, 30, 20); + ConfirmLoaderBufferForwardCapacity(10); + + // Try to read further ahead than kForwardWaitThreshold allows. Ensure + // forward capacity is not changed. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(0x00300000, 1, buffer); + + ConfirmLoaderBufferForwardCapacity(10); + + // Try to read more than maximum forward capacity. Ensure forward capacity is + // not changed. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0)); + ReadLoader(30, 0x01400001, buffer); + + ConfirmLoaderBufferForwardCapacity(10); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { + Initialize(kHttpUrl, 10, 0x00FFFFFF); + Start(); + PartialResponse(10, 0x00FFFFFF, 0x01000000); + + uint8 buffer[10]; + InSequence s; + + // Read very far ahead will get a cache miss. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(0x00FFFFFF, 1, buffer); + + // The following call will not call ReadCallback() because it is waiting for + // data to arrive. + ReadLoader(10, 10, buffer); + + // Writing to loader will fulfill the read request. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + WriteLoader(10, 20); + VerifyBuffer(buffer, 10, 10); + + // The following call cannot be fulfilled now. + ReadLoader(25, 10, buffer); + + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFinished)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 5)); + loader_->didFinishLoading(url_loader_, 0, -1); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // We should convert any error we receive to BufferedResourceLoader::kFailed. + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0)); + WebURLError error; + error.reason = net::ERR_TIMED_OUT; + error.isCancellation = false; + loader_->didFail(url_loader_, error); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWithNoPendingReads) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Write enough data so that a read would technically complete had the request + // not failed. + WriteLoader(10, 20); + + // Fail without a pending read. + WebURLError error; + error.reason = net::ERR_TIMED_OUT; + error.isCancellation = false; + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed)); + loader_->didFail(url_loader_, error); + + // Now we should immediately fail any read even if we have data buffered. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0)); + ReadLoader(10, 10, buffer); +} + +TEST_F(BufferedResourceLoaderTest, RequestCancelledWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // We should convert any error we receive to BufferedResourceLoader::kFailed. + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingFailed)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kFailed, 0)); + WebURLError error; + error.reason = 0; + error.isCancellation = true; + loader_->didFail(url_loader_, error); +} + +// Tests the data buffering logic of NeverDefer strategy. +TEST_F(BufferedResourceLoaderTest, NeverDeferStrategy) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Read past the buffer size; should not defer regardless. + WriteLoader(10, 10); + WriteLoader(20, 50); + + // Should move past window. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(10, 10, buffer); + + StopWhenLoad(); +} + +// Tests the data buffering logic of ReadThenDefer strategy. +TEST_F(BufferedResourceLoaderTest, ReadThenDeferStrategy) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kReadThenDefer); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Make an outstanding read request. + ReadLoader(10, 10, buffer); + + // Receive almost enough data to cover, shouldn't defer. + WriteLoader(10, 9); + + // As soon as we have received enough data to fulfill the read, defer. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + WriteLoader(19, 1); + + VerifyBuffer(buffer, 10, 10); + + // Read again which should disable deferring since there should be nothing + // left in our internal buffer. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(20, 10, buffer); + + // Over-fulfill requested bytes, then deferring should be enabled again. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + WriteLoader(20, 40); + + VerifyBuffer(buffer, 20, 10); + + // Read far ahead, which should disable deferring. In this case we still have + // bytes in our internal buffer. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(80, 10, buffer); + + // Fulfill requested bytes, then deferring should be enabled again. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + WriteLoader(60, 40); + + VerifyBuffer(buffer, 80, 10); + + StopWhenLoad(); +} + +// Tests the data buffering logic of kCapacityDefer strategy. +TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + InSequence s; + + // Write half of capacity: keep not deferring. + WriteData(5); + + // Write rest of space until capacity: start deferring. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + WriteData(5); + + // Read a byte from the buffer: stop deferring. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 1)); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(10, 1, buffer); + + // Write a byte to hit capacity: start deferring. + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoadingDeferred)); + WriteData(6); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_ReadForwardsPastBuffered) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 1)); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(10, 1, buffer); + ConfirmBufferState(1, 10, 9, 10); + ConfirmLoaderOffsets(11, 0, 0); + + // *** TRICKY BUSINESS, PT. I *** + // Read past buffered: stop deferring. + // + // In order for the read to complete we must: + // 1) Stop deferring to receive more data. + // + // BEFORE + // offset=11 [xxxxxxxxx_] + // ^ ^^^ requested 4 bytes @ offset 20 + // AFTER + // offset=24 [__________] + // + ReadLoader(20, 4, buffer); + + // Write a little, make sure we didn't start deferring. + WriteData(2); + + // Write the rest, read should complete. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 4)); + WriteData(2); + + // POSTCONDITION + ConfirmBufferState(4, 10, 0, 10); + ConfirmLoaderOffsets(24, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_ReadBackwardsPastBuffered) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + // *** TRICKY BUSINESS, PT. II *** + // Read backwards a little too much: cache miss. + // + // BEFORE + // offset=10 [__________|xxxxxxxxxx] + // ^ ^^^ requested 10 bytes @ offset 9 + // AFTER + // offset=10 [__________|xxxxxxxxxx] !!! cache miss !!! + // + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(9, 4, buffer); + + // POSTCONDITION + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_SmallReadWithinThreshold) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + // *** TRICKY BUSINESS, PT. III *** + // Read past forward capacity but within capacity: stop deferring. + // + // In order for the read to complete we must: + // 1) Adjust offset forward to create capacity. + // 2) Stop deferring to receive more data. + // + // BEFORE + // offset=10 [xxxxxxxxxx] + // ^^^^ requested 4 bytes @ offset 24 + // ADJUSTED OFFSET + // offset=20 [__________] + // ^^^^ requested 4 bytes @ offset 24 + // AFTER + // offset=28 [__________] + // + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(24, 4, buffer); + ConfirmLoaderOffsets(20, 4, 8); + + // Write a little, make sure we didn't start deferring. + WriteData(4); + + // Write the rest, read should complete. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 4)); + WriteData(4); + + // POSTCONDITION + ConfirmBufferState(8, 10, 0, 10); + ConfirmLoaderOffsets(28, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadWithinThreshold) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + // *** TRICKY BUSINESS, PT. IV *** + // Read a large amount past forward capacity but within + // capacity: stop deferring. + // + // In order for the read to complete we must: + // 1) Adjust offset forward to create capacity. + // 2) Expand capacity to make sure we don't defer as data arrives. + // 3) Stop deferring to receive more data. + // + // BEFORE + // offset=10 [xxxxxxxxxx] + // ^^^^^^^^^^^^ requested 12 bytes @ offset 24 + // ADJUSTED OFFSET + // offset=20 [__________] + // ^^^^^^ ^^^^^^ requested 12 bytes @ offset 24 + // ADJUSTED CAPACITY + // offset=20 [________________] + // ^^^^^^^^^^^^ requested 12 bytes @ offset 24 + // AFTER + // offset=36 [__________] + // + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(24, 12, buffer); + ConfirmLoaderOffsets(20, 4, 16); + ConfirmBufferState(10, 10, 0, 16); + + // Write a little, make sure we didn't start deferring. + WriteData(10); + + // Write the rest, read should complete and capacity should go back to normal. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 12)); + WriteData(6); + ConfirmLoaderBufferForwardCapacity(10); + + // POSTCONDITION + ConfirmBufferState(6, 10, 0, 10); + ConfirmLoaderOffsets(36, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_LargeReadBackwards) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 10)); + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(10, 10, buffer); + WriteUntilThreshold(); + ConfirmBufferState(10, 10, 10, 10); + ConfirmLoaderOffsets(20, 0, 0); + + // *** TRICKY BUSINESS, PT. V *** + // Read a large amount that involves backwards data: stop deferring. + // + // In order for the read to complete we must: + // 1) Adjust offset *backwards* to create capacity. + // 2) Expand capacity to make sure we don't defer as data arrives. + // 3) Stop deferring to receive more data. + // + // BEFORE + // offset=20 [xxxxxxxxxx|xxxxxxxxxx] + // ^^^^ ^^^^^^^^^^ ^^^^ requested 18 bytes @ offset 16 + // ADJUSTED OFFSET + // offset=16 [____xxxxxx|xxxxxxxxxx]xxxx + // ^^^^^^^^^^ ^^^^^^^^ requested 18 bytes @ offset 16 + // ADJUSTED CAPACITY + // offset=16 [____xxxxxx|xxxxxxxxxxxxxx____] + // ^^^^^^^^^^^^^^^^^^ requested 18 bytes @ offset 16 + // AFTER + // offset=34 [xxxxxxxxxx|__________] + // + EXPECT_CALL(*this, LoadingCallback(BufferedResourceLoader::kLoading)); + ReadLoader(16, 18, buffer); + ConfirmLoaderOffsets(16, 0, 18); + ConfirmBufferState(6, 10, 14, 18); + + // Write a little, make sure we didn't start deferring. + WriteData(2); + + // Write the rest, read should complete and capacity should go back to normal. + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kOk, 18)); + WriteData(2); + ConfirmLoaderBufferForwardCapacity(10); + + // POSTCONDITION + ConfirmBufferState(4, 10, 0, 10); + ConfirmLoaderOffsets(34, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_ReadPastThreshold) { + const int kSize = 5 * 1024 * 1024; + const int kThreshold = 2 * 1024 * 1024; + + Initialize(kHttpUrl, 10, kSize); + SetLoaderBuffer(10, 10); + Start(); + PartialResponse(10, kSize - 1, kSize); + + uint8 buffer[256]; + InSequence s; + + // PRECONDITION + WriteUntilThreshold(); + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + // *** TRICKY BUSINESS, PT. VI *** + // Read past the forward wait threshold: cache miss. + // + // BEFORE + // offset=10 [xxxxxxxxxx] ... + // ^^^^ requested 10 bytes @ threshold + // AFTER + // offset=10 [xxxxxxxxxx] !!! cache miss !!! + // + EXPECT_CALL(*this, ReadCallback(BufferedResourceLoader::kCacheMiss, 0)); + ReadLoader(kThreshold + 20, 10, buffer); + + // POSTCONDITION + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, HasSingleOrigin) { + // Make sure no redirect case works as expected. + Initialize(kHttpUrl, -1, -1); + Start(); + FullResponse(1024); + EXPECT_TRUE(loader_->HasSingleOrigin()); + StopWhenLoad(); + + // Test redirect to the same domain. + Initialize(kHttpUrl, -1, -1); + Start(); + Redirect(kHttpRedirectToSameDomainUrl1); + FullResponse(1024); + EXPECT_TRUE(loader_->HasSingleOrigin()); + StopWhenLoad(); + + // Test redirect twice to the same domain. + Initialize(kHttpUrl, -1, -1); + Start(); + Redirect(kHttpRedirectToSameDomainUrl1); + Redirect(kHttpRedirectToSameDomainUrl2); + FullResponse(1024); + EXPECT_TRUE(loader_->HasSingleOrigin()); + StopWhenLoad(); + + // Test redirect to a different domain. + Initialize(kHttpUrl, -1, -1); + Start(); + Redirect(kHttpRedirectToDifferentDomainUrl1); + FullResponse(1024); + EXPECT_FALSE(loader_->HasSingleOrigin()); + StopWhenLoad(); + + // Test redirect to the same domain and then to a different domain. + Initialize(kHttpUrl, -1, -1); + Start(); + Redirect(kHttpRedirectToSameDomainUrl1); + Redirect(kHttpRedirectToDifferentDomainUrl1); + FullResponse(1024); + EXPECT_FALSE(loader_->HasSingleOrigin()); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_Default) { + Initialize(kHttpUrl, -1, -1); + Start(); + + // Test ensures that default construction of a BufferedResourceLoader has sane + // values. + // + // Please do not change these values in order to make a test pass! Instead, + // start a conversation on what the default buffer window capacities should + // be. + ConfirmLoaderBufferBackwardCapacity(2 * 1024 * 1024); + ConfirmLoaderBufferForwardCapacity(2 * 1024 * 1024); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_Unknown) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetBitrate(0); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_BelowLowerBound) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetBitrate(1024 * 8); // 1 Kbps. + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_WithinBounds) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetBitrate(2 * 1024 * 1024 * 8); // 2 Mbps. + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_Bitrate_AboveUpperBound) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetBitrate(100 * 1024 * 1024 * 8); // 100 Mbps. + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Negative) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetPlaybackRate(-10); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_Zero) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetPlaybackRate(0); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_BelowLowerBound) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetPlaybackRate(0.1f); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_WithinBounds) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetPlaybackRate(10); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, BufferWindow_PlaybackRate_AboveUpperBound) { + Initialize(kHttpUrl, -1, -1); + Start(); + loader_->SetPlaybackRate(100); + CheckBufferWindowBounds(); + StopWhenLoad(); +} + +static void ExpectContentRange( + const std::string& str, bool expect_success, + int64 expected_first, int64 expected_last, int64 expected_size) { + int64 first, last, size; + ASSERT_EQ(expect_success, BufferedResourceLoader::ParseContentRange( + str, &first, &last, &size)) << str; + if (!expect_success) + return; + EXPECT_EQ(first, expected_first); + EXPECT_EQ(last, expected_last); + EXPECT_EQ(size, expected_size); +} + +static void ExpectContentRangeFailure(const std::string& str) { + ExpectContentRange(str, false, 0, 0, 0); +} + +static void ExpectContentRangeSuccess( + const std::string& str, + int64 expected_first, int64 expected_last, int64 expected_size) { + ExpectContentRange(str, true, expected_first, expected_last, expected_size); +} + +TEST(BufferedResourceLoaderStandaloneTest, ParseContentRange) { + ExpectContentRangeFailure("cytes 0-499/500"); + ExpectContentRangeFailure("bytes 0499/500"); + ExpectContentRangeFailure("bytes 0-499500"); + ExpectContentRangeFailure("bytes 0-499/500-blorg"); + ExpectContentRangeFailure("bytes 0-499/500-1"); + ExpectContentRangeFailure("bytes 0-499/400"); + ExpectContentRangeFailure("bytes 0-/400"); + ExpectContentRangeFailure("bytes -300/400"); + ExpectContentRangeFailure("bytes 20-10/400"); + + ExpectContentRangeSuccess("bytes 0-499/500", 0, 499, 500); + ExpectContentRangeSuccess("bytes 0-0/500", 0, 0, 500); + ExpectContentRangeSuccess("bytes 10-11/50", 10, 11, 50); + ExpectContentRangeSuccess("bytes 10-11/*", 10, 11, + kPositionNotSpecified); +} + +} // namespace media diff --git a/media/blink/cache_util.cc b/media/blink/cache_util.cc new file mode 100644 index 0000000..65bed43 --- /dev/null +++ b/media/blink/cache_util.cc @@ -0,0 +1,87 @@ +// 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 "media/blink/cache_util.h" + +#include <string> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "net/http/http_util.h" +#include "net/http/http_version.h" +#include "third_party/WebKit/public/platform/WebCString.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +using base::Time; +using base::TimeDelta; +using net::HttpVersion; +using blink::WebURLResponse; + +namespace media { + +enum { kHttpOK = 200, kHttpPartialContent = 206 }; + +uint32 GetReasonsForUncacheability(const WebURLResponse& response) { + uint32 reasons = 0; + const int code = response.httpStatusCode(); + const int version = response.httpVersion(); + const HttpVersion http_version = + version == WebURLResponse::HTTP_1_1 ? HttpVersion(1, 1) : + version == WebURLResponse::HTTP_1_0 ? HttpVersion(1, 0) : + version == WebURLResponse::HTTP_0_9 ? HttpVersion(0, 9) : + HttpVersion(); + if (code != kHttpOK && code != kHttpPartialContent) + reasons |= kNoData; + if (http_version < HttpVersion(1, 1) && code == kHttpPartialContent) + reasons |= kPre11PartialResponse; + if (code == kHttpPartialContent && + !net::HttpUtil::HasStrongValidators( + http_version, + response.httpHeaderField("etag").utf8(), + response.httpHeaderField("Last-Modified").utf8(), + response.httpHeaderField("Date").utf8())) { + reasons |= kNoStrongValidatorOnPartialResponse; + } + + std::string cache_control_header = + response.httpHeaderField("cache-control").utf8(); + base::StringToLowerASCII(&cache_control_header); + if (cache_control_header.find("no-cache") != std::string::npos) + reasons |= kNoCache; + if (cache_control_header.find("no-store") != std::string::npos) + reasons |= kNoStore; + if (cache_control_header.find("must-revalidate") != std::string::npos) + reasons |= kHasMustRevalidate; + + const TimeDelta kMinimumAgeForUsefulness = + TimeDelta::FromSeconds(3600); // Arbitrary value. + + const char kMaxAgePrefix[] = "max-age="; + const size_t kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1; + if (cache_control_header.substr(0, kMaxAgePrefixLen) == kMaxAgePrefix) { + int64 max_age_seconds; + base::StringToInt64( + base::StringPiece(cache_control_header.begin() + kMaxAgePrefixLen, + cache_control_header.end()), + &max_age_seconds); + if (TimeDelta::FromSeconds(max_age_seconds) < kMinimumAgeForUsefulness) + reasons |= kShortMaxAge; + } + + Time date; + Time expires; + if (Time::FromString(response.httpHeaderField("Date").utf8().data(), &date) && + Time::FromString(response.httpHeaderField("Expires").utf8().data(), + &expires) && + date > Time() && expires > Time() && + (expires - date) < kMinimumAgeForUsefulness) { + reasons |= kExpiresTooSoon; + } + + return reasons; +} + +} // namespace media diff --git a/media/blink/cache_util.h b/media/blink/cache_util.h new file mode 100644 index 0000000..5c59f14 --- /dev/null +++ b/media/blink/cache_util.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef MEDIA_BLINK_CACHE_UTIL_H_ +#define MEDIA_BLINK_CACHE_UTIL_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace blink { +class WebURLResponse; +} + +namespace media { + +// Reasons that a cached WebURLResponse will *not* prevent a future request to +// the server. Reported via UMA, so don't change/reuse previously-existing +// values. +enum UncacheableReason { + kNoData = 1 << 0, // Not 200 or 206. + kPre11PartialResponse = 1 << 1, // 206 but HTTP version < 1.1. + kNoStrongValidatorOnPartialResponse = 1 << 2, // 206, no strong validator. + kShortMaxAge = 1 << 3, // Max age less than 1h (arbitrary value). + kExpiresTooSoon = 1 << 4, // Expires in less than 1h (arbitrary value). + kHasMustRevalidate = 1 << 5, // Response asks for revalidation. + kNoCache = 1 << 6, // Response included a no-cache header. + kNoStore = 1 << 7, // Response included a no-store header. + kMaxReason // Needs to be one more than max legitimate reason. +}; + +// Return the logical OR of the reasons "response" cannot be used for a future +// request (using the disk cache), or 0 if it might be useful. +uint32 MEDIA_EXPORT GetReasonsForUncacheability( + const blink::WebURLResponse& response); + +} // namespace media + +#endif // MEDIA_BLINK_CACHE_UTIL_H_ diff --git a/media/blink/cache_util_unittest.cc b/media/blink/cache_util_unittest.cc new file mode 100644 index 0000000..7ea2f13 --- /dev/null +++ b/media/blink/cache_util_unittest.cc @@ -0,0 +1,97 @@ +// 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 "media/blink/cache_util.h" + +#include <string> + +#include "base/format_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +using blink::WebString; +using blink::WebURLResponse; + +namespace media { + +// Inputs & expected output for GetReasonsForUncacheability. +struct GRFUTestCase { + WebURLResponse::HTTPVersion version; + int status_code; + const char* headers; + uint32 expected_reasons; +}; + +// Create a new WebURLResponse object. +static WebURLResponse CreateResponse(const GRFUTestCase& test) { + WebURLResponse response; + response.initialize(); + response.setHTTPVersion(test.version); + response.setHTTPStatusCode(test.status_code); + std::vector<std::string> lines; + Tokenize(test.headers, "\n", &lines); + for (size_t i = 0; i < lines.size(); ++i) { + size_t colon = lines[i].find(": "); + response.addHTTPHeaderField( + WebString::fromUTF8(lines[i].substr(0, colon)), + WebString::fromUTF8(lines[i].substr(colon + 2))); + } + return response; +} + +TEST(CacheUtilTest, GetReasonsForUncacheability) { + enum { kNoReasons = 0 }; + + const GRFUTestCase tests[] = { + { + WebURLResponse::HTTP_1_1, 206, "ETag: 'fooblort'", kNoReasons + }, + { + WebURLResponse::HTTP_1_1, 206, "", kNoStrongValidatorOnPartialResponse + }, + { + WebURLResponse::HTTP_1_0, 206, "", + kPre11PartialResponse | kNoStrongValidatorOnPartialResponse + }, + { + WebURLResponse::HTTP_1_1, 200, "cache-control: max-Age=42", kShortMaxAge + }, + { + WebURLResponse::HTTP_1_1, 200, "cache-control: max-Age=4200", kNoReasons + }, + { + WebURLResponse::HTTP_1_1, 200, + "Date: Tue, 22 May 2012 23:46:08 GMT\n" + "Expires: Tue, 22 May 2012 23:56:08 GMT", kExpiresTooSoon + }, + { + WebURLResponse::HTTP_1_1, 200, "cache-control: must-revalidate", + kHasMustRevalidate + }, + { + WebURLResponse::HTTP_1_1, 200, "cache-control: no-cache", kNoCache + }, + { + WebURLResponse::HTTP_1_1, 200, "cache-control: no-store", kNoStore + }, + { + WebURLResponse::HTTP_1_1, 200, + "cache-control: no-cache\ncache-control: no-store", kNoCache | kNoStore + }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + SCOPED_TRACE(base::StringPrintf("case: %" PRIuS + ", version: %d, code: %d, headers: %s", + i, tests[i].version, tests[i].status_code, + tests[i].headers)); + EXPECT_EQ(GetReasonsForUncacheability(CreateResponse(tests[i])), + tests[i].expected_reasons); + } +} + +} // namespace media diff --git a/media/blink/encrypted_media_player_support.cc b/media/blink/encrypted_media_player_support.cc new file mode 100644 index 0000000..663141f --- /dev/null +++ b/media/blink/encrypted_media_player_support.cc @@ -0,0 +1,15 @@ +// Copyright 2014 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 "media/blink/encrypted_media_player_support.h" + +namespace media { + +EncryptedMediaPlayerSupport::EncryptedMediaPlayerSupport() { +} + +EncryptedMediaPlayerSupport::~EncryptedMediaPlayerSupport() { +} + +} // namespace media diff --git a/media/blink/encrypted_media_player_support.h b/media/blink/encrypted_media_player_support.h new file mode 100644 index 0000000..b387686 --- /dev/null +++ b/media/blink/encrypted_media_player_support.h @@ -0,0 +1,80 @@ +// Copyright 2014 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. + +#ifndef MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_ +#define MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_ + +#include "media/base/decryptor.h" +#include "media/base/demuxer.h" +#include "media/base/media_export.h" +#include "third_party/WebKit/public/platform/WebMediaPlayer.h" + +namespace blink { +class WebContentDecryptionModule; +class WebContentDecryptionModuleResult; +class WebLocalFrame; +class WebMediaPlayerClient; +class WebString; +} + +namespace media { + +class MEDIA_EXPORT EncryptedMediaPlayerSupport { + public: + EncryptedMediaPlayerSupport(); + virtual ~EncryptedMediaPlayerSupport(); + + // Prefixed API methods. + virtual blink::WebMediaPlayer::MediaKeyException GenerateKeyRequest( + blink::WebLocalFrame* frame, + const blink::WebString& key_system, + const unsigned char* init_data, + unsigned init_data_length) = 0; + + virtual blink::WebMediaPlayer::MediaKeyException AddKey( + const blink::WebString& key_system, + const unsigned char* key, + unsigned key_length, + const unsigned char* init_data, + unsigned init_data_length, + const blink::WebString& session_id) = 0; + + virtual blink::WebMediaPlayer::MediaKeyException CancelKeyRequest( + const blink::WebString& key_system, + const blink::WebString& session_id) = 0; + + + // Unprefixed API methods. + virtual void SetContentDecryptionModule( + blink::WebContentDecryptionModule* cdm) = 0; + virtual void SetContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result) = 0; + virtual void SetContentDecryptionModuleSync( + blink::WebContentDecryptionModule* cdm) = 0; + + + // Callback factory and notification methods used by WebMediaPlayerImpl. + + // Creates a callback that Demuxers can use to signal that the content + // requires a key. This method make sure the callback returned can be safely + // invoked from any thread. + virtual Demuxer::NeedKeyCB CreateNeedKeyCB() = 0; + + // Creates a callback that renderers can use to set decryptor + // ready callback. This method make sure the callback returned can be safely + // invoked from any thread. + virtual SetDecryptorReadyCB CreateSetDecryptorReadyCB() = 0; + + // Called to inform this object that the media pipeline encountered + // and handled a decryption error. + virtual void OnPipelineDecryptError() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(EncryptedMediaPlayerSupport); +}; + +} // namespace media + +#endif // MEDIA_BLINK_ENCRYPTED_MEDIA_PLAYER_SUPPORT_H_ diff --git a/media/blink/media_blink.gyp b/media/blink/media_blink.gyp new file mode 100644 index 0000000..975a701 --- /dev/null +++ b/media/blink/media_blink.gyp @@ -0,0 +1,104 @@ +# Copyright 2014 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. + +{ + 'targets': [ + { + # GN version: //media/blink + 'target_name': 'media_blink', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../cc/cc.gyp:cc', + '../../cc/blink/cc_blink.gyp:cc_blink', + '../../ui/gfx/gfx.gyp:gfx_geometry', + '../../net/net.gyp:net', + '../../third_party/WebKit/public/blink.gyp:blink', + '../media.gyp:media', + '../media.gyp:shared_memory_support', + '../../url/url.gyp:url_lib', + ], + 'defines': [ + 'MEDIA_IMPLEMENTATION', + ], + # This sources list is duplicated in //media/blink/BUILD.gn + 'sources': [ + 'active_loader.cc', + 'active_loader.h', + 'buffered_data_source.cc', + 'buffered_data_source.h', + 'buffered_data_source_host_impl.cc', + 'buffered_data_source_host_impl.h', + 'buffered_resource_loader.cc', + 'buffered_resource_loader.h', + 'encrypted_media_player_support.cc', + 'encrypted_media_player_support.h', + 'cache_util.cc', + 'cache_util.h', + 'texttrack_impl.cc', + 'texttrack_impl.h', + 'video_frame_compositor.cc', + 'video_frame_compositor.h', + 'webaudiosourceprovider_impl.cc', + 'webaudiosourceprovider_impl.h', + 'webinbandtexttrack_impl.cc', + 'webinbandtexttrack_impl.h', + 'webmediaplayer_delegate.h', + 'webmediaplayer_impl.cc', + 'webmediaplayer_impl.h', + 'webmediaplayer_params.cc', + 'webmediaplayer_params.h', + 'webmediaplayer_util.cc', + 'webmediaplayer_util.h', + 'webmediasource_impl.cc', + 'webmediasource_impl.h', + 'websourcebuffer_impl.cc', + 'websourcebuffer_impl.h', + ], + 'conditions': [ + ['OS=="android"', { + 'sources!': [ + 'webmediaplayer_impl.cc', + ], + }, + ], + ], + }, + { + 'target_name': 'media_blink_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'media_blink', + '../media.gyp:media', + '../media.gyp:media_test_support', + '../../base/base.gyp:base', + '../../base/base.gyp:test_support_base', + '../../cc/cc.gyp:cc', + '../../cc/blink/cc_blink.gyp:cc_blink', + '../../net/net.gyp:net', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + '../../third_party/WebKit/public/blink.gyp:blink', + '../../ui/gfx/gfx.gyp:gfx', + '../../ui/gfx/gfx.gyp:gfx_geometry', + '../../ui/gfx/gfx.gyp:gfx_test_support', + '../../url/url.gyp:url_lib', + ], + 'sources': [ + 'buffered_data_source_host_impl_unittest.cc', + 'buffered_data_source_unittest.cc', + 'buffered_resource_loader_unittest.cc', + 'cache_util_unittest.cc', + 'mock_webframeclient.h', + 'mock_weburlloader.cc', + 'mock_weburlloader.h', + 'run_all_unittests.cc', + 'test_response_generator.cc', + 'test_response_generator.h', + 'video_frame_compositor_unittest.cc', + 'webaudiosourceprovider_impl_unittest.cc', + ], + }, + ] +} diff --git a/media/blink/mock_webframeclient.h b/media/blink/mock_webframeclient.h new file mode 100644 index 0000000..7a63358 --- /dev/null +++ b/media/blink/mock_webframeclient.h @@ -0,0 +1,16 @@ +// Copyright 2014 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. + +#ifndef MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_ +#define MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_ + +#include "third_party/WebKit/public/web/WebFrameClient.h" + +namespace media { + +class MockWebFrameClient : public blink::WebFrameClient {}; + +} // namespace media + +#endif // MEDIA_BLINK_MOCK_WEBFRAMECLIENT_H_ diff --git a/media/blink/mock_weburlloader.cc b/media/blink/mock_weburlloader.cc new file mode 100644 index 0000000..578476a --- /dev/null +++ b/media/blink/mock_weburlloader.cc @@ -0,0 +1,18 @@ +// Copyright 2014 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 "media/blink/mock_weburlloader.h" + +#include "third_party/WebKit/public/platform/WebData.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +namespace media { + +MockWebURLLoader::MockWebURLLoader() {} + +MockWebURLLoader::~MockWebURLLoader() {} + +} // namespace media diff --git a/media/blink/mock_weburlloader.h b/media/blink/mock_weburlloader.h new file mode 100644 index 0000000..70dd5a6 --- /dev/null +++ b/media/blink/mock_weburlloader.h @@ -0,0 +1,33 @@ +// Copyright 2014 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. + +#ifndef MEDIA_BLINK_MOCK_WEBURLLOADER_H_ +#define MEDIA_BLINK_MOCK_WEBURLLOADER_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" + +namespace media { + +class MockWebURLLoader : public blink::WebURLLoader { + public: + MockWebURLLoader(); + virtual ~MockWebURLLoader(); + + MOCK_METHOD4(loadSynchronously, void(const blink::WebURLRequest& request, + blink::WebURLResponse& response, + blink::WebURLError& error, + blink::WebData& data)); + MOCK_METHOD2(loadAsynchronously, void(const blink::WebURLRequest& request, + blink::WebURLLoaderClient* client)); + MOCK_METHOD0(cancel, void()); + MOCK_METHOD1(setDefersLoading, void(bool value)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockWebURLLoader); +}; + +} // namespace media + +#endif // MEDIA_BLINK_MOCK_WEBURLLOADER_H_ diff --git a/media/blink/run_all_unittests.cc b/media/blink/run_all_unittests.cc new file mode 100644 index 0000000..081a121 --- /dev/null +++ b/media/blink/run_all_unittests.cc @@ -0,0 +1,85 @@ +// Copyright 2014 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 "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "media/base/media.h" +#include "third_party/WebKit/public/web/WebKit.h" + +#if defined(OS_ANDROID) +#include "base/android/jni_android.h" +#include "media/base/android/media_jni_registrar.h" +#include "ui/gl/android/gl_jni_registrar.h" +#endif + +class TestBlinkPlatformSupport : NON_EXPORTED_BASE(public blink::Platform) { + public: + virtual ~TestBlinkPlatformSupport(); + + virtual void cryptographicallyRandomValues(unsigned char* buffer, + size_t length) OVERRIDE; + virtual const unsigned char* getTraceCategoryEnabledFlag( + const char* categoryName) OVERRIDE; +}; + +TestBlinkPlatformSupport::~TestBlinkPlatformSupport() {} + +void TestBlinkPlatformSupport::cryptographicallyRandomValues( + unsigned char* buffer, + size_t length) { +} + +const unsigned char* TestBlinkPlatformSupport::getTraceCategoryEnabledFlag( + const char* categoryName) { + static const unsigned char tracingIsDisabled = 0; + return &tracingIsDisabled; +} + +class BlinkMediaTestSuite : public base::TestSuite { + public: + BlinkMediaTestSuite(int argc, char** argv); + virtual ~BlinkMediaTestSuite(); + + protected: + virtual void Initialize() OVERRIDE; + + private: + scoped_ptr<TestBlinkPlatformSupport> blink_platform_support_; +}; + +BlinkMediaTestSuite::BlinkMediaTestSuite(int argc, char** argv) + : TestSuite(argc, argv), + blink_platform_support_(new TestBlinkPlatformSupport()) { +} + +BlinkMediaTestSuite::~BlinkMediaTestSuite() {} + +void BlinkMediaTestSuite::Initialize() { + // Run TestSuite::Initialize first so that logging is initialized. + base::TestSuite::Initialize(); + +#if defined(OS_ANDROID) + // Register JNI bindings for android. + JNIEnv* env = base::android::AttachCurrentThread(); + // Needed for surface texture support. + ui::gl::android::RegisterJni(env); + media::RegisterJni(env); +#endif + + // Run this here instead of main() to ensure an AtExitManager is already + // present. + media::InitializeMediaLibraryForTesting(); + + blink::initialize(blink_platform_support_.get()); +} + +int main(int argc, char** argv) { + BlinkMediaTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&BlinkMediaTestSuite::Run, + base::Unretained(&test_suite))); +} diff --git a/media/blink/test_response_generator.cc b/media/blink/test_response_generator.cc new file mode 100644 index 0000000..aa3b748 --- /dev/null +++ b/media/blink/test_response_generator.cc @@ -0,0 +1,101 @@ +// 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 "media/blink/test_response_generator.h" + +#include "base/format_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "net/base/net_errors.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +using blink::WebString; +using blink::WebURLError; +using blink::WebURLResponse; + +namespace media { + +TestResponseGenerator::TestResponseGenerator(const GURL& gurl, + int64 content_length) + : gurl_(gurl), + content_length_(content_length) { +} + +WebURLError TestResponseGenerator::GenerateError() { + WebURLError error; + error.reason = net::ERR_ABORTED; + error.domain = WebString::fromUTF8(net::kErrorDomain); + return error; +} + +WebURLResponse TestResponseGenerator::Generate200() { + WebURLResponse response(gurl_); + response.setHTTPStatusCode(200); + + response.setHTTPHeaderField( + WebString::fromUTF8("Content-Length"), + WebString::fromUTF8(base::Int64ToString(content_length_))); + response.setExpectedContentLength(content_length_); + return response; +} + +WebURLResponse TestResponseGenerator::Generate206(int64 first_byte_offset) { + return Generate206(first_byte_offset, kNormal); +} + +WebURLResponse TestResponseGenerator::Generate206(int64 first_byte_offset, + Flags flags) { + int64 range_content_length = content_length_ - first_byte_offset; + int64 last_byte_offset = content_length_ - 1; + + WebURLResponse response(gurl_); + response.setHTTPStatusCode(206); + + if ((flags & kNoAcceptRanges) == 0) { + response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), + WebString::fromUTF8("bytes")); + } + + if ((flags & kNoContentRange) == 0) { + std::string content_range = base::StringPrintf( + "bytes %" PRId64 "-%" PRId64 "/", + first_byte_offset, last_byte_offset); + if (flags & kNoContentRangeInstanceSize) + content_range += "*"; + else + content_range += base::StringPrintf("%" PRId64, content_length_); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(content_range)); + } + + if ((flags & kNoContentLength) == 0) { + response.setHTTPHeaderField( + WebString::fromUTF8("Content-Length"), + WebString::fromUTF8(base::Int64ToString(range_content_length))); + response.setExpectedContentLength(range_content_length); + } + return response; +} + +WebURLResponse TestResponseGenerator::Generate404() { + WebURLResponse response(gurl_); + response.setHTTPStatusCode(404); + return response; +} + +WebURLResponse TestResponseGenerator::GenerateFileResponse( + int64 first_byte_offset) { + WebURLResponse response(gurl_); + response.setHTTPStatusCode(0); + + if (first_byte_offset >= 0) { + response.setExpectedContentLength(content_length_ - first_byte_offset); + } else { + response.setExpectedContentLength(-1); + } + return response; +} + +} // namespace media diff --git a/media/blink/test_response_generator.h b/media/blink/test_response_generator.h new file mode 100644 index 0000000..7e3e98d --- /dev/null +++ b/media/blink/test_response_generator.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_ +#define MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_ + +#include "base/basictypes.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" +#include "url/gurl.h" + +namespace media { + +// Generates WebURLErrors and WebURLResponses suitable for testing purposes. +class TestResponseGenerator { + public: + enum Flags { + kNormal = 0, + kNoAcceptRanges = 1 << 0, // Don't include Accept-Ranges in 206 response. + kNoContentRange = 1 << 1, // Don't include Content-Range in 206 response. + kNoContentLength = 1 << 2, // Don't include Content-Length in 206 response. + kNoContentRangeInstanceSize = 1 << 3, // Content-Range: N-M/* in 206. + }; + + // Build an HTTP response generator for the given URL. |content_length| is + // used to generate Content-Length and Content-Range headers. + TestResponseGenerator(const GURL& gurl, int64 content_length); + + // Generates a WebURLError object. + blink::WebURLError GenerateError(); + + // Generates a regular HTTP 200 response. + blink::WebURLResponse Generate200(); + + // Generates a regular HTTP 206 response starting from |first_byte_offset| + // until the end of the resource. + blink::WebURLResponse Generate206(int64 first_byte_offset); + + // Generates a custom HTTP 206 response starting from |first_byte_offset| + // until the end of the resource. You can tweak what gets included in the + // headers via |flags|. + blink::WebURLResponse Generate206(int64 first_byte_offset, Flags flags); + + // Generates a regular HTTP 404 response. + blink::WebURLResponse Generate404(); + + // Generates a file:// response starting from |first_byte_offset| until the + // end of the resource. + // + // If |first_byte_offset| is negative a response containing no content length + // will be returned. + blink::WebURLResponse GenerateFileResponse(int64 first_byte_offset); + + int64 content_length() { return content_length_; } + + private: + GURL gurl_; + int64 content_length_; + + DISALLOW_COPY_AND_ASSIGN(TestResponseGenerator); +}; + +} // namespace media + +#endif // MEDIA_BLINK_TEST_RESPONSE_GENERATOR_H_ diff --git a/media/blink/texttrack_impl.cc b/media/blink/texttrack_impl.cc new file mode 100644 index 0000000..1d58af9 --- /dev/null +++ b/media/blink/texttrack_impl.cc @@ -0,0 +1,70 @@ +// 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 "media/blink/texttrack_impl.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "media/base/bind_to_current_loop.h" +#include "media/blink/webinbandtexttrack_impl.h" +#include "third_party/WebKit/public/platform/WebInbandTextTrackClient.h" +#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" + +namespace media { + +TextTrackImpl::TextTrackImpl( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + blink::WebMediaPlayerClient* client, + scoped_ptr<WebInbandTextTrackImpl> text_track) + : task_runner_(task_runner), + client_(client), + text_track_(text_track.Pass()) { + client_->addTextTrack(text_track_.get()); +} + +TextTrackImpl::~TextTrackImpl() { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&TextTrackImpl::OnRemoveTrack, + client_, + base::Passed(&text_track_))); +} + +void TextTrackImpl::addWebVTTCue(const base::TimeDelta& start, + const base::TimeDelta& end, + const std::string& id, + const std::string& content, + const std::string& settings) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&TextTrackImpl::OnAddCue, + text_track_.get(), + start, end, + id, content, settings)); +} + +void TextTrackImpl::OnAddCue(WebInbandTextTrackImpl* text_track, + const base::TimeDelta& start, + const base::TimeDelta& end, + const std::string& id, + const std::string& content, + const std::string& settings) { + if (blink::WebInbandTextTrackClient* client = text_track->client()) { + client->addWebVTTCue(start.InSecondsF(), + end.InSecondsF(), + blink::WebString::fromUTF8(id), + blink::WebString::fromUTF8(content), + blink::WebString::fromUTF8(settings)); + } +} + +void TextTrackImpl::OnRemoveTrack( + blink::WebMediaPlayerClient* client, + scoped_ptr<WebInbandTextTrackImpl> text_track) { + if (text_track->client()) + client->removeTextTrack(text_track.get()); +} + +} // namespace media diff --git a/media/blink/texttrack_impl.h b/media/blink/texttrack_impl.h new file mode 100644 index 0000000..1d85bde --- /dev/null +++ b/media/blink/texttrack_impl.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef MEDIA_BLINK_TEXTTRACK_IMPL_H_ +#define MEDIA_BLINK_TEXTTRACK_IMPL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/text_track.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace blink { +class WebInbandTextTrackClient; +class WebMediaPlayerClient; +} + +namespace media { + +class WebInbandTextTrackImpl; + +class TextTrackImpl : public TextTrack { + public: + // Constructor assumes ownership of the |text_track| object. + TextTrackImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + blink::WebMediaPlayerClient* client, + scoped_ptr<WebInbandTextTrackImpl> text_track); + + virtual ~TextTrackImpl(); + + virtual void addWebVTTCue(const base::TimeDelta& start, + const base::TimeDelta& end, + const std::string& id, + const std::string& content, + const std::string& settings) OVERRIDE; + + private: + static void OnAddCue(WebInbandTextTrackImpl* text_track, + const base::TimeDelta& start, + const base::TimeDelta& end, + const std::string& id, + const std::string& content, + const std::string& settings); + + static void OnRemoveTrack(blink::WebMediaPlayerClient* client, + scoped_ptr<WebInbandTextTrackImpl> text_track); + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + blink::WebMediaPlayerClient* client_; + scoped_ptr<WebInbandTextTrackImpl> text_track_; + DISALLOW_COPY_AND_ASSIGN(TextTrackImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_TEXTTRACK_IMPL_H_ diff --git a/media/blink/video_frame_compositor.cc b/media/blink/video_frame_compositor.cc new file mode 100644 index 0000000..7f254d4 --- /dev/null +++ b/media/blink/video_frame_compositor.cc @@ -0,0 +1,77 @@ +// Copyright 2014 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 "media/blink/video_frame_compositor.h" + +#include "media/base/video_frame.h" + +namespace media { + +static bool IsOpaque(const scoped_refptr<VideoFrame>& frame) { + switch (frame->format()) { + case VideoFrame::UNKNOWN: + case VideoFrame::YV12: + case VideoFrame::YV12J: + case VideoFrame::YV16: + case VideoFrame::I420: + case VideoFrame::YV24: + case VideoFrame::NV12: + return true; + + case VideoFrame::YV12A: +#if defined(VIDEO_HOLE) + case VideoFrame::HOLE: +#endif // defined(VIDEO_HOLE) + case VideoFrame::NATIVE_TEXTURE: + break; + } + return false; +} + +VideoFrameCompositor::VideoFrameCompositor( + const base::Callback<void(gfx::Size)>& natural_size_changed_cb, + const base::Callback<void(bool)>& opacity_changed_cb) + : natural_size_changed_cb_(natural_size_changed_cb), + opacity_changed_cb_(opacity_changed_cb), + client_(NULL) { +} + +VideoFrameCompositor::~VideoFrameCompositor() { + if (client_) + client_->StopUsingProvider(); +} + +void VideoFrameCompositor::SetVideoFrameProviderClient( + cc::VideoFrameProvider::Client* client) { + if (client_) + client_->StopUsingProvider(); + client_ = client; +} + +scoped_refptr<VideoFrame> VideoFrameCompositor::GetCurrentFrame() { + return current_frame_; +} + +void VideoFrameCompositor::PutCurrentFrame( + const scoped_refptr<VideoFrame>& frame) { +} + +void VideoFrameCompositor::UpdateCurrentFrame( + const scoped_refptr<VideoFrame>& frame) { + if (current_frame_.get() && + current_frame_->natural_size() != frame->natural_size()) { + natural_size_changed_cb_.Run(frame->natural_size()); + } + + if (!current_frame_.get() || IsOpaque(current_frame_) != IsOpaque(frame)) { + opacity_changed_cb_.Run(IsOpaque(frame)); + } + + current_frame_ = frame; + + if (client_) + client_->DidReceiveFrame(); +} + +} // namespace media diff --git a/media/blink/video_frame_compositor.h b/media/blink/video_frame_compositor.h new file mode 100644 index 0000000..9fc8b6a --- /dev/null +++ b/media/blink/video_frame_compositor.h @@ -0,0 +1,68 @@ +// Copyright 2014 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. + +#ifndef MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_ +#define MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "cc/layers/video_frame_provider.h" +#include "media/base/media_export.h" +#include "ui/gfx/size.h" + +namespace media { +class VideoFrame; + +// VideoFrameCompositor handles incoming frames by notifying the compositor and +// dispatching callbacks when detecting changes in video frames. +// +// Typical usage is to deliver ready-to-be-displayed video frames to +// UpdateCurrentFrame() so that VideoFrameCompositor can take care of tracking +// changes in video frames and firing callbacks as needed. +// +// VideoFrameCompositor must live on the same thread as the compositor. +class MEDIA_EXPORT VideoFrameCompositor + : NON_EXPORTED_BASE(public cc::VideoFrameProvider) { + public: + // |natural_size_changed_cb| is run with the new natural size of the video + // frame whenever a change in natural size is detected. It is not called the + // first time UpdateCurrentFrame() is called. Run on the same thread as the + // caller of UpdateCurrentFrame(). + // + // |opacity_changed_cb| is run when a change in opacity is detected. It *is* + // called the first time UpdateCurrentFrame() is called. Run on the same + // thread as the caller of UpdateCurrentFrame(). + // + // TODO(scherkus): Investigate the inconsistency between the callbacks with + // respect to why we don't call |natural_size_changed_cb| on the first frame. + // I suspect it was for historical reasons that no longer make sense. + VideoFrameCompositor( + const base::Callback<void(gfx::Size)>& natural_size_changed_cb, + const base::Callback<void(bool)>& opacity_changed_cb); + virtual ~VideoFrameCompositor(); + + // cc::VideoFrameProvider implementation. + virtual void SetVideoFrameProviderClient( + cc::VideoFrameProvider::Client* client) OVERRIDE; + virtual scoped_refptr<VideoFrame> GetCurrentFrame() OVERRIDE; + virtual void PutCurrentFrame( + const scoped_refptr<VideoFrame>& frame) OVERRIDE; + + // Updates the current frame and notifies the compositor. + void UpdateCurrentFrame(const scoped_refptr<VideoFrame>& frame); + + private: + base::Callback<void(gfx::Size)> natural_size_changed_cb_; + base::Callback<void(bool)> opacity_changed_cb_; + + cc::VideoFrameProvider::Client* client_; + + scoped_refptr<VideoFrame> current_frame_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositor); +}; + +} // namespace media + +#endif // MEDIA_BLINK_VIDEO_FRAME_COMPOSITOR_H_ diff --git a/media/blink/video_frame_compositor_unittest.cc b/media/blink/video_frame_compositor_unittest.cc new file mode 100644 index 0000000..a7c46ca --- /dev/null +++ b/media/blink/video_frame_compositor_unittest.cc @@ -0,0 +1,160 @@ +// Copyright 2014 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 "base/bind.h" +#include "cc/layers/video_frame_provider.h" +#include "media/base/video_frame.h" +#include "media/blink/video_frame_compositor.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class VideoFrameCompositorTest : public testing::Test, + public cc::VideoFrameProvider::Client { + public: + VideoFrameCompositorTest() + : compositor_(new VideoFrameCompositor( + base::Bind(&VideoFrameCompositorTest::NaturalSizeChanged, + base::Unretained(this)), + base::Bind(&VideoFrameCompositorTest::OpacityChanged, + base::Unretained(this)))), + did_receive_frame_count_(0), + natural_size_changed_count_(0), + opacity_changed_count_(0), + opaque_(false) { + compositor_->SetVideoFrameProviderClient(this); + } + + virtual ~VideoFrameCompositorTest() { + compositor_->SetVideoFrameProviderClient(NULL); + } + + VideoFrameCompositor* compositor() { return compositor_.get(); } + int did_receive_frame_count() { return did_receive_frame_count_; } + int natural_size_changed_count() { return natural_size_changed_count_; } + gfx::Size natural_size() { return natural_size_; } + + int opacity_changed_count() { return opacity_changed_count_; } + bool opaque() { return opaque_; } + + private: + // cc::VideoFrameProvider::Client implementation. + virtual void StopUsingProvider() OVERRIDE {} + virtual void DidReceiveFrame() OVERRIDE { + ++did_receive_frame_count_; + } + virtual void DidUpdateMatrix(const float* matrix) OVERRIDE {} + + void NaturalSizeChanged(gfx::Size natural_size) { + ++natural_size_changed_count_; + natural_size_ = natural_size; + } + + void OpacityChanged(bool opaque) { + ++opacity_changed_count_; + opaque_ = opaque; + } + + scoped_ptr<VideoFrameCompositor> compositor_; + int did_receive_frame_count_; + int natural_size_changed_count_; + gfx::Size natural_size_; + int opacity_changed_count_; + bool opaque_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositorTest); +}; + +TEST_F(VideoFrameCompositorTest, InitialValues) { + EXPECT_FALSE(compositor()->GetCurrentFrame().get()); +} + +TEST_F(VideoFrameCompositorTest, UpdateCurrentFrame) { + scoped_refptr<VideoFrame> expected = VideoFrame::CreateEOSFrame(); + + // Should notify compositor synchronously. + EXPECT_EQ(0, did_receive_frame_count()); + compositor()->UpdateCurrentFrame(expected); + scoped_refptr<VideoFrame> actual = compositor()->GetCurrentFrame(); + EXPECT_EQ(expected, actual); + EXPECT_EQ(1, did_receive_frame_count()); +} + +TEST_F(VideoFrameCompositorTest, NaturalSizeChanged) { + gfx::Size initial_size(8, 8); + scoped_refptr<VideoFrame> initial_frame = + VideoFrame::CreateBlackFrame(initial_size); + + gfx::Size larger_size(16, 16); + scoped_refptr<VideoFrame> larger_frame = + VideoFrame::CreateBlackFrame(larger_size); + + // Initial expectations. + EXPECT_EQ(0, natural_size().width()); + EXPECT_EQ(0, natural_size().height()); + EXPECT_EQ(0, natural_size_changed_count()); + + // Callback isn't fired for the first frame. + compositor()->UpdateCurrentFrame(initial_frame); + EXPECT_EQ(0, natural_size().width()); + EXPECT_EQ(0, natural_size().height()); + EXPECT_EQ(0, natural_size_changed_count()); + + // Callback should be fired once. + compositor()->UpdateCurrentFrame(larger_frame); + EXPECT_EQ(larger_size.width(), natural_size().width()); + EXPECT_EQ(larger_size.height(), natural_size().height()); + EXPECT_EQ(1, natural_size_changed_count()); + + compositor()->UpdateCurrentFrame(larger_frame); + EXPECT_EQ(larger_size.width(), natural_size().width()); + EXPECT_EQ(larger_size.height(), natural_size().height()); + EXPECT_EQ(1, natural_size_changed_count()); + + // Callback is fired once more when switching back to initial size. + compositor()->UpdateCurrentFrame(initial_frame); + EXPECT_EQ(initial_size.width(), natural_size().width()); + EXPECT_EQ(initial_size.height(), natural_size().height()); + EXPECT_EQ(2, natural_size_changed_count()); + + compositor()->UpdateCurrentFrame(initial_frame); + EXPECT_EQ(initial_size.width(), natural_size().width()); + EXPECT_EQ(initial_size, natural_size()); + EXPECT_EQ(2, natural_size_changed_count()); +} + +TEST_F(VideoFrameCompositorTest, OpacityChanged) { + gfx::Size size(8, 8); + gfx::Rect rect(gfx::Point(0, 0), size); + scoped_refptr<VideoFrame> opaque_frame = VideoFrame::CreateFrame( + VideoFrame::YV12, size, rect, size, base::TimeDelta()); + scoped_refptr<VideoFrame> not_opaque_frame = VideoFrame::CreateFrame( + VideoFrame::YV12A, size, rect, size, base::TimeDelta()); + + // Initial expectations. + EXPECT_FALSE(opaque()); + EXPECT_EQ(0, opacity_changed_count()); + + // Callback is fired for the first frame. + compositor()->UpdateCurrentFrame(not_opaque_frame); + EXPECT_FALSE(opaque()); + EXPECT_EQ(1, opacity_changed_count()); + + // Callback shouldn't be first subsequent times with same opaqueness. + compositor()->UpdateCurrentFrame(not_opaque_frame); + EXPECT_FALSE(opaque()); + EXPECT_EQ(1, opacity_changed_count()); + + // Callback is fired when using opacity changes. + compositor()->UpdateCurrentFrame(opaque_frame); + EXPECT_TRUE(opaque()); + EXPECT_EQ(2, opacity_changed_count()); + + // Callback shouldn't be first subsequent times with same opaqueness. + compositor()->UpdateCurrentFrame(opaque_frame); + EXPECT_TRUE(opaque()); + EXPECT_EQ(2, opacity_changed_count()); +} + +} // namespace media diff --git a/media/blink/webaudiosourceprovider_impl.cc b/media/blink/webaudiosourceprovider_impl.cc new file mode 100644 index 0000000..6637719 --- /dev/null +++ b/media/blink/webaudiosourceprovider_impl.cc @@ -0,0 +1,193 @@ +// 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 "media/blink/webaudiosourceprovider_impl.h" + +#include <vector> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "media/base/bind_to_current_loop.h" +#include "third_party/WebKit/public/platform/WebAudioSourceProviderClient.h" + +using blink::WebVector; + +namespace media { + +namespace { + +// Simple helper class for Try() locks. Lock is Try()'d on construction and +// must be checked via the locked() attribute. If acquisition was successful +// the lock will be released upon destruction. +// TODO(dalecurtis): This should probably move to base/ if others start using +// this pattern. +class AutoTryLock { + public: + explicit AutoTryLock(base::Lock& lock) + : lock_(lock), + acquired_(lock_.Try()) {} + + bool locked() const { return acquired_; } + + ~AutoTryLock() { + if (acquired_) { + lock_.AssertAcquired(); + lock_.Release(); + } + } + + private: + base::Lock& lock_; + const bool acquired_; + DISALLOW_COPY_AND_ASSIGN(AutoTryLock); +}; + +} // namespace + +WebAudioSourceProviderImpl::WebAudioSourceProviderImpl( + const scoped_refptr<AudioRendererSink>& sink) + : channels_(0), + sample_rate_(0), + volume_(1.0), + state_(kStopped), + renderer_(NULL), + client_(NULL), + sink_(sink), + weak_factory_(this) {} + +WebAudioSourceProviderImpl::~WebAudioSourceProviderImpl() { +} + +void WebAudioSourceProviderImpl::setClient( + blink::WebAudioSourceProviderClient* client) { + base::AutoLock auto_lock(sink_lock_); + if (client && client != client_) { + // Detach the audio renderer from normal playback. + sink_->Stop(); + + // The client will now take control by calling provideInput() periodically. + client_ = client; + + set_format_cb_ = BindToCurrentLoop(base::Bind( + &WebAudioSourceProviderImpl::OnSetFormat, weak_factory_.GetWeakPtr())); + + // If |renderer_| is set, then run |set_format_cb_| to send |client_| + // the current format info. If |renderer_| is not set, then |set_format_cb_| + // will get called when Initialize() is called. + // Note: Always using |set_format_cb_| ensures we have the same + // locking order when calling into |client_|. + if (renderer_) + base::ResetAndReturn(&set_format_cb_).Run(); + } else if (!client && client_) { + // Restore normal playback. + client_ = NULL; + sink_->SetVolume(volume_); + if (state_ >= kStarted) + sink_->Start(); + if (state_ >= kPlaying) + sink_->Play(); + } +} + +void WebAudioSourceProviderImpl::provideInput( + const WebVector<float*>& audio_data, size_t number_of_frames) { + if (!bus_wrapper_ || + static_cast<size_t>(bus_wrapper_->channels()) != audio_data.size()) { + bus_wrapper_ = AudioBus::CreateWrapper(static_cast<int>(audio_data.size())); + } + + bus_wrapper_->set_frames(static_cast<int>(number_of_frames)); + for (size_t i = 0; i < audio_data.size(); ++i) + bus_wrapper_->SetChannelData(static_cast<int>(i), audio_data[i]); + + // Use a try lock to avoid contention in the real-time audio thread. + AutoTryLock auto_try_lock(sink_lock_); + if (!auto_try_lock.locked() || state_ != kPlaying) { + // Provide silence if we failed to acquire the lock or the source is not + // running. + bus_wrapper_->Zero(); + return; + } + + DCHECK(renderer_); + DCHECK(client_); + DCHECK_EQ(channels_, bus_wrapper_->channels()); + const int frames = renderer_->Render(bus_wrapper_.get(), 0); + if (frames < static_cast<int>(number_of_frames)) { + bus_wrapper_->ZeroFramesPartial( + frames, + static_cast<int>(number_of_frames - frames)); + } + + bus_wrapper_->Scale(volume_); +} + +void WebAudioSourceProviderImpl::Start() { + base::AutoLock auto_lock(sink_lock_); + DCHECK_EQ(state_, kStopped); + state_ = kStarted; + if (!client_) + sink_->Start(); +} + +void WebAudioSourceProviderImpl::Stop() { + base::AutoLock auto_lock(sink_lock_); + state_ = kStopped; + if (!client_) + sink_->Stop(); +} + +void WebAudioSourceProviderImpl::Play() { + base::AutoLock auto_lock(sink_lock_); + DCHECK_EQ(state_, kStarted); + state_ = kPlaying; + if (!client_) + sink_->Play(); +} + +void WebAudioSourceProviderImpl::Pause() { + base::AutoLock auto_lock(sink_lock_); + DCHECK(state_ == kPlaying || state_ == kStarted); + state_ = kStarted; + if (!client_) + sink_->Pause(); +} + +bool WebAudioSourceProviderImpl::SetVolume(double volume) { + base::AutoLock auto_lock(sink_lock_); + volume_ = volume; + if (!client_) + sink_->SetVolume(volume); + return true; +} + +void WebAudioSourceProviderImpl::Initialize( + const AudioParameters& params, + RenderCallback* renderer) { + base::AutoLock auto_lock(sink_lock_); + CHECK(!renderer_); + renderer_ = renderer; + + DCHECK_EQ(state_, kStopped); + sink_->Initialize(params, renderer); + + // Keep track of the format in case the client hasn't yet been set. + channels_ = params.channels(); + sample_rate_ = params.sample_rate(); + + if (!set_format_cb_.is_null()) + base::ResetAndReturn(&set_format_cb_).Run(); +} + +void WebAudioSourceProviderImpl::OnSetFormat() { + base::AutoLock auto_lock(sink_lock_); + if (!client_) + return; + + // Inform Blink about the audio stream format. + client_->setFormat(channels_, sample_rate_); +} + +} // namespace media diff --git a/media/blink/webaudiosourceprovider_impl.h b/media/blink/webaudiosourceprovider_impl.h new file mode 100644 index 0000000..e686e9d --- /dev/null +++ b/media/blink/webaudiosourceprovider_impl.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_ +#define MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_ + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/synchronization/lock.h" +#include "media/base/audio_renderer_sink.h" +#include "media/base/media_export.h" +#include "third_party/WebKit/public/platform/WebAudioSourceProvider.h" +#include "third_party/WebKit/public/platform/WebVector.h" + +namespace blink { +class WebAudioSourceProviderClient; +} + +namespace media { + +// WebAudioSourceProviderImpl provides a bridge between classes: +// blink::WebAudioSourceProvider <---> AudioRendererSink +// +// WebAudioSourceProviderImpl wraps an existing audio sink that is used unless +// WebKit has set a client via setClient(). While a client is set WebKit will +// periodically call provideInput() to render a certain number of audio +// sample-frames using the sink's RenderCallback to get the data. +// +// All calls are protected by a lock. +class MEDIA_EXPORT WebAudioSourceProviderImpl + : NON_EXPORTED_BASE(public blink::WebAudioSourceProvider), + NON_EXPORTED_BASE(public AudioRendererSink) { + public: + explicit WebAudioSourceProviderImpl( + const scoped_refptr<AudioRendererSink>& sink); + + // blink::WebAudioSourceProvider implementation. + virtual void setClient(blink::WebAudioSourceProviderClient* client); + virtual void provideInput(const blink::WebVector<float*>& audio_data, + size_t number_of_frames); + + // AudioRendererSink implementation. + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void Play() OVERRIDE; + virtual void Pause() OVERRIDE; + virtual bool SetVolume(double volume) OVERRIDE; + virtual void Initialize(const AudioParameters& params, + RenderCallback* renderer) OVERRIDE; + + protected: + virtual ~WebAudioSourceProviderImpl(); + + private: + // Calls setFormat() on |client_| from the Blink renderer thread. + void OnSetFormat(); + + // Closure that posts a task to call OnSetFormat() on the renderer thread. + base::Closure set_format_cb_; + + // Set to true when Initialize() is called. + int channels_; + int sample_rate_; + double volume_; + + // Tracks the current playback state. + enum PlaybackState { kStopped, kStarted, kPlaying }; + PlaybackState state_; + + // Where audio comes from. + AudioRendererSink::RenderCallback* renderer_; + + // When set via setClient() it overrides |sink_| for consuming audio. + blink::WebAudioSourceProviderClient* client_; + + // Where audio ends up unless overridden by |client_|. + base::Lock sink_lock_; + scoped_refptr<AudioRendererSink> sink_; + scoped_ptr<AudioBus> bus_wrapper_; + + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<WebAudioSourceProviderImpl> weak_factory_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebAudioSourceProviderImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBAUDIOSOURCEPROVIDER_IMPL_H_ diff --git a/media/blink/webaudiosourceprovider_impl_unittest.cc b/media/blink/webaudiosourceprovider_impl_unittest.cc new file mode 100644 index 0000000..c1725fa --- /dev/null +++ b/media/blink/webaudiosourceprovider_impl_unittest.cc @@ -0,0 +1,240 @@ +// 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 "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "media/audio/audio_parameters.h" +#include "media/base/fake_audio_render_callback.h" +#include "media/base/mock_audio_renderer_sink.h" +#include "media/blink/webaudiosourceprovider_impl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebAudioSourceProviderClient.h" + +namespace media { + +namespace { +const float kTestVolume = 0.25; +} // namespace + +class WebAudioSourceProviderImplTest + : public testing::Test, + public blink::WebAudioSourceProviderClient { + public: + WebAudioSourceProviderImplTest() + : params_(AudioParameters::AUDIO_PCM_LINEAR, + CHANNEL_LAYOUT_STEREO, 48000, 16, 64), + fake_callback_(0.1), + mock_sink_(new MockAudioRendererSink()), + wasp_impl_(new WebAudioSourceProviderImpl(mock_sink_)) { + } + + virtual ~WebAudioSourceProviderImplTest() {} + + void CallAllSinkMethodsAndVerify(bool verify) { + testing::InSequence s; + + EXPECT_CALL(*mock_sink_.get(), Start()).Times(verify); + wasp_impl_->Start(); + + EXPECT_CALL(*mock_sink_.get(), Play()).Times(verify); + wasp_impl_->Play(); + + EXPECT_CALL(*mock_sink_.get(), Pause()).Times(verify); + wasp_impl_->Pause(); + + EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume)).Times(verify); + wasp_impl_->SetVolume(kTestVolume); + + EXPECT_CALL(*mock_sink_.get(), Stop()).Times(verify); + wasp_impl_->Stop(); + + testing::Mock::VerifyAndClear(mock_sink_.get()); + } + + void SetClient(blink::WebAudioSourceProviderClient* client) { + testing::InSequence s; + + if (client) { + EXPECT_CALL(*mock_sink_.get(), Stop()); + EXPECT_CALL(*this, setFormat(params_.channels(), params_.sample_rate())); + } + wasp_impl_->setClient(client); + base::RunLoop().RunUntilIdle(); + + testing::Mock::VerifyAndClear(mock_sink_.get()); + testing::Mock::VerifyAndClear(this); + } + + bool CompareBusses(const AudioBus* bus1, const AudioBus* bus2) { + EXPECT_EQ(bus1->channels(), bus2->channels()); + EXPECT_EQ(bus1->frames(), bus2->frames()); + for (int ch = 0; ch < bus1->channels(); ++ch) { + if (memcmp(bus1->channel(ch), bus2->channel(ch), + sizeof(*bus1->channel(ch)) * bus1->frames()) != 0) { + return false; + } + } + return true; + } + + // blink::WebAudioSourceProviderClient implementation. + MOCK_METHOD2(setFormat, void(size_t numberOfChannels, float sampleRate)); + + protected: + AudioParameters params_; + FakeAudioRenderCallback fake_callback_; + scoped_refptr<MockAudioRendererSink> mock_sink_; + scoped_refptr<WebAudioSourceProviderImpl> wasp_impl_; + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(WebAudioSourceProviderImplTest); +}; + +TEST_F(WebAudioSourceProviderImplTest, SetClientBeforeInitialize) { + // setClient() with a NULL client should do nothing if no client is set. + wasp_impl_->setClient(NULL); + + EXPECT_CALL(*mock_sink_.get(), Stop()); + wasp_impl_->setClient(this); + base::RunLoop().RunUntilIdle(); + + // When Initialize() is called after setClient(), the params should propagate + // to the client via setFormat() during the call. + EXPECT_CALL(*this, setFormat(params_.channels(), params_.sample_rate())); + wasp_impl_->Initialize(params_, &fake_callback_); + base::RunLoop().RunUntilIdle(); + + // setClient() with the same client should do nothing. + wasp_impl_->setClient(this); + base::RunLoop().RunUntilIdle(); +} + +// Verify AudioRendererSink functionality w/ and w/o a client. +TEST_F(WebAudioSourceProviderImplTest, SinkMethods) { + wasp_impl_->Initialize(params_, &fake_callback_); + ASSERT_EQ(mock_sink_->callback(), &fake_callback_); + + // Without a client all WASP calls should fall through to the underlying sink. + CallAllSinkMethodsAndVerify(true); + + // With a client no calls should reach the Stop()'d sink. Also, setClient() + // should propagate the params provided during Initialize() at call time. + SetClient(this); + CallAllSinkMethodsAndVerify(false); + + // Removing the client should cause WASP to revert to the underlying sink. + EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume)); + SetClient(NULL); + CallAllSinkMethodsAndVerify(true); +} + +// Verify underlying sink state is restored after client removal. +TEST_F(WebAudioSourceProviderImplTest, SinkStateRestored) { + wasp_impl_->Initialize(params_, &fake_callback_); + + // Verify state set before the client is set propagates back afterward. + EXPECT_CALL(*mock_sink_.get(), Start()); + wasp_impl_->Start(); + SetClient(this); + + EXPECT_CALL(*mock_sink_.get(), SetVolume(1.0)); + EXPECT_CALL(*mock_sink_.get(), Start()); + SetClient(NULL); + + // Verify state set while the client was attached propagates back afterward. + SetClient(this); + wasp_impl_->Play(); + wasp_impl_->SetVolume(kTestVolume); + + EXPECT_CALL(*mock_sink_.get(), SetVolume(kTestVolume)); + EXPECT_CALL(*mock_sink_.get(), Start()); + EXPECT_CALL(*mock_sink_.get(), Play()); + SetClient(NULL); +} + +// Test the AudioRendererSink state machine and its effects on provideInput(). +TEST_F(WebAudioSourceProviderImplTest, ProvideInput) { + scoped_ptr<AudioBus> bus1 = AudioBus::Create(params_); + scoped_ptr<AudioBus> bus2 = AudioBus::Create(params_); + + // Point the WebVector into memory owned by |bus1|. + blink::WebVector<float*> audio_data(static_cast<size_t>(bus1->channels())); + for (size_t i = 0; i < audio_data.size(); ++i) + audio_data[i] = bus1->channel(i); + + // Verify provideInput() works before Initialize() and returns silence. + bus1->channel(0)[0] = 1; + bus2->Zero(); + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + + wasp_impl_->Initialize(params_, &fake_callback_); + SetClient(this); + + // Verify provideInput() is muted prior to Start() and no calls to the render + // callback have occurred. + bus1->channel(0)[0] = 1; + bus2->Zero(); + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + ASSERT_EQ(fake_callback_.last_audio_delay_milliseconds(), -1); + + wasp_impl_->Start(); + + // Ditto for Play(). + bus1->channel(0)[0] = 1; + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + ASSERT_EQ(fake_callback_.last_audio_delay_milliseconds(), -1); + + wasp_impl_->Play(); + + // Now we should get real audio data. + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_FALSE(CompareBusses(bus1.get(), bus2.get())); + + // Ensure volume adjustment is working. + fake_callback_.reset(); + fake_callback_.Render(bus2.get(), 0); + bus2->Scale(kTestVolume); + + fake_callback_.reset(); + wasp_impl_->SetVolume(kTestVolume); + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + + // Pause should return to silence. + wasp_impl_->Pause(); + bus1->channel(0)[0] = 1; + bus2->Zero(); + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + + // Ensure if a renderer properly fill silence for partial Render() calls by + // configuring the fake callback to return half the data. After these calls + // bus1 is full of junk data, and bus2 is partially filled. + wasp_impl_->SetVolume(1); + fake_callback_.Render(bus1.get(), 0); + fake_callback_.reset(); + fake_callback_.Render(bus2.get(), 0); + bus2->ZeroFramesPartial(bus2->frames() / 2, + bus2->frames() - bus2->frames() / 2); + fake_callback_.reset(); + fake_callback_.set_half_fill(true); + wasp_impl_->Play(); + + // Play should return real audio data again, but the last half should be zero. + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); + + // Stop() should return silence. + wasp_impl_->Stop(); + bus1->channel(0)[0] = 1; + bus2->Zero(); + wasp_impl_->provideInput(audio_data, params_.frames_per_buffer()); + ASSERT_TRUE(CompareBusses(bus1.get(), bus2.get())); +} + +} // namespace media diff --git a/media/blink/webinbandtexttrack_impl.cc b/media/blink/webinbandtexttrack_impl.cc new file mode 100644 index 0000000..49a4880 --- /dev/null +++ b/media/blink/webinbandtexttrack_impl.cc @@ -0,0 +1,58 @@ +// 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 "media/blink/webinbandtexttrack_impl.h" + +#include "base/logging.h" + +namespace media { + +WebInbandTextTrackImpl::WebInbandTextTrackImpl( + Kind kind, + const blink::WebString& label, + const blink::WebString& language, + const blink::WebString& id, + int index) + : client_(NULL), + kind_(kind), + label_(label), + language_(language), + id_(id), + index_(index) { +} + +WebInbandTextTrackImpl::~WebInbandTextTrackImpl() { + DCHECK(!client_); +} + +void WebInbandTextTrackImpl::setClient( + blink::WebInbandTextTrackClient* client) { + client_ = client; +} + +blink::WebInbandTextTrackClient* WebInbandTextTrackImpl::client() { + return client_; +} + +WebInbandTextTrackImpl::Kind WebInbandTextTrackImpl::kind() const { + return kind_; +} + +blink::WebString WebInbandTextTrackImpl::label() const { + return label_; +} + +blink::WebString WebInbandTextTrackImpl::language() const { + return language_; +} + +blink::WebString WebInbandTextTrackImpl::id() const { + return id_; +} + +int WebInbandTextTrackImpl::textTrackIndex() const { + return index_; +} + +} // namespace media diff --git a/media/blink/webinbandtexttrack_impl.h b/media/blink/webinbandtexttrack_impl.h new file mode 100644 index 0000000..c08bfad --- /dev/null +++ b/media/blink/webinbandtexttrack_impl.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_ +#define MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_ + +#include "third_party/WebKit/public/platform/WebInbandTextTrack.h" +#include "third_party/WebKit/public/platform/WebString.h" + +namespace media { + +class WebInbandTextTrackImpl : public blink::WebInbandTextTrack { + public: + WebInbandTextTrackImpl(Kind kind, + const blink::WebString& label, + const blink::WebString& language, + const blink::WebString& id, + int index); + virtual ~WebInbandTextTrackImpl(); + + virtual void setClient(blink::WebInbandTextTrackClient* client); + virtual blink::WebInbandTextTrackClient* client(); + + virtual Kind kind() const; + + virtual blink::WebString label() const; + virtual blink::WebString language() const; + virtual blink::WebString id() const; + + virtual int textTrackIndex() const; + + private: + blink::WebInbandTextTrackClient* client_; + Kind kind_; + blink::WebString label_; + blink::WebString language_; + blink::WebString id_; + int index_; + DISALLOW_COPY_AND_ASSIGN(WebInbandTextTrackImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBINBANDTEXTTRACK_IMPL_H_ diff --git a/media/blink/webmediaplayer_delegate.h b/media/blink/webmediaplayer_delegate.h new file mode 100644 index 0000000..7139676 --- /dev/null +++ b/media/blink/webmediaplayer_delegate.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_ +#define MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_ + +namespace blink { +class WebMediaPlayer; +} +namespace media { + +// An interface to allow a WebMediaPlayerImpl to communicate changes of state +// to objects that need to know. +class WebMediaPlayerDelegate { + public: + WebMediaPlayerDelegate() {} + + // The specified player started playing media. + virtual void DidPlay(blink::WebMediaPlayer* player) = 0; + + // The specified player stopped playing media. + virtual void DidPause(blink::WebMediaPlayer* player) = 0; + + // The specified player was destroyed. Do not call any methods on it. + virtual void PlayerGone(blink::WebMediaPlayer* player) = 0; + + protected: + virtual ~WebMediaPlayerDelegate() {} +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBMEDIAPLAYER_DELEGATE_H_ diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc new file mode 100644 index 0000000..bed9299 --- /dev/null +++ b/media/blink/webmediaplayer_impl.cc @@ -0,0 +1,1034 @@ +// 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 "media/blink/webmediaplayer_impl.h" + +#include <algorithm> +#include <limits> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/debug/alias.h" +#include "base/debug/crash_logging.h" +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/metrics/histogram.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "cc/blink/web_layer_impl.h" +#include "cc/layers/video_layer.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "gpu/command_buffer/common/mailbox_holder.h" +#include "media/audio/null_audio_sink.h" +#include "media/base/audio_hardware_config.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/limits.h" +#include "media/base/media_log.h" +#include "media/base/pipeline.h" +#include "media/base/text_renderer.h" +#include "media/base/video_frame.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/encrypted_media_player_support.h" +#include "media/blink/texttrack_impl.h" +#include "media/blink/webaudiosourceprovider_impl.h" +#include "media/blink/webinbandtexttrack_impl.h" +#include "media/blink/webmediaplayer_delegate.h" +#include "media/blink/webmediaplayer_params.h" +#include "media/blink/webmediaplayer_util.h" +#include "media/blink/webmediasource_impl.h" +#include "media/filters/audio_renderer_impl.h" +#include "media/filters/chunk_demuxer.h" +#include "media/filters/ffmpeg_audio_decoder.h" +#include "media/filters/ffmpeg_demuxer.h" +#include "media/filters/ffmpeg_video_decoder.h" +#include "media/filters/gpu_video_accelerator_factories.h" +#include "media/filters/gpu_video_decoder.h" +#include "media/filters/opus_audio_decoder.h" +#include "media/filters/renderer_impl.h" +#include "media/filters/video_renderer_impl.h" +#include "media/filters/vpx_video_decoder.h" +#include "third_party/WebKit/public/platform/WebMediaSource.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebSize.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURL.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebView.h" + +using blink::WebCanvas; +using blink::WebMediaPlayer; +using blink::WebRect; +using blink::WebSize; +using blink::WebString; + +namespace { + +// Limits the range of playback rate. +// +// TODO(kylep): Revisit these. +// +// Vista has substantially lower performance than XP or Windows7. If you speed +// up a video too much, it can't keep up, and rendering stops updating except on +// the time bar. For really high speeds, audio becomes a bottleneck and we just +// use up the data we have, which may not achieve the speed requested, but will +// not crash the tab. +// +// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems +// like a busy loop). It gets unresponsive, although its not completely dead. +// +// Also our timers are not very accurate (especially for ogg), which becomes +// evident at low speeds and on Vista. Since other speeds are risky and outside +// the norms, we think 1/16x to 16x is a safe and useful range for now. +const double kMinRate = 0.0625; +const double kMaxRate = 16.0; + +class SyncPointClientImpl : public media::VideoFrame::SyncPointClient { + public: + explicit SyncPointClientImpl( + blink::WebGraphicsContext3D* web_graphics_context) + : web_graphics_context_(web_graphics_context) {} + virtual ~SyncPointClientImpl() {} + virtual uint32 InsertSyncPoint() OVERRIDE { + return web_graphics_context_->insertSyncPoint(); + } + virtual void WaitSyncPoint(uint32 sync_point) OVERRIDE { + web_graphics_context_->waitSyncPoint(sync_point); + } + + private: + blink::WebGraphicsContext3D* web_graphics_context_; +}; + +} // namespace + +namespace media { + +class BufferedDataSourceHostImpl; + +#define COMPILE_ASSERT_MATCHING_ENUM(name) \ + COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::CORSMode ## name) == \ + static_cast<int>(BufferedResourceLoader::k ## name), \ + mismatching_enums) +COMPILE_ASSERT_MATCHING_ENUM(Unspecified); +COMPILE_ASSERT_MATCHING_ENUM(Anonymous); +COMPILE_ASSERT_MATCHING_ENUM(UseCredentials); +#undef COMPILE_ASSERT_MATCHING_ENUM + +#define BIND_TO_RENDER_LOOP(function) \ + (DCHECK(main_task_runner_->BelongsToCurrentThread()), \ + BindToCurrentLoop(base::Bind(function, AsWeakPtr()))) + +#define BIND_TO_RENDER_LOOP1(function, arg1) \ + (DCHECK(main_task_runner_->BelongsToCurrentThread()), \ + BindToCurrentLoop(base::Bind(function, AsWeakPtr(), arg1))) + +static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log, + const std::string& error) { + media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error)); +} + +WebMediaPlayerImpl::WebMediaPlayerImpl( + blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + base::WeakPtr<WebMediaPlayerDelegate> delegate, + const WebMediaPlayerParams& params) + : frame_(frame), + network_state_(WebMediaPlayer::NetworkStateEmpty), + ready_state_(WebMediaPlayer::ReadyStateHaveNothing), + preload_(BufferedDataSource::AUTO), + main_task_runner_(base::MessageLoopProxy::current()), + media_task_runner_(params.media_task_runner()), + media_log_(params.media_log()), + pipeline_(media_task_runner_, media_log_.get()), + load_type_(LoadTypeURL), + opaque_(false), + paused_(true), + seeking_(false), + playback_rate_(0.0f), + ended_(false), + pending_seek_(false), + pending_seek_seconds_(0.0f), + should_notify_time_changed_(false), + client_(client), + delegate_(delegate), + defer_load_cb_(params.defer_load_cb()), + gpu_factories_(params.gpu_factories()), + supports_save_(true), + chunk_demuxer_(NULL), + compositor_task_runner_(params.compositor_task_runner()), + compositor_(new VideoFrameCompositor( + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnNaturalSizeChanged), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnOpacityChanged))), + text_track_index_(0), + encrypted_media_support_( + params.CreateEncryptedMediaPlayerSupport(client)), + audio_hardware_config_(params.audio_hardware_config()) { + DCHECK(encrypted_media_support_); + + // Threaded compositing isn't enabled universally yet. + if (!compositor_task_runner_.get()) + compositor_task_runner_ = base::MessageLoopProxy::current(); + + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED)); + + // |gpu_factories_| requires that its entry points be called on its + // |GetTaskRunner()|. Since |pipeline_| will own decoders created from the + // factories, require that their message loops are identical. + DCHECK(!gpu_factories_.get() || + (gpu_factories_->GetTaskRunner() == media_task_runner_.get())); + + // Use the null sink if no sink was provided. + audio_source_provider_ = new WebAudioSourceProviderImpl( + params.audio_renderer_sink().get() + ? params.audio_renderer_sink() + : new NullAudioSink(media_task_runner_)); +} + +WebMediaPlayerImpl::~WebMediaPlayerImpl() { + client_->setWebLayer(NULL); + + DCHECK(main_task_runner_->BelongsToCurrentThread()); + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_DESTROYED)); + + if (delegate_.get()) + delegate_->PlayerGone(this); + + // Abort any pending IO so stopping the pipeline doesn't get blocked. + if (data_source_) + data_source_->Abort(); + if (chunk_demuxer_) { + chunk_demuxer_->Shutdown(); + chunk_demuxer_ = NULL; + } + + gpu_factories_ = NULL; + + // Make sure to kill the pipeline so there's no more media threads running. + // Note: stopping the pipeline might block for a long time. + base::WaitableEvent waiter(false, false); + pipeline_.Stop( + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&waiter))); + waiter.Wait(); + + compositor_task_runner_->DeleteSoon(FROM_HERE, compositor_); +} + +void WebMediaPlayerImpl::load(LoadType load_type, const blink::WebURL& url, + CORSMode cors_mode) { + DVLOG(1) << __FUNCTION__ << "(" << load_type << ", " << url << ", " + << cors_mode << ")"; + if (!defer_load_cb_.is_null()) { + defer_load_cb_.Run(base::Bind( + &WebMediaPlayerImpl::DoLoad, AsWeakPtr(), load_type, url, cors_mode)); + return; + } + DoLoad(load_type, url, cors_mode); +} + +void WebMediaPlayerImpl::DoLoad(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + GURL gurl(url); + ReportMediaSchemeUma(gurl); + + // Set subresource URL for crash reporting. + base::debug::SetCrashKeyValue("subresource_url", gurl.spec()); + + load_type_ = load_type; + + SetNetworkState(WebMediaPlayer::NetworkStateLoading); + SetReadyState(WebMediaPlayer::ReadyStateHaveNothing); + media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec())); + + // Media source pipelines can start immediately. + if (load_type == LoadTypeMediaSource) { + supports_save_ = false; + StartPipeline(); + return; + } + + // Otherwise it's a regular request which requires resolving the URL first. + data_source_.reset(new BufferedDataSource( + url, + static_cast<BufferedResourceLoader::CORSMode>(cors_mode), + main_task_runner_, + frame_, + media_log_.get(), + &buffered_data_source_host_, + base::Bind(&WebMediaPlayerImpl::NotifyDownloading, AsWeakPtr()))); + data_source_->Initialize( + base::Bind(&WebMediaPlayerImpl::DataSourceInitialized, AsWeakPtr())); + data_source_->SetPreload(preload_); +} + +void WebMediaPlayerImpl::play() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + paused_ = false; + pipeline_.SetPlaybackRate(playback_rate_); + if (data_source_) + data_source_->MediaIsPlaying(); + + media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY)); + + if (delegate_.get()) + delegate_->DidPlay(this); +} + +void WebMediaPlayerImpl::pause() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + paused_ = true; + pipeline_.SetPlaybackRate(0.0f); + if (data_source_) + data_source_->MediaIsPaused(); + paused_time_ = pipeline_.GetMediaTime(); + + media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PAUSE)); + + if (delegate_.get()) + delegate_->DidPause(this); +} + +bool WebMediaPlayerImpl::supportsSave() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return supports_save_; +} + +void WebMediaPlayerImpl::seek(double seconds) { + DVLOG(1) << __FUNCTION__ << "(" << seconds << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + ended_ = false; + + if (ready_state_ > WebMediaPlayer::ReadyStateHaveMetadata) + SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); + + base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds); + + if (seeking_) { + pending_seek_ = true; + pending_seek_seconds_ = seconds; + if (chunk_demuxer_) + chunk_demuxer_->CancelPendingSeek(seek_time); + return; + } + + media_log_->AddEvent(media_log_->CreateSeekEvent(seconds)); + + // Update our paused time. + if (paused_) + paused_time_ = seek_time; + + seeking_ = true; + + if (chunk_demuxer_) + chunk_demuxer_->StartWaitingForSeek(seek_time); + + // Kick off the asynchronous seek! + pipeline_.Seek( + seek_time, + BIND_TO_RENDER_LOOP1(&WebMediaPlayerImpl::OnPipelineSeeked, true)); +} + +void WebMediaPlayerImpl::setRate(double rate) { + DVLOG(1) << __FUNCTION__ << "(" << rate << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // TODO(kylep): Remove when support for negatives is added. Also, modify the + // following checks so rewind uses reasonable values also. + if (rate < 0.0) + return; + + // Limit rates to reasonable values by clamping. + if (rate != 0.0) { + if (rate < kMinRate) + rate = kMinRate; + else if (rate > kMaxRate) + rate = kMaxRate; + } + + playback_rate_ = rate; + if (!paused_) { + pipeline_.SetPlaybackRate(rate); + if (data_source_) + data_source_->MediaPlaybackRateChanged(rate); + } +} + +void WebMediaPlayerImpl::setVolume(double volume) { + DVLOG(1) << __FUNCTION__ << "(" << volume << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + pipeline_.SetVolume(volume); +} + +#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ + COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \ + static_cast<int>(BufferedDataSource::chromium_name), \ + mismatching_enums) +COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE); +COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA); +COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO); +#undef COMPILE_ASSERT_MATCHING_ENUM + +void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) { + DVLOG(1) << __FUNCTION__ << "(" << preload << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + preload_ = static_cast<BufferedDataSource::Preload>(preload); + if (data_source_) + data_source_->SetPreload(preload_); +} + +bool WebMediaPlayerImpl::hasVideo() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return pipeline_metadata_.has_video; +} + +bool WebMediaPlayerImpl::hasAudio() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return pipeline_metadata_.has_audio; +} + +blink::WebSize WebMediaPlayerImpl::naturalSize() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return blink::WebSize(pipeline_metadata_.natural_size); +} + +bool WebMediaPlayerImpl::paused() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return pipeline_.GetPlaybackRate() == 0.0f; +} + +bool WebMediaPlayerImpl::seeking() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) + return false; + + return seeking_; +} + +double WebMediaPlayerImpl::duration() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) + return std::numeric_limits<double>::quiet_NaN(); + + return GetPipelineDuration(); +} + +double WebMediaPlayerImpl::timelineOffset() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (pipeline_metadata_.timeline_offset.is_null()) + return std::numeric_limits<double>::quiet_NaN(); + + return pipeline_metadata_.timeline_offset.ToJsTime(); +} + +double WebMediaPlayerImpl::currentTime() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing); + + // TODO(scherkus): Replace with an explicit ended signal to HTMLMediaElement, + // see http://crbug.com/409280 + if (ended_) + return duration(); + + return (paused_ ? paused_time_ : pipeline_.GetMediaTime()).InSecondsF(); +} + +WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return network_state_; +} + +WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return ready_state_; +} + +blink::WebTimeRanges WebMediaPlayerImpl::buffered() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + Ranges<base::TimeDelta> buffered_time_ranges = + pipeline_.GetBufferedTimeRanges(); + + const base::TimeDelta duration = pipeline_.GetMediaDuration(); + if (duration != kInfiniteDuration()) { + buffered_data_source_host_.AddBufferedTimeRanges( + &buffered_time_ranges, duration); + } + return ConvertToWebTimeRanges(buffered_time_ranges); +} + +double WebMediaPlayerImpl::maxTimeSeekable() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // If we haven't even gotten to ReadyStateHaveMetadata yet then just + // return 0 so that the seekable range is empty. + if (ready_state_ < WebMediaPlayer::ReadyStateHaveMetadata) + return 0.0; + + // We don't support seeking in streaming media. + if (data_source_ && data_source_->IsStreaming()) + return 0.0; + return duration(); +} + +bool WebMediaPlayerImpl::didLoadingProgress() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + bool pipeline_progress = pipeline_.DidLoadingProgress(); + bool data_progress = buffered_data_source_host_.DidLoadingProgress(); + return pipeline_progress || data_progress; +} + +void WebMediaPlayerImpl::paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha) { + paint(canvas, rect, alpha, SkXfermode::kSrcOver_Mode); +} + +void WebMediaPlayerImpl::paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha, + SkXfermode::Mode mode) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + TRACE_EVENT0("media", "WebMediaPlayerImpl:paint"); + + // TODO(scherkus): Clarify paint() API contract to better understand when and + // why it's being called. For example, today paint() is called when: + // - We haven't reached HAVE_CURRENT_DATA and need to paint black + // - We're painting to a canvas + // See http://crbug.com/341225 http://crbug.com/342621 for details. + scoped_refptr<VideoFrame> video_frame = + GetCurrentFrameFromCompositor(); + + gfx::Rect gfx_rect(rect); + + skcanvas_video_renderer_.Paint(video_frame.get(), + canvas, + gfx_rect, + alpha, + mode, + pipeline_metadata_.video_rotation); +} + +bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const { + if (data_source_) + return data_source_->HasSingleOrigin(); + return true; +} + +bool WebMediaPlayerImpl::didPassCORSAccessCheck() const { + if (data_source_) + return data_source_->DidPassCORSAccessCheck(); + return false; +} + +double WebMediaPlayerImpl::mediaTimeForTimeValue(double timeValue) const { + return ConvertSecondsToTimestamp(timeValue).InSecondsF(); +} + +unsigned WebMediaPlayerImpl::decodedFrameCount() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + PipelineStatistics stats = pipeline_.GetStatistics(); + return stats.video_frames_decoded; +} + +unsigned WebMediaPlayerImpl::droppedFrameCount() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + PipelineStatistics stats = pipeline_.GetStatistics(); + return stats.video_frames_dropped; +} + +unsigned WebMediaPlayerImpl::audioDecodedByteCount() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + PipelineStatistics stats = pipeline_.GetStatistics(); + return stats.audio_bytes_decoded; +} + +unsigned WebMediaPlayerImpl::videoDecodedByteCount() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + PipelineStatistics stats = pipeline_.GetStatistics(); + return stats.video_bytes_decoded; +} + +bool WebMediaPlayerImpl::copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int level, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y) { + TRACE_EVENT0("media", "WebMediaPlayerImpl:copyVideoTextureToPlatformTexture"); + + scoped_refptr<VideoFrame> video_frame = + GetCurrentFrameFromCompositor(); + + if (!video_frame.get()) + return false; + if (video_frame->format() != VideoFrame::NATIVE_TEXTURE) + return false; + + const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder(); + if (mailbox_holder->texture_target != GL_TEXTURE_2D) + return false; + + web_graphics_context->waitSyncPoint(mailbox_holder->sync_point); + uint32 source_texture = web_graphics_context->createAndConsumeTextureCHROMIUM( + GL_TEXTURE_2D, mailbox_holder->mailbox.name); + + // The video is stored in a unmultiplied format, so premultiply + // if necessary. + web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, + premultiply_alpha); + // Application itself needs to take care of setting the right flip_y + // value down to get the expected result. + // flip_y==true means to reverse the video orientation while + // flip_y==false means to keep the intrinsic orientation. + web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, flip_y); + web_graphics_context->copyTextureCHROMIUM(GL_TEXTURE_2D, + source_texture, + texture, + level, + internal_format, + type); + web_graphics_context->pixelStorei(GL_UNPACK_FLIP_Y_CHROMIUM, false); + web_graphics_context->pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, + false); + + web_graphics_context->deleteTexture(source_texture); + web_graphics_context->flush(); + + SyncPointClientImpl client(web_graphics_context); + video_frame->UpdateReleaseSyncPoint(&client); + return true; +} + +WebMediaPlayer::MediaKeyException +WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system, + const unsigned char* init_data, + unsigned init_data_length) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return encrypted_media_support_->GenerateKeyRequest( + frame_, key_system, init_data, init_data_length); +} + +WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey( + const WebString& key_system, + const unsigned char* key, + unsigned key_length, + const unsigned char* init_data, + unsigned init_data_length, + const WebString& session_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return encrypted_media_support_->AddKey( + key_system, key, key_length, init_data, init_data_length, session_id); +} + +WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest( + const WebString& key_system, + const WebString& session_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return encrypted_media_support_->CancelKeyRequest(key_system, session_id); +} + +void WebMediaPlayerImpl::setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + encrypted_media_support_->SetContentDecryptionModule(cdm); +} + +void WebMediaPlayerImpl::setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + encrypted_media_support_->SetContentDecryptionModule(cdm, result); +} + +void WebMediaPlayerImpl::setContentDecryptionModuleSync( + blink::WebContentDecryptionModule* cdm) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + encrypted_media_support_->SetContentDecryptionModuleSync(cdm); +} + +void WebMediaPlayerImpl::OnPipelineSeeked(bool time_changed, + PipelineStatus status) { + DVLOG(1) << __FUNCTION__ << "(" << time_changed << ", " << status << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + seeking_ = false; + if (pending_seek_) { + pending_seek_ = false; + seek(pending_seek_seconds_); + return; + } + + if (status != PIPELINE_OK) { + OnPipelineError(status); + return; + } + + // Update our paused time. + if (paused_) + paused_time_ = pipeline_.GetMediaTime(); + + should_notify_time_changed_ = time_changed; +} + +void WebMediaPlayerImpl::OnPipelineEnded() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // Ignore state changes until we've completed all outstanding seeks. + if (seeking_ || pending_seek_) + return; + + ended_ = true; + client_->timeChanged(); +} + +void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(error, PIPELINE_OK); + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) { + // Any error that occurs before reaching ReadyStateHaveMetadata should + // be considered a format error. + SetNetworkState(WebMediaPlayer::NetworkStateFormatError); + return; + } + + SetNetworkState(PipelineErrorToNetworkState(error)); + + if (error == PIPELINE_ERROR_DECRYPT) + encrypted_media_support_->OnPipelineDecryptError(); +} + +void WebMediaPlayerImpl::OnPipelineMetadata( + PipelineMetadata metadata) { + DVLOG(1) << __FUNCTION__; + + pipeline_metadata_ = metadata; + + UMA_HISTOGRAM_ENUMERATION("Media.VideoRotation", + metadata.video_rotation, + VIDEO_ROTATION_MAX + 1); + SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); + + if (hasVideo()) { + DCHECK(!video_weblayer_); + scoped_refptr<cc::VideoLayer> layer = + cc::VideoLayer::Create(compositor_, pipeline_metadata_.video_rotation); + + if (pipeline_metadata_.video_rotation == VIDEO_ROTATION_90 || + pipeline_metadata_.video_rotation == VIDEO_ROTATION_270) { + gfx::Size size = pipeline_metadata_.natural_size; + pipeline_metadata_.natural_size = gfx::Size(size.height(), size.width()); + } + + video_weblayer_.reset(new cc_blink::WebLayerImpl(layer)); + video_weblayer_->setOpaque(opaque_); + client_->setWebLayer(video_weblayer_.get()); + } +} + +void WebMediaPlayerImpl::OnPipelineBufferingStateChanged( + BufferingState buffering_state) { + DVLOG(1) << __FUNCTION__ << "(" << buffering_state << ")"; + + // Ignore buffering state changes until we've completed all outstanding seeks. + if (seeking_ || pending_seek_) + return; + + // TODO(scherkus): Handle other buffering states when Pipeline starts using + // them and translate them ready state changes http://crbug.com/144683 + DCHECK_EQ(buffering_state, BUFFERING_HAVE_ENOUGH); + SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); + + // Blink expects a timeChanged() in response to a seek(). + if (should_notify_time_changed_) + client_->timeChanged(); +} + +void WebMediaPlayerImpl::OnDemuxerOpened() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + client_->mediaSourceOpened(new WebMediaSourceImpl( + chunk_demuxer_, base::Bind(&LogMediaSourceError, media_log_))); +} + +void WebMediaPlayerImpl::OnAddTextTrack( + const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + const WebInbandTextTrackImpl::Kind web_kind = + static_cast<WebInbandTextTrackImpl::Kind>(config.kind()); + const blink::WebString web_label = + blink::WebString::fromUTF8(config.label()); + const blink::WebString web_language = + blink::WebString::fromUTF8(config.language()); + const blink::WebString web_id = + blink::WebString::fromUTF8(config.id()); + + scoped_ptr<WebInbandTextTrackImpl> web_inband_text_track( + new WebInbandTextTrackImpl(web_kind, web_label, web_language, web_id, + text_track_index_++)); + + scoped_ptr<TextTrack> text_track(new TextTrackImpl( + main_task_runner_, client_, web_inband_text_track.Pass())); + + done_cb.Run(text_track.Pass()); +} + +void WebMediaPlayerImpl::DataSourceInitialized(bool success) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (!success) { + SetNetworkState(WebMediaPlayer::NetworkStateFormatError); + return; + } + + StartPipeline(); +} + +void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) { + if (!is_downloading && network_state_ == WebMediaPlayer::NetworkStateLoading) + SetNetworkState(WebMediaPlayer::NetworkStateIdle); + else if (is_downloading && network_state_ == WebMediaPlayer::NetworkStateIdle) + SetNetworkState(WebMediaPlayer::NetworkStateLoading); + media_log_->AddEvent( + media_log_->CreateBooleanEvent( + MediaLogEvent::NETWORK_ACTIVITY_SET, + "is_downloading_data", is_downloading)); +} + +// TODO(xhwang): Move this to a factory class so that we can create different +// renderers. +scoped_ptr<Renderer> WebMediaPlayerImpl::CreateRenderer() { + SetDecryptorReadyCB set_decryptor_ready_cb = + encrypted_media_support_->CreateSetDecryptorReadyCB(); + + // Create our audio decoders and renderer. + ScopedVector<AudioDecoder> audio_decoders; + + LogCB log_cb = base::Bind(&LogMediaSourceError, media_log_); + audio_decoders.push_back(new FFmpegAudioDecoder(media_task_runner_, log_cb)); + audio_decoders.push_back(new OpusAudioDecoder(media_task_runner_)); + + scoped_ptr<AudioRenderer> audio_renderer(new AudioRendererImpl( + media_task_runner_, + audio_source_provider_.get(), + audio_decoders.Pass(), + set_decryptor_ready_cb, + audio_hardware_config_)); + + // Create our video decoders and renderer. + ScopedVector<VideoDecoder> video_decoders; + + if (gpu_factories_.get()) { + video_decoders.push_back( + new GpuVideoDecoder(gpu_factories_, media_log_)); + } + +#if !defined(MEDIA_DISABLE_LIBVPX) + video_decoders.push_back(new VpxVideoDecoder(media_task_runner_)); +#endif // !defined(MEDIA_DISABLE_LIBVPX) + + video_decoders.push_back(new FFmpegVideoDecoder(media_task_runner_)); + + scoped_ptr<VideoRenderer> video_renderer( + new VideoRendererImpl( + media_task_runner_, + video_decoders.Pass(), + set_decryptor_ready_cb, + base::Bind(&WebMediaPlayerImpl::FrameReady, base::Unretained(this)), + true)); + + // Create renderer. + return scoped_ptr<Renderer>(new RendererImpl( + media_task_runner_, + demuxer_.get(), + audio_renderer.Pass(), + video_renderer.Pass())); +} + +void WebMediaPlayerImpl::StartPipeline() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // Keep track if this is a MSE or non-MSE playback. + UMA_HISTOGRAM_BOOLEAN("Media.MSE.Playback", + (load_type_ == LoadTypeMediaSource)); + + LogCB mse_log_cb; + Demuxer::NeedKeyCB need_key_cb = + encrypted_media_support_->CreateNeedKeyCB(); + + // Figure out which demuxer to use. + if (load_type_ != LoadTypeMediaSource) { + DCHECK(!chunk_demuxer_); + DCHECK(data_source_); + + demuxer_.reset(new FFmpegDemuxer( + media_task_runner_, data_source_.get(), + need_key_cb, + media_log_)); + } else { + DCHECK(!chunk_demuxer_); + DCHECK(!data_source_); + + mse_log_cb = base::Bind(&LogMediaSourceError, media_log_); + + chunk_demuxer_ = new ChunkDemuxer( + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDemuxerOpened), + need_key_cb, + mse_log_cb, + true); + demuxer_.reset(chunk_demuxer_); + } + + // ... and we're ready to go! + seeking_ = true; + pipeline_.Start( + demuxer_.get(), + CreateRenderer(), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError), + BIND_TO_RENDER_LOOP1(&WebMediaPlayerImpl::OnPipelineSeeked, false), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineMetadata), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineBufferingStateChanged), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged), + BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnAddTextTrack)); +} + +void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) { + DVLOG(1) << __FUNCTION__ << "(" << state << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + network_state_ = state; + // Always notify to ensure client has the latest value. + client_->networkStateChanged(); +} + +void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) { + DVLOG(1) << __FUNCTION__ << "(" << state << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (state == WebMediaPlayer::ReadyStateHaveEnoughData && data_source_ && + data_source_->assume_fully_buffered() && + network_state_ == WebMediaPlayer::NetworkStateLoading) + SetNetworkState(WebMediaPlayer::NetworkStateLoaded); + + ready_state_ = state; + // Always notify to ensure client has the latest value. + client_->readyStateChanged(); +} + +blink::WebAudioSourceProvider* WebMediaPlayerImpl::audioSourceProvider() { + return audio_source_provider_.get(); +} + +double WebMediaPlayerImpl::GetPipelineDuration() const { + base::TimeDelta duration = pipeline_.GetMediaDuration(); + + // Return positive infinity if the resource is unbounded. + // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-media-duration + if (duration == kInfiniteDuration()) + return std::numeric_limits<double>::infinity(); + + return duration.InSecondsF(); +} + +void WebMediaPlayerImpl::OnDurationChanged() { + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) + return; + + client_->durationChanged(); +} + +void WebMediaPlayerImpl::OnNaturalSizeChanged(gfx::Size size) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing); + TRACE_EVENT0("media", "WebMediaPlayerImpl::OnNaturalSizeChanged"); + + media_log_->AddEvent( + media_log_->CreateVideoSizeSetEvent(size.width(), size.height())); + pipeline_metadata_.natural_size = size; + + client_->sizeChanged(); +} + +void WebMediaPlayerImpl::OnOpacityChanged(bool opaque) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK_NE(ready_state_, WebMediaPlayer::ReadyStateHaveNothing); + + opaque_ = opaque; + if (video_weblayer_) + video_weblayer_->setOpaque(opaque_); +} + +void WebMediaPlayerImpl::FrameReady( + const scoped_refptr<VideoFrame>& frame) { + compositor_task_runner_->PostTask( + FROM_HERE, + base::Bind(&VideoFrameCompositor::UpdateCurrentFrame, + base::Unretained(compositor_), + frame)); +} + +static void GetCurrentFrameAndSignal( + VideoFrameCompositor* compositor, + scoped_refptr<VideoFrame>* video_frame_out, + base::WaitableEvent* event) { + TRACE_EVENT0("media", "GetCurrentFrameAndSignal"); + *video_frame_out = compositor->GetCurrentFrame(); + event->Signal(); +} + +scoped_refptr<VideoFrame> +WebMediaPlayerImpl::GetCurrentFrameFromCompositor() { + TRACE_EVENT0("media", "WebMediaPlayerImpl::GetCurrentFrameFromCompositor"); + if (compositor_task_runner_->BelongsToCurrentThread()) + return compositor_->GetCurrentFrame(); + + // Use a posted task and waitable event instead of a lock otherwise + // WebGL/Canvas can see different content than what the compositor is seeing. + scoped_refptr<VideoFrame> video_frame; + base::WaitableEvent event(false, false); + compositor_task_runner_->PostTask(FROM_HERE, + base::Bind(&GetCurrentFrameAndSignal, + base::Unretained(compositor_), + &video_frame, + &event)); + event.Wait(); + return video_frame; +} + +} // namespace media diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h new file mode 100644 index 0000000..bbdf080 --- /dev/null +++ b/media/blink/webmediaplayer_impl.h @@ -0,0 +1,319 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_ +#define MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "media/base/audio_renderer_sink.h" +#include "media/base/media_export.h" +// TODO(xhwang): Remove when we remove prefixed EME implementation. +#include "media/base/media_keys.h" +#include "media/base/pipeline.h" +#include "media/base/text_track.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/buffered_data_source_host_impl.h" +#include "media/blink/video_frame_compositor.h" +#include "media/filters/skcanvas_video_renderer.h" +#include "third_party/WebKit/public/platform/WebAudioSourceProvider.h" +#include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h" +#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h" +#include "third_party/WebKit/public/platform/WebMediaPlayer.h" +#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" +#include "url/gurl.h" + +namespace blink { +class WebLocalFrame; +} + +namespace base { +class SingleThreadTaskRunner; +} + +namespace cc_blink { +class WebLayerImpl; +} + +namespace media { +class AudioHardwareConfig; +class ChunkDemuxer; +class EncryptedMediaPlayerSupport; +class GpuVideoAcceleratorFactories; +class MediaLog; +class VideoFrameCompositor; +class WebAudioSourceProviderImpl; +class WebMediaPlayerDelegate; +class WebMediaPlayerParams; +class WebTextTrackImpl; + +// The canonical implementation of blink::WebMediaPlayer that's backed by +// Pipeline. Handles normal resource loading, Media Source, and +// Encrypted Media. +class MEDIA_EXPORT WebMediaPlayerImpl + : public NON_EXPORTED_BASE(blink::WebMediaPlayer), + public base::SupportsWeakPtr<WebMediaPlayerImpl> { + public: + // Constructs a WebMediaPlayer implementation using Chromium's media stack. + // |delegate| may be null. + WebMediaPlayerImpl(blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + base::WeakPtr<WebMediaPlayerDelegate> delegate, + const WebMediaPlayerParams& params); + virtual ~WebMediaPlayerImpl(); + + virtual void load(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode); + + // Playback controls. + virtual void play(); + virtual void pause(); + virtual bool supportsSave() const; + virtual void seek(double seconds); + virtual void setRate(double rate); + virtual void setVolume(double volume); + virtual void setPreload(blink::WebMediaPlayer::Preload preload); + virtual blink::WebTimeRanges buffered() const; + virtual double maxTimeSeekable() const; + + // Methods for painting. + virtual void paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha, + SkXfermode::Mode mode); + // TODO(dshwang): remove it because above method replaces. crbug.com/401027 + virtual void paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha); + + // True if the loaded media has a playable video/audio track. + virtual bool hasVideo() const; + virtual bool hasAudio() const; + + // Dimensions of the video. + virtual blink::WebSize naturalSize() const; + + // Getters of playback state. + virtual bool paused() const; + virtual bool seeking() const; + virtual double duration() const; + virtual double timelineOffset() const; + virtual double currentTime() const; + + // Internal states of loading and network. + // TODO(hclam): Ask the pipeline about the state rather than having reading + // them from members which would cause race conditions. + virtual blink::WebMediaPlayer::NetworkState networkState() const; + virtual blink::WebMediaPlayer::ReadyState readyState() const; + + virtual bool didLoadingProgress(); + + virtual bool hasSingleSecurityOrigin() const; + virtual bool didPassCORSAccessCheck() const; + + virtual double mediaTimeForTimeValue(double timeValue) const; + + virtual unsigned decodedFrameCount() const; + virtual unsigned droppedFrameCount() const; + virtual unsigned audioDecodedByteCount() const; + virtual unsigned videoDecodedByteCount() const; + + virtual bool copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int level, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y); + + virtual blink::WebAudioSourceProvider* audioSourceProvider(); + + virtual MediaKeyException generateKeyRequest( + const blink::WebString& key_system, + const unsigned char* init_data, + unsigned init_data_length); + + virtual MediaKeyException addKey(const blink::WebString& key_system, + const unsigned char* key, + unsigned key_length, + const unsigned char* init_data, + unsigned init_data_length, + const blink::WebString& session_id); + + virtual MediaKeyException cancelKeyRequest( + const blink::WebString& key_system, + const blink::WebString& session_id); + + // TODO(jrummell): Remove this method once Blink updated to use the other + // two methods. + virtual void setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm); + virtual void setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result); + virtual void setContentDecryptionModuleSync( + blink::WebContentDecryptionModule* cdm); + + void OnPipelineSeeked(bool time_changed, PipelineStatus status); + void OnPipelineEnded(); + void OnPipelineError(PipelineStatus error); + void OnPipelineMetadata(PipelineMetadata metadata); + void OnPipelineBufferingStateChanged(BufferingState buffering_state); + void OnDemuxerOpened(); + void OnAddTextTrack(const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb); + + private: + // Called after |defer_load_cb_| has decided to allow the load. If + // |defer_load_cb_| is null this is called immediately. + void DoLoad(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode); + + // Called after asynchronous initialization of a data source completed. + void DataSourceInitialized(bool success); + + // Called when the data source is downloading or paused. + void NotifyDownloading(bool is_downloading); + + // Creates a Renderer that will be used by the |pipeline_|. + scoped_ptr<Renderer> CreateRenderer(); + + // Finishes starting the pipeline due to a call to load(). + void StartPipeline(); + + // Helpers that set the network/ready state and notifies the client if + // they've changed. + void SetNetworkState(blink::WebMediaPlayer::NetworkState state); + void SetReadyState(blink::WebMediaPlayer::ReadyState state); + + // Gets the duration value reported by the pipeline. + double GetPipelineDuration() const; + + // Callbacks from |pipeline_| that are forwarded to |client_|. + void OnDurationChanged(); + void OnNaturalSizeChanged(gfx::Size size); + void OnOpacityChanged(bool opaque); + + // Called by VideoRendererImpl on its internal thread with the new frame to be + // painted. + void FrameReady(const scoped_refptr<VideoFrame>& frame); + + // Returns the current video frame from |compositor_|. Blocks until the + // compositor can return the frame. + scoped_refptr<VideoFrame> GetCurrentFrameFromCompositor(); + + blink::WebLocalFrame* frame_; + + // TODO(hclam): get rid of these members and read from the pipeline directly. + blink::WebMediaPlayer::NetworkState network_state_; + blink::WebMediaPlayer::ReadyState ready_state_; + + // Preload state for when |data_source_| is created after setPreload(). + BufferedDataSource::Preload preload_; + + // Task runner for posting tasks on Chrome's main thread. Also used + // for DCHECKs so methods calls won't execute in the wrong thread. + const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; + scoped_refptr<MediaLog> media_log_; + Pipeline pipeline_; + + // The LoadType passed in the |load_type| parameter of the load() call. + LoadType load_type_; + + // Cache of metadata for answering hasAudio(), hasVideo(), and naturalSize(). + PipelineMetadata pipeline_metadata_; + + // Whether the video is known to be opaque or not. + bool opaque_; + + // Playback state. + // + // TODO(scherkus): we have these because Pipeline favours the simplicity of a + // single "playback rate" over worrying about paused/stopped etc... It forces + // all clients to manage the pause+playback rate externally, but is that + // really a bad thing? + // + // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't want + // to hang the render thread during pause(), we record the time at the same + // time we pause and then return that value in currentTime(). Otherwise our + // clock can creep forward a little bit while the asynchronous + // SetPlaybackRate(0) is being executed. + bool paused_; + bool seeking_; + double playback_rate_; + base::TimeDelta paused_time_; + + // TODO(scherkus): Replace with an explicit ended signal to HTMLMediaElement, + // see http://crbug.com/409280 + bool ended_; + + // Seek gets pending if another seek is in progress. Only last pending seek + // will have effect. + bool pending_seek_; + double pending_seek_seconds_; + + // Tracks whether to issue time changed notifications during buffering state + // changes. + bool should_notify_time_changed_; + + blink::WebMediaPlayerClient* client_; + + base::WeakPtr<WebMediaPlayerDelegate> delegate_; + + base::Callback<void(const base::Closure&)> defer_load_cb_; + + // Factories for supporting video accelerators. May be null. + scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_; + + // Routes audio playback to either AudioRendererSink or WebAudio. + scoped_refptr<WebAudioSourceProviderImpl> audio_source_provider_; + + bool supports_save_; + + // These two are mutually exclusive: + // |data_source_| is used for regular resource loads. + // |chunk_demuxer_| is used for Media Source resource loads. + // + // |demuxer_| will contain the appropriate demuxer based on which resource + // load strategy we're using. + scoped_ptr<BufferedDataSource> data_source_; + scoped_ptr<Demuxer> demuxer_; + ChunkDemuxer* chunk_demuxer_; + + BufferedDataSourceHostImpl buffered_data_source_host_; + + // Video rendering members. + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; + VideoFrameCompositor* compositor_; // Deleted on |compositor_task_runner_|. + SkCanvasVideoRenderer skcanvas_video_renderer_; + + // The compositor layer for displaying the video content when using composited + // playback. + scoped_ptr<cc_blink::WebLayerImpl> video_weblayer_; + + // Text track objects get a unique index value when they're created. + int text_track_index_; + + scoped_ptr<EncryptedMediaPlayerSupport> encrypted_media_support_; + + const AudioHardwareConfig& audio_hardware_config_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBMEDIAPLAYER_IMPL_H_ diff --git a/media/blink/webmediaplayer_params.cc b/media/blink/webmediaplayer_params.cc new file mode 100644 index 0000000..c157e34 --- /dev/null +++ b/media/blink/webmediaplayer_params.cc @@ -0,0 +1,44 @@ +// 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 "media/blink/webmediaplayer_params.h" + +#include "base/single_thread_task_runner.h" +#include "media/base/audio_renderer_sink.h" +#include "media/base/media_log.h" +#include "media/filters/gpu_video_accelerator_factories.h" + +namespace media { + +WebMediaPlayerParams::WebMediaPlayerParams( + const base::Callback<void(const base::Closure&)>& defer_load_cb, + const scoped_refptr<AudioRendererSink>& audio_renderer_sink, + const AudioHardwareConfig& audio_hardware_config, + const scoped_refptr<MediaLog>& media_log, + const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories, + const scoped_refptr<base::SingleThreadTaskRunner>& + media_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + compositor_task_runner, + const EncryptedMediaPlayerSupportCreateCB& + encrypted_media_player_support_cb) + : defer_load_cb_(defer_load_cb), + audio_renderer_sink_(audio_renderer_sink), + audio_hardware_config_(audio_hardware_config), + media_log_(media_log), + gpu_factories_(gpu_factories), + media_task_runner_(media_task_runner), + compositor_task_runner_(compositor_task_runner), + encrypted_media_player_support_cb_(encrypted_media_player_support_cb) { +} + +WebMediaPlayerParams::~WebMediaPlayerParams() {} + +scoped_ptr<EncryptedMediaPlayerSupport> +WebMediaPlayerParams::CreateEncryptedMediaPlayerSupport( + blink::WebMediaPlayerClient* client) const { + return encrypted_media_player_support_cb_.Run(client); +} + +} // namespace media diff --git a/media/blink/webmediaplayer_params.h b/media/blink/webmediaplayer_params.h new file mode 100644 index 0000000..5cdf95e --- /dev/null +++ b/media/blink/webmediaplayer_params.h @@ -0,0 +1,102 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_ +#define MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "media/base/media_export.h" +#include "media/blink/encrypted_media_player_support.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace blink { +class WebMediaPlayerClient; +} + +namespace media { +class AudioHardwareConfig; +class AudioRendererSink; +class GpuVideoAcceleratorFactories; +class MediaLog; + +// Holds parameters for constructing WebMediaPlayerImpl without having +// to plumb arguments through various abstraction layers. +class MEDIA_EXPORT WebMediaPlayerParams { + public: + // Callback used to create EncryptedMediaPlayerSupport instances. This + // callback must always return a valid EncryptedMediaPlayerSupport object. + typedef base::Callback<scoped_ptr<EncryptedMediaPlayerSupport>( + blink::WebMediaPlayerClient*)> EncryptedMediaPlayerSupportCreateCB; + + // |defer_load_cb|, |audio_renderer_sink|, and |compositor_task_runner| may be + // null. + WebMediaPlayerParams( + const base::Callback<void(const base::Closure&)>& defer_load_cb, + const scoped_refptr<AudioRendererSink>& audio_renderer_sink, + const AudioHardwareConfig& audio_hardware_config, + const scoped_refptr<MediaLog>& media_log, + const scoped_refptr<GpuVideoAcceleratorFactories>& gpu_factories, + const scoped_refptr<base::SingleThreadTaskRunner>& + media_task_runner, + const scoped_refptr<base::SingleThreadTaskRunner>& + compositor_task_runner, + const EncryptedMediaPlayerSupportCreateCB& + encrypted_media_player_support_cb); + + ~WebMediaPlayerParams(); + + base::Callback<void(const base::Closure&)> defer_load_cb() const { + return defer_load_cb_; + } + + const scoped_refptr<AudioRendererSink>& audio_renderer_sink() const { + return audio_renderer_sink_; + } + + const AudioHardwareConfig& audio_hardware_config() const { + return audio_hardware_config_; + } + + const scoped_refptr<MediaLog>& media_log() const { + return media_log_; + } + + const scoped_refptr<GpuVideoAcceleratorFactories>& + gpu_factories() const { + return gpu_factories_; + } + + const scoped_refptr<base::SingleThreadTaskRunner>& + media_task_runner() const { + return media_task_runner_; + } + + const scoped_refptr<base::SingleThreadTaskRunner>& + compositor_task_runner() const { + return compositor_task_runner_; + } + + scoped_ptr<EncryptedMediaPlayerSupport> + CreateEncryptedMediaPlayerSupport(blink::WebMediaPlayerClient* client) const; + + private: + base::Callback<void(const base::Closure&)> defer_load_cb_; + scoped_refptr<AudioRendererSink> audio_renderer_sink_; + const AudioHardwareConfig& audio_hardware_config_; + scoped_refptr<MediaLog> media_log_; + scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_; + scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_; + scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_; + EncryptedMediaPlayerSupportCreateCB encrypted_media_player_support_cb_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebMediaPlayerParams); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBMEDIAPLAYER_PARAMS_H_ diff --git a/media/blink/webmediaplayer_util.cc b/media/blink/webmediaplayer_util.cc new file mode 100644 index 0000000..bcaf135 --- /dev/null +++ b/media/blink/webmediaplayer_util.cc @@ -0,0 +1,120 @@ +// 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 "media/blink/webmediaplayer_util.h" + +#include <math.h> + +#include "base/metrics/histogram.h" +#include "media/base/media_keys.h" +#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" + +namespace media { + +// Compile asserts shared by all platforms. + +#define COMPILE_ASSERT_MATCHING_ENUM(name) \ + COMPILE_ASSERT( \ + static_cast<int>(blink::WebMediaPlayerClient::MediaKeyErrorCode ## name) == \ + static_cast<int>(MediaKeys::k ## name ## Error), \ + mismatching_enums) +COMPILE_ASSERT_MATCHING_ENUM(Unknown); +COMPILE_ASSERT_MATCHING_ENUM(Client); +#undef COMPILE_ASSERT_MATCHING_ENUM + +base::TimeDelta ConvertSecondsToTimestamp(double seconds) { + double microseconds = seconds * base::Time::kMicrosecondsPerSecond; + return base::TimeDelta::FromMicroseconds( + microseconds > 0 ? microseconds + 0.5 : ceil(microseconds - 0.5)); +} + +blink::WebTimeRanges ConvertToWebTimeRanges( + const Ranges<base::TimeDelta>& ranges) { + blink::WebTimeRanges result(ranges.size()); + for (size_t i = 0; i < ranges.size(); ++i) { + result[i].start = ranges.start(i).InSecondsF(); + result[i].end = ranges.end(i).InSecondsF(); + } + return result; +} + +blink::WebMediaPlayer::NetworkState PipelineErrorToNetworkState( + PipelineStatus error) { + DCHECK_NE(error, PIPELINE_OK); + + switch (error) { + case PIPELINE_ERROR_NETWORK: + case PIPELINE_ERROR_READ: + return blink::WebMediaPlayer::NetworkStateNetworkError; + + // TODO(vrk): Because OnPipelineInitialize() directly reports the + // NetworkStateFormatError instead of calling OnPipelineError(), I believe + // this block can be deleted. Should look into it! (crbug.com/126070) + case PIPELINE_ERROR_INITIALIZATION_FAILED: + case PIPELINE_ERROR_COULD_NOT_RENDER: + case PIPELINE_ERROR_URL_NOT_FOUND: + case DEMUXER_ERROR_COULD_NOT_OPEN: + case DEMUXER_ERROR_COULD_NOT_PARSE: + case DEMUXER_ERROR_NO_SUPPORTED_STREAMS: + case DECODER_ERROR_NOT_SUPPORTED: + return blink::WebMediaPlayer::NetworkStateFormatError; + + case PIPELINE_ERROR_DECODE: + case PIPELINE_ERROR_ABORT: + case PIPELINE_ERROR_OPERATION_PENDING: + case PIPELINE_ERROR_INVALID_STATE: + return blink::WebMediaPlayer::NetworkStateDecodeError; + + case PIPELINE_ERROR_DECRYPT: + // TODO(xhwang): Change to use NetworkStateDecryptError once it's added in + // Webkit (see http://crbug.com/124486). + return blink::WebMediaPlayer::NetworkStateDecodeError; + + case PIPELINE_OK: + NOTREACHED() << "Unexpected status! " << error; + } + return blink::WebMediaPlayer::NetworkStateFormatError; +} + +namespace { + +// Helper enum for reporting scheme histograms. +enum URLSchemeForHistogram { + kUnknownURLScheme, + kMissingURLScheme, + kHttpURLScheme, + kHttpsURLScheme, + kFtpURLScheme, + kChromeExtensionURLScheme, + kJavascriptURLScheme, + kFileURLScheme, + kBlobURLScheme, + kDataURLScheme, + kFileSystemScheme, + kMaxURLScheme = kFileSystemScheme // Must be equal to highest enum value. +}; + +URLSchemeForHistogram URLScheme(const GURL& url) { + if (!url.has_scheme()) return kMissingURLScheme; + if (url.SchemeIs("http")) return kHttpURLScheme; + if (url.SchemeIs("https")) return kHttpsURLScheme; + if (url.SchemeIs("ftp")) return kFtpURLScheme; + if (url.SchemeIs("chrome-extension")) return kChromeExtensionURLScheme; + if (url.SchemeIs("javascript")) return kJavascriptURLScheme; + if (url.SchemeIs("file")) return kFileURLScheme; + if (url.SchemeIs("blob")) return kBlobURLScheme; + if (url.SchemeIs("data")) return kDataURLScheme; + if (url.SchemeIs("filesystem")) return kFileSystemScheme; + + return kUnknownURLScheme; +} + +} // namespace + +void ReportMediaSchemeUma(const GURL& url) { + UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), + kMaxURLScheme + 1); +} + +} // namespace media diff --git a/media/blink/webmediaplayer_util.h b/media/blink/webmediaplayer_util.h new file mode 100644 index 0000000..159be77 --- /dev/null +++ b/media/blink/webmediaplayer_util.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_ +#define MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_ + +#include "base/time/time.h" +#include "media/base/media_export.h" +#include "media/base/pipeline_status.h" +#include "media/base/ranges.h" +#include "third_party/WebKit/public/platform/WebMediaPlayer.h" +#include "third_party/WebKit/public/platform/WebTimeRange.h" +#include "url/gurl.h" + +namespace media { + +// Platform independent method for converting and rounding floating point +// seconds to an int64 timestamp. +// +// Refer to https://bugs.webkit.org/show_bug.cgi?id=52697 for details. +base::TimeDelta MEDIA_EXPORT ConvertSecondsToTimestamp(double seconds); + +blink::WebTimeRanges MEDIA_EXPORT ConvertToWebTimeRanges( + const Ranges<base::TimeDelta>& ranges); + +blink::WebMediaPlayer::NetworkState MEDIA_EXPORT PipelineErrorToNetworkState( + PipelineStatus error); + +// Report the scheme of Media URIs. +void MEDIA_EXPORT ReportMediaSchemeUma(const GURL& url); + +} // namespace media + +#endif // MEDIA_BLINK_WEBMEDIAPLAYER_UTIL_H_ diff --git a/media/blink/webmediasource_impl.cc b/media/blink/webmediasource_impl.cc new file mode 100644 index 0000000..aecad6f --- /dev/null +++ b/media/blink/webmediasource_impl.cc @@ -0,0 +1,86 @@ +// 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 "media/blink/webmediasource_impl.h" + +#include "base/guid.h" +#include "media/blink/websourcebuffer_impl.h" +#include "media/filters/chunk_demuxer.h" +#include "third_party/WebKit/public/platform/WebCString.h" +#include "third_party/WebKit/public/platform/WebString.h" + +using ::blink::WebString; +using ::blink::WebMediaSource; + +namespace media { + +#define COMPILE_ASSERT_MATCHING_STATUS_ENUM(webkit_name, chromium_name) \ + COMPILE_ASSERT(static_cast<int>(WebMediaSource::webkit_name) == \ + static_cast<int>(ChunkDemuxer::chromium_name), \ + mismatching_status_enums) +COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusOk, kOk); +COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusNotSupported, kNotSupported); +COMPILE_ASSERT_MATCHING_STATUS_ENUM(AddStatusReachedIdLimit, kReachedIdLimit); +#undef COMPILE_ASSERT_MATCHING_STATUS_ENUM + +WebMediaSourceImpl::WebMediaSourceImpl( + ChunkDemuxer* demuxer, LogCB log_cb) + : demuxer_(demuxer), + log_cb_(log_cb) { + DCHECK(demuxer_); +} + +WebMediaSourceImpl::~WebMediaSourceImpl() {} + +WebMediaSource::AddStatus WebMediaSourceImpl::addSourceBuffer( + const blink::WebString& type, + const blink::WebVector<blink::WebString>& codecs, + blink::WebSourceBuffer** source_buffer) { + std::string id = base::GenerateGUID(); + std::vector<std::string> new_codecs(codecs.size()); + for (size_t i = 0; i < codecs.size(); ++i) + new_codecs[i] = codecs[i].utf8().data(); + + WebMediaSource::AddStatus result = + static_cast<WebMediaSource::AddStatus>( + demuxer_->AddId(id, type.utf8().data(), new_codecs)); + + if (result == WebMediaSource::AddStatusOk) + *source_buffer = new WebSourceBufferImpl(id, demuxer_); + + return result; +} + +double WebMediaSourceImpl::duration() { + return demuxer_->GetDuration(); +} + +void WebMediaSourceImpl::setDuration(double new_duration) { + DCHECK_GE(new_duration, 0); + demuxer_->SetDuration(new_duration); +} + +void WebMediaSourceImpl::markEndOfStream( + WebMediaSource::EndOfStreamStatus status) { + PipelineStatus pipeline_status = PIPELINE_OK; + + switch (status) { + case WebMediaSource::EndOfStreamStatusNoError: + break; + case WebMediaSource::EndOfStreamStatusNetworkError: + pipeline_status = PIPELINE_ERROR_NETWORK; + break; + case WebMediaSource::EndOfStreamStatusDecodeError: + pipeline_status = PIPELINE_ERROR_DECODE; + break; + } + + demuxer_->MarkEndOfStream(pipeline_status); +} + +void WebMediaSourceImpl::unmarkEndOfStream() { + demuxer_->UnmarkEndOfStream(); +} + +} // namespace media diff --git a/media/blink/webmediasource_impl.h b/media/blink/webmediasource_impl.h new file mode 100644 index 0000000..ac442f0 --- /dev/null +++ b/media/blink/webmediasource_impl.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_ +#define MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_ + +#include <string> +#include <vector> + +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "third_party/WebKit/public/platform/WebMediaSource.h" + +namespace media { +class ChunkDemuxer; + +class MEDIA_EXPORT WebMediaSourceImpl + : NON_EXPORTED_BASE(public blink::WebMediaSource) { + public: + WebMediaSourceImpl(ChunkDemuxer* demuxer, LogCB log_cb); + virtual ~WebMediaSourceImpl(); + + // blink::WebMediaSource implementation. + virtual AddStatus addSourceBuffer( + const blink::WebString& type, + const blink::WebVector<blink::WebString>& codecs, + blink::WebSourceBuffer** source_buffer); + virtual double duration(); + virtual void setDuration(double duration); + virtual void markEndOfStream(EndOfStreamStatus status); + virtual void unmarkEndOfStream(); + + private: + ChunkDemuxer* demuxer_; // Owned by WebMediaPlayerImpl. + LogCB log_cb_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaSourceImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBMEDIASOURCE_IMPL_H_ diff --git a/media/blink/websourcebuffer_impl.cc b/media/blink/websourcebuffer_impl.cc new file mode 100644 index 0000000..c87c2b9 --- /dev/null +++ b/media/blink/websourcebuffer_impl.cc @@ -0,0 +1,134 @@ +// 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 "media/blink/websourcebuffer_impl.h" + +#include <limits> + +#include "base/float_util.h" +#include "media/filters/chunk_demuxer.h" + +namespace media { + +static base::TimeDelta DoubleToTimeDelta(double time) { + DCHECK(!base::IsNaN(time)); + DCHECK_NE(time, -std::numeric_limits<double>::infinity()); + + if (time == std::numeric_limits<double>::infinity()) + return kInfiniteDuration(); + + // Don't use base::TimeDelta::Max() here, as we want the largest finite time + // delta. + base::TimeDelta max_time = base::TimeDelta::FromInternalValue(kint64max - 1); + double max_time_in_seconds = max_time.InSecondsF(); + + if (time >= max_time_in_seconds) + return max_time; + + return base::TimeDelta::FromMicroseconds( + time * base::Time::kMicrosecondsPerSecond); +} + +WebSourceBufferImpl::WebSourceBufferImpl( + const std::string& id, ChunkDemuxer* demuxer) + : id_(id), + demuxer_(demuxer), + append_window_end_(kInfiniteDuration()) { + DCHECK(demuxer_); +} + +WebSourceBufferImpl::~WebSourceBufferImpl() { + DCHECK(!demuxer_) << "Object destroyed w/o removedFromMediaSource() call"; +} + +bool WebSourceBufferImpl::setMode(WebSourceBuffer::AppendMode mode) { + if (demuxer_->IsParsingMediaSegment(id_)) + return false; + + switch (mode) { + case WebSourceBuffer::AppendModeSegments: + demuxer_->SetSequenceMode(id_, false); + return true; + case WebSourceBuffer::AppendModeSequence: + demuxer_->SetSequenceMode(id_, true); + return true; + } + + NOTREACHED(); + return false; +} + +blink::WebTimeRanges WebSourceBufferImpl::buffered() { + Ranges<base::TimeDelta> ranges = demuxer_->GetBufferedRanges(id_); + blink::WebTimeRanges result(ranges.size()); + for (size_t i = 0; i < ranges.size(); i++) { + result[i].start = ranges.start(i).InSecondsF(); + result[i].end = ranges.end(i).InSecondsF(); + } + return result; +} + +void WebSourceBufferImpl::append( + const unsigned char* data, + unsigned length, + double* timestamp_offset) { + base::TimeDelta old_offset = timestamp_offset_; + demuxer_->AppendData(id_, data, length, + append_window_start_, append_window_end_, + ×tamp_offset_); + + // Coded frame processing may update the timestamp offset. If the caller + // provides a non-NULL |timestamp_offset| and frame processing changes the + // timestamp offset, report the new offset to the caller. Do not update the + // caller's offset otherwise, to preserve any pre-existing value that may have + // more than microsecond precision. + if (timestamp_offset && old_offset != timestamp_offset_) + *timestamp_offset = timestamp_offset_.InSecondsF(); +} + +void WebSourceBufferImpl::abort() { + demuxer_->Abort(id_, + append_window_start_, append_window_end_, + ×tamp_offset_); + + // TODO(wolenetz): abort should be able to modify the caller timestamp offset + // (just like WebSourceBufferImpl::append). + // See http://crbug.com/370229 for further details. +} + +void WebSourceBufferImpl::remove(double start, double end) { + DCHECK_GE(start, 0); + DCHECK_GE(end, 0); + demuxer_->Remove(id_, DoubleToTimeDelta(start), DoubleToTimeDelta(end)); +} + +bool WebSourceBufferImpl::setTimestampOffset(double offset) { + if (demuxer_->IsParsingMediaSegment(id_)) + return false; + + timestamp_offset_ = DoubleToTimeDelta(offset); + + // http://www.w3.org/TR/media-source/#widl-SourceBuffer-timestampOffset + // Step 6: If the mode attribute equals "sequence", then set the group start + // timestamp to new timestamp offset. + demuxer_->SetGroupStartTimestampIfInSequenceMode(id_, timestamp_offset_); + return true; +} + +void WebSourceBufferImpl::setAppendWindowStart(double start) { + DCHECK_GE(start, 0); + append_window_start_ = DoubleToTimeDelta(start); +} + +void WebSourceBufferImpl::setAppendWindowEnd(double end) { + DCHECK_GE(end, 0); + append_window_end_ = DoubleToTimeDelta(end); +} + +void WebSourceBufferImpl::removedFromMediaSource() { + demuxer_->RemoveId(id_); + demuxer_ = NULL; +} + +} // namespace media diff --git a/media/blink/websourcebuffer_impl.h b/media/blink/websourcebuffer_impl.h new file mode 100644 index 0000000..e938178 --- /dev/null +++ b/media/blink/websourcebuffer_impl.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_ +#define MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/time/time.h" +#include "third_party/WebKit/public/platform/WebSourceBuffer.h" + +namespace media { +class ChunkDemuxer; + +class WebSourceBufferImpl : public blink::WebSourceBuffer { + public: + WebSourceBufferImpl(const std::string& id, ChunkDemuxer* demuxer); + virtual ~WebSourceBufferImpl(); + + // blink::WebSourceBuffer implementation. + virtual bool setMode(AppendMode mode); + virtual blink::WebTimeRanges buffered(); + virtual void append( + const unsigned char* data, + unsigned length, + double* timestamp_offset); + virtual void abort(); + virtual void remove(double start, double end); + virtual bool setTimestampOffset(double offset); + virtual void setAppendWindowStart(double start); + virtual void setAppendWindowEnd(double end); + virtual void removedFromMediaSource(); + + private: + std::string id_; + ChunkDemuxer* demuxer_; // Owned by WebMediaPlayerImpl. + + // Controls the offset applied to timestamps when processing appended media + // segments. It is initially 0, which indicates that no offset is being + // applied. Both setTimestampOffset() and append() may update this value. + base::TimeDelta timestamp_offset_; + + base::TimeDelta append_window_start_; + base::TimeDelta append_window_end_; + + DISALLOW_COPY_AND_ASSIGN(WebSourceBufferImpl); +}; + +} // namespace media + +#endif // MEDIA_BLINK_WEBSOURCEBUFFER_IMPL_H_ |