diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-17 05:18:16 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-17 05:18:16 +0000 |
commit | a9288f57632869274ef505d86dc020ef2f3f9311 (patch) | |
tree | 7daef2b18772b5362eeea3b3b8d55881c5eb97d1 /webkit/media | |
parent | 084b6bb674002dddd9839248c3cc6b7d6c6dc4c7 (diff) | |
download | chromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.zip chromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.tar.gz chromium_src-a9288f57632869274ef505d86dc020ef2f3f9311.tar.bz2 |
Moving media-related files from webkit/glue/ to webkit/media/.
Files under webkit/media/ are now under their very own webkit_media namespace.
Review URL: http://codereview.chromium.org/8570010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110438 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/media')
28 files changed, 7271 insertions, 0 deletions
diff --git a/webkit/media/OWNERS b/webkit/media/OWNERS new file mode 100644 index 0000000..21a33d5 --- /dev/null +++ b/webkit/media/OWNERS @@ -0,0 +1 @@ +scherkus@chromium.org diff --git a/webkit/media/audio_decoder.cc b/webkit/media/audio_decoder.cc new file mode 100644 index 0000000..f6b79ed --- /dev/null +++ b/webkit/media/audio_decoder.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2011 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 "webkit/media/audio_decoder.h" + +#include <vector> +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/time.h" +#include "media/base/limits.h" +#include "media/filters/audio_file_reader.h" +#include "media/filters/in_memory_url_protocol.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAudioBus.h" + +using media::AudioFileReader; +using media::InMemoryUrlProtocol; +using std::vector; +using WebKit::WebAudioBus; + +namespace webkit_media { + +// Decode in-memory audio file data. +bool DecodeAudioFileData( + WebKit::WebAudioBus* destination_bus, + const char* data, size_t data_size, double sample_rate) { + DCHECK(destination_bus); + if (!destination_bus) + return false; + + // Uses the FFmpeg library for audio file reading. + InMemoryUrlProtocol url_protocol(reinterpret_cast<const uint8*>(data), + data_size, false); + AudioFileReader reader(&url_protocol); + + if (!reader.Open()) + return false; + + size_t number_of_channels = reader.channels(); + double file_sample_rate = reader.sample_rate(); + double duration = reader.duration().InSecondsF(); + size_t number_of_frames = static_cast<size_t>(reader.number_of_frames()); + + // Apply sanity checks to make sure crazy values aren't coming out of + // FFmpeg. + if (!number_of_channels || + number_of_channels > static_cast<size_t>(media::Limits::kMaxChannels) || + file_sample_rate < media::Limits::kMinSampleRate || + file_sample_rate > media::Limits::kMaxSampleRate) + return false; + + // TODO(crogers) : do sample-rate conversion with FFmpeg. + // For now, we're ignoring the requested 'sample_rate' and returning + // the WebAudioBus at the file's sample-rate. + // double destination_sample_rate = + // (sample_rate != 0.0) ? sample_rate : file_sample_rate; + double destination_sample_rate = file_sample_rate; + + DLOG(INFO) << "Decoding file data -" + << " data: " << data + << " data size: " << data_size + << " duration: " << duration + << " number of frames: " << number_of_frames + << " sample rate: " << file_sample_rate + << " number of channels: " << number_of_channels; + + // Change to destination sample-rate. + number_of_frames = static_cast<size_t>(number_of_frames * + (destination_sample_rate / file_sample_rate)); + + // Allocate and configure the output audio channel data. + destination_bus->initialize(number_of_channels, + number_of_frames, + destination_sample_rate); + + // Wrap the channel pointers which will receive the decoded PCM audio. + vector<float*> audio_data; + audio_data.reserve(number_of_channels); + for (size_t i = 0; i < number_of_channels; ++i) { + audio_data.push_back(destination_bus->channelData(i)); + } + + // Decode the audio file data. + return reader.Read(audio_data, number_of_frames); +} + +} // namespace webkit_media diff --git a/webkit/media/audio_decoder.h b/webkit/media/audio_decoder.h new file mode 100644 index 0000000..aae5581d --- /dev/null +++ b/webkit/media/audio_decoder.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_AUDIO_DECODER_H_ +#define WEBKIT_MEDIA_AUDIO_DECODER_H_ + +#include "base/basictypes.h" + +namespace WebKit { class WebAudioBus; } + +namespace webkit_media { + +// Decode in-memory audio file data. +bool DecodeAudioFileData(WebKit::WebAudioBus* destination_bus, const char* data, + size_t data_size, double sample_rate); + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_AUDIO_DECODER_H_ diff --git a/webkit/media/buffered_data_source.cc b/webkit/media/buffered_data_source.cc new file mode 100644 index 0000000..01d4675 --- /dev/null +++ b/webkit/media/buffered_data_source.cc @@ -0,0 +1,672 @@ +// Copyright (c) 2011 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 "webkit/media/buffered_data_source.h" + +#include "base/bind.h" +#include "media/base/filter_host.h" +#include "media/base/media_log.h" +#include "net/base/net_errors.h" +#include "webkit/media/web_data_source_factory.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebFrame; + +namespace webkit_media { + +// 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. +static const int kInitialReadBufferSize = 32768; + +// Number of cache misses we allow for a single Read() before signalling an +// error. +static const int kNumCacheMissRetries = 3; + +static WebDataSource* NewBufferedDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log) { + return new BufferedDataSource(render_loop, frame, media_log); +} + +// static +media::DataSourceFactory* BufferedDataSource::CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log, + const WebDataSourceBuildObserverHack& build_observer) { + return new WebDataSourceFactory(render_loop, frame, media_log, + &NewBufferedDataSource, build_observer); +} + +BufferedDataSource::BufferedDataSource( + MessageLoop* render_loop, + WebFrame* frame, + media::MediaLog* media_log) + : total_bytes_(kPositionNotSpecified), + buffered_bytes_(0), + loaded_(false), + streaming_(false), + frame_(frame), + loader_(NULL), + is_downloading_data_(false), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + intermediate_read_buffer_(new uint8[kInitialReadBufferSize]), + intermediate_read_buffer_size_(kInitialReadBufferSize), + render_loop_(render_loop), + stop_signal_received_(false), + stopped_on_render_loop_(false), + media_is_paused_(true), + media_has_played_(false), + preload_(media::AUTO), + using_range_request_(true), + cache_miss_retries_left_(kNumCacheMissRetries), + bitrate_(0), + playback_rate_(0.0), + media_log_(media_log) { +} + +BufferedDataSource::~BufferedDataSource() {} + +// A factory method to create BufferedResourceLoader using the read parameters. +// This method can be overrided to inject mock BufferedResourceLoader object +// for testing purpose. +BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position) { + DCHECK(MessageLoop::current() == render_loop_); + + return new BufferedResourceLoader(url_, + first_byte_position, + last_byte_position, + ChooseDeferStrategy(), + bitrate_, + playback_rate_, + media_log_); +} + +void BufferedDataSource::set_host(media::FilterHost* host) { + DataSource::set_host(host); + + if (loader_.get()) { + base::AutoLock auto_lock(lock_); + UpdateHostState_Locked(); + } +} + +void BufferedDataSource::Initialize(const std::string& url, + const media::PipelineStatusCB& callback) { + // Saves the url. + url_ = GURL(url); + + // This data source doesn't support data:// protocol so reject it. + if (url_.SchemeIs(kDataScheme)) { + callback.Run(media::DATASOURCE_ERROR_URL_NOT_SUPPORTED); + return; + } else if (!webkit_glue::IsProtocolSupportedForMedia(url_)) { + callback.Run(media::PIPELINE_ERROR_NETWORK); + return; + } + + DCHECK(!callback.is_null()); + { + base::AutoLock auto_lock(lock_); + initialize_cb_ = callback; + } + + // Post a task to complete the initialization task. + render_loop_->PostTask(FROM_HERE, + base::Bind(&BufferedDataSource::InitializeTask, this)); +} + +void BufferedDataSource::CancelInitialize() { + base::AutoLock auto_lock(lock_); + DCHECK(!initialize_cb_.is_null()); + + initialize_cb_.Reset(); + + render_loop_->PostTask( + FROM_HERE, base::Bind(&BufferedDataSource::CleanupTask, this)); +} + +///////////////////////////////////////////////////////////////////////////// +// media::Filter implementation. +void BufferedDataSource::Stop(const base::Closure& callback) { + { + base::AutoLock auto_lock(lock_); + stop_signal_received_ = true; + } + if (!callback.is_null()) + callback.Run(); + + render_loop_->PostTask(FROM_HERE, + base::Bind(&BufferedDataSource::CleanupTask, this)); +} + +void BufferedDataSource::SetPlaybackRate(float playback_rate) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &BufferedDataSource::SetPlaybackRateTask, this, playback_rate)); +} + +void BufferedDataSource::SetPreload(media::Preload preload) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &BufferedDataSource::SetPreloadTask, this, preload)); +} + +void BufferedDataSource::SetBitrate(int bitrate) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &BufferedDataSource::SetBitrateTask, this, bitrate)); +} + +///////////////////////////////////////////////////////////////////////////// +// media::DataSource implementation. +void BufferedDataSource::Read( + int64 position, size_t size, uint8* data, + const media::DataSource::ReadCallback& read_callback) { + VLOG(1) << "Read: " << position << " offset, " << size << " bytes"; + DCHECK(!read_callback.is_null()); + + { + base::AutoLock auto_lock(lock_); + DCHECK(read_callback_.is_null()); + + if (stop_signal_received_ || stopped_on_render_loop_) { + read_callback.Run(kReadError); + return; + } + + read_callback_ = read_callback; + } + + render_loop_->PostTask(FROM_HERE, base::Bind( + &BufferedDataSource::ReadTask, this, + position, static_cast<int>(size), data)); +} + +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_; +} + +bool BufferedDataSource::HasSingleOrigin() { + DCHECK(MessageLoop::current() == render_loop_); + return loader_.get() ? loader_->HasSingleOrigin() : true; +} + +void BufferedDataSource::Abort() { + DCHECK(MessageLoop::current() == render_loop_); + + CleanupTask(); + frame_ = NULL; +} + +///////////////////////////////////////////////////////////////////////////// +// Render thread tasks. +void BufferedDataSource::InitializeTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!loader_.get()); + + { + base::AutoLock auto_lock(lock_); + if (stopped_on_render_loop_ || initialize_cb_.is_null() || + stop_signal_received_) { + return; + } + } + + if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { + // Do an unbounded range request starting at the beginning. If the server + // responds with 200 instead of 206 we'll fall back into a streaming mode. + loader_ = CreateResourceLoader(0, kPositionNotSpecified); + loader_->Start( + NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), + base::Bind(&BufferedDataSource::NetworkEventCallback, this), + frame_); + } 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_ = CreateResourceLoader(kPositionNotSpecified, + kPositionNotSpecified); + loader_->Start( + NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback), + base::Bind(&BufferedDataSource::NetworkEventCallback, this), + frame_); + } +} + +void BufferedDataSource::ReadTask( + int64 position, + int read_size, + uint8* buffer) { + DCHECK(MessageLoop::current() == render_loop_); + { + base::AutoLock auto_lock(lock_); + if (stopped_on_render_loop_) + return; + + DCHECK(!read_callback_.is_null()); + } + + // Saves the read parameters. + read_position_ = position; + read_size_ = read_size; + read_buffer_ = buffer; + cache_miss_retries_left_ = kNumCacheMissRetries; + + // Call to read internal to perform the actual read. + ReadInternal(); +} + +void BufferedDataSource::CleanupTask() { + DCHECK(MessageLoop::current() == render_loop_); + + { + base::AutoLock auto_lock(lock_); + initialize_cb_.Reset(); + if (stopped_on_render_loop_) + return; + + // Signal that stop task has finished execution. + // NOTE: it's vital that this be set under lock, as that's how Read() tests + // before registering a new |read_callback_| (which is cleared below). + stopped_on_render_loop_ = true; + + if (!read_callback_.is_null()) + DoneRead_Locked(net::ERR_FAILED); + } + + // We just need to stop the loader, so it stops activity. + if (loader_.get()) + loader_->Stop(); + + // Reset the parameters of the current read request. + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; +} + +void BufferedDataSource::RestartLoadingTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (stopped_on_render_loop_) + return; + + { + // If there's no outstanding read then return early. + base::AutoLock auto_lock(lock_); + if (read_callback_.is_null()) + return; + } + + loader_ = CreateResourceLoader(read_position_, kPositionNotSpecified); + loader_->Start( + NewCallback(this, &BufferedDataSource::PartialReadStartCallback), + base::Bind(&BufferedDataSource::NetworkEventCallback, this), + frame_); +} + +void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + playback_rate_ = playback_rate; + loader_->SetPlaybackRate(playback_rate); + + bool previously_paused = media_is_paused_; + media_is_paused_ = (playback_rate == 0.0); + + if (!media_has_played_ && previously_paused && !media_is_paused_) + media_has_played_ = true; + + BufferedResourceLoader::DeferStrategy strategy = ChooseDeferStrategy(); + loader_->UpdateDeferStrategy(strategy); +} + +void BufferedDataSource::SetPreloadTask(media::Preload preload) { + DCHECK(MessageLoop::current() == render_loop_); + preload_ = preload; +} + +void BufferedDataSource::SetBitrateTask(int bitrate) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + bitrate_ = bitrate; + loader_->SetBitrate(bitrate); +} + +BufferedResourceLoader::DeferStrategy +BufferedDataSource::ChooseDeferStrategy() { + DCHECK(MessageLoop::current() == render_loop_); + // If the page indicated preload=metadata, then load exactly what is needed + // needed for starting playback. + if (!media_has_played_ && preload_ == media::METADATA) + return BufferedResourceLoader::kReadThenDefer; + + // 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. + if (media_has_played_ && media_is_paused_) + return BufferedResourceLoader::kNeverDefer; + + // If media is currently playing or the page indicated preload=auto, + // use threshold strategy to enable/disable deferring when the buffer + // is full/depleted. + return BufferedResourceLoader::kThresholdDefer; +} + +// This method is the place where actual read happens, |loader_| must be valid +// prior to make this method call. +void BufferedDataSource::ReadInternal() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_); + + // First we prepare the intermediate read buffer for BufferedResourceLoader + // to write to. + if (read_size_ > intermediate_read_buffer_size_) { + intermediate_read_buffer_.reset(new uint8[read_size_]); + } + + // Perform the actual read with BufferedResourceLoader. + loader_->Read(read_position_, read_size_, intermediate_read_buffer_.get(), + NewCallback(this, &BufferedDataSource::ReadCallback)); +} + +// Method to report the results of the current read request. Also reset all +// the read parameters. +void BufferedDataSource::DoneRead_Locked(int error) { + VLOG(1) << "DoneRead: " << error << " bytes"; + + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!read_callback_.is_null()); + lock_.AssertAcquired(); + + if (error >= 0) { + read_callback_.Run(static_cast<size_t>(error)); + } else { + read_callback_.Run(kReadError); + } + + read_callback_.Reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; +} + +void BufferedDataSource::DoneInitialization_Locked( + media::PipelineStatus status) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!initialize_cb_.is_null()); + lock_.AssertAcquired(); + + initialize_cb_.Run(status); + initialize_cb_.Reset(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader callback methods. +void BufferedDataSource::HttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + int64 instance_size = loader_->instance_size(); + bool success = error == net::OK; + + bool initialize_cb_is_null = false; + { + base::AutoLock auto_lock(lock_); + initialize_cb_is_null = initialize_cb_.is_null(); + } + if (initialize_cb_is_null) { + loader_->Stop(); + return; + } + + if (success) { + // TODO(hclam): Needs more thinking about supporting servers without range + // request or their partial response is not complete. + total_bytes_ = instance_size; + loaded_ = false; + streaming_ = (instance_size == kPositionNotSpecified) || + !loader_->range_supported(); + } else { + // TODO(hclam): In case of failure, we can retry several times. + loader_->Stop(); + } + + if (error == net::ERR_INVALID_RESPONSE && using_range_request_) { + // Assuming that the Range header was causing the problem. Retry without + // the Range header. + using_range_request_ = false; + loader_ = CreateResourceLoader(kPositionNotSpecified, + kPositionNotSpecified); + loader_->Start( + NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), + base::Bind(&BufferedDataSource::NetworkEventCallback, this), + frame_); + return; + } + + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + // TODO: Review locking in this class and figure out a way to run the callback + // w/o the lock. + scoped_refptr<BufferedDataSource> destruction_guard(this); + { + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is + // safe because |lock_| is only acquired in tasks on render thread. + base::AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (!success) { + DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK); + return; + } + + UpdateHostState_Locked(); + DoneInitialization_Locked(media::PIPELINE_OK); + } +} + +void BufferedDataSource::NonHttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + bool initialize_cb_is_null = false; + { + base::AutoLock auto_lock(lock_); + initialize_cb_is_null = initialize_cb_.is_null(); + } + if (initialize_cb_is_null) { + loader_->Stop(); + return; + } + + int64 instance_size = loader_->instance_size(); + bool success = error == net::OK && instance_size != kPositionNotSpecified; + + if (success) { + total_bytes_ = instance_size; + buffered_bytes_ = total_bytes_; + loaded_ = true; + } else { + loader_->Stop(); + } + + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + // TODO: Review locking in this class and figure out a way to run the callback + // w/o the lock. + scoped_refptr<BufferedDataSource> destruction_guard(this); + { + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is + // safe because |lock_| is only acquired in tasks on render thread. + base::AutoLock auto_lock(lock_); + if (stop_signal_received_ || initialize_cb_.is_null()) + return; + + if (!success) { + DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK); + return; + } + + UpdateHostState_Locked(); + DoneInitialization_Locked(media::PIPELINE_OK); + } +} + +void BufferedDataSource::PartialReadStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + if (error == net::OK) { + // 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(); + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is + // safe because |lock_| is only acquired in tasks on render thread. + base::AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + DoneRead_Locked(net::ERR_INVALID_RESPONSE); +} + +void BufferedDataSource::ReadCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + + if (error < 0) { + DCHECK(loader_.get()); + + // Stop the resource load if it failed. + loader_->Stop(); + + if (error == net::ERR_CACHE_MISS && cache_miss_retries_left_ > 0) { + cache_miss_retries_left_--; + render_loop_->PostTask(FROM_HERE, + base::Bind(&BufferedDataSource::RestartLoadingTask, this)); + return; + } + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + base::AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (error > 0) { + // If a position error code is received, read was successful. So copy + // from intermediate read buffer to the target read buffer. + memcpy(read_buffer_, intermediate_read_buffer_.get(), error); + } else if (error == 0 && total_bytes_ == kPositionNotSpecified) { + // We've reached the end of the file and we didn't know the total size + // before. Update the total size so Read()s past the end of the file will + // fail like they would if we had known the file size at the beginning. + total_bytes_ = loader_->instance_size(); + + if (host() && total_bytes_ != kPositionNotSpecified) + host()->SetTotalBytes(total_bytes_); + } + DoneRead_Locked(error); +} + +void BufferedDataSource::NetworkEventCallback() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // In case of non-HTTP request we don't need to report network events, + // so return immediately. + if (loaded_) + return; + + bool is_downloading_data = loader_->is_downloading_data(); + int64 buffered_position = loader_->GetBufferedPosition(); + + // If we get an unspecified value, return immediately. + if (buffered_position == kPositionNotSpecified) + return; + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + base::AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (is_downloading_data != is_downloading_data_) { + is_downloading_data_ = is_downloading_data; + if (host()) + host()->SetNetworkActivity(is_downloading_data); + } + + buffered_bytes_ = buffered_position + 1; + if (host()) + host()->SetBufferedBytes(buffered_bytes_); +} + +void BufferedDataSource::UpdateHostState_Locked() { + // Called from various threads, under lock. + lock_.AssertAcquired(); + + media::FilterHost* filter_host = host(); + if (!filter_host) + return; + + filter_host->SetLoaded(loaded_); + + if (streaming_) { + filter_host->SetStreaming(true); + } else { + filter_host->SetTotalBytes(total_bytes_); + filter_host->SetBufferedBytes(buffered_bytes_); + } +} + +} // namespace webkit_media diff --git a/webkit/media/buffered_data_source.h b/webkit/media/buffered_data_source.h new file mode 100644 index 0000000..7987d09 --- /dev/null +++ b/webkit/media/buffered_data_source.h @@ -0,0 +1,235 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_H_ +#define WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "media/base/filter_factories.h" +#include "media/base/filters.h" +#include "webkit/media/buffered_resource_loader.h" + +namespace media { +class MediaLog; +} + +namespace webkit_media { + +// This class may be created on any thread, and is callable from the render +// thread as well as media-specific threads. +class BufferedDataSource : public WebDataSource { + public: + // Creates a DataSourceFactory for building BufferedDataSource objects. + static media::DataSourceFactory* CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log, + const WebDataSourceBuildObserverHack& build_observer); + + BufferedDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log); + + virtual ~BufferedDataSource(); + + // media::Filter implementation. + virtual void set_host(media::FilterHost* host) OVERRIDE; + virtual void Stop(const base::Closure& callback) OVERRIDE; + virtual void SetPlaybackRate(float playback_rate) OVERRIDE; + + // media::DataSource implementation. + // Called from demuxer thread. + virtual void Read( + int64 position, + size_t size, + uint8* data, + const media::DataSource::ReadCallback& read_callback) OVERRIDE; + virtual bool GetSize(int64* size_out) OVERRIDE; + virtual bool IsStreaming() OVERRIDE; + virtual void SetPreload(media::Preload preload) OVERRIDE; + virtual void SetBitrate(int bitrate) OVERRIDE; + + // webkit_glue::WebDataSource implementation. + virtual void Initialize(const std::string& url, + const media::PipelineStatusCB& callback) OVERRIDE; + virtual void CancelInitialize() OVERRIDE; + virtual bool HasSingleOrigin() OVERRIDE; + virtual void Abort() 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 BufferedDataSourceTest2; + + // Posted to perform initialization on render thread and start resource + // loading. + void InitializeTask(); + + // Task posted to perform actual reading on the render thread. + void ReadTask(int64 position, int read_size, uint8* read_buffer); + + // Task posted when Stop() is called. Stops |watch_dog_timer_| and + // |loader_|, reset Read() variables, and set |stopped_on_render_loop_| + // to signal any remaining tasks to stop. + void CleanupTask(); + + // Restart resource loading on render thread. + void RestartLoadingTask(); + + // This task uses the current playback rate with the previous playback rate + // to determine whether we are going from pause to play and play to pause, + // and signals the buffered resource loader accordingly. + void SetPlaybackRateTask(float playback_rate); + + // This task saves the preload value for the media. + void SetPreloadTask(media::Preload preload); + + // Tells |loader_| the bitrate of the media. + void SetBitrateTask(int bitrate); + + // Decides which DeferStrategy to used based on the current state of the + // BufferedDataSource. + BufferedResourceLoader::DeferStrategy ChooseDeferStrategy(); + + // The method that performs actual read. This method can only be executed on + // the render thread. + void ReadInternal(); + + // Calls |read_callback_| and reset all read parameters. + void DoneRead_Locked(int error); + + // Calls |initialize_cb_| and reset it. + void DoneInitialization_Locked(media::PipelineStatus status); + + // Callback method for |loader_| if URL for the resource requested is using + // HTTP protocol. This method is called when response for initial request is + // received. + void HttpInitialStartCallback(int error); + + // Callback method for |loader_| if URL for the resource requested is using + // a non-HTTP protocol, e.g. local files. This method is called when response + // for initial request is received. + void NonHttpInitialStartCallback(int error); + + // Callback method to be passed to BufferedResourceLoader during range + // request. Once a resource request has started, this method will be called + // with the error code. This method will be executed on the thread + // BufferedResourceLoader lives, i.e. render thread. + void PartialReadStartCallback(int error); + + // Callback method for making a read request to BufferedResourceLoader. + // If data arrives or the request has failed, this method is called with + // the error code or the number of bytes read. + void ReadCallback(int error); + + // Callback method when a network event is received. + void NetworkEventCallback(); + + void UpdateHostState_Locked(); + + // URL of the resource requested. + GURL url_; + + // Members for total bytes of the requested object. It is written once on + // render thread but may be read from any thread. However reading of this + // member is guaranteed to happen after it is first written, so we don't + // need to protect it. + int64 total_bytes_; + int64 buffered_bytes_; + + // True if this data source is considered loaded. + bool loaded_; + + // 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. + WebKit::WebFrame* frame_; + + // A resource loader for the media resource. + scoped_refptr<BufferedResourceLoader> loader_; + + // True if |loader| is downloading data. + bool is_downloading_data_; + + // Callback method from the pipeline for initialization. + media::PipelineStatusCB initialize_cb_; + + // Read parameters received from the Read() method call. + media::DataSource::ReadCallback read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + + // 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_array<uint8> intermediate_read_buffer_; + int intermediate_read_buffer_size_; + + // The message loop of the render thread. + MessageLoop* render_loop_; + + // Protects |stop_signal_received_|, |stopped_on_render_loop_| and + // |initialize_cb_|. + base::Lock lock_; + + // Stop signal to suppressing activities. This variable is set on the pipeline + // thread and read from the render thread. + bool stop_signal_received_; + + // This variable is set by CleanupTask() that indicates this object is stopped + // on the render thread and work should no longer progress. + bool stopped_on_render_loop_; + + // This variable is true when we are in a paused state and false when we + // are in a playing state. + bool media_is_paused_; + + // 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. + media::Preload preload_; + + // Keeps track of whether we used a Range header in the initialization + // request. + bool using_range_request_; + + // Number of cache miss retries left. + int cache_miss_retries_left_; + + // Bitrate of the content, 0 if unknown. + int bitrate_; + + // Current playback rate. + float playback_rate_; + + scoped_refptr<media::MediaLog> media_log_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSource); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_H_ diff --git a/webkit/media/buffered_data_source_unittest.cc b/webkit/media/buffered_data_source_unittest.cc new file mode 100644 index 0000000..f7f7131 --- /dev/null +++ b/webkit/media/buffered_data_source_unittest.cc @@ -0,0 +1,791 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/test/test_timeouts.h" +#include "media/base/media_log.h" +#include "media/base/mock_callback.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "net/base/net_errors.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "webkit/media/buffered_data_source.h" +#include "webkit/mocks/mock_webframeclient.h" +#include "webkit/mocks/mock_weburlloader.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::AtLeast; +using ::testing::DeleteArg; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::NiceMock; +using ::testing::WithArgs; + +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebURLError; +using WebKit::WebURLResponse; +using WebKit::WebView; + +using webkit_glue::MockWebFrameClient; +using webkit_glue::MockWebURLLoader; + +namespace webkit_media { + +static const char* kHttpUrl = "http://test"; +static const char* kFileUrl = "file://test"; +static const int kDataSize = 1024; +static const int kMaxCacheMissesBeforeFailTest = 20; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +// A mock BufferedDataSource to inject mock BufferedResourceLoader through +// CreateResourceLoader() method. +class MockBufferedDataSource : public BufferedDataSource { + public: + MockBufferedDataSource(MessageLoop* message_loop, WebFrame* frame) + : BufferedDataSource(message_loop, frame, new media::MediaLog()) { + } + + virtual base::TimeDelta GetTimeoutMilliseconds() { + return base::TimeDelta::FromMilliseconds( + TestTimeouts::tiny_timeout_ms()); + } + + MOCK_METHOD2(CreateResourceLoader, + BufferedResourceLoader*(int64 first_position, + int64 last_position)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); +}; + +class MockBufferedResourceLoader : public BufferedResourceLoader { + public: + MockBufferedResourceLoader() + : BufferedResourceLoader(GURL(), 0, 0, kThresholdDefer, + 0, 0, new media::MediaLog()) { + } + + MOCK_METHOD3(Start, void(net::OldCompletionCallback* read_callback, + const base::Closure& network_callback, + WebFrame* frame)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer, + net::OldCompletionCallback* callback)); + MOCK_METHOD0(content_length, int64()); + MOCK_METHOD0(instance_size, int64()); + MOCK_METHOD0(range_supported, bool()); + MOCK_METHOD0(network_activity, bool()); + MOCK_METHOD0(url, const GURL&()); + MOCK_METHOD0(GetBufferedFirstBytePosition, int64()); + MOCK_METHOD0(GetBufferedLastBytePosition, int64()); + + protected: + ~MockBufferedResourceLoader() {} + + DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader); +}; + +class BufferedDataSourceTest : public testing::Test { + public: + BufferedDataSourceTest() + : view_(WebView::create(NULL)) { + view_->initializeMainFrame(&client_); + message_loop_ = MessageLoop::current(); + + for (size_t i = 0; i < sizeof(data_); ++i) { + data_[i] = i; + } + } + + virtual ~BufferedDataSourceTest() { + view_->close(); + } + + void ExpectCreateAndStartResourceLoader(int start_error) { + EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) + .WillOnce(Return(loader_.get())); + + EXPECT_CALL(*loader_, Start(NotNull(), _, NotNull())) + .WillOnce( + DoAll(Assign(&error_, start_error), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + } + + void InitializeDataSource(const char* url, int error, + bool partial_response, int64 instance_size, + NetworkState networkState) { + // Saves the url first. + gurl_ = GURL(url); + + data_source_ = new MockBufferedDataSource(MessageLoop::current(), + view_->mainFrame()); + data_source_->set_host(&host_); + + scoped_refptr<NiceMock<MockBufferedResourceLoader> > first_loader( + new NiceMock<MockBufferedResourceLoader>()); + + // Creates the mock loader to be injected. + loader_ = first_loader; + + bool initialized_ok = (error == net::OK); + bool loaded = networkState == LOADED; + { + InSequence s; + ExpectCreateAndStartResourceLoader(error); + + // In the case of an invalid partial response we expect a second loader + // to be created. + if (partial_response && (error == net::ERR_INVALID_RESPONSE)) { + // Verify that the initial loader is stopped. + EXPECT_CALL(*loader_, url()) + .WillRepeatedly(ReturnRef(gurl_)); + EXPECT_CALL(*loader_, Stop()); + + // Replace loader_ with a new instance. + loader_ = new NiceMock<MockBufferedResourceLoader>(); + + // Create and start. Make sure Start() is called on the new loader. + ExpectCreateAndStartResourceLoader(net::OK); + + // Update initialization variable since we know the second loader will + // return OK. + initialized_ok = true; + } + } + + // Attach a static function that deletes the memory referred by the + // "callback" parameter. + ON_CALL(*loader_, Read(_, _, _ , _)) + .WillByDefault(DeleteArg<3>()); + + ON_CALL(*loader_, instance_size()) + .WillByDefault(Return(instance_size)); + + // range_supported() return true if we expect to get a partial response. + ON_CALL(*loader_, range_supported()) + .WillByDefault(Return(partial_response)); + + ON_CALL(*loader_, url()) + .WillByDefault(ReturnRef(gurl_)); + media::PipelineStatus expected_init_status = media::PIPELINE_OK; + if (initialized_ok) { + // Expected loaded or not. + EXPECT_CALL(host_, SetLoaded(loaded)); + + // TODO(hclam): The condition for streaming needs to be adjusted. + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_CALL(host_, SetTotalBytes(instance_size)); + if (loaded) + EXPECT_CALL(host_, SetBufferedBytes(instance_size)); + else + EXPECT_CALL(host_, SetBufferedBytes(0)); + } else { + EXPECT_CALL(host_, SetStreaming(true)); + } + } else { + expected_init_status = media::PIPELINE_ERROR_NETWORK; + EXPECT_CALL(*loader_, Stop()); + } + + // Actual initialization of the data source. + data_source_->Initialize(url, + media::NewExpectedStatusCB(expected_init_status)); + message_loop_->RunAllPending(); + + if (initialized_ok) { + // Verify the size of the data source. + int64 size; + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_TRUE(data_source_->GetSize(&size)); + EXPECT_EQ(instance_size, size); + } else { + EXPECT_TRUE(data_source_->IsStreaming()); + } + } + } + + void StopDataSource() { + if (loader_) { + InSequence s; + EXPECT_CALL(*loader_, Stop()); + } + + data_source_->Stop(media::NewExpectedClosure()); + message_loop_->RunAllPending(); + } + + void InvokeStartCallback( + net::OldCompletionCallback* callback, + const base::Closure& network_callback, + WebFrame* frame) { + callback->RunWithParams(Tuple1<int>(error_)); + delete callback; + // TODO(hclam): Save network_callback. + } + + void InvokeReadCallback(int64 position, int size, uint8* buffer, + net::OldCompletionCallback* callback) { + if (error_ > 0) + memcpy(buffer, data_ + static_cast<int>(position), error_); + callback->RunWithParams(Tuple1<int>(error_)); + delete callback; + } + + void ReadDataSourceHit(int64 position, int size, int read_size) { + EXPECT_TRUE(loader_); + + InSequence s; + // Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, read_size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // The read has succeeded, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(read_size)); + + data_source_->Read( + position, size, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + message_loop_->RunAllPending(); + + // Make sure data is correct. + EXPECT_EQ(0, + memcmp(buffer_, data_ + static_cast<int>(position), read_size)); + } + + void ReadDataSourceHang(int64 position, int size) { + EXPECT_TRUE(loader_); + + // Expect a call to read, but the call never returns. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())); + data_source_->Read( + position, size, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + message_loop_->RunAllPending(); + + // Now expect the read to return after aborting the data source. + EXPECT_CALL(*this, ReadCallback(_)); + EXPECT_CALL(*loader_, Stop()); + data_source_->Abort(); + message_loop_->RunAllPending(); + + // The loader has now been stopped. Set this to null so that when the + // DataSource is stopped, it does not expect a call to stop the loader. + loader_ = NULL; + } + + void ReadDataSourceMiss(int64 position, int size, int start_error) { + EXPECT_TRUE(loader_); + + // 1. Reply with a cache miss for the read. + { + InSequence s; + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + EXPECT_CALL(*loader_, Stop()); + } + + // 2. Then the current loader will be stop and destroyed. + NiceMock<MockBufferedResourceLoader> *new_loader = + new NiceMock<MockBufferedResourceLoader>(); + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillOnce(Return(new_loader)); + + // 3. Then the new loader will be started. + EXPECT_CALL(*new_loader, Start(NotNull(), _, NotNull())) + .WillOnce(DoAll(Assign(&error_, start_error), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + + if (start_error == net::OK) { + EXPECT_CALL(*new_loader, range_supported()) + .WillRepeatedly(Return(loader_->range_supported())); + + // 4a. Then again a read request is made to the new loader. + EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + EXPECT_CALL(*this, ReadCallback(size)); + } else { + // 4b. The read callback is called with an error because Start() on the + // new loader returned an error. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + } + + data_source_->Read( + position, size, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + message_loop_->RunAllPending(); + + // Make sure data is correct. + if (start_error == net::OK) + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); + + loader_ = new_loader; + } + + void ReadDataSourceFailed(int64 position, int size, int error) { + EXPECT_TRUE(loader_); + + // 1. Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // 2. Host will then receive an error. + EXPECT_CALL(*loader_, Stop()); + + // 3. The read has failed, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + + data_source_->Read( + position, size, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + + message_loop_->RunAllPending(); + } + + BufferedResourceLoader* InvokeCacheMissCreateResourceLoader(int64 start, + int64 end) { + NiceMock<MockBufferedResourceLoader>* new_loader = + new NiceMock<MockBufferedResourceLoader>(); + + EXPECT_CALL(*new_loader, Start(NotNull(), _, NotNull())) + .WillOnce(DoAll(Assign(&error_, net::OK), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + + EXPECT_CALL(*new_loader, range_supported()) + .WillRepeatedly(Return(loader_->range_supported())); + + int error = net::ERR_FAILED; + if (cache_miss_count_ < kMaxCacheMissesBeforeFailTest) { + cache_miss_count_++; + error = net::ERR_CACHE_MISS; + } + + EXPECT_CALL(*new_loader, Read(start, _, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + loader_ = new_loader; + return new_loader; + } + + void ReadDataSourceAlwaysCacheMiss(int64 position, int size) { + cache_miss_count_ = 0; + + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillRepeatedly(Invoke( + this, + &BufferedDataSourceTest::InvokeCacheMissCreateResourceLoader)); + + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + + data_source_->Read( + position, size, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(this))); + + message_loop_->RunAllPending(); + + EXPECT_LT(cache_miss_count_, kMaxCacheMissesBeforeFailTest); + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + scoped_refptr<NiceMock<MockBufferedResourceLoader> > loader_; + scoped_refptr<MockBufferedDataSource> data_source_; + + MockWebFrameClient client_; + WebView* view_; + + StrictMock<media::MockFilterHost> host_; + GURL gurl_; + MessageLoop* message_loop_; + + int error_; + uint8 buffer_[1024]; + uint8 data_[1024]; + + int cache_miss_count_; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest); +}; + +TEST_F(BufferedDataSourceTest, InitializationSuccess) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, InitiailizationFailed) { + InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, false, 0, NONE); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, MissingContentLength) { + InitializeDataSource(kHttpUrl, net::OK, true, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, RangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, 1024, LOADING); + StopDataSource(); +} + +// Test the case where we get a 206 response, but no Content-Range header. +TEST_F(BufferedDataSourceTest, MissingContentRange) { + InitializeDataSource(kHttpUrl, net::ERR_INVALID_RESPONSE, true, 1024, + LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, + MissingContentLengthAndRangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheHit) { + InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); + + // Performs read with cache hit. + ReadDataSourceHit(10, 10, 10); + + // Performs read with cache hit but partially filled. + ReadDataSourceHit(20, 10, 5); + + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheMiss) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceMiss(1000, 10, net::OK); + ReadDataSourceMiss(20, 10, net::OK); + StopDataSource(); +} + +// 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, ServerLiesAboutRangeSupport) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceHit(10, 10, 10); + ReadDataSourceMiss(1000, 10, net::ERR_INVALID_RESPONSE); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadHang) { + InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); + ReadDataSourceHang(10, 10); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadFailed) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceHit(10, 10, 10); + ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET); + StopDataSource(); +} + +// Helper that sets |*value| to true. Useful for binding into a Closure. +static void SetTrue(bool* value) { + *value = true; +} + +// This test makes sure that Stop() does not require a task to run on +// |message_loop_| before it calls its callback. This prevents accidental +// introduction of a pipeline teardown deadlock. The pipeline owner blocks +// the render message loop while waiting for Stop() to complete. Since this +// object runs on the render message loop, Stop() will not complete if it +// requires a task to run on the the message loop that is being blocked. +TEST_F(BufferedDataSourceTest, StopDoesNotUseMessageLoopForCallback) { + InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); + + // Stop() the data source, using a callback that lets us verify that it was + // called before Stop() returns. This is to make sure that the callback does + // not require |message_loop_| to execute tasks before being called. + bool stop_done_called = false; + data_source_->Stop(base::Bind(&SetTrue, &stop_done_called)); + + // Verify that the callback was called inside the Stop() call. + EXPECT_TRUE(stop_done_called); + + message_loop_->RunAllPending(); +} + +TEST_F(BufferedDataSourceTest, AbortDuringPendingRead) { + InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); + + // Setup a way to verify that Read() is not called on the loader. + // We are doing this to make sure that the ReadTask() is still on + // the message loop queue when Abort() is called. + bool read_called = false; + ON_CALL(*loader_, Read(_, _, _ , _)) + .WillByDefault(DoAll(Assign(&read_called, true), + DeleteArg<3>())); + + // Initiate a Read() on the data source, but don't allow the + // message loop to run. + data_source_->Read( + 0, 10, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(static_cast<BufferedDataSourceTest*>(this)))); + + // Call Abort() with the read pending. + EXPECT_CALL(*this, ReadCallback(-1)); + EXPECT_CALL(*loader_, Stop()); + data_source_->Abort(); + + // Verify that Read()'s after the Abort() issue callback with an error. + EXPECT_CALL(*this, ReadCallback(-1)); + data_source_->Read( + 0, 10, buffer_, + base::Bind(&BufferedDataSourceTest::ReadCallback, + base::Unretained(static_cast<BufferedDataSourceTest*>(this)))); + + // Stop() the data source like normal. + data_source_->Stop(media::NewExpectedClosure()); + + // Allow cleanup task to run. + message_loop_->RunAllPending(); + + // Verify that Read() was not called on the loader. + EXPECT_FALSE(read_called); +} + +// Test that we only allow a limited number of cache misses for a +// single Read() request. +TEST_F(BufferedDataSourceTest, BoundedCacheMisses) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + + ReadDataSourceAlwaysCacheMiss(0, 10); + + StopDataSource(); +} + +// TODO(scherkus): de-dupe from buffered_resource_loader_unittest.cc +ACTION_P(RequestCanceled, loader) { + WebURLError error; + error.reason = net::ERR_ABORTED; + error.domain = WebString::fromUTF8(net::kErrorDomain); + loader->didFail(NULL, error); +} + +// A more realistic BufferedDataSource that uses BufferedResourceLoader instead +// of a mocked version but injects a MockWebURLLoader. +// +// TODO(scherkus): re-write these tests to use this class then drop the "2" +// suffix. +class MockBufferedDataSource2 : public BufferedDataSource { + public: + MockBufferedDataSource2(MessageLoop* message_loop, WebFrame* frame) + : BufferedDataSource(message_loop, frame, new media::MediaLog()), + url_loader_(NULL) { + } + + virtual base::TimeDelta GetTimeoutMilliseconds() { + return base::TimeDelta::FromMilliseconds( + TestTimeouts::tiny_timeout_ms()); + } + + virtual BufferedResourceLoader* CreateResourceLoader(int64 first_position, + int64 last_position) { + loader_ = BufferedDataSource::CreateResourceLoader(first_position, + last_position); + + url_loader_ = new NiceMock<MockWebURLLoader>(); + ON_CALL(*url_loader_, cancel()) + .WillByDefault(RequestCanceled(loader_)); + + loader_->SetURLLoaderForTest(url_loader_); + return loader_; + } + + const scoped_refptr<BufferedResourceLoader>& loader() { return loader_; } + NiceMock<MockWebURLLoader>* url_loader() { return url_loader_; } + + private: + scoped_refptr<BufferedResourceLoader> loader_; + NiceMock<MockWebURLLoader>* url_loader_; + + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource2); +}; + +class BufferedDataSourceTest2 : public testing::Test { + public: + BufferedDataSourceTest2() + : view_(WebView::create(NULL)), + message_loop_(MessageLoop::current()) { + view_->initializeMainFrame(&client_); + } + + virtual ~BufferedDataSourceTest2() { + view_->close(); + } + + void InitializeDataSource(const char* url) { + gurl_ = GURL(url); + + data_source_ = new MockBufferedDataSource2(message_loop_, + view_->mainFrame()); + data_source_->set_host(&host_); + data_source_->Initialize(url, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + message_loop_->RunAllPending(); + + // Simulate 206 response for a 5,000,000 byte length file. + WebURLResponse response(gurl_); + response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), + WebString::fromUTF8("bytes")); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Range"), + WebString::fromUTF8("bytes 0-4999999/5000000")); + response.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("5000000")); + response.setExpectedContentLength(5000000); + response.setHTTPStatusCode(206); + + // We should receive corresponding information about the media resource. + EXPECT_CALL(host_, SetLoaded(false)); + EXPECT_CALL(host_, SetTotalBytes(5000000)); + EXPECT_CALL(host_, SetBufferedBytes(0)); + + data_source_->loader()->didReceiveResponse(data_source_->url_loader(), + response); + + message_loop_->RunAllPending(); + } + + void StopDataSource() { + data_source_->Stop(media::NewExpectedClosure()); + message_loop_->RunAllPending(); + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + media::DataSource::ReadCallback NewReadCallback(size_t size) { + EXPECT_CALL(*this, ReadCallback(size)); + return base::Bind(&BufferedDataSourceTest2::ReadCallback, + base::Unretained(this)); + } + + // Accessors for private variables on |data_source_|. + media::Preload preload() { return data_source_->preload_; } + BufferedResourceLoader::DeferStrategy defer_strategy() { + return data_source_->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 data_source_->loader()->bitrate_; } + int loader_playback_rate() { return data_source_->loader()->playback_rate_; } + + scoped_refptr<MockBufferedDataSource2> data_source_; + + GURL gurl_; + MockWebFrameClient client_; + WebView* view_; + + StrictMock<media::MockFilterHost> host_; + MessageLoop* message_loop_; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest2); +}; + +TEST_F(BufferedDataSourceTest2, Default) { + InitializeDataSource("http://localhost/foo.webm"); + + // Ensure we have sane values for default loading scenario. + EXPECT_EQ(media::AUTO, preload()); + EXPECT_EQ(BufferedResourceLoader::kThresholdDefer, 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()); + + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest2, SetBitrate) { + InitializeDataSource("http://localhost/foo.webm"); + + data_source_->SetBitrate(1234); + message_loop_->RunAllPending(); + 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 = data_source_->loader(); + + uint8 buffer[1024]; + data_source_->Read(4000000, 1024, buffer, + NewReadCallback(media::DataSource::kReadError)); + message_loop_->RunAllPending(); + + // Verify loader changed but still has same bitrate. + EXPECT_NE(old_loader, data_source_->loader().get()); + EXPECT_EQ(1234, loader_bitrate()); + + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest2, SetPlaybackRate) { + InitializeDataSource("http://localhost/foo.webm"); + + data_source_->SetPlaybackRate(2.0f); + message_loop_->RunAllPending(); + 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 = data_source_->loader(); + + uint8 buffer[1024]; + data_source_->Read(4000000, 1024, buffer, + NewReadCallback(media::DataSource::kReadError)); + message_loop_->RunAllPending(); + + // Verify loader changed but still has same bitrate. + EXPECT_NE(old_loader, data_source_->loader().get()); + EXPECT_EQ(2.0f, loader_playback_rate()); + + StopDataSource(); +} + +} // namespace webkit_media diff --git a/webkit/media/buffered_resource_loader.cc b/webkit/media/buffered_resource_loader.cc new file mode 100644 index 0000000..6a9f031 --- /dev/null +++ b/webkit/media/buffered_resource_loader.cc @@ -0,0 +1,813 @@ +// Copyright (c) 2011 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 "webkit/media/buffered_resource_loader.h" + +#include "base/format_macros.h" +#include "base/stringprintf.h" +#include "base/string_util.h" +#include "media/base/media_log.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitPlatformSupport.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h" +#include "webkit/glue/multipart_response_delegate.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderOptions; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using webkit_glue::MultipartResponseDelegate; + +namespace webkit_media { + +static const int kHttpOK = 200; +static const int kHttpPartialContent = 206; + +// Define the number of bytes in a megabyte. +static const size_t 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 size_t 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 size_t 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, + size_t* out_backward_capacity, + size_t* out_forward_capacity) { + static const size_t kDefaultBitrate = 200 * 1024 * 8; // 200 Kbps. + static const size_t kMaxBitrate = 20 * kMegabyte * 8; // 20 Mbps. + static const float kMaxPlaybackRate = 25.0; + static const size_t kTargetSecondsBufferedAhead = 10; + static const size_t kTargetSecondsBufferedBehind = 2; + + // Use a default bit rate if unknown and clamp to prevent overflow. + if (bitrate <= 0) + bitrate = kDefaultBitrate; + bitrate = std::min(static_cast<size_t>(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); + + size_t bytes_per_second = static_cast<size_t>(playback_rate * bitrate / 8.0); + + // 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, + int64 first_byte_position, + int64 last_byte_position, + DeferStrategy strategy, + int bitrate, + float playback_rate, + media::MediaLog* media_log) + : deferred_(false), + defer_strategy_(strategy), + completed_(false), + range_requested_(false), + range_supported_(false), + saved_forward_capacity_(0), + url_(url), + first_byte_position_(first_byte_position), + last_byte_position_(last_byte_position), + single_origin_(true), + start_callback_(NULL), + offset_(0), + content_length_(kPositionNotSpecified), + instance_size_(kPositionNotSpecified), + read_callback_(NULL), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + first_offset_(0), + last_offset_(0), + keep_test_loader_(false), + bitrate_(bitrate), + playback_rate_(playback_rate), + media_log_(media_log) { + + size_t backward_capacity; + size_t forward_capacity; + ComputeTargetBufferWindow( + playback_rate_, bitrate_, &backward_capacity, &forward_capacity); + buffer_.reset(new media::SeekableBuffer(backward_capacity, forward_capacity)); +} + +BufferedResourceLoader::~BufferedResourceLoader() { + if (!completed_ && url_loader_.get()) + url_loader_->cancel(); +} + +void BufferedResourceLoader::Start(net::OldCompletionCallback* start_callback, + const base::Closure& event_callback, + WebFrame* frame) { + // Make sure we have not started. + DCHECK(!start_callback_.get()); + DCHECK(event_callback_.is_null()); + DCHECK(start_callback); + DCHECK(!event_callback.is_null()); + CHECK(frame); + + start_callback_.reset(start_callback); + event_callback_ = event_callback; + + 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_; + } + + // Increment the reference count right before we start the request. This + // reference will be release when this request has ended. + AddRef(); + + // Prepare the request. + WebURLRequest request(url_); + request.setTargetType(WebURLRequest::TargetIsMedia); + + if (IsRangeRequest()) { + range_requested_ = true; + request.setHTTPHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kRange), + WebString::fromUTF8(GenerateHeaders(first_byte_position_, + last_byte_position_))); + } + frame->setReferrerForRequest(request, WebKit::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")); + + // This flag is for unittests as we don't want to reset |url_loader| + if (!keep_test_loader_) { + WebURLLoaderOptions options; + options.allowCredentials = true; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyAllow; + url_loader_.reset(frame->createAssociatedURLLoader(options)); + } + + // Start the resource loading. + url_loader_->loadAsynchronously(request, this); +} + +void BufferedResourceLoader::Stop() { + // Reset callbacks. + start_callback_.reset(); + event_callback_.Reset(); + read_callback_.reset(); + + // Use the internal buffer to signal that we have been stopped. + // TODO(hclam): Not so pretty to do this. + if (!buffer_.get()) + return; + + // Destroy internal buffer. + buffer_.reset(); + + if (url_loader_.get()) { + if (deferred_) + url_loader_->setDefersLoading(false); + deferred_ = false; + + if (!completed_) { + url_loader_->cancel(); + completed_ = true; + } + } +} + +void BufferedResourceLoader::Read(int64 position, + int read_size, + uint8* buffer, + net::OldCompletionCallback* read_callback) { + DCHECK(!read_callback_.get()); + DCHECK(buffer_.get()); + DCHECK(read_callback); + DCHECK(buffer); + DCHECK_GT(read_size, 0); + + // Save the parameter of reading. + read_callback_.reset(read_callback); + read_position_ = position; + read_size_ = read_size; + read_buffer_ = buffer; + + // If read position is beyond the instance size, we cannot read there. + if (instance_size_ != kPositionNotSpecified && + instance_size_ <= read_position_) { + DoneRead(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(net::ERR_CACHE_MISS); + 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(net::ERR_FAILED); + return; + } + + // Prepare the parameters. + first_offset_ = static_cast<int>(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_, + static_cast<int>(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_ > static_cast<int>(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. + // + // XXX: can we DCHECK(url_loader_.get()) at this point in time? + if (deferred_) + SetDeferred(false); + + DCHECK(!ShouldEnableDefer()) + << "Capacity was not adjusted properly to prevent deferring."; + + return; + } + + // Make a callback to report failure. + DoneRead(net::ERR_CACHE_MISS); +} + +int64 BufferedResourceLoader::GetBufferedPosition() { + if (buffer_.get()) + return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1; + return kPositionNotSpecified; +} + +int64 BufferedResourceLoader::content_length() { + return content_length_; +} + +int64 BufferedResourceLoader::instance_size() { + return instance_size_; +} + +bool BufferedResourceLoader::range_supported() { + return range_supported_; +} + +bool BufferedResourceLoader::is_downloading_data() { + return !completed_ && !deferred_; +} + +const GURL& BufferedResourceLoader::url() { + return url_; +} + +void BufferedResourceLoader::SetURLLoaderForTest(WebURLLoader* mock_loader) { + url_loader_.reset(mock_loader); + keep_test_loader_ = true; +} + +///////////////////////////////////////////////////////////////////////////// +// WebKit::WebURLLoaderClient implementation. +void BufferedResourceLoader::willSendRequest( + WebURLLoader* loader, + WebURLRequest& newRequest, + const WebURLResponse& redirectResponse) { + + // The load may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) { + // Set the url in the request to an invalid value (empty url). + newRequest.setURL(WebKit::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(); + + if (!webkit_glue::IsProtocolSupportedForMedia(newRequest.url())) { + // Set the url in the request to an invalid value (empty url). + newRequest.setURL(WebKit::WebURL()); + DoneStart(net::ERR_ADDRESS_INVALID); + return; + } + + 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) { + VLOG(1) << "didReceiveResponse: " << response.httpStatusCode(); + + // The loader may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) + return; + + bool partial_response = false; + + // 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_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { + int error = net::OK; + + // 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); + + partial_response = (response.httpStatusCode() == kHttpPartialContent); + + if (range_requested_) { + // If we have verified the partial response and it is correct, we will + // return net::OK. 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 + error = net::ERR_INVALID_RESPONSE; + } else if (response.httpStatusCode() != kHttpOK) { + // We didn't request a range but server didn't reply with "200 OK". + error = net::ERR_FAILED; + } + + if (error != net::OK) { + DoneStart(error); + Stop(); + return; + } + } else { + // For any protocol other than HTTP and HTTPS, assume range request is + // always fulfilled. + partial_response = range_requested_; + } + + // Expected content length can be |kPositionNotSpecified|, in that case + // |content_length_| is not specified and this is a streaming response. + content_length_ = response.expectedContentLength(); + + // If we have not requested a range, then the size of the instance is equal + // to the content length. + if (!partial_response) + instance_size_ = content_length_; + + // Calls with a successful response. + DoneStart(net::OK); +} + +void BufferedResourceLoader::didReceiveData( + WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length) { + VLOG(1) << "didReceiveData: " << data_length << " bytes"; + + DCHECK(!completed_); + DCHECK_GT(data_length, 0); + + // If this loader has been stopped, |buffer_| would be destroyed. + // In this case we shouldn't do anything. + if (!buffer_.get()) + return; + + // Writes more data to |buffer_|. + 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()) { + size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity(); + bool success = buffer_->Seek(excess); + DCHECK(success); + offset_ += first_offset_ + excess; + } + + // Notify that we have received some data. + NotifyNetworkEvent(); + Log(); +} + +void BufferedResourceLoader::didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength) { + NOTIMPLEMENTED(); +} + +void BufferedResourceLoader::didReceiveCachedMetadata( + WebURLLoader* loader, + const char* data, + int data_length) { + NOTIMPLEMENTED(); +} + +void BufferedResourceLoader::didFinishLoading( + WebURLLoader* loader, + double finishTime) { + VLOG(1) << "didFinishLoading"; + + DCHECK(!completed_); + completed_ = true; + + // 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, calls it. + if (start_callback_.get()) { + DoneStart(net::OK); + } + + // If there is a pending read but the request has ended, returns with what + // we have. + if (HasPendingRead()) { + // Make sure we have a valid buffer before we satisfy a read request. + DCHECK(buffer_.get()); + + // Try to fulfill with what is in the buffer. + if (CanFulfillRead()) + ReadInternal(); + else + DoneRead(net::ERR_CACHE_MISS); + } + + // There must not be any outstanding read request. + DCHECK(!HasPendingRead()); + + // Notify that network response is completed. + NotifyNetworkEvent(); + + url_loader_.reset(); + Release(); +} + +void BufferedResourceLoader::didFail( + WebURLLoader* loader, + const WebURLError& error) { + VLOG(1) << "didFail: " << error.reason; + + DCHECK(!completed_); + completed_ = true; + + // If there is a start callback, calls it. + if (start_callback_.get()) { + DoneStart(error.reason); + } + + // If there is a pending read but the request failed, return with the + // reason for the error. + if (HasPendingRead()) { + DoneRead(error.reason); + } + + // Notify that network response is completed. + NotifyNetworkEvent(); + + url_loader_.reset(); + Release(); +} + +bool BufferedResourceLoader::HasSingleOrigin() const { + return single_origin_; +} + +void BufferedResourceLoader::UpdateDeferStrategy(DeferStrategy strategy) { + 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() { + if (!buffer_.get()) + return; + + size_t backward_capacity; + size_t 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 (!url_loader_.get() || !buffer_.get()) + return; + + // If necessary, toggle defer state and continue/pause downloading data + // accordingly. + if (ShouldEnableDefer() || ShouldDisableDefer()) + SetDeferred(!deferred_); +} + +void BufferedResourceLoader::SetDeferred(bool deferred) { + deferred_ = deferred; + if (url_loader_.get()) { + url_loader_->setDefersLoading(deferred); + NotifyNetworkEvent(); + } +} + +bool BufferedResourceLoader::ShouldEnableDefer() { + // If we're already deferring, then enabling makes no sense. + if (deferred_) + return false; + + switch(defer_strategy_) { + // Never defer at all, so never enable defer. + case kNeverDefer: + return false; + + // Defer if nothing is being requested. + case kReadThenDefer: + return !read_callback_.get(); + + // Defer if we've reached the max capacity of the threshold. + case kThresholdDefer: + return buffer_->forward_bytes() >= buffer_->forward_capacity(); + } + // Otherwise don't enable defer. + return false; +} + +bool BufferedResourceLoader::ShouldDisableDefer() { + // If we're not deferring, then disabling makes no sense. + if (!deferred_) + return false; + + switch(defer_strategy_) { + // Always disable deferring. + case kNeverDefer: + return true; + + // We have an outstanding read request, and we have not buffered enough + // yet to fulfill the request; disable defer to get more data. + case kReadThenDefer: + return read_callback_.get() && + last_offset_ > static_cast<int>(buffer_->forward_bytes()); + + // We have less than half the capacity of our threshold, so + // disable defer to get more data. + case kThresholdDefer: { + size_t amount_buffered = buffer_->forward_bytes(); + size_t half_capacity = buffer_->forward_capacity() / 2; + return amount_buffered < half_capacity; + } + } + + // Otherwise keep deferring. + return false; +} + +bool BufferedResourceLoader::CanFulfillRead() { + // If we are reading too far in the backward direction. + if (first_offset_ < 0 && + first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) + return false; + + // If the start offset is too far ahead. + if (first_offset_ >= static_cast<int>(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 (completed_) + return true; + + // If the resource request is still active, make sure the whole requested + // range is covered. + if (last_offset_ > static_cast<int>(buffer_->forward_bytes())) + return false; + + return true; +} + +bool BufferedResourceLoader::WillFulfillRead() { + // Trying to read too far behind. + if (first_offset_ < 0 && + first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) + return false; + + // Trying to read too far ahead. + if (first_offset_ - static_cast<int>(buffer_->forward_bytes()) >= + kForwardWaitThreshold) + return false; + + // The resource request has completed, there's no way we can fulfill the + // read request. + if (completed_) + 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 = static_cast<int>(buffer_->Read(read_buffer_, read_size_)); + offset_ += first_offset_ + read; + + // And report with what we have read. + DoneRead(read); +} + +bool BufferedResourceLoader::VerifyPartialResponse( + const WebURLResponse& response) { + int64 first_byte_position, last_byte_position, instance_size; + + if (!MultipartResponseDelegate::ReadContentRanges(response, + &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; +} + +std::string BufferedResourceLoader::GenerateHeaders( + int64 first_byte_position, + int64 last_byte_position) { + // Construct the value for the range header. + std::string header; + if (first_byte_position > kPositionNotSpecified && + last_byte_position > kPositionNotSpecified) { + if (first_byte_position <= last_byte_position) { + header = base::StringPrintf("bytes=%" PRId64 "-%" PRId64, + first_byte_position, + last_byte_position); + } + } else if (first_byte_position > kPositionNotSpecified) { + header = base::StringPrintf("bytes=%" PRId64 "-", + first_byte_position); + } else if (last_byte_position > kPositionNotSpecified) { + NOTIMPLEMENTED() << "Suffix range not implemented"; + } + return header; +} + +void BufferedResourceLoader::DoneRead(int error) { + read_callback_->RunWithParams(Tuple1<int>(error)); + read_callback_.reset(); + if (buffer_.get() && 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(); +} + +void BufferedResourceLoader::DoneStart(int error) { + start_callback_->RunWithParams(Tuple1<int>(error)); + start_callback_.reset(); +} + +void BufferedResourceLoader::NotifyNetworkEvent() { + if (!event_callback_.is_null()) + event_callback_.Run(); +} + +bool BufferedResourceLoader::IsRangeRequest() const { + return first_byte_position_ != kPositionNotSpecified; +} + +void BufferedResourceLoader::Log() { + if (buffer_.get()) { + media_log_->AddEvent( + media_log_->CreateBufferedExtentsChangedEvent( + offset_ - buffer_->backward_bytes(), + offset_, + offset_ + buffer_->forward_bytes())); + } +} + +} // namespace webkit_media diff --git a/webkit/media/buffered_resource_loader.h b/webkit/media/buffered_resource_loader.h new file mode 100644 index 0000000..e92eb37 --- /dev/null +++ b/webkit/media/buffered_resource_loader.h @@ -0,0 +1,304 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_BUFFERED_RESOURCE_LOADER_H_ +#define WEBKIT_MEDIA_BUFFERED_RESOURCE_LOADER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "media/base/seekable_buffer.h" +#include "net/base/file_stream.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/media/web_data_source.h" +#include "webkit/media/webmediaplayer_impl.h" + +namespace media { +class MediaLog; +} + +namespace webkit_media { + +const int64 kPositionNotSpecified = -1; + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; + +// This class works inside demuxer thread and render thread. It contains a +// WebURLLoader and does the actual resource loading. This object does +// buffering internally, it defers the resource loading if buffer is full +// and un-defers the resource loading if it is under buffered. +class BufferedResourceLoader + : public base::RefCountedThreadSafe<BufferedResourceLoader>, + public WebKit::WebURLLoaderClient { + public: + // kNeverDefer - Aggresively buffer; never defer loading while paused. + // kReadThenDefer - Request only enough data to fulfill read requests. + // kThresholdDefer - Try to keep amount of buffered data at a threshold. + enum DeferStrategy { + kNeverDefer, + kReadThenDefer, + kThresholdDefer, + }; + + // |url| - URL for the resource to be loaded. + // |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, + int64 first_byte_position, + int64 last_byte_position, + DeferStrategy strategy, + int bitrate, + float playback_rate, + media::MediaLog* media_log); + + // Start the resource loading with the specified URL and range. + // This method operates in asynchronous mode. Once there's a response from the + // server, success or fail |callback| is called with the result. + // |callback| is called with the following values: + // - net::OK + // The request has started successfully. + // - net::ERR_FAILED + // The request has failed because of an error with the network. + // - net::ERR_INVALID_RESPONSE + // An invalid response is received from the server. + // - (Anything else) + // An error code that indicates the request has failed. + // |event_callback| is called when the response is completed, data is + // received, the request is suspended or resumed. + virtual void Start(net::OldCompletionCallback* callback, + const base::Closure& event_callback, + WebKit::WebFrame* frame); + + // Stop this loader, cancels and request and release internal buffer. + virtual void Stop(); + + // Reads the specified |read_size| from |position| into |buffer| and when + // the operation is done invoke |callback| with number of bytes read or an + // error code. If necessary, will temporarily increase forward capacity of + // buffer to accomodate an unusually large read. + // |callback| is called with the following values: + // - (Anything greater than or equal 0) + // Read was successful with the indicated number of bytes read. + // - net::ERR_FAILED + // The read has failed because of an error with the network. + // - net::ERR_CACHE_MISS + // The read was made too far away from the current buffered position. + virtual void Read(int64 position, int read_size, + uint8* buffer, net::OldCompletionCallback* callback); + + // Returns the position of the last byte buffered. Returns + // |kPositionNotSpecified| if such value is not available. + virtual int64 GetBufferedPosition(); + + // 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. + virtual int64 content_length(); + + // Gets the original size of the file requested. If this value is + // |kPositionNotSpecified|, then the size is unknown. + virtual int64 instance_size(); + + // Returns true if the server supports byte range requests. + virtual bool range_supported(); + + // Returns true if the resource loader is currently downloading data. + virtual bool is_downloading_data(); + + // Returns resulting URL. + virtual const GURL& url(); + + // Used to inject a mock used for unittests. + virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader); + + // WebKit::WebURLLoaderClient implementation. + virtual void willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse); + virtual void didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didDownloadData( + WebKit::WebURLLoader* loader, + int data_length); + virtual void didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length); + virtual void didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, int dataLength); + virtual void didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime); + virtual void didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError&); + + bool HasSingleOrigin() const; + + // Sets the defer strategy to the given value. + 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); + + protected: + friend class base::RefCountedThreadSafe<BufferedResourceLoader>; + virtual ~BufferedResourceLoader(); + + private: + friend class BufferedDataSourceTest2; + friend class BufferedResourceLoaderTest; + + // Updates the |buffer_|'s forward and backward capacities. + void UpdateBufferWindow(); + + // Returns true if we should defer resource loading, based + // on current buffering scheme. + bool ShouldEnableDefer(); + + // Returns true if we should enable resource loading, based + // on current buffering scheme. + bool ShouldDisableDefer(); + + // Updates deferring behavior based on current buffering scheme. + void UpdateDeferBehavior(); + + // Set defer state to |deferred| and cease/continue downloading data + // accordingly. + void SetDeferred(bool deferred); + + // Returns true if the current read request can be fulfilled by what is in + // the buffer. + bool CanFulfillRead(); + + // Returns true if the current read request will be fulfilled in the future. + bool WillFulfillRead(); + + // Method that does the actual read and calls the |read_callback_|, 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 WebKit::WebURLResponse& response); + + // Returns the value for a range request header using parameters + // |first_byte_position| and |last_byte_position|. Negative numbers other + // than |kPositionNotSpecified| are not allowed for |first_byte_position| and + // |last_byte_position|. |first_byte_position| should always be less than or + // equal to |last_byte_position| if they are both not |kPositionNotSpecified|. + // Empty string is returned on invalid parameters. + std::string GenerateHeaders(int64 first_byte_position, + int64 last_byte_position); + + // Done with read. Invokes the read callback and reset parameters for the + // read request. + void DoneRead(int error); + + // Done with start. Invokes the start callback and reset it. + void DoneStart(int error); + + // Calls |event_callback_| in terms of a network event. + void NotifyNetworkEvent(); + + bool HasPendingRead() { return read_callback_.get() != 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. + scoped_ptr<media::SeekableBuffer> buffer_; + + // True if resource loading was deferred. + bool deferred_; + + // Current buffering algorithm in place for resource loading. + DeferStrategy defer_strategy_; + + // True if resource loading has completed. + bool completed_; + + // True if a range request was made. + bool range_requested_; + + // True if Range header is supported. + bool range_supported_; + + // Forward capacity to reset to after an extension. + size_t saved_forward_capacity_; + + // Does the work of loading and sends data back to this client. + scoped_ptr<WebKit::WebURLLoader> url_loader_; + + GURL url_; + int64 first_byte_position_; + int64 last_byte_position_; + bool single_origin_; + + // Callback method that listens to network events. + base::Closure event_callback_; + + // Members used during request start. + scoped_ptr<net::OldCompletionCallback> start_callback_; + 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. + scoped_ptr<net::OldCompletionCallback> read_callback_; + int64 read_position_; + size_t 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_; + + // Used to ensure mocks for unittests are used instead of reset in Start(). + bool keep_test_loader_; + + // Bitrate of the media. Set to 0 if unknown. + int bitrate_; + + // Playback rate of the media. + float playback_rate_; + + scoped_refptr<media::MediaLog> media_log_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_BUFFERED_RESOURCE_LOADER_H_ diff --git a/webkit/media/buffered_resource_loader_unittest.cc b/webkit/media/buffered_resource_loader_unittest.cc new file mode 100644 index 0000000..e434dff --- /dev/null +++ b/webkit/media/buffered_resource_loader_unittest.cc @@ -0,0 +1,1111 @@ +// Copyright (c) 2011 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/stringprintf.h" +#include "media/base/media_log.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_util.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "webkit/media/buffered_resource_loader.h" +#include "webkit/mocks/mock_webframeclient.h" +#include "webkit/mocks/mock_weburlloader.h" + +using ::testing::InSequence; +using ::testing::Return; +using ::testing::Truly; +using ::testing::NiceMock; + +using WebKit::WebString; +using WebKit::WebURLError; +using WebKit::WebURLResponse; +using WebKit::WebView; + +using webkit_glue::MockWebFrameClient; +using webkit_glue::MockWebURLLoader; + +namespace webkit_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 char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing"; + +static const int kDataSize = 1024; +static const int kHttpOK = 200; +static const int kHttpPartialContent = 206; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +// Submit a request completed event to the resource loader due to request +// being canceled. Pretending the event is from external. +ACTION_P(RequestCanceled, loader) { + WebURLError error; + error.reason = net::ERR_ABORTED; + error.domain = WebString::fromUTF8(net::kErrorDomain); + loader->didFail(NULL, error); +} + +// Predicate that tests that request disallows compressed data. +static bool CorrectAcceptEncoding(const WebKit::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)) { + view_->initializeMainFrame(&client_); + + for (int i = 0; i < kDataSize; ++i) { + data_[i] = i; + } + } + + virtual ~BufferedResourceLoaderTest() { + view_->close(); + } + + void Initialize(const char* url, int first_position, int last_position) { + gurl_ = GURL(url); + first_position_ = first_position; + last_position_ = last_position; + + url_loader_ = new NiceMock<MockWebURLLoader>(); + loader_ = new BufferedResourceLoader( + gurl_, first_position_, last_position_, + BufferedResourceLoader::kThresholdDefer, 0, 0, + new media::MediaLog()); + loader_->SetURLLoaderForTest(url_loader_); + } + + void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) { + loader_->buffer_.reset( + new media::SeekableBuffer(backward_capacity, forward_capacity)); + } + + void Start() { + InSequence s; + EXPECT_CALL(*url_loader_, loadAsynchronously(Truly(CorrectAcceptEncoding), + loader_.get())); + loader_->Start( + NewCallback(this, &BufferedResourceLoaderTest::StartCallback), + base::Bind(&BufferedResourceLoaderTest::NetworkCallback, + base::Unretained(this)), + view_->mainFrame()); + } + + void FullResponse(int64 instance_size) { + FullResponse(instance_size, net::OK); + } + + void FullResponse(int64 instance_size, int status) { + EXPECT_CALL(*this, StartCallback(status)); + if (status != net::OK) { + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + } + + 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 == net::OK) { + 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(net::OK)); + + 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); + WebKit::WebURLRequest newRequest(redirectUrl); + WebKit::WebURLResponse redirectResponse(gurl_); + + loader_->willSendRequest(url_loader_, newRequest, redirectResponse); + + MessageLoop::current()->RunAllPending(); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + loader_->Stop(); + loader_ = NULL; + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + EXPECT_CALL(*this, NetworkCallback()) + .RetiresOnSaturation(); + loader_->didReceiveData(url_loader_, + reinterpret_cast<char*>(data_ + position), + size, + size); + } + + void WriteData(int size) { + EXPECT_CALL(*this, NetworkCallback()) + .RetiresOnSaturation(); + + scoped_array<char> data(new char[size]); + loader_->didReceiveData(url_loader_, data.get(), size, size); + } + + void WriteUntilThreshold() { + size_t buffered = loader_->buffer_->forward_bytes(); + size_t capacity = loader_->buffer_->forward_capacity(); + CHECK_LT(buffered, capacity); + + EXPECT_CALL(*this, NetworkCallback()); + WriteData(capacity - buffered); + ConfirmLoaderDeferredState(true); + } + + // Helper method to read from |loader_|. + void ReadLoader(int64 position, int size, uint8* buffer) { + loader_->Read(position, size, buffer, + NewCallback(this, &BufferedResourceLoaderTest::ReadCallback)); + } + + // 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(size_t backward_bytes, + size_t backward_capacity, + size_t forward_bytes, + size_t 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(size_t expected_backward_capacity) { + EXPECT_EQ(loader_->buffer_->backward_capacity(), + expected_backward_capacity); + } + + void ConfirmLoaderBufferForwardCapacity(size_t expected_forward_capacity) { + EXPECT_EQ(loader_->buffer_->forward_capacity(), expected_forward_capacity); + } + + void ConfirmLoaderDeferredState(bool expectedVal) { + EXPECT_EQ(loader_->deferred_, expectedVal); + } + + // Makes sure the |loader_| buffer window is in a reasonable range. + void CheckBufferWindowBounds() { + // Corresponds to value defined in buffered_resource_loader.cc. + static const size_t 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 size_t kMaxBufferCapacity = 20 * 1024 * 1024; + EXPECT_LE(loader_->buffer_->forward_capacity(), kMaxBufferCapacity); + EXPECT_LE(loader_->buffer_->backward_capacity(), kMaxBufferCapacity); + } + + MOCK_METHOD1(StartCallback, void(int error)); + MOCK_METHOD1(ReadCallback, void(int error)); + MOCK_METHOD0(NetworkCallback, void()); + + // Accessors for private variables on |loader_|. + size_t forward_bytes() { return loader_->buffer_->forward_bytes(); } + size_t forward_capacity() { return loader_->buffer_->forward_capacity(); } + + protected: + GURL gurl_; + int64 first_position_; + int64 last_position_; + + scoped_refptr<BufferedResourceLoader> loader_; + NiceMock<MockWebURLLoader>* url_loader_; + + MockWebFrameClient client_; + WebView* view_; + + 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(net::ERR_FAILED)); + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + + WebURLResponse response(gurl_); + response.setHTTPStatusCode(404); + response.setHTTPStatusText("Not Found\n"); + loader_->didReceiveResponse(url_loader_, response); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100, -1); + Start(); + FullResponse(1024, net::ERR_INVALID_RESPONSE); +} + +// 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(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*url_loader_, cancel()) + .WillOnce(RequestCanceled(loader_)); + + 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); +} + +// Tests the logic of sliding window for data buffering and reading. +TEST_F(BufferedResourceLoaderTest, BufferAndRead) { + Initialize(kHttpUrl, 10, 29); + loader_->UpdateDeferStrategy(BufferedResourceLoader::kThresholdDefer); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Writes 10 bytes and read them back. + WriteLoader(10, 10); + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Writes 10 bytes and read 2 times. + WriteLoader(20, 10); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(20, 5, buffer); + VerifyBuffer(buffer, 20, 5); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 5, buffer); + VerifyBuffer(buffer, 25, 5); + + // Read backward within buffer. + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Read backward outside buffer. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(9, 10, buffer); + + // Response has completed. + EXPECT_CALL(*this, NetworkCallback()); + loader_->didFinishLoading(url_loader_, 0); + + // Try to read 10 from position 25 will just return with 5 bytes. + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 10, buffer); + VerifyBuffer(buffer, 25, 5); + + // Try to read outside buffered range after request has completed. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(5, 10, buffer); + + // Try to read beyond the instance size. + EXPECT_CALL(*this, ReadCallback(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, 0x01500000); + + // Don't test for network callbacks (covered by *Strategy tests). + EXPECT_CALL(*this, NetworkCallback()) + .WillRepeatedly(Return()); + + uint8 buffer[20]; + InSequence s; + + // Write more than forward capacity and read it back. Ensure forward capacity + // gets reset. + WriteLoader(10, 20); + EXPECT_CALL(*this, ReadCallback(20)); + 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(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(net::ERR_CACHE_MISS)); + ReadLoader(0x00300000, 1, buffer); + + ConfirmLoaderBufferForwardCapacity(10); + + // Try to read more than maximum forward capacity. Ensure forward capacity is + // not changed. + EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); + 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(net::ERR_CACHE_MISS)); + 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(10)); + WriteLoader(10, 20); + VerifyBuffer(buffer, 10, 10); + + // The following call cannot be fulfilled now. + ReadLoader(25, 10, buffer); + + EXPECT_CALL(*this, ReadCallback(5)); + EXPECT_CALL(*this, NetworkCallback()); + loader_->didFinishLoading(url_loader_, 0); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); + EXPECT_CALL(*this, NetworkCallback()); + WebURLError error; + error.reason = net::ERR_FAILED; + 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); + ConfirmLoaderDeferredState(false); + + // Should move past window. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + 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. + // We should disable deferring after the read request, so expect + // a network event. + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(10, 10, buffer); + + // Receive almost enough data to cover, shouldn't defer. + WriteLoader(10, 9); + ConfirmLoaderDeferredState(false); + + // As soon as we have received enough data to fulfill the read, defer. + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(19, 1); + + ConfirmLoaderDeferredState(true); + VerifyBuffer(buffer, 10, 10); + + // Read again which should disable deferring since there should be nothing + // left in our internal buffer. + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(20, 10, buffer); + + ConfirmLoaderDeferredState(false); + + // Over-fulfill requested bytes, then deferring should be enabled again. + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(20, 40); + + ConfirmLoaderDeferredState(true); + 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, NetworkCallback()); + ReadLoader(80, 10, buffer); + + ConfirmLoaderDeferredState(false); + + // Fulfill requested bytes, then deferring should be enabled again. + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(60, 40); + + ConfirmLoaderDeferredState(true); + VerifyBuffer(buffer, 80, 10); + + StopWhenLoad(); +} + +// Tests the data buffering logic of ThresholdDefer strategy. +TEST_F(BufferedResourceLoaderTest, ThresholdDeferStrategy) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + InSequence s; + + // Initial expectation: we're not deferring. + ConfirmLoaderDeferredState(false); + + // Write half of threshold: keep not deferring. + WriteData(5); + ConfirmLoaderDeferredState(false); + + // Write rest of space until threshold: start deferring. + EXPECT_CALL(*this, NetworkCallback()); + WriteData(5); + ConfirmLoaderDeferredState(true); + + // Read a little from the buffer: keep deferring. + EXPECT_CALL(*this, ReadCallback(2)); + ReadLoader(10, 2, buffer); + ConfirmLoaderDeferredState(true); + + // Read a little more and go under threshold: stop deferring. + EXPECT_CALL(*this, ReadCallback(4)); + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(12, 4, buffer); + ConfirmLoaderDeferredState(false); + + // Write rest of space until threshold: start deferring. + EXPECT_CALL(*this, NetworkCallback()); + WriteData(6); + ConfirmLoaderDeferredState(true); + + // Read a little from the buffer: keep deferring. + EXPECT_CALL(*this, ReadCallback(4)); + ReadLoader(16, 4, buffer); + ConfirmLoaderDeferredState(true); + + 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(4)); + ReadLoader(10, 4, buffer); + ConfirmBufferState(4, 10, 6, 10); + ConfirmLoaderOffsets(14, 0, 0); + ConfirmLoaderDeferredState(true); + + // *** 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=14 [xxxxxx____] + // ^^^^ requested 4 bytes @ offset 20 + // AFTER + // offset=24 [__________] + // + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(20, 4, buffer); + ConfirmLoaderDeferredState(false); + + // Write a little, make sure we didn't start deferring. + WriteData(2); + ConfirmLoaderDeferredState(false); + + // Write the rest, read should complete. + EXPECT_CALL(*this, ReadCallback(4)); + WriteData(2); + ConfirmLoaderDeferredState(false); + + // POSTCONDITION + ConfirmBufferState(4, 10, 0, 10); + ConfirmLoaderOffsets(24, 0, 0); + ConfirmLoaderDeferredState(false); + + 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); + ConfirmLoaderDeferredState(true); + + // *** 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(net::ERR_CACHE_MISS)); + ReadLoader(9, 4, buffer); + + // POSTCONDITION + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + ConfirmLoaderDeferredState(true); + + 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); + ConfirmLoaderDeferredState(true); + + // *** TRICKY BUSINESS, PT. III *** + // Read past forward capacity but within threshold: 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, NetworkCallback()); + ReadLoader(24, 4, buffer); + ConfirmLoaderOffsets(20, 4, 8); + ConfirmLoaderDeferredState(false); + + // Write a little, make sure we didn't start deferring. + WriteData(4); + ConfirmLoaderDeferredState(false); + + // Write the rest, read should complete. + EXPECT_CALL(*this, ReadCallback(4)); + WriteData(4); + ConfirmLoaderDeferredState(false); + + // POSTCONDITION + ConfirmBufferState(8, 10, 0, 10); + ConfirmLoaderOffsets(28, 0, 0); + ConfirmLoaderDeferredState(false); + + 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); + ConfirmLoaderDeferredState(true); + + // *** TRICKY BUSINESS, PT. IV *** + // Read a large amount past forward capacity but within + // threshold: 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, NetworkCallback()); + ReadLoader(24, 12, buffer); + ConfirmLoaderOffsets(20, 4, 16); + ConfirmBufferState(10, 10, 0, 16); + ConfirmLoaderDeferredState(false); + + // Write a little, make sure we didn't start deferring. + WriteData(10); + ConfirmLoaderDeferredState(false); + + // Write the rest, read should complete and capacity should go back to normal. + EXPECT_CALL(*this, ReadCallback(12)); + WriteData(6); + ConfirmLoaderBufferForwardCapacity(10); + ConfirmLoaderDeferredState(false); + + // POSTCONDITION + ConfirmBufferState(6, 10, 0, 10); + ConfirmLoaderOffsets(36, 0, 0); + ConfirmLoaderDeferredState(false); + + 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(10)); + EXPECT_CALL(*this, NetworkCallback()); + ReadLoader(10, 10, buffer); + WriteUntilThreshold(); + ConfirmBufferState(10, 10, 10, 10); + ConfirmLoaderOffsets(20, 0, 0); + ConfirmLoaderDeferredState(true); + + // *** 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, NetworkCallback()); + ReadLoader(16, 18, buffer); + ConfirmLoaderOffsets(16, 0, 18); + ConfirmBufferState(6, 10, 14, 18); + ConfirmLoaderDeferredState(false); + + // Write a little, make sure we didn't start deferring. + WriteData(2); + ConfirmLoaderDeferredState(false); + + // Write the rest, read should complete and capacity should go back to normal. + EXPECT_CALL(*this, ReadCallback(18)); + WriteData(2); + ConfirmLoaderBufferForwardCapacity(10); + ConfirmLoaderDeferredState(false); + + // POSTCONDITION + ConfirmBufferState(4, 10, 0, 10); + ConfirmLoaderOffsets(34, 0, 0); + ConfirmLoaderDeferredState(false); + + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, Tricky_ReadPastThreshold) { + const size_t kSize = 5 * 1024 * 1024; + const size_t 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); + ConfirmLoaderDeferredState(true); + + // *** 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(net::ERR_CACHE_MISS)); + ReadLoader(kThreshold + 20, 10, buffer); + + // POSTCONDITION + ConfirmBufferState(0, 10, 10, 10); + ConfirmLoaderOffsets(10, 0, 0); + ConfirmLoaderDeferredState(true); + + StopWhenLoad(); +} + +// NOTE: This test will need to be reworked a little once +// http://code.google.com/p/chromium/issues/detail?id=72578 +// is fixed. +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); + 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); + 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(); +} + +} // namespace webkit_media diff --git a/webkit/media/media_stream_client.h b/webkit/media/media_stream_client.h new file mode 100644 index 0000000..ab1e767 --- /dev/null +++ b/webkit/media/media_stream_client.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_MEDIA_STREAM_CLIENT_H_ +#define WEBKIT_MEDIA_MEDIA_STREAM_CLIENT_H_ + +#include "base/memory/ref_counted.h" + +class GURL; + +namespace media { +class VideoDecoder; +class MessageLoopFactory; +} + +namespace webkit_media { + +// Define an interface for media stream client to get some information about +// the media stream. +class MediaStreamClient { + public: + virtual scoped_refptr<media::VideoDecoder> GetVideoDecoder( + const GURL& url, + media::MessageLoopFactory* message_loop_factory) = 0; + + protected: + virtual ~MediaStreamClient() {} +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_MEDIA_STREAM_CLIENT_H_ diff --git a/webkit/media/simple_data_source.cc b/webkit/media/simple_data_source.cc new file mode 100644 index 0000000..18b464a --- /dev/null +++ b/webkit/media/simple_data_source.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2011 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 "webkit/media/simple_data_source.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "media/base/filter_host.h" +#include "media/base/media_log.h" +#include "net/base/data_url.h" +#include "net/base/load_flags.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request_status.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitPlatformSupport.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderOptions.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/media/web_data_source_factory.h" + +using WebKit::WebString; +using WebKit::WebURLLoaderOptions; + +namespace webkit_media { + +static const char kDataScheme[] = "data"; + +static WebDataSource* NewSimpleDataSource(MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log) { + return new SimpleDataSource(render_loop, frame); +} + +// static +media::DataSourceFactory* SimpleDataSource::CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log, + const WebDataSourceBuildObserverHack& build_observer) { + return new WebDataSourceFactory(render_loop, frame, media_log, + &NewSimpleDataSource, build_observer); +} + +SimpleDataSource::SimpleDataSource( + MessageLoop* render_loop, + WebKit::WebFrame* frame) + : render_loop_(render_loop), + frame_(frame), + size_(-1), + single_origin_(true), + state_(UNINITIALIZED), + keep_test_loader_(false) { + DCHECK(render_loop); +} + +SimpleDataSource::~SimpleDataSource() { + base::AutoLock auto_lock(lock_); + DCHECK(state_ == UNINITIALIZED || state_ == STOPPED); +} + +void SimpleDataSource::set_host(media::FilterHost* host) { + DataSource::set_host(host); + + base::AutoLock auto_lock(lock_); + if (state_ == INITIALIZED) { + UpdateHostState(); + } +} + +void SimpleDataSource::Stop(const base::Closure& callback) { + base::AutoLock auto_lock(lock_); + state_ = STOPPED; + if (!callback.is_null()) + callback.Run(); + + // Post a task to the render thread to cancel loading the resource. + render_loop_->PostTask(FROM_HERE, + base::Bind(&SimpleDataSource::CancelTask, this)); +} + +void SimpleDataSource::Initialize( + const std::string& url, + const media::PipelineStatusCB& callback) { + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + scoped_refptr<SimpleDataSource> destruction_guard(this); + { + base::AutoLock auto_lock(lock_); + DCHECK_EQ(state_, UNINITIALIZED); + DCHECK(!callback.is_null()); + state_ = INITIALIZING; + initialize_cb_ = callback; + + // Validate the URL. + url_ = GURL(url); + if (!url_.is_valid() || !webkit_glue::IsProtocolSupportedForMedia(url_)) { + DoneInitialization_Locked(false); + return; + } + + // Post a task to the render thread to start loading the resource. + render_loop_->PostTask(FROM_HERE, + base::Bind(&SimpleDataSource::StartTask, this)); + } +} + +void SimpleDataSource::CancelInitialize() { + base::AutoLock auto_lock(lock_); + DCHECK(!initialize_cb_.is_null()); + state_ = STOPPED; + initialize_cb_.Reset(); + + // Post a task to the render thread to cancel loading the resource. + render_loop_->PostTask(FROM_HERE, + base::Bind(&SimpleDataSource::CancelTask, this)); +} + +void SimpleDataSource::Read(int64 position, + size_t size, + uint8* data, + const DataSource::ReadCallback& read_callback) { + DCHECK_GE(size_, 0); + if (position >= size_) { + read_callback.Run(0); + } else { + size_t copied = std::min(size, static_cast<size_t>(size_ - position)); + memcpy(data, data_.c_str() + position, copied); + read_callback.Run(copied); + } +} + +bool SimpleDataSource::GetSize(int64* size_out) { + *size_out = size_; + return true; +} + +bool SimpleDataSource::IsStreaming() { + return false; +} + +void SimpleDataSource::SetPreload(media::Preload preload) { +} + +void SimpleDataSource::SetBitrate(int bitrate) { +} + +void SimpleDataSource::SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader) { + url_loader_.reset(mock_loader); + keep_test_loader_ = true; +} + +void SimpleDataSource::willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse) { + DCHECK(MessageLoop::current() == render_loop_); + base::AutoLock auto_lock(lock_); + + // 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 SimpleDataSource::didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent) { + NOTIMPLEMENTED(); +} + +void SimpleDataSource::didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response) { + DCHECK(MessageLoop::current() == render_loop_); + size_ = response.expectedContentLength(); +} + +void SimpleDataSource::didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength) { + NOTIMPLEMENTED(); +} + +void SimpleDataSource::didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length) { + DCHECK(MessageLoop::current() == render_loop_); + data_.append(data, data_length); +} + +void SimpleDataSource::didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, + int dataLength) { + NOTIMPLEMENTED(); +} + +void SimpleDataSource::didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime) { + DCHECK(MessageLoop::current() == render_loop_); + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + scoped_refptr<SimpleDataSource> destruction_guard(this); + { + base::AutoLock auto_lock(lock_); + // It's possible this gets called after Stop(), in which case |host_| is no + // longer valid. + if (state_ == STOPPED) + return; + + // Otherwise we should be initializing and have created a WebURLLoader. + DCHECK_EQ(state_, INITIALIZING); + + // If we don't get a content length or the request has failed, report it + // as a network error. + if (size_ == -1) + size_ = data_.length(); + DCHECK(static_cast<size_t>(size_) == data_.length()); + + DoneInitialization_Locked(true); + } +} + +void SimpleDataSource::didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError& error) { + DCHECK(MessageLoop::current() == render_loop_); + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + scoped_refptr<SimpleDataSource> destruction_guard(this); + { + base::AutoLock auto_lock(lock_); + // It's possible this gets called after Stop(), in which case |host_| is no + // longer valid. + if (state_ == STOPPED) + return; + + // Otherwise we should be initializing and have created a WebURLLoader. + DCHECK_EQ(state_, INITIALIZING); + + // If we don't get a content length or the request has failed, report it + // as a network error. + if (size_ == -1) + size_ = data_.length(); + DCHECK(static_cast<size_t>(size_) == data_.length()); + + DoneInitialization_Locked(false); + } +} + +bool SimpleDataSource::HasSingleOrigin() { + DCHECK(MessageLoop::current() == render_loop_); + return single_origin_; +} + +void SimpleDataSource::Abort() { + DCHECK(MessageLoop::current() == render_loop_); + frame_ = NULL; +} + +void SimpleDataSource::StartTask() { + DCHECK(MessageLoop::current() == render_loop_); + // Reference to prevent destruction while inside the |initialize_cb_| + // call. This is a temporary fix to prevent crashes caused by holding the + // lock and running the destructor. + scoped_refptr<SimpleDataSource> destruction_guard(this); + { + base::AutoLock auto_lock(lock_); + + // We may have stopped. + if (state_ == STOPPED) + return; + + CHECK(frame_); + + DCHECK_EQ(state_, INITIALIZING); + + if (url_.SchemeIs(kDataScheme)) { + // If this using data protocol, we just need to decode it. + std::string mime_type, charset; + bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_); + + // Don't care about the mime-type just proceed if decoding was successful. + size_ = data_.length(); + DoneInitialization_Locked(success); + } else { + // Prepare the request. + WebKit::WebURLRequest request(url_); + request.setTargetType(WebKit::WebURLRequest::TargetIsMedia); + + frame_->setReferrerForRequest(request, WebKit::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")); + + // This flag is for unittests as we don't want to reset |url_loader| + if (!keep_test_loader_) { + WebURLLoaderOptions options; + options.allowCredentials = true; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyAllow; + url_loader_.reset(frame_->createAssociatedURLLoader(options)); + } + + // Start the resource loading. + url_loader_->loadAsynchronously(request, this); + } + } +} + +void SimpleDataSource::CancelTask() { + DCHECK(MessageLoop::current() == render_loop_); + base::AutoLock auto_lock(lock_); + DCHECK_EQ(state_, STOPPED); + + // Cancel any pending requests. + if (url_loader_.get()) { + url_loader_->cancel(); + url_loader_.reset(); + } +} + +void SimpleDataSource::DoneInitialization_Locked(bool success) { + lock_.AssertAcquired(); + media::PipelineStatus status = media::PIPELINE_ERROR_NETWORK; + if (success) { + state_ = INITIALIZED; + + UpdateHostState(); + status = media::PIPELINE_OK; + } else { + state_ = UNINITIALIZED; + url_loader_.reset(); + } + + initialize_cb_.Run(status); + initialize_cb_.Reset(); +} + +void SimpleDataSource::UpdateHostState() { + if (host()) { + host()->SetTotalBytes(size_); + host()->SetBufferedBytes(size_); + // If scheme is file or data, say we are loaded. + host()->SetLoaded(url_.SchemeIsFile() || url_.SchemeIs(kDataScheme)); + } +} + +} // namespace webkit_media diff --git a/webkit/media/simple_data_source.h b/webkit/media/simple_data_source.h new file mode 100644 index 0000000..ca1e78a --- /dev/null +++ b/webkit/media/simple_data_source.h @@ -0,0 +1,153 @@ +// Copyright (c) 2011 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. + +// An extremely simple implementation of DataSource that downloads the entire +// media resource into memory before signaling that initialization has finished. +// Primarily used to test <audio> and <video> with buffering/caching removed +// from the equation. + +#ifndef WEBKIT_MEDIA_SIMPLE_DATA_SOURCE_H_ +#define WEBKIT_MEDIA_SIMPLE_DATA_SOURCE_H_ + +#include <algorithm> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "media/base/filter_factories.h" +#include "media/base/filters.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" +#include "webkit/media/web_data_source.h" + +class MessageLoop; + +namespace media { +class MediaLog; +} + +namespace webkit_media { + +class SimpleDataSource + : public WebDataSource, + public WebKit::WebURLLoaderClient { + public: + // Creates a DataSourceFactory for building SimpleDataSource objects. + static media::DataSourceFactory* CreateFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log, + const WebDataSourceBuildObserverHack& build_observer); + + SimpleDataSource(MessageLoop* render_loop, WebKit::WebFrame* frame); + virtual ~SimpleDataSource(); + + // media::Filter implementation. + virtual void set_host(media::FilterHost* host) OVERRIDE; + virtual void Stop(const base::Closure& callback) OVERRIDE; + + // media::DataSource implementation. + virtual void Read(int64 position, + size_t size, + uint8* data, + const DataSource::ReadCallback& read_callback) OVERRIDE; + virtual bool GetSize(int64* size_out) OVERRIDE; + virtual bool IsStreaming() OVERRIDE; + virtual void SetPreload(media::Preload preload) OVERRIDE; + virtual void SetBitrate(int bitrate) OVERRIDE; + + // Used to inject a mock used for unittests. + virtual void SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader); + + // WebKit::WebURLLoaderClient implementations. + virtual void willSendRequest( + WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& newRequest, + const WebKit::WebURLResponse& redirectResponse); + virtual void didSendData( + WebKit::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse( + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didDownloadData( + WebKit::WebURLLoader* loader, + int dataLength); + virtual void didReceiveData( + WebKit::WebURLLoader* loader, + const char* data, + int dataLength, + int encodedDataLength); + virtual void didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, + const char* data, int dataLength); + virtual void didFinishLoading( + WebKit::WebURLLoader* loader, + double finishTime); + virtual void didFail( + WebKit::WebURLLoader* loader, + const WebKit::WebURLError&); + + // webkit_glue::WebDataSource implementation. + virtual void Initialize(const std::string& url, + const media::PipelineStatusCB& callback) OVERRIDE; + virtual void CancelInitialize() OVERRIDE; + virtual bool HasSingleOrigin() OVERRIDE; + virtual void Abort() OVERRIDE; + + private: + // Creates and starts the resource loading on the render thread. + void StartTask(); + + // Cancels and deletes the resource loading on the render thread. + void CancelTask(); + + // Perform initialization completion tasks under a lock. + void DoneInitialization_Locked(bool success); + + // Update host() stats like total bytes & buffered bytes. + void UpdateHostState(); + + // Primarily used for asserting the bridge is loading on the render thread. + MessageLoop* render_loop_; + + // A webframe for loading. + WebKit::WebFrame* frame_; + + // Does the work of loading and sends data back to this client. + scoped_ptr<WebKit::WebURLLoader> url_loader_; + + GURL url_; + std::string data_; + int64 size_; + bool single_origin_; + + // Simple state tracking variable. + enum State { + UNINITIALIZED, + INITIALIZING, + INITIALIZED, + STOPPED, + }; + State state_; + + // Used for accessing |state_|. + base::Lock lock_; + + // Filter callbacks. + media::PipelineStatusCB initialize_cb_; + + // Used to ensure mocks for unittests are used instead of reset in Start(). + bool keep_test_loader_; + + DISALLOW_COPY_AND_ASSIGN(SimpleDataSource); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_SIMPLE_DATA_SOURCE_H_ diff --git a/webkit/media/simple_data_source_unittest.cc b/webkit/media/simple_data_source_unittest.cc new file mode 100644 index 0000000..62b933c3 --- /dev/null +++ b/webkit/media/simple_data_source_unittest.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2011 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 "media/base/filters.h" +#include "media/base/mock_callback.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "net/base/net_errors.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "webkit/media/simple_data_source.h" +#include "webkit/mocks/mock_webframeclient.h" +#include "webkit/mocks/mock_weburlloader.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::WithArgs; + +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using WebKit::WebView; + +using webkit_glue::MockWebFrameClient; +using webkit_glue::MockWebURLLoader; + +namespace webkit_media { + +static const int kDataSize = 1024; +static const char kHttpUrl[] = "http://test"; +static const char kHttpsUrl[] = "https://test"; +static const char kFileUrl[] = "file://test"; +static const char kDataUrl[] = + "data:text/plain;base64,YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoK"; +static const char kDataUrlDecoded[] = "abcdefghijklmnopqrstuvwxyz"; +static const char kInvalidUrl[] = "whatever://test"; +static const char kHttpRedirectToSameDomainUrl1[] = "http://test/ing"; +static const char kHttpRedirectToSameDomainUrl2[] = "http://test/ing2"; +static const char kHttpRedirectToDifferentDomainUrl1[] = "http://test2"; +static const char kHttpRedirectToDifferentDomainUrl2[] = "http://test2/ing"; + +class SimpleDataSourceTest : public testing::Test { + public: + SimpleDataSourceTest() + : view_(WebView::create(NULL)) { + view_->initializeMainFrame(&client_); + + for (int i = 0; i < kDataSize; ++i) { + data_[i] = i; + } + } + + virtual ~SimpleDataSourceTest() { + view_->close(); + } + + void InitializeDataSource(const char* url, + const media::PipelineStatusCB& callback) { + gurl_ = GURL(url); + + url_loader_ = new NiceMock<MockWebURLLoader>(); + + data_source_ = new SimpleDataSource(MessageLoop::current(), + view_->mainFrame()); + + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + data_source_->SetURLLoaderForTest(url_loader_); + + data_source_->Initialize(url, callback); + MessageLoop::current()->RunAllPending(); + } + + void RequestSucceeded(bool is_loaded) { + WebURLResponse response(gurl_); + response.setExpectedContentLength(kDataSize); + + data_source_->didReceiveResponse(NULL, response); + int64 size; + EXPECT_TRUE(data_source_->GetSize(&size)); + EXPECT_EQ(kDataSize, size); + + for (int i = 0; i < kDataSize; ++i) { + data_source_->didReceiveData(NULL, data_ + i, 1, 1); + } + + EXPECT_CALL(host_, SetLoaded(is_loaded)); + + InSequence s; + EXPECT_CALL(host_, SetTotalBytes(kDataSize)); + EXPECT_CALL(host_, SetBufferedBytes(kDataSize)); + + data_source_->didFinishLoading(NULL, 0); + + // Let the tasks to be executed. + MessageLoop::current()->RunAllPending(); + } + + void RequestFailed() { + InSequence s; + + WebURLError error; + error.reason = net::ERR_FAILED; + data_source_->didFail(NULL, error); + + // Let the tasks to be executed. + MessageLoop::current()->RunAllPending(); + } + + void Redirect(const char* url) { + GURL redirectUrl(url); + WebURLRequest newRequest(redirectUrl); + WebURLResponse redirectResponse(gurl_); + + data_source_->willSendRequest(url_loader_, newRequest, redirectResponse); + + MessageLoop::current()->RunAllPending(); + } + + void DestroyDataSource() { + data_source_->Stop(media::NewExpectedClosure()); + MessageLoop::current()->RunAllPending(); + + data_source_ = NULL; + } + + void AsyncRead() { + for (int i = 0; i < kDataSize; ++i) { + uint8 buffer[1]; + + EXPECT_CALL(*this, ReadCallback(1)); + data_source_->Read( + i, 1, buffer, + base::Bind(&SimpleDataSourceTest::ReadCallback, + base::Unretained(this))); + EXPECT_EQ(static_cast<uint8>(data_[i]), buffer[0]); + } + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + protected: + GURL gurl_; + scoped_ptr<MessageLoop> message_loop_; + NiceMock<MockWebURLLoader>* url_loader_; + scoped_refptr<SimpleDataSource> data_source_; + StrictMock<media::MockFilterHost> host_; + + MockWebFrameClient client_; + WebView* view_; + + char data_[kDataSize]; + + DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest); +}; + +TEST_F(SimpleDataSourceTest, InitializeHTTP) { + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + RequestSucceeded(false); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeHTTPS) { + InitializeDataSource(kHttpsUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + RequestSucceeded(false); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeFile) { + InitializeDataSource(kFileUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + RequestSucceeded(true); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeData) { + url_loader_ = new NiceMock<MockWebURLLoader>(); + + data_source_ = new SimpleDataSource(MessageLoop::current(), + view_->mainFrame()); + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + data_source_->SetURLLoaderForTest(url_loader_); + + EXPECT_CALL(host_, SetLoaded(true)); + EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded))); + EXPECT_CALL(host_, SetBufferedBytes(sizeof(kDataUrlDecoded))); + + data_source_->Initialize(kDataUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + MessageLoop::current()->RunAllPending(); + + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, RequestFailed) { + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_ERROR_NETWORK)); + RequestFailed(); + DestroyDataSource(); +} + +static void OnStatusCB(bool* called, media::PipelineStatus status) { + *called = true; +} + +TEST_F(SimpleDataSourceTest, StopWhenDownloading) { + // The callback should be deleted, but not executed. + // TODO(scherkus): should this really be the behaviour? Seems strange... + bool was_called = false; + InitializeDataSource(kHttpUrl, base::Bind(&OnStatusCB, &was_called)); + + EXPECT_CALL(*url_loader_, cancel()); + DestroyDataSource(); + EXPECT_FALSE(was_called); +} + +TEST_F(SimpleDataSourceTest, AsyncRead) { + InitializeDataSource(kFileUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + RequestSucceeded(true); + AsyncRead(); + DestroyDataSource(); +} + +// NOTE: This test will need to be reworked a little once +// http://code.google.com/p/chromium/issues/detail?id=72578 +// is fixed. +TEST_F(SimpleDataSourceTest, HasSingleOrigin) { + // Make sure no redirect case works as expected. + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + RequestSucceeded(false); + EXPECT_TRUE(data_source_->HasSingleOrigin()); + DestroyDataSource(); + + // Test redirect to the same domain. + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + Redirect(kHttpRedirectToSameDomainUrl1); + RequestSucceeded(false); + EXPECT_TRUE(data_source_->HasSingleOrigin()); + DestroyDataSource(); + + // Test redirect twice to the same domain. + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + Redirect(kHttpRedirectToSameDomainUrl1); + Redirect(kHttpRedirectToSameDomainUrl2); + RequestSucceeded(false); + EXPECT_TRUE(data_source_->HasSingleOrigin()); + DestroyDataSource(); + + // Test redirect to a different domain. + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + Redirect(kHttpRedirectToDifferentDomainUrl1); + RequestSucceeded(false); + EXPECT_FALSE(data_source_->HasSingleOrigin()); + DestroyDataSource(); + + // Test redirect to the same domain and then to a different domain. + InitializeDataSource(kHttpUrl, + media::NewExpectedStatusCB(media::PIPELINE_OK)); + Redirect(kHttpRedirectToSameDomainUrl1); + Redirect(kHttpRedirectToDifferentDomainUrl1); + RequestSucceeded(false); + EXPECT_FALSE(data_source_->HasSingleOrigin()); + DestroyDataSource(); +} + +} // namespace webkit_media diff --git a/webkit/media/video_renderer_impl.cc b/webkit/media/video_renderer_impl.cc new file mode 100644 index 0000000..955512f --- /dev/null +++ b/webkit/media/video_renderer_impl.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2011 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 "webkit/media/video_renderer_impl.h" + +#include "base/logging.h" +#include "media/base/video_frame.h" +#include "media/base/yuv_convert.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "webkit/media/webmediaplayer_proxy.h" + +namespace webkit_media { + +VideoRendererImpl::VideoRendererImpl(bool pts_logging) + : last_converted_frame_(NULL), + pts_logging_(pts_logging) { +} + +VideoRendererImpl::~VideoRendererImpl() {} + +bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) { + natural_size_ = decoder->natural_size(); + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, + natural_size_.width(), natural_size_.height()); + bitmap_.allocPixels(); + bitmap_.eraseRGB(0x00, 0x00, 0x00); + bitmap_.setIsVolatile(true); + return true; +} + +void VideoRendererImpl::OnStop(const base::Closure& callback) { + if (!callback.is_null()) + callback.Run(); +} + +void VideoRendererImpl::OnFrameAvailable() { + proxy_->Repaint(); +} + +void VideoRendererImpl::SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) { + proxy_ = proxy; +} + +void VideoRendererImpl::SetRect(const gfx::Rect& rect) {} + +// This method is always called on the renderer's thread. +void VideoRendererImpl::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) { + scoped_refptr<media::VideoFrame> video_frame; + GetCurrentFrame(&video_frame); + if (!video_frame) { + SkPaint paint; + paint.setColor(SK_ColorBLACK); + canvas->drawRectCoords( + static_cast<float>(dest_rect.x()), + static_cast<float>(dest_rect.y()), + static_cast<float>(dest_rect.right()), + static_cast<float>(dest_rect.bottom()), + paint); + } else { + if (CanFastPaint(canvas, dest_rect)) { + FastPaint(video_frame, canvas, dest_rect); + } else { + SlowPaint(video_frame, canvas, dest_rect); + } + + // Presentation timestamp logging is primarily used to measure performance + // on low-end devices. When profiled on an Intel Atom N280 @ 1.66GHz this + // code had a ~63 microsecond perf hit when logging to a file (not stdout), + // which is neglible enough for measuring playback performance. + if (pts_logging_) + VLOG(1) << "pts=" << video_frame->GetTimestamp().InMicroseconds(); + } + + PutCurrentFrame(video_frame); +} + +// CanFastPaint is a helper method to determine the conditions for fast +// painting. The conditions are: +// 1. No skew in canvas matrix. +// 2. No flipping nor mirroring. +// 3. Canvas has pixel format ARGB8888. +// 4. Canvas is opaque. +// TODO(hclam): The fast paint method should support flipping and mirroring. +// Disable the flipping and mirroring checks once we have it. +bool VideoRendererImpl::CanFastPaint(SkCanvas* canvas, + const gfx::Rect& dest_rect) { + // Fast paint does not handle opacity value other than 1.0. Hence use slow + // paint if opacity is not 1.0. Since alpha = opacity * 0xFF, we check that + // alpha != 0xFF. + // + // Additonal notes: If opacity = 0.0, the chrome display engine does not try + // to render the video. So, this method is never called. However, if the + // opacity = 0.0001, alpha is again 0, but the display engine tries to render + // the video. If we use Fast paint, the video shows up with opacity = 1.0. + // Hence we use slow paint also in the case where alpha = 0. It would be ideal + // if rendering was never called even for cases where alpha is 0. Created + // bug 48090 for this. + SkCanvas::LayerIter layer_iter(canvas, false); + SkColor sk_color = layer_iter.paint().getColor(); + SkAlpha sk_alpha = SkColorGetA(sk_color); + if (sk_alpha != 0xFF) { + return false; + } + + const SkMatrix& total_matrix = canvas->getTotalMatrix(); + // Perform the following checks here: + // 1. Check for skewing factors of the transformation matrix. They should be + // zero. + // 2. Check for mirroring and flipping. Make sure they are greater than zero. + if (SkScalarNearlyZero(total_matrix.getSkewX()) && + SkScalarNearlyZero(total_matrix.getSkewY()) && + total_matrix.getScaleX() > 0 && + total_matrix.getScaleY() > 0) { + SkDevice* device = canvas->getDevice(); + const SkBitmap::Config config = device->config(); + + if (config == SkBitmap::kARGB_8888_Config && device->isOpaque()) { + return true; + } + } + + return false; +} + +void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame, + SkCanvas* canvas, + const gfx::Rect& dest_rect) { + // 1. Convert YUV frame to RGB. + base::TimeDelta timestamp = video_frame->GetTimestamp(); + if (video_frame != last_converted_frame_ || + timestamp != last_converted_timestamp_) { + last_converted_frame_ = video_frame; + last_converted_timestamp_ = timestamp; + DCHECK(video_frame->format() == media::VideoFrame::YV12 || + video_frame->format() == media::VideoFrame::YV16); + DCHECK(video_frame->stride(media::VideoFrame::kUPlane) == + video_frame->stride(media::VideoFrame::kVPlane)); + DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes); + bitmap_.lockPixels(); + media::YUVType yuv_type = + (video_frame->format() == media::VideoFrame::YV12) ? + media::YV12 : media::YV16; + media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane), + video_frame->data(media::VideoFrame::kUPlane), + video_frame->data(media::VideoFrame::kVPlane), + static_cast<uint8*>(bitmap_.getPixels()), + video_frame->width(), + video_frame->height(), + video_frame->stride(media::VideoFrame::kYPlane), + video_frame->stride(media::VideoFrame::kUPlane), + bitmap_.rowBytes(), + yuv_type); + bitmap_.notifyPixelsChanged(); + bitmap_.unlockPixels(); + } + + // 2. Paint the bitmap to canvas. + SkMatrix matrix; + matrix.setTranslate(static_cast<SkScalar>(dest_rect.x()), + static_cast<SkScalar>(dest_rect.y())); + if (dest_rect.width() != natural_size_.width() || + dest_rect.height() != natural_size_.height()) { + matrix.preScale(SkIntToScalar(dest_rect.width()) / + SkIntToScalar(natural_size_.width()), + SkIntToScalar(dest_rect.height()) / + SkIntToScalar(natural_size_.height())); + } + SkPaint paint; + paint.setFlags(SkPaint::kFilterBitmap_Flag); + canvas->drawBitmapMatrix(bitmap_, matrix, &paint); +} + +void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame, + SkCanvas* canvas, + const gfx::Rect& dest_rect) { + DCHECK(video_frame->format() == media::VideoFrame::YV12 || + video_frame->format() == media::VideoFrame::YV16); + DCHECK(video_frame->stride(media::VideoFrame::kUPlane) == + video_frame->stride(media::VideoFrame::kVPlane)); + DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes); + const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true); + media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ? + media::YV12 : media::YV16; + int y_shift = yuv_type; // 1 for YV12, 0 for YV16. + + // Create a rectangle backed by SkScalar. + SkRect scalar_dest_rect; + scalar_dest_rect.iset(dest_rect.x(), dest_rect.y(), + dest_rect.right(), dest_rect.bottom()); + + // Transform the destination rectangle to local coordinates. + const SkMatrix& local_matrix = canvas->getTotalMatrix(); + SkRect local_dest_rect; + local_matrix.mapRect(&local_dest_rect, scalar_dest_rect); + + // After projecting the destination rectangle to local coordinates, round + // the projected rectangle to integer values, this will give us pixel values + // of the rectangle. + SkIRect local_dest_irect, local_dest_irect_saved; + local_dest_rect.round(&local_dest_irect); + local_dest_rect.round(&local_dest_irect_saved); + + // Only does the paint if the destination rect intersects with the clip + // rect. + if (local_dest_irect.intersect(canvas->getTotalClip().getBounds())) { + // At this point |local_dest_irect| contains the rect that we should draw + // to within the clipping rect. + + // Calculate the address for the top left corner of destination rect in + // the canvas that we will draw to. The address is obtained by the base + // address of the canvas shifted by "left" and "top" of the rect. + uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) + + local_dest_irect.fTop * bitmap.rowBytes() + + local_dest_irect.fLeft * 4; + + // Project the clip rect to the original video frame, obtains the + // dimensions of the projected clip rect, "left" and "top" of the rect. + // The math here are all integer math so we won't have rounding error and + // write outside of the canvas. + // We have the assumptions of dest_rect.width() and dest_rect.height() + // being non-zero, these are valid assumptions since finding intersection + // above rejects empty rectangle so we just do a DCHECK here. + DCHECK_NE(0, dest_rect.width()); + DCHECK_NE(0, dest_rect.height()); + size_t frame_clip_width = local_dest_irect.width() * + video_frame->width() / local_dest_irect_saved.width(); + size_t frame_clip_height = local_dest_irect.height() * + video_frame->height() / local_dest_irect_saved.height(); + + // Project the "left" and "top" of the final destination rect to local + // coordinates of the video frame, use these values to find the offsets + // in the video frame to start reading. + size_t frame_clip_left = + (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) * + video_frame->width() / local_dest_irect_saved.width(); + size_t frame_clip_top = + (local_dest_irect.fTop - local_dest_irect_saved.fTop) * + video_frame->height() / local_dest_irect_saved.height(); + + // Use the "left" and "top" of the destination rect to locate the offset + // in Y, U and V planes. + size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) * + frame_clip_top + frame_clip_left; + // For format YV12, there is one U, V value per 2x2 block. + // For format YV16, there is one u, V value per 2x1 block. + size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) * + (frame_clip_top >> y_shift)) + (frame_clip_left >> 1); + uint8* frame_clip_y = + video_frame->data(media::VideoFrame::kYPlane) + y_offset; + uint8* frame_clip_u = + video_frame->data(media::VideoFrame::kUPlane) + uv_offset; + uint8* frame_clip_v = + video_frame->data(media::VideoFrame::kVPlane) + uv_offset; + bitmap.lockPixels(); + + // TODO(hclam): do rotation and mirroring here. + // TODO(fbarchard): switch filtering based on performance. + media::ScaleYUVToRGB32(frame_clip_y, + frame_clip_u, + frame_clip_v, + dest_rect_pointer, + frame_clip_width, + frame_clip_height, + local_dest_irect.width(), + local_dest_irect.height(), + video_frame->stride(media::VideoFrame::kYPlane), + video_frame->stride(media::VideoFrame::kUPlane), + bitmap.rowBytes(), + yuv_type, + media::ROTATE_0, + media::FILTER_BILINEAR); + bitmap.unlockPixels(); + } +} + +} // namespace webkit_media diff --git a/webkit/media/video_renderer_impl.h b/webkit/media/video_renderer_impl.h new file mode 100644 index 0000000..af67208 --- /dev/null +++ b/webkit/media/video_renderer_impl.h @@ -0,0 +1,82 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_ +#define WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_ + +#include "media/base/buffers.h" +#include "media/base/filters.h" +#include "media/filters/video_renderer_base.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h" +#include "ui/gfx/size.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "webkit/media/web_video_renderer.h" + +namespace webkit_media { + +// The video renderer implementation to be use by the media pipeline. It lives +// inside video renderer thread and also WebKit's main thread. We need to be +// extra careful about members shared by two different threads, especially +// video frame buffers. +class VideoRendererImpl : public WebVideoRenderer { + public: + explicit VideoRendererImpl(bool pts_logging); + virtual ~VideoRendererImpl(); + + // WebVideoRenderer implementation. + virtual void SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) OVERRIDE; + virtual void SetRect(const gfx::Rect& rect) OVERRIDE; + virtual void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) OVERRIDE; + + protected: + // VideoRendererBase implementation. + virtual bool OnInitialize(media::VideoDecoder* decoder) OVERRIDE; + virtual void OnStop(const base::Closure& callback) OVERRIDE; + virtual void OnFrameAvailable() OVERRIDE; + + private: + // Determine the conditions to perform fast paint. Returns true if we can do + // fast paint otherwise false. + bool CanFastPaint(SkCanvas* canvas, const gfx::Rect& dest_rect); + + // Slow paint does a YUV => RGB, and scaled blit in two separate operations. + void SlowPaint(media::VideoFrame* video_frame, + SkCanvas* canvas, + const gfx::Rect& dest_rect); + + // Fast paint does YUV => RGB, scaling, blitting all in one step into the + // canvas. It's not always safe and appropriate to perform fast paint. + // CanFastPaint() is used to determine the conditions. + void FastPaint(media::VideoFrame* video_frame, + SkCanvas* canvas, + const gfx::Rect& dest_rect); + + // Pointer to our parent object that is called to request repaints. + scoped_refptr<WebMediaPlayerProxy> proxy_; + + // An RGB bitmap used to convert the video frames. + SkBitmap bitmap_; + + // These two members are used to determine if the |bitmap_| contains + // an already converted image of the current frame. IMPORTANT NOTE: The + // value of |last_converted_frame_| must only be used for comparison purposes, + // and it should be assumed that the value of the pointer is INVALID unless + // it matches the pointer returned from GetCurrentFrame(). Even then, just + // to make sure, we compare the timestamp to be sure the bits in the + // |current_frame_bitmap_| are valid. + media::VideoFrame* last_converted_frame_; + base::TimeDelta last_converted_timestamp_; + + // The natural size of the video. + gfx::Size natural_size_; + + // Whether we're logging video presentation timestamps (PTS). + bool pts_logging_; + + DISALLOW_COPY_AND_ASSIGN(VideoRendererImpl); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_VIDEO_RENDERER_IMPL_H_ diff --git a/webkit/media/web_data_source.cc b/webkit/media/web_data_source.cc new file mode 100644 index 0000000..4795caa --- /dev/null +++ b/webkit/media/web_data_source.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2011 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/base/filters.h" +#include "webkit/media/web_data_source.h" + +namespace webkit_media { + +WebDataSource::WebDataSource() + : media::DataSource() { +} + +WebDataSource::~WebDataSource() { +} + +} // namespace webkit_media diff --git a/webkit/media/web_data_source.h b/webkit/media/web_data_source.h new file mode 100644 index 0000000..5cb27c4 --- /dev/null +++ b/webkit/media/web_data_source.h @@ -0,0 +1,59 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_WEB_DATA_SOURCE_H_ +#define WEBKIT_MEDIA_WEB_DATA_SOURCE_H_ + +#include "base/callback.h" +#include "media/base/filters.h" +#include "media/base/pipeline_status.h" + +namespace webkit_media { + +// An interface that allows WebMediaPlayerImpl::Proxy to communicate with the +// DataSource in the pipeline. +class WebDataSource : public media::DataSource { + public: + WebDataSource(); + virtual ~WebDataSource(); + + // Initialize this object using |url|. This object calls |callback| when + // initialization has completed. + virtual void Initialize(const std::string& url, + const media::PipelineStatusCB& callback) = 0; + + // Called to cancel initialization. The callback passed in Initialize() will + // be destroyed and will not be called after this method returns. Once this + // method returns, the object will be in an uninitialized state and + // Initialize() cannot be called again. The caller is expected to release + // its handle to this object and never call it again. + virtual void CancelInitialize() = 0; + + // Returns true if the media resource has a single origin, false otherwise. + // + // Method called on the render thread. + virtual bool HasSingleOrigin() = 0; + + // This method is used to unblock any read calls that would cause the + // media pipeline to stall. + // + // Method called on the render thread. + virtual void Abort() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(WebDataSource); +}; + +// Temporary hack to allow WebMediaPlayerImpl::Proxy::AddDataSource() to +// be called when WebDataSource objects are created. This can be removed +// once WebMediaPlayerImpl::Proxy is fixed so it doesn't have to track +// WebDataSources. Proxy only has to track WebDataSources so it can call Abort() +// on them at shutdown. Once cancellation is added to DataSource and pause +// support in Demuxers cancel pending reads, Proxy shouldn't have to keep +// a WebDataSource list or call Abort(). +typedef base::Callback<void(WebDataSource*)> WebDataSourceBuildObserverHack; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEB_DATA_SOURCE_H_ diff --git a/webkit/media/web_data_source_factory.cc b/webkit/media/web_data_source_factory.cc new file mode 100644 index 0000000..4df0dcf --- /dev/null +++ b/webkit/media/web_data_source_factory.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2011 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 "webkit/media/web_data_source_factory.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "media/base/media_log.h" + +namespace webkit_media { + +class WebDataSourceFactory::BuildRequest + : public media::AsyncDataSourceFactoryBase::BuildRequest { + public: + BuildRequest(const std::string& url, const BuildCallback& callback, + WebDataSource* data_source, + const WebDataSourceBuildObserverHack& build_observer); + virtual ~BuildRequest(); + + protected: + // AsyncDataSourceFactoryBase::BuildRequest method. + virtual void DoStart(); + + private: + void InitDone(media::PipelineStatus status); + + scoped_refptr<WebDataSource> data_source_; + WebDataSourceBuildObserverHack build_observer_; + + DISALLOW_COPY_AND_ASSIGN(BuildRequest); +}; + +WebDataSourceFactory::WebDataSourceFactory( + MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log, + FactoryFunction factory_function, + const WebDataSourceBuildObserverHack& build_observer) + : render_loop_(render_loop), + frame_(frame), + media_log_(media_log), + factory_function_(factory_function), + build_observer_(build_observer) { + DCHECK(render_loop_); + DCHECK(frame_); + DCHECK(media_log_); + DCHECK(factory_function_); +} + +WebDataSourceFactory::~WebDataSourceFactory() {} + +media::DataSourceFactory* WebDataSourceFactory::Clone() const { + return new WebDataSourceFactory(render_loop_, frame_, media_log_, + factory_function_, build_observer_); +} + +bool WebDataSourceFactory::AllowRequests() const { + return true; +} + +media::AsyncDataSourceFactoryBase::BuildRequest* +WebDataSourceFactory::CreateRequest(const std::string& url, + const BuildCallback& callback) { + WebDataSource* data_source = factory_function_(render_loop_, frame_, + media_log_); + + return new WebDataSourceFactory::BuildRequest(url, callback, data_source, + build_observer_); +} + +WebDataSourceFactory::BuildRequest::BuildRequest( + const std::string& url, + const BuildCallback& callback, + WebDataSource* data_source, + const WebDataSourceBuildObserverHack& build_observer) + : AsyncDataSourceFactoryBase::BuildRequest(url, callback), + data_source_(data_source), + build_observer_(build_observer) { +} + +WebDataSourceFactory::BuildRequest::~BuildRequest() { + if (data_source_.get()) { + data_source_->CancelInitialize(); + data_source_ = NULL; + } +} + +void WebDataSourceFactory::BuildRequest::DoStart() { + data_source_->Initialize(url(), base::Bind(&BuildRequest::InitDone, + base::Unretained(this))); +} + +void WebDataSourceFactory::BuildRequest::InitDone( + media::PipelineStatus status) { + scoped_refptr<WebDataSource> data_source; + + data_source = (status == media::PIPELINE_OK) ? data_source_ : NULL; + data_source_ = NULL; + + if (!build_observer_.is_null() && data_source.get()) { + build_observer_.Run(data_source.get()); + } + + RequestComplete(status, data_source); + // Don't do anything after this line. This object is deleted by + // RequestComplete(). +} + +} // namespace webkit_media diff --git a/webkit/media/web_data_source_factory.h b/webkit/media/web_data_source_factory.h new file mode 100644 index 0000000..2b0dd65 --- /dev/null +++ b/webkit/media/web_data_source_factory.h @@ -0,0 +1,59 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ +#define WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ + +#include "base/memory/ref_counted.h" +#include "media/base/async_filter_factory_base.h" +#include "webkit/media/web_data_source.h" + +class MessageLoop; + +namespace media { +class MediaLog; +} + +namespace WebKit { +class WebFrame; +} + +namespace webkit_media { + +class WebDataSourceFactory : public media::AsyncDataSourceFactoryBase { + public: + typedef WebDataSource* (*FactoryFunction)(MessageLoop* render_loop, + WebKit::WebFrame* frame, + media::MediaLog* media_log); + + WebDataSourceFactory(MessageLoop* render_loop, WebKit::WebFrame* frame, + media::MediaLog* media_log, + FactoryFunction factory_function, + const WebDataSourceBuildObserverHack& build_observer); + virtual ~WebDataSourceFactory(); + + // DataSourceFactory method. + virtual media::DataSourceFactory* Clone() const OVERRIDE; + + protected: + // AsyncDataSourceFactoryBase methods. + virtual bool AllowRequests() const OVERRIDE; + virtual AsyncDataSourceFactoryBase::BuildRequest* CreateRequest( + const std::string& url, const BuildCallback& callback) OVERRIDE; + + private: + class BuildRequest; + + MessageLoop* render_loop_; + WebKit::WebFrame* frame_; + scoped_refptr<media::MediaLog> media_log_; + FactoryFunction factory_function_; + WebDataSourceBuildObserverHack build_observer_; + + DISALLOW_COPY_AND_ASSIGN(WebDataSourceFactory); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_BUFFERED_DATA_SOURCE_FACTORY_H_ diff --git a/webkit/media/web_video_renderer.h b/webkit/media/web_video_renderer.h new file mode 100644 index 0000000..a1770fd --- /dev/null +++ b/webkit/media/web_video_renderer.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_WEB_VIDEO_RENDERER_H_ +#define WEBKIT_MEDIA_WEB_VIDEO_RENDERER_H_ + +#include "media/base/video_frame.h" +#include "media/filters/video_renderer_base.h" +#include "ui/gfx/rect.h" + +class SkCanvas; + +namespace webkit_media { + +class WebMediaPlayerProxy; + +// A specialized version of a VideoRenderer designed to be used inside WebKit. +class WebVideoRenderer : public media::VideoRendererBase { + public: + WebVideoRenderer() : media::VideoRendererBase() {} + virtual ~WebVideoRenderer() {} + + // Saves the reference to WebMediaPlayerProxy. + virtual void SetWebMediaPlayerProxy(WebMediaPlayerProxy* proxy) = 0; + + // This method is called with the same rect as the Paint() method and could + // be used by future implementations to implement an improved color space + + // scale code on a separate thread. Since we always do the stretch on the + // same thread as the Paint method, we just ignore the call for now. + // + // Method called on the render thread. + virtual void SetRect(const gfx::Rect& rect) = 0; + + // Paint the current front frame on the |canvas| stretching it to fit the + // |dest_rect|. + // + // Method called on the render thread. + virtual void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(WebVideoRenderer); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEB_VIDEO_RENDERER_H_ diff --git a/webkit/media/webkit_media.gypi b/webkit/media/webkit_media.gypi new file mode 100644 index 0000000..38327d8 --- /dev/null +++ b/webkit/media/webkit_media.gypi @@ -0,0 +1,50 @@ +# Copyright (c) 2011 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': [ + { + 'target_name': 'webkit_media', + 'type': 'static_library', + 'variables': { 'enable_wexit_time_destructors': 1, }, + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/media/media.gyp:yuv_convert', + '<(DEPTH)/skia/skia.gyp:skia', + ], + 'sources': [ + 'audio_decoder.cc', + 'audio_decoder.h', + 'buffered_data_source.cc', + 'buffered_data_source.h', + 'buffered_resource_loader.cc', + 'buffered_resource_loader.h', + 'media_stream_client.h', + 'simple_data_source.cc', + 'simple_data_source.h', + 'video_renderer_impl.cc', + 'video_renderer_impl.h', + 'web_data_source.cc', + 'web_data_source_factory.cc', + 'web_data_source_factory.h', + 'web_data_source.h', + 'web_video_renderer.h', + 'webmediaplayer_delegate.h', + 'webmediaplayer_impl.cc', + 'webmediaplayer_impl.h', + 'webmediaplayer_proxy.cc', + 'webmediaplayer_proxy.h', + 'webvideoframe_impl.cc', + 'webvideoframe_impl.h', + ], + 'conditions': [ + ['inside_chromium_build==0', { + 'dependencies': [ + '<(DEPTH)/webkit/support/setup_third_party.gyp:third_party_headers', + ], + }], + ], + }, + ], +} diff --git a/webkit/media/webmediaplayer_delegate.h b/webkit/media/webmediaplayer_delegate.h new file mode 100644 index 0000000..7fa3409 --- /dev/null +++ b/webkit/media/webmediaplayer_delegate.h @@ -0,0 +1,31 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_ +#define WEBKIT_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_ + +namespace webkit_media { + +class WebMediaPlayerImpl; + +// An interface to allow a WebMediaPlayerImpl to communicate changes of state +// to objects that need to know. +class WebMediaPlayerDelegate { + public: + WebMediaPlayerDelegate() {} + virtual ~WebMediaPlayerDelegate() {} + + // The specified player started playing media. + virtual void DidPlay(WebMediaPlayerImpl* player) {} + + // The specified player stopped playing media. + virtual void DidPause(WebMediaPlayerImpl* player) {} + + // The specified player was destroyed. Do not call any methods on it. + virtual void PlayerGone(WebMediaPlayerImpl* player) {} +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_DELEGATE_H_ diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc new file mode 100644 index 0000000..a4fe3e9 --- /dev/null +++ b/webkit/media/webmediaplayer_impl.cc @@ -0,0 +1,917 @@ +// Copyright (c) 2011 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 "webkit/media/webmediaplayer_impl.h" + +#include <limits> +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "media/base/composite_data_source_factory.h" +#include "media/base/filter_collection.h" +#include "media/base/limits.h" +#include "media/base/media_log.h" +#include "media/base/media_switches.h" +#include "media/base/pipeline_impl.h" +#include "media/base/video_frame.h" +#include "media/filters/chunk_demuxer_factory.h" +#include "media/filters/dummy_demuxer_factory.h" +#include "media/filters/ffmpeg_audio_decoder.h" +#include "media/filters/ffmpeg_demuxer_factory.h" +#include "media/filters/ffmpeg_video_decoder.h" +#include "media/filters/null_audio_renderer.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "v8/include/v8.h" +#include "webkit/media/buffered_data_source.h" +#include "webkit/media/media_stream_client.h" +#include "webkit/media/simple_data_source.h" +#include "webkit/media/video_renderer_impl.h" +#include "webkit/media/web_video_renderer.h" +#include "webkit/media/webmediaplayer_delegate.h" +#include "webkit/media/webmediaplayer_proxy.h" +#include "webkit/media/webvideoframe_impl.h" + +using WebKit::WebCanvas; +using WebKit::WebRect; +using WebKit::WebSize; +using media::PipelineStatus; + +namespace { + +// Amount of extra memory used by each player instance reported to V8. +// It is not exact number -- first, it differs on different platforms, +// and second, it is very hard to calculate. Instead, use some arbitrary +// value that will cause garbage collection from time to time. We don't want +// it to happen on every allocation, but don't want 5k players to sit in memory +// either. Looks that chosen constant achieves both goals, at least for audio +// objects. (Do not worry about video objects yet, JS programs do not create +// thousands of them...) +const int kPlayerExtraMemory = 1024 * 1024; + +// 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 float kMinRate = 0.0625f; +const float kMaxRate = 16.0f; + +// 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 ConvertSecondsToTimestamp(float seconds) { + float microseconds = seconds * base::Time::kMicrosecondsPerSecond; + float integer = ceilf(microseconds); + float difference = integer - microseconds; + + // Round down if difference is large enough. + if ((microseconds > 0 && difference > 0.5f) || + (microseconds <= 0 && difference >= 0.5f)) { + integer -= 1.0f; + } + + // Now we can safely cast to int64 microseconds. + return base::TimeDelta::FromMicroseconds(static_cast<int64>(integer)); +} + +} // namespace + +namespace webkit_media { + +WebMediaPlayerImpl::WebMediaPlayerImpl( + WebKit::WebMediaPlayerClient* client, + base::WeakPtr<WebMediaPlayerDelegate> delegate, + media::FilterCollection* collection, + media::MessageLoopFactory* message_loop_factory, + MediaStreamClient* media_stream_client, + media::MediaLog* media_log) + : network_state_(WebKit::WebMediaPlayer::Empty), + ready_state_(WebKit::WebMediaPlayer::HaveNothing), + main_loop_(NULL), + filter_collection_(collection), + pipeline_(NULL), + message_loop_factory_(message_loop_factory), + paused_(true), + seeking_(false), + playback_rate_(0.0f), + pending_seek_(false), + client_(client), + proxy_(NULL), + delegate_(delegate), + media_stream_client_(media_stream_client), + media_log_(media_log), + is_accelerated_compositing_active_(false), + incremented_externally_allocated_memory_(false) { + // Saves the current message loop. + DCHECK(!main_loop_); + main_loop_ = MessageLoop::current(); + media_log_->AddEvent( + media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_CREATED)); +} + +bool WebMediaPlayerImpl::Initialize( + WebKit::WebFrame* frame, + bool use_simple_data_source, + scoped_refptr<WebVideoRenderer> web_video_renderer) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + MessageLoop* pipeline_message_loop = + message_loop_factory_->GetMessageLoop("PipelineThread"); + if (!pipeline_message_loop) { + NOTREACHED() << "Could not start PipelineThread"; + return false; + } + + // Let V8 know we started new thread if we did not did it yet. + // Made separate task to avoid deletion of player currently being created. + // Also, delaying GC until after player starts gets rid of starting lag -- + // collection happens in parallel with playing. + // TODO(enal): remove when we get rid of per-audio-stream thread. + if (!incremented_externally_allocated_memory_) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&WebMediaPlayerImpl::IncrementExternallyAllocatedMemory, + AsWeakPtr())); + } + + is_accelerated_compositing_active_ = + frame->view()->isAcceleratedCompositingActive(); + + pipeline_ = new media::PipelineImpl(pipeline_message_loop, media_log_); + + // Also we want to be notified of |main_loop_| destruction. + main_loop_->AddDestructionObserver(this); + + // Creates the proxy. + proxy_ = new WebMediaPlayerProxy(main_loop_, this); + web_video_renderer->SetWebMediaPlayerProxy(proxy_); + proxy_->SetVideoRenderer(web_video_renderer); + + // Set our pipeline callbacks. + pipeline_->Init( + base::Bind(&WebMediaPlayerProxy::PipelineEndedCallback, + proxy_.get()), + base::Bind(&WebMediaPlayerProxy::PipelineErrorCallback, + proxy_.get()), + base::Bind(&WebMediaPlayerProxy::NetworkEventCallback, + proxy_.get())); + + // A simple data source that keeps all data in memory. + scoped_ptr<media::DataSourceFactory> simple_data_source_factory( + SimpleDataSource::CreateFactory(MessageLoop::current(), frame, + media_log_, + proxy_->GetBuildObserver())); + + // A sophisticated data source that does memory caching. + scoped_ptr<media::DataSourceFactory> buffered_data_source_factory( + BufferedDataSource::CreateFactory(MessageLoop::current(), frame, + media_log_, + proxy_->GetBuildObserver())); + + scoped_ptr<media::CompositeDataSourceFactory> data_source_factory( + new media::CompositeDataSourceFactory()); + + if (use_simple_data_source) { + data_source_factory->AddFactory(simple_data_source_factory.release()); + data_source_factory->AddFactory(buffered_data_source_factory.release()); + } else { + data_source_factory->AddFactory(buffered_data_source_factory.release()); + data_source_factory->AddFactory(simple_data_source_factory.release()); + } + + scoped_ptr<media::DemuxerFactory> demuxer_factory( + new media::FFmpegDemuxerFactory(data_source_factory.release(), + pipeline_message_loop)); + + std::string source_url = GetClient()->sourceURL().spec(); + + if (!source_url.empty()) { + demuxer_factory.reset( + new media::ChunkDemuxerFactory(source_url, + demuxer_factory.release(), + proxy_)); + } + filter_collection_->SetDemuxerFactory(demuxer_factory.release()); + + // Add in the default filter factories. + filter_collection_->AddAudioDecoder(new media::FFmpegAudioDecoder( + message_loop_factory_->GetMessageLoop("AudioDecoderThread"))); + filter_collection_->AddVideoDecoder(new media::FFmpegVideoDecoder( + message_loop_factory_->GetMessageLoop("VideoDecoderThread"))); + filter_collection_->AddAudioRenderer(new media::NullAudioRenderer()); + + return true; +} + +WebMediaPlayerImpl::~WebMediaPlayerImpl() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + Destroy(); + media_log_->AddEvent( + media_log_->CreateEvent(media::MediaLogEvent::WEBMEDIAPLAYER_DESTROYED)); + + if (delegate_) + delegate_->PlayerGone(this); + + // Finally tell the |main_loop_| we don't want to be notified of destruction + // event. + if (main_loop_) { + main_loop_->RemoveDestructionObserver(this); + } +} + +namespace { + +// Helper enum for reporting scheme histograms. +enum URLSchemeForHistogram { + kUnknownURLScheme, + kMissingURLScheme, + kHttpURLScheme, + kHttpsURLScheme, + kFtpURLScheme, + kChromeExtensionURLScheme, + kJavascriptURLScheme, + kFileURLScheme, + kBlobURLScheme, + kDataURLScheme, + kMaxURLScheme = kDataURLScheme // 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; + return kUnknownURLScheme; +} + +} // anonymous namespace + +void WebMediaPlayerImpl::load(const WebKit::WebURL& url) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + DCHECK(proxy_); + + UMA_HISTOGRAM_ENUMERATION("Media.URLScheme", URLScheme(url), kMaxURLScheme); + + if (media_stream_client_) { + bool has_video = false; + bool has_audio = false; + scoped_refptr<media::VideoDecoder> new_decoder = + media_stream_client_->GetVideoDecoder(url, message_loop_factory_.get()); + if (new_decoder.get()) { + // Remove the default decoder. + scoped_refptr<media::VideoDecoder> old_videodecoder; + filter_collection_->SelectVideoDecoder(&old_videodecoder); + filter_collection_->AddVideoDecoder(new_decoder.get()); + has_video = true; + } + + // TODO(wjia): add audio decoder handling when it's available. + if (has_video || has_audio) + filter_collection_->SetDemuxerFactory( + new media::DummyDemuxerFactory(has_video, has_audio)); + } + + // Handle any volume changes that occured before load(). + setVolume(GetClient()->volume()); + // Get the preload value. + setPreload(GetClient()->preload()); + + // Initialize the pipeline. + SetNetworkState(WebKit::WebMediaPlayer::Loading); + SetReadyState(WebKit::WebMediaPlayer::HaveNothing); + pipeline_->Start( + filter_collection_.release(), + url.spec(), + base::Bind(&WebMediaPlayerProxy::PipelineInitializationCallback, + proxy_.get())); + + media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec())); +} + +void WebMediaPlayerImpl::cancelLoad() { + DCHECK_EQ(main_loop_, MessageLoop::current()); +} + +void WebMediaPlayerImpl::play() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + paused_ = false; + pipeline_->SetPlaybackRate(playback_rate_); + + media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY)); + + if (delegate_) + delegate_->DidPlay(this); +} + +void WebMediaPlayerImpl::pause() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + paused_ = true; + pipeline_->SetPlaybackRate(0.0f); + paused_time_ = pipeline_->GetCurrentTime(); + + media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE)); + + if (delegate_) + delegate_->DidPause(this); +} + +bool WebMediaPlayerImpl::supportsFullscreen() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + return true; +} + +bool WebMediaPlayerImpl::supportsSave() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + return true; +} + +void WebMediaPlayerImpl::seek(float seconds) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // WebKit fires a seek(0) at the very start, however pipeline already does a + // seek(0) internally. Avoid doing seek(0) the second time because this will + // cause extra pre-rolling and will break servers without range request + // support. + // + // We still have to notify WebKit that time has changed otherwise + // HTMLMediaElement gets into an inconsistent state. + if (pipeline_->GetCurrentTime().ToInternalValue() == 0 && seconds == 0) { + GetClient()->timeChanged(); + return; + } + + if (seeking_) { + pending_seek_ = true; + pending_seek_seconds_ = seconds; + return; + } + + media_log_->AddEvent(media_log_->CreateSeekEvent(seconds)); + + base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds); + + // Update our paused time. + if (paused_) { + paused_time_ = seek_time; + } + + seeking_ = true; + + proxy_->DemuxerFlush(); + + // Kick off the asynchronous seek! + pipeline_->Seek( + seek_time, + base::Bind(&WebMediaPlayerProxy::PipelineSeekCallback, + proxy_.get())); +} + +void WebMediaPlayerImpl::setEndTime(float seconds) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // TODO(hclam): add method call when it has been implemented. + return; +} + +void WebMediaPlayerImpl::setRate(float rate) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // TODO(kylep): Remove when support for negatives is added. Also, modify the + // following checks so rewind uses reasonable values also. + if (rate < 0.0f) + return; + + // Limit rates to reasonable values by clamping. + if (rate != 0.0f) { + if (rate < kMinRate) + rate = kMinRate; + else if (rate > kMaxRate) + rate = kMaxRate; + } + + playback_rate_ = rate; + if (!paused_) { + pipeline_->SetPlaybackRate(rate); + } +} + +void WebMediaPlayerImpl::setVolume(float volume) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + pipeline_->SetVolume(volume); +} + +void WebMediaPlayerImpl::setVisible(bool visible) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // TODO(hclam): add appropriate method call when pipeline has it implemented. + return; +} + +#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ + COMPILE_ASSERT(static_cast<int>(WebKit::WebMediaPlayer::webkit_name) == \ + static_cast<int>(media::chromium_name), \ + mismatching_enums) +COMPILE_ASSERT_MATCHING_ENUM(None, NONE); +COMPILE_ASSERT_MATCHING_ENUM(MetaData, METADATA); +COMPILE_ASSERT_MATCHING_ENUM(Auto, AUTO); + +void WebMediaPlayerImpl::setPreload(WebKit::WebMediaPlayer::Preload preload) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + pipeline_->SetPreload(static_cast<media::Preload>(preload)); +} + +bool WebMediaPlayerImpl::totalBytesKnown() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->GetTotalBytes() != 0; +} + +bool WebMediaPlayerImpl::hasVideo() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->HasVideo(); +} + +bool WebMediaPlayerImpl::hasAudio() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->HasAudio(); +} + +WebKit::WebSize WebMediaPlayerImpl::naturalSize() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + gfx::Size size; + pipeline_->GetNaturalVideoSize(&size); + return WebKit::WebSize(size); +} + +bool WebMediaPlayerImpl::paused() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->GetPlaybackRate() == 0.0f; +} + +bool WebMediaPlayerImpl::seeking() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + if (ready_state_ == WebKit::WebMediaPlayer::HaveNothing) + return false; + + return seeking_; +} + +float WebMediaPlayerImpl::duration() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + base::TimeDelta duration = pipeline_->GetMediaDuration(); + if (duration.InMicroseconds() == media::Limits::kMaxTimeInMicroseconds) + return std::numeric_limits<float>::infinity(); + return static_cast<float>(duration.InSecondsF()); +} + +float WebMediaPlayerImpl::currentTime() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + if (paused_) { + return static_cast<float>(paused_time_.InSecondsF()); + } + return static_cast<float>(pipeline_->GetCurrentTime().InSecondsF()); +} + +int WebMediaPlayerImpl::dataRate() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // TODO(hclam): Add this method call if pipeline has it in the interface. + return 0; +} + +WebKit::WebMediaPlayer::NetworkState WebMediaPlayerImpl::networkState() const { + return network_state_; +} + +WebKit::WebMediaPlayer::ReadyState WebMediaPlayerImpl::readyState() const { + return ready_state_; +} + +const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // Update buffered_ with the most recent buffered time. + if (buffered_.size() > 0) { + float buffered_time = static_cast<float>( + pipeline_->GetBufferedTime().InSecondsF()); + if (buffered_time >= buffered_[0].start) + buffered_[0].end = buffered_time; + } + + return buffered_; +} + +float WebMediaPlayerImpl::maxTimeSeekable() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // If we are performing streaming, we report that we cannot seek at all. + // We are using this flag to indicate if the data source supports seeking + // or not. We should be able to seek even if we are performing streaming. + // TODO(hclam): We need to update this when we have better caching. + if (pipeline_->IsStreaming()) + return 0.0f; + return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF()); +} + +unsigned long long WebMediaPlayerImpl::bytesLoaded() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->GetBufferedBytes(); +} + +unsigned long long WebMediaPlayerImpl::totalBytes() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + return pipeline_->GetTotalBytes(); +} + +void WebMediaPlayerImpl::setSize(const WebSize& size) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + DCHECK(proxy_); + + proxy_->SetSize(gfx::Rect(0, 0, size.width, size.height)); +} + +void WebMediaPlayerImpl::paint(WebCanvas* canvas, + const WebRect& rect) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + DCHECK(proxy_); + +#if WEBKIT_USING_SKIA + proxy_->Paint(canvas, rect); +#elif WEBKIT_USING_CG + // Get the current scaling in X and Y. + CGAffineTransform mat = CGContextGetCTM(canvas); + float scale_x = sqrt(mat.a * mat.a + mat.b * mat.b); + float scale_y = sqrt(mat.c * mat.c + mat.d * mat.d); + float inverse_scale_x = SkScalarNearlyZero(scale_x) ? 0.0f : 1.0f / scale_x; + float inverse_scale_y = SkScalarNearlyZero(scale_y) ? 0.0f : 1.0f / scale_y; + int scaled_width = static_cast<int>(rect.width * fabs(scale_x)); + int scaled_height = static_cast<int>(rect.height * fabs(scale_y)); + + // Make sure we don't create a huge canvas. + // TODO(hclam): Respect the aspect ratio. + if (scaled_width > static_cast<int>(media::Limits::kMaxCanvas)) + scaled_width = media::Limits::kMaxCanvas; + if (scaled_height > static_cast<int>(media::Limits::kMaxCanvas)) + scaled_height = media::Limits::kMaxCanvas; + + // If there is no preexisting platform canvas, or if the size has + // changed, recreate the canvas. This is to avoid recreating the bitmap + // buffer over and over for each frame of video. + if (!skia_canvas_.get() || + skia_canvas_->getDevice()->width() != scaled_width || + skia_canvas_->getDevice()->height() != scaled_height) { + skia_canvas_.reset( + new skia::PlatformCanvas(scaled_width, scaled_height, true)); + } + + // Draw to our temporary skia canvas. + gfx::Rect normalized_rect(scaled_width, scaled_height); + proxy_->Paint(skia_canvas_.get(), normalized_rect); + + // The mac coordinate system is flipped vertical from the normal skia + // coordinates. During painting of the frame, flip the coordinates + // system and, for simplicity, also translate the clip rectangle to + // start at 0,0. + CGContextSaveGState(canvas); + CGContextTranslateCTM(canvas, rect.x, rect.height + rect.y); + CGContextScaleCTM(canvas, inverse_scale_x, -inverse_scale_y); + + // We need a local variable CGRect version for DrawToContext. + CGRect normalized_cgrect = + CGRectMake(normalized_rect.x(), normalized_rect.y(), + normalized_rect.width(), normalized_rect.height()); + + // Copy the frame rendered to our temporary skia canvas onto the passed in + // canvas. + skia::DrawToNativeContext(skia_canvas_.get(), canvas, 0, 0, + &normalized_cgrect); + + CGContextRestoreGState(canvas); +#else + NOTIMPLEMENTED() << "We only support rendering to skia or CG"; +#endif +} + +bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const { + if (proxy_) + return proxy_->HasSingleOrigin(); + return true; +} + +WebKit::WebMediaPlayer::MovieLoadType + WebMediaPlayerImpl::movieLoadType() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // TODO(hclam): If the pipeline is performing streaming, we say that this is + // a live stream. But instead it should be a StoredStream if we have proper + // caching. + if (pipeline_->IsStreaming()) + return WebKit::WebMediaPlayer::LiveStream; + return WebKit::WebMediaPlayer::Unknown; +} + +float WebMediaPlayerImpl::mediaTimeForTimeValue(float timeValue) const { + return ConvertSecondsToTimestamp(timeValue).InSecondsF(); +} + +unsigned WebMediaPlayerImpl::decodedFrameCount() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + media::PipelineStatistics stats = pipeline_->GetStatistics(); + return stats.video_frames_decoded; +} + +unsigned WebMediaPlayerImpl::droppedFrameCount() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + media::PipelineStatistics stats = pipeline_->GetStatistics(); + return stats.video_frames_dropped; +} + +unsigned WebMediaPlayerImpl::audioDecodedByteCount() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + media::PipelineStatistics stats = pipeline_->GetStatistics(); + return stats.audio_bytes_decoded; +} + +unsigned WebMediaPlayerImpl::videoDecodedByteCount() const { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + media::PipelineStatistics stats = pipeline_->GetStatistics(); + return stats.video_bytes_decoded; +} + +WebKit::WebVideoFrame* WebMediaPlayerImpl::getCurrentFrame() { + scoped_refptr<media::VideoFrame> video_frame; + proxy_->GetCurrentFrame(&video_frame); + if (video_frame.get()) + return new WebVideoFrameImpl(video_frame); + return NULL; +} + +void WebMediaPlayerImpl::putCurrentFrame( + WebKit::WebVideoFrame* web_video_frame) { + if (web_video_frame) { + scoped_refptr<media::VideoFrame> video_frame( + WebVideoFrameImpl::toVideoFrame(web_video_frame)); + proxy_->PutCurrentFrame(video_frame); + delete web_video_frame; + } +} + +bool WebMediaPlayerImpl::sourceAppend(const unsigned char* data, + unsigned length) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + return proxy_->DemuxerAppend(data, length); +} + +void WebMediaPlayerImpl::sourceEndOfStream( + WebKit::WebMediaPlayer::EndOfStreamStatus status) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + media::PipelineStatus pipeline_status = media::PIPELINE_OK; + + switch(status) { + case WebKit::WebMediaPlayer::EosNoError: + break; + case WebKit::WebMediaPlayer::EosNetworkError: + pipeline_status = media::PIPELINE_ERROR_NETWORK; + break; + case WebKit::WebMediaPlayer::EosDecodeError: + pipeline_status = media::PIPELINE_ERROR_DECODE; + break; + default: + NOTIMPLEMENTED(); + } + + proxy_->DemuxerEndOfStream(pipeline_status); +} + +void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() { + Destroy(); + main_loop_ = NULL; +} + +void WebMediaPlayerImpl::Repaint() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + GetClient()->repaint(); +} + +void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + if (status == media::PIPELINE_OK) { + // Only keep one time range starting from 0. + WebKit::WebTimeRanges new_buffered(static_cast<size_t>(1)); + new_buffered[0].start = 0.0f; + new_buffered[0].end = + static_cast<float>(pipeline_->GetMediaDuration().InSecondsF()); + buffered_.swap(new_buffered); + + if (hasVideo()) { + UMA_HISTOGRAM_BOOLEAN("Media.AcceleratedCompositingActive", + is_accelerated_compositing_active_); + } + + if (pipeline_->IsLoaded()) { + SetNetworkState(WebKit::WebMediaPlayer::Loaded); + } + + // Since we have initialized the pipeline, say we have everything otherwise + // we'll remain either loading/idle. + // TODO(hclam): change this to report the correct status. + SetReadyState(WebKit::WebMediaPlayer::HaveMetadata); + SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData); + } else { + // TODO(hclam): should use |status| to determine the state + // properly and reports error using MediaError. + // WebKit uses FormatError to indicate an error for bogus URL or bad file. + // Since we are at the initialization stage we can safely treat every error + // as format error. Should post a task to call to |webmediaplayer_|. + SetNetworkState(WebKit::WebMediaPlayer::FormatError); + } + + // Repaint to trigger UI update. + Repaint(); +} + +void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + seeking_ = false; + if (pending_seek_) { + pending_seek_ = false; + seek(pending_seek_seconds_); + return; + } + + if (status == media::PIPELINE_OK) { + // Update our paused time. + if (paused_) { + paused_time_ = pipeline_->GetCurrentTime(); + } + + SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData); + GetClient()->timeChanged(); + } +} + +void WebMediaPlayerImpl::OnPipelineEnded(PipelineStatus status) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + if (status == media::PIPELINE_OK) { + GetClient()->timeChanged(); + } +} + +void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + switch (error) { + case media::PIPELINE_OK: + LOG(DFATAL) << "PIPELINE_OK isn't an error!"; + break; + + case media::PIPELINE_ERROR_NETWORK: + SetNetworkState(WebMediaPlayer::NetworkError); + break; + + case media::PIPELINE_ERROR_INITIALIZATION_FAILED: + case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING: + case media::PIPELINE_ERROR_COULD_NOT_RENDER: + case media::PIPELINE_ERROR_URL_NOT_FOUND: + case media::PIPELINE_ERROR_READ: + case media::DEMUXER_ERROR_COULD_NOT_OPEN: + case media::DEMUXER_ERROR_COULD_NOT_PARSE: + case media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS: + case media::DEMUXER_ERROR_COULD_NOT_CREATE_THREAD: + case media::DECODER_ERROR_NOT_SUPPORTED: + case media::DATASOURCE_ERROR_URL_NOT_SUPPORTED: + // Format error. + SetNetworkState(WebMediaPlayer::FormatError); + break; + + case media::PIPELINE_ERROR_DECODE: + case media::PIPELINE_ERROR_ABORT: + case media::PIPELINE_ERROR_OUT_OF_MEMORY: + case media::PIPELINE_ERROR_AUDIO_HARDWARE: + case media::PIPELINE_ERROR_OPERATION_PENDING: + case media::PIPELINE_ERROR_INVALID_STATE: + // Decode error. + SetNetworkState(WebMediaPlayer::DecodeError); + break; + } + + // Repaint to trigger UI update. + Repaint(); +} + +void WebMediaPlayerImpl::OnNetworkEvent(bool is_downloading_data) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + if (is_downloading_data) + SetNetworkState(WebKit::WebMediaPlayer::Loading); + else + SetNetworkState(WebKit::WebMediaPlayer::Idle); +} + +void WebMediaPlayerImpl::OnDemuxerOpened() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + GetClient()->sourceOpened(); +} + +void WebMediaPlayerImpl::SetNetworkState( + WebKit::WebMediaPlayer::NetworkState state) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + // Always notify to ensure client has the latest value. + network_state_ = state; + GetClient()->networkStateChanged(); +} + +void WebMediaPlayerImpl::SetReadyState( + WebKit::WebMediaPlayer::ReadyState state) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + // Always notify to ensure client has the latest value. + ready_state_ = state; + GetClient()->readyStateChanged(); +} + +void WebMediaPlayerImpl::Destroy() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + // Tell the data source to abort any pending reads so that the pipeline is + // not blocked when issuing stop commands to the other filters. + if (proxy_) { + proxy_->AbortDataSources(); + proxy_->DemuxerShutdown(); + } + + // Make sure to kill the pipeline so there's no more media threads running. + // Note: stopping the pipeline might block for a long time. + if (pipeline_) { + media::PipelineStatusNotification note; + pipeline_->Stop(note.Callback()); + note.Wait(); + + // Let V8 know we are not using extra resources anymore. + if (incremented_externally_allocated_memory_) { + v8::V8::AdjustAmountOfExternalAllocatedMemory(-kPlayerExtraMemory); + incremented_externally_allocated_memory_ = false; + } + } + + message_loop_factory_.reset(); + + // And then detach the proxy, it may live on the render thread for a little + // longer until all the tasks are finished. + if (proxy_) { + proxy_->Detach(); + proxy_ = NULL; + } +} + +WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + DCHECK(client_); + return client_; +} + +void WebMediaPlayerImpl::IncrementExternallyAllocatedMemory() { + DCHECK_EQ(main_loop_, MessageLoop::current()); + incremented_externally_allocated_memory_ = true; + v8::V8::AdjustAmountOfExternalAllocatedMemory(kPlayerExtraMemory); +} + +} // namespace webkit_media diff --git a/webkit/media/webmediaplayer_impl.h b/webkit/media/webmediaplayer_impl.h new file mode 100644 index 0000000..d7e7296 --- /dev/null +++ b/webkit/media/webmediaplayer_impl.h @@ -0,0 +1,276 @@ +// Copyright (c) 2011 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. + +// Delegate calls from WebCore::MediaPlayerPrivate to Chrome's video player. +// It contains PipelineImpl which is the actual media player pipeline, it glues +// the media player pipeline, data source, audio renderer and renderer. +// PipelineImpl would creates multiple threads and access some public methods +// of this class, so we need to be extra careful about concurrent access of +// methods and members. +// +// WebMediaPlayerImpl works with multiple objects, the most important ones are: +// +// media::PipelineImpl +// The media playback pipeline. +// +// WebVideoRenderer +// Video renderer object. +// +// WebKit::WebMediaPlayerClient +// WebKit client of this media player object. +// +// The following diagram shows the relationship of these objects: +// (note: ref-counted reference is marked by a "r".) +// +// WebMediaPlayerClient (WebKit object) +// ^ +// | +// WebMediaPlayerImpl ---> PipelineImpl +// | ^ | +// | | v r +// | | WebVideoRenderer +// | | | ^ r +// | r | v r | +// '---> WebMediaPlayerProxy --' +// +// Notice that WebMediaPlayerProxy and WebVideoRenderer are referencing each +// other. This interdependency has to be treated carefully. +// +// Other issues: +// During tear down of the whole browser or a tab, the DOM tree may not be +// destructed nicely, and there will be some dangling media threads trying to +// the main thread, so we need this class to listen to destruction event of the +// main thread and cleanup the media threads when the even is received. Also +// at destruction of this class we will need to unhook it from destruction event +// list of the main thread. + +#ifndef WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_ +#define WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "media/base/filters.h" +#include "media/base/message_loop_factory.h" +#include "media/base/pipeline.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayerClient.h" + +namespace WebKit { +class WebFrame; +} + +namespace media { +class MediaLog; +} + +namespace webkit_media { + +class MediaStreamClient; +class WebMediaPlayerDelegate; +class WebMediaPlayerProxy; +class WebVideoRenderer; + +class WebMediaPlayerImpl + : public WebKit::WebMediaPlayer, + public MessageLoop::DestructionObserver, + public base::SupportsWeakPtr<WebMediaPlayerImpl> { + public: + // Construct a WebMediaPlayerImpl with reference to the client, and media + // filter collection. By providing the filter collection the implementor can + // provide more specific media filters that does resource loading and + // rendering. |collection| should contain filter factories for: + // 1. Data source + // 2. Audio renderer + // 3. Video renderer (optional) + // + // There are some default filters provided by this method: + // 1. FFmpeg demuxer + // 2. FFmpeg audio decoder + // 3. FFmpeg video decoder + // 4. Video renderer + // 5. Null audio renderer + // The video renderer provided by this class is using the graphics context + // provided by WebKit to perform renderering. The simple data source does + // resource loading by loading the whole resource object into memory. Null + // audio renderer is a fake audio device that plays silence. Provider of the + // |collection| can override the default filters by adding extra filters to + // |collection| before calling this method. + // + // Callers must call |Initialize()| before they can use the object. + WebMediaPlayerImpl(WebKit::WebMediaPlayerClient* client, + base::WeakPtr<WebMediaPlayerDelegate> delegate, + media::FilterCollection* collection, + media::MessageLoopFactory* message_loop_factory, + MediaStreamClient* media_stream_client, + media::MediaLog* media_log); + virtual ~WebMediaPlayerImpl(); + + // Finalizes initialization of the object. + bool Initialize( + WebKit::WebFrame* frame, + bool use_simple_data_source, + scoped_refptr<WebVideoRenderer> web_video_renderer); + + virtual void load(const WebKit::WebURL& url); + virtual void cancelLoad(); + + // Playback controls. + virtual void play(); + virtual void pause(); + virtual bool supportsFullscreen() const; + virtual bool supportsSave() const; + virtual void seek(float seconds); + virtual void setEndTime(float seconds); + virtual void setRate(float rate); + virtual void setVolume(float volume); + virtual void setVisible(bool visible); + virtual void setPreload(WebKit::WebMediaPlayer::Preload preload); + virtual bool totalBytesKnown(); + virtual const WebKit::WebTimeRanges& buffered(); + virtual float maxTimeSeekable() const; + + // Methods for painting. + virtual void setSize(const WebKit::WebSize& size); + + virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect); + + // True if the loaded media has a playable video/audio track. + virtual bool hasVideo() const; + virtual bool hasAudio() const; + + // Dimensions of the video. + virtual WebKit::WebSize naturalSize() const; + + // Getters of playback state. + virtual bool paused() const; + virtual bool seeking() const; + virtual float duration() const; + virtual float currentTime() const; + + // Get rate of loading the resource. + virtual int32 dataRate() 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 WebKit::WebMediaPlayer::NetworkState networkState() const; + virtual WebKit::WebMediaPlayer::ReadyState readyState() const; + + virtual unsigned long long bytesLoaded() const; + virtual unsigned long long totalBytes() const; + + virtual bool hasSingleSecurityOrigin() const; + virtual WebKit::WebMediaPlayer::MovieLoadType movieLoadType() const; + + virtual float mediaTimeForTimeValue(float timeValue) const; + + virtual unsigned decodedFrameCount() const; + virtual unsigned droppedFrameCount() const; + virtual unsigned audioDecodedByteCount() const; + virtual unsigned videoDecodedByteCount() const; + + virtual WebKit::WebVideoFrame* getCurrentFrame(); + virtual void putCurrentFrame(WebKit::WebVideoFrame* web_video_frame); + + virtual bool sourceAppend(const unsigned char* data, unsigned length); + virtual void sourceEndOfStream(EndOfStreamStatus status); + + // As we are closing the tab or even the browser, |main_loop_| is destroyed + // even before this object gets destructed, so we need to know when + // |main_loop_| is being destroyed and we can stop posting repaint task + // to it. + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + void Repaint(); + + void OnPipelineInitialize(media::PipelineStatus status); + void OnPipelineSeek(media::PipelineStatus status); + void OnPipelineEnded(media::PipelineStatus status); + void OnPipelineError(media::PipelineStatus error); + void OnNetworkEvent(bool is_downloading_data); + void OnDemuxerOpened(); + + private: + // Helpers that set the network/ready state and notifies the client if + // they've changed. + void SetNetworkState(WebKit::WebMediaPlayer::NetworkState state); + void SetReadyState(WebKit::WebMediaPlayer::ReadyState state); + + // Destroy resources held. + void Destroy(); + + // Getter method to |client_|. + WebKit::WebMediaPlayerClient* GetClient(); + + // Lets V8 know that player uses extra resources not managed by V8. + void IncrementExternallyAllocatedMemory(); + + // TODO(hclam): get rid of these members and read from the pipeline directly. + WebKit::WebMediaPlayer::NetworkState network_state_; + WebKit::WebMediaPlayer::ReadyState ready_state_; + + // Keep a list of buffered time ranges. + WebKit::WebTimeRanges buffered_; + + // Message loops for posting tasks between Chrome's main thread. Also used + // for DCHECKs so methods calls won't execute in the wrong thread. + MessageLoop* main_loop_; + + // A collection of filters. + scoped_ptr<media::FilterCollection> filter_collection_; + + // The actual pipeline and the thread it runs on. + scoped_refptr<media::Pipeline> pipeline_; + + scoped_ptr<media::MessageLoopFactory> message_loop_factory_; + + // 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_; + float playback_rate_; + base::TimeDelta paused_time_; + + // Seek gets pending if another seek is in progress. Only last pending seek + // will have effect. + bool pending_seek_; + float pending_seek_seconds_; + + WebKit::WebMediaPlayerClient* client_; + + scoped_refptr<WebMediaPlayerProxy> proxy_; + + base::WeakPtr<WebMediaPlayerDelegate> delegate_; + + MediaStreamClient* media_stream_client_; + +#if WEBKIT_USING_CG + scoped_ptr<skia::PlatformCanvas> skia_canvas_; +#endif + + scoped_refptr<media::MediaLog> media_log_; + + bool is_accelerated_compositing_active_; + + bool incremented_externally_allocated_memory_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_ diff --git a/webkit/media/webmediaplayer_proxy.cc b/webkit/media/webmediaplayer_proxy.cc new file mode 100644 index 0000000..a596ed8 --- /dev/null +++ b/webkit/media/webmediaplayer_proxy.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2011 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 "webkit/media/webmediaplayer_proxy.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "media/base/pipeline_status.h" +#include "media/filters/chunk_demuxer.h" +#include "webkit/media/web_video_renderer.h" +#include "webkit/media/webmediaplayer_impl.h" + +using media::PipelineStatus; + +namespace webkit_media { + +// Limits the maximum outstanding repaints posted on render thread. +// This number of 50 is a guess, it does not take too much memory on the task +// queue but gives up a pretty good latency on repaint. +static const int kMaxOutstandingRepaints = 50; + +WebMediaPlayerProxy::WebMediaPlayerProxy(MessageLoop* render_loop, + WebMediaPlayerImpl* webmediaplayer) + : render_loop_(render_loop), + webmediaplayer_(webmediaplayer), + outstanding_repaints_(0) { + DCHECK(render_loop_); + DCHECK(webmediaplayer_); +} + +WebMediaPlayerProxy::~WebMediaPlayerProxy() { + Detach(); +} + +void WebMediaPlayerProxy::Repaint() { + base::AutoLock auto_lock(lock_); + if (outstanding_repaints_ < kMaxOutstandingRepaints) { + ++outstanding_repaints_; + + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &WebMediaPlayerProxy::RepaintTask)); + } +} + +void WebMediaPlayerProxy::SetVideoRenderer( + scoped_refptr<WebVideoRenderer> video_renderer) { + video_renderer_ = video_renderer; +} + +WebDataSourceBuildObserverHack WebMediaPlayerProxy::GetBuildObserver() { + if (build_observer_.is_null()) + build_observer_ = base::Bind(&WebMediaPlayerProxy::AddDataSource, this); + return build_observer_; +} + +void WebMediaPlayerProxy::Paint(SkCanvas* canvas, const gfx::Rect& dest_rect) { + DCHECK(MessageLoop::current() == render_loop_); + if (video_renderer_) { + video_renderer_->Paint(canvas, dest_rect); + } +} + +void WebMediaPlayerProxy::SetSize(const gfx::Rect& rect) { + DCHECK(MessageLoop::current() == render_loop_); + if (video_renderer_) { + video_renderer_->SetRect(rect); + } +} + +bool WebMediaPlayerProxy::HasSingleOrigin() { + DCHECK(MessageLoop::current() == render_loop_); + + base::AutoLock auto_lock(data_sources_lock_); + + for (DataSourceList::iterator itr = data_sources_.begin(); + itr != data_sources_.end(); + itr++) { + if (!(*itr)->HasSingleOrigin()) + return false; + } + return true; +} + +void WebMediaPlayerProxy::AbortDataSources() { + DCHECK(MessageLoop::current() == render_loop_); + base::AutoLock auto_lock(data_sources_lock_); + + for (DataSourceList::iterator itr = data_sources_.begin(); + itr != data_sources_.end(); + itr++) { + (*itr)->Abort(); + } +} + +void WebMediaPlayerProxy::Detach() { + DCHECK(MessageLoop::current() == render_loop_); + webmediaplayer_ = NULL; + video_renderer_ = NULL; + + { + base::AutoLock auto_lock(data_sources_lock_); + data_sources_.clear(); + } +} + +void WebMediaPlayerProxy::PipelineInitializationCallback( + PipelineStatus status) { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::PipelineInitializationTask, status)); +} + +void WebMediaPlayerProxy::PipelineSeekCallback(PipelineStatus status) { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::PipelineSeekTask, status)); +} + +void WebMediaPlayerProxy::PipelineEndedCallback(PipelineStatus status) { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::PipelineEndedTask, status)); +} + +void WebMediaPlayerProxy::PipelineErrorCallback(PipelineStatus error) { + DCHECK_NE(error, media::PIPELINE_OK); + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::PipelineErrorTask, error)); +} + +void WebMediaPlayerProxy::NetworkEventCallback(bool is_downloading_data) { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::NetworkEventTask, is_downloading_data)); +} + +void WebMediaPlayerProxy::AddDataSource(WebDataSource* data_source) { + base::AutoLock auto_lock(data_sources_lock_); + data_sources_.push_back(make_scoped_refptr(data_source)); +} + +void WebMediaPlayerProxy::RepaintTask() { + DCHECK(MessageLoop::current() == render_loop_); + { + base::AutoLock auto_lock(lock_); + --outstanding_repaints_; + DCHECK_GE(outstanding_repaints_, 0); + } + if (webmediaplayer_) { + webmediaplayer_->Repaint(); + } +} + +void WebMediaPlayerProxy::PipelineInitializationTask(PipelineStatus status) { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) + webmediaplayer_->OnPipelineInitialize(status); +} + +void WebMediaPlayerProxy::PipelineSeekTask(PipelineStatus status) { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) + webmediaplayer_->OnPipelineSeek(status); +} + +void WebMediaPlayerProxy::PipelineEndedTask(PipelineStatus status) { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) + webmediaplayer_->OnPipelineEnded(status); +} + +void WebMediaPlayerProxy::PipelineErrorTask(PipelineStatus error) { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) + webmediaplayer_->OnPipelineError(error); +} + +void WebMediaPlayerProxy::NetworkEventTask(bool is_downloading_data) { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) + webmediaplayer_->OnNetworkEvent(is_downloading_data); +} + +void WebMediaPlayerProxy::GetCurrentFrame( + scoped_refptr<media::VideoFrame>* frame_out) { + if (video_renderer_) + video_renderer_->GetCurrentFrame(frame_out); +} + +void WebMediaPlayerProxy::PutCurrentFrame( + scoped_refptr<media::VideoFrame> frame) { + if (video_renderer_) + video_renderer_->PutCurrentFrame(frame); +} + +void WebMediaPlayerProxy::DemuxerOpened(media::ChunkDemuxer* demuxer) { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::DemuxerOpenedTask, + scoped_refptr<media::ChunkDemuxer>(demuxer))); +} + +void WebMediaPlayerProxy::DemuxerClosed() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &WebMediaPlayerProxy::DemuxerClosedTask)); +} + +void WebMediaPlayerProxy::DemuxerFlush() { + if (chunk_demuxer_.get()) + chunk_demuxer_->FlushData(); +} + +bool WebMediaPlayerProxy::DemuxerAppend(const uint8* data, size_t length) { + if (chunk_demuxer_.get()) + return chunk_demuxer_->AppendData(data, length); + return false; +} + +void WebMediaPlayerProxy::DemuxerEndOfStream(media::PipelineStatus status) { + if (chunk_demuxer_.get()) + chunk_demuxer_->EndOfStream(status); +} + +void WebMediaPlayerProxy::DemuxerShutdown() { + if (chunk_demuxer_.get()) + chunk_demuxer_->Shutdown(); +} + +void WebMediaPlayerProxy::DemuxerOpenedTask( + const scoped_refptr<media::ChunkDemuxer>& demuxer) { + DCHECK(MessageLoop::current() == render_loop_); + chunk_demuxer_ = demuxer; + if (webmediaplayer_) + webmediaplayer_->OnDemuxerOpened(); +} + +void WebMediaPlayerProxy::DemuxerClosedTask() { + chunk_demuxer_ = NULL; +} + +} // namespace webkit_media diff --git a/webkit/media/webmediaplayer_proxy.h b/webkit/media/webmediaplayer_proxy.h new file mode 100644 index 0000000..dc96111 --- /dev/null +++ b/webkit/media/webmediaplayer_proxy.h @@ -0,0 +1,120 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_WEBMEDIAPLAYER_PROXY_H_ +#define WEBKIT_MEDIA_WEBMEDIAPLAYER_PROXY_H_ + +#include <list> + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "media/filters/chunk_demuxer_client.h" +#include "webkit/media/web_data_source.h" + +class MessageLoop; +class SkCanvas; + +namespace gfx { +class Rect; +} + +namespace webkit_media { + +class WebMediaPlayerImpl; +class WebVideoRenderer; + +// Acts as a thread proxy between the various threads used for multimedia and +// the render thread that WebMediaPlayerImpl is running on. +class WebMediaPlayerProxy + : public base::RefCountedThreadSafe<WebMediaPlayerProxy>, + public media::ChunkDemuxerClient { + public: + WebMediaPlayerProxy(MessageLoop* render_loop, + WebMediaPlayerImpl* webmediaplayer); + + // Methods for Filter -> WebMediaPlayerImpl communication. + void Repaint(); + void SetVideoRenderer(scoped_refptr<WebVideoRenderer> video_renderer); + WebDataSourceBuildObserverHack GetBuildObserver(); + + // Methods for WebMediaPlayerImpl -> Filter communication. + void Paint(SkCanvas* canvas, const gfx::Rect& dest_rect); + void SetSize(const gfx::Rect& rect); + void Detach(); + void GetCurrentFrame(scoped_refptr<media::VideoFrame>* frame_out); + void PutCurrentFrame(scoped_refptr<media::VideoFrame> frame); + bool HasSingleOrigin(); + void AbortDataSources(); + + // Methods for PipelineImpl -> WebMediaPlayerImpl communication. + void PipelineInitializationCallback(media::PipelineStatus status); + void PipelineSeekCallback(media::PipelineStatus status); + void PipelineEndedCallback(media::PipelineStatus status); + void PipelineErrorCallback(media::PipelineStatus error); + void NetworkEventCallback(bool network_activity); + + // ChunkDemuxerClient implementation. + virtual void DemuxerOpened(media::ChunkDemuxer* demuxer) OVERRIDE; + virtual void DemuxerClosed() OVERRIDE; + + // Methods for Demuxer communication. + void DemuxerFlush(); + bool DemuxerAppend(const uint8* data, size_t length); + void DemuxerEndOfStream(media::PipelineStatus status); + void DemuxerShutdown(); + + void DemuxerOpenedTask(const scoped_refptr<media::ChunkDemuxer>& demuxer); + void DemuxerClosedTask(); + + // Returns the message loop used by the proxy. + MessageLoop* message_loop() { return render_loop_; } + + private: + friend class base::RefCountedThreadSafe<WebMediaPlayerProxy>; + virtual ~WebMediaPlayerProxy(); + + // Adds a data source to data_sources_. + void AddDataSource(WebDataSource* data_source); + + // Invoke |webmediaplayer_| to perform a repaint. + void RepaintTask(); + + // Notify |webmediaplayer_| that initialization has finished. + void PipelineInitializationTask(media::PipelineStatus status); + + // Notify |webmediaplayer_| that a seek has finished. + void PipelineSeekTask(media::PipelineStatus status); + + // Notify |webmediaplayer_| that the media has ended. + void PipelineEndedTask(media::PipelineStatus status); + + // Notify |webmediaplayer_| that a pipeline error has occurred during + // playback. + void PipelineErrorTask(media::PipelineStatus error); + + // Notify |webmediaplayer_| that there's a network event. + void NetworkEventTask(bool network_activity); + + // The render message loop where WebKit lives. + MessageLoop* render_loop_; + WebMediaPlayerImpl* webmediaplayer_; + + base::Lock data_sources_lock_; + typedef std::list<scoped_refptr<WebDataSource> > DataSourceList; + DataSourceList data_sources_; + WebDataSourceBuildObserverHack build_observer_; + + scoped_refptr<WebVideoRenderer> video_renderer_; + + base::Lock lock_; + int outstanding_repaints_; + + scoped_refptr<media::ChunkDemuxer> chunk_demuxer_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebMediaPlayerProxy); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEBMEDIAPLAYER_PROXY_H_ diff --git a/webkit/media/webvideoframe_impl.cc b/webkit/media/webvideoframe_impl.cc new file mode 100644 index 0000000..5a5b182 --- /dev/null +++ b/webkit/media/webvideoframe_impl.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 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 "webkit/media/webvideoframe_impl.h" + +#include "base/logging.h" +#include "media/base/video_frame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h" + +using WebKit::WebVideoFrame; + +namespace webkit_media { + +media::VideoFrame* WebVideoFrameImpl::toVideoFrame( + WebVideoFrame* web_video_frame) { + WebVideoFrameImpl* wrapped_frame = + static_cast<WebVideoFrameImpl*>(web_video_frame); + if (wrapped_frame) + return wrapped_frame->video_frame_.get(); + return NULL; +} + +WebVideoFrameImpl::WebVideoFrameImpl( + scoped_refptr<media::VideoFrame> video_frame) + : video_frame_(video_frame) { +} + +WebVideoFrameImpl::~WebVideoFrameImpl() {} + +#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ + COMPILE_ASSERT(int(WebKit::WebVideoFrame::webkit_name) == \ + int(media::VideoFrame::chromium_name), \ + mismatching_enums) +COMPILE_ASSERT_MATCHING_ENUM(FormatInvalid, INVALID); +COMPILE_ASSERT_MATCHING_ENUM(FormatRGB555, RGB555); +COMPILE_ASSERT_MATCHING_ENUM(FormatRGB565, RGB565); +COMPILE_ASSERT_MATCHING_ENUM(FormatRGB24, RGB24); +COMPILE_ASSERT_MATCHING_ENUM(FormatRGB32, RGB32); +COMPILE_ASSERT_MATCHING_ENUM(FormatRGBA, RGBA); +COMPILE_ASSERT_MATCHING_ENUM(FormatYV12, YV12); +COMPILE_ASSERT_MATCHING_ENUM(FormatYV16, YV16); +COMPILE_ASSERT_MATCHING_ENUM(FormatNV12, NV12); +COMPILE_ASSERT_MATCHING_ENUM(FormatEmpty, EMPTY); +COMPILE_ASSERT_MATCHING_ENUM(FormatASCII, ASCII); + +WebVideoFrame::Format WebVideoFrameImpl::format() const { + if (video_frame_.get()) + return static_cast<WebVideoFrame::Format>(video_frame_->format()); + return WebVideoFrame::FormatInvalid; +} + +unsigned WebVideoFrameImpl::width() const { + if (video_frame_.get()) + return video_frame_->width(); + return 0; +} + +unsigned WebVideoFrameImpl::height() const { + if (video_frame_.get()) + return video_frame_->height(); + return 0; +} + +unsigned WebVideoFrameImpl::planes() const { + if (video_frame_.get()) + return video_frame_->planes(); + return 0; +} + +int WebVideoFrameImpl::stride(unsigned plane) const { + if (video_frame_.get()) + return static_cast<int>(video_frame_->stride(plane)); + return 0; +} + +const void* WebVideoFrameImpl::data(unsigned plane) const { + if (video_frame_.get()) + return static_cast<const void*>(video_frame_->data(plane)); + return NULL; +} + +} // namespace webkit_media diff --git a/webkit/media/webvideoframe_impl.h b/webkit/media/webvideoframe_impl.h new file mode 100644 index 0000000..f82c4d7 --- /dev/null +++ b/webkit/media/webvideoframe_impl.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011 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 WEBKIT_MEDIA_WEBVIDEOFRAME_IMPL_H_ +#define WEBKIT_MEDIA_WEBVIDEOFRAME_IMPL_H_ + +#include "base/compiler_specific.h" +#include "media/base/video_frame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebVideoFrame.h" + +namespace webkit_media { + +class WebVideoFrameImpl : public WebKit::WebVideoFrame { + public: + // This converts a WebKit::WebVideoFrame to a media::VideoFrame. + static media::VideoFrame* toVideoFrame( + WebKit::WebVideoFrame* web_video_frame); + + WebVideoFrameImpl(scoped_refptr<media::VideoFrame> video_frame); + virtual ~WebVideoFrameImpl(); + virtual WebVideoFrame::Format format() const OVERRIDE; + virtual unsigned width() const OVERRIDE; + virtual unsigned height() const OVERRIDE; + virtual unsigned planes() const OVERRIDE; + virtual int stride(unsigned plane) const OVERRIDE; + virtual const void* data(unsigned plane) const OVERRIDE; + + private: + scoped_refptr<media::VideoFrame> video_frame_; + DISALLOW_COPY_AND_ASSIGN(WebVideoFrameImpl); +}; + +} // namespace webkit_media + +#endif // WEBKIT_MEDIA_WEBVIDEOFRAME_IMPL_H_ |