blob: 0b135c6f69a89904510d6f02262de7e06a0d2725 [file] [log] [blame]
// Copyright (c) 2012 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 "base/message_loop.h"
#include "media/base/media_log.h"
#include "net/base/net_errors.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;
BufferedDataSource::BufferedDataSource(
MessageLoop* render_loop,
WebFrame* frame,
media::MediaLog* media_log)
: total_bytes_(kPositionNotSpecified),
buffered_bytes_(0),
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_(AUTO),
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::DataSourceHost* host) {
DataSource::set_host(host);
if (loader_.get()) {
base::AutoLock auto_lock(lock_);
UpdateHostState_Locked();
}
}
void BufferedDataSource::Initialize(
const GURL& url,
const media::PipelineStatusCB& initialize_cb) {
DCHECK(MessageLoop::current() == render_loop_);
DCHECK(!initialize_cb.is_null());
DCHECK(!loader_.get());
url_ = url;
initialize_cb_ = initialize_cb;
if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) {
// Do an unbounded range request starting at the beginning. If the server
// responds with 200 instead of 206 we'll fall back into a streaming mode.
loader_.reset(CreateResourceLoader(0, kPositionNotSpecified));
loader_->Start(
base::Bind(&BufferedDataSource::HttpInitialStartCallback, this),
base::Bind(&BufferedDataSource::NetworkEventCallback, this),
frame_);
return;
}
// For all other protocols, assume they support range request. We fetch
// the full range of the resource to obtain the instance size because
// we won't be served HTTP headers.
loader_.reset(CreateResourceLoader(kPositionNotSpecified,
kPositionNotSpecified));
loader_->Start(
base::Bind(&BufferedDataSource::NonHttpInitialStartCallback, this),
base::Bind(&BufferedDataSource::NetworkEventCallback, this),
frame_);
}
void BufferedDataSource::SetPreload(Preload preload) {
DCHECK(MessageLoop::current() == render_loop_);
preload_ = preload;
}
bool BufferedDataSource::HasSingleOrigin() {
DCHECK(MessageLoop::current() == render_loop_);
DCHECK(initialize_cb_.is_null() && loader_.get())
<< "Initialize() must complete before calling HasSingleOrigin()";
return loader_->HasSingleOrigin();
}
void BufferedDataSource::Abort() {
DCHECK(MessageLoop::current() == render_loop_);
CleanupTask();
frame_ = NULL;
}
/////////////////////////////////////////////////////////////////////////////
// media::Filter implementation.
void BufferedDataSource::Stop(const base::Closure& closure) {
{
base::AutoLock auto_lock(lock_);
stop_signal_received_ = true;
}
if (!closure.is_null())
closure.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::SetBitrate(int bitrate) {
render_loop_->PostTask(FROM_HERE, base::Bind(
&BufferedDataSource::SetBitrateTask, this, bitrate));
}
/////////////////////////////////////////////////////////////////////////////
// media::DataSource implementation.
void BufferedDataSource::Read(
int64 position, int size, uint8* data,
const media::DataSource::ReadCB& read_cb) {
DVLOG(1) << "Read: " << position << " offset, " << size << " bytes";
DCHECK(!read_cb.is_null());
{
base::AutoLock auto_lock(lock_);
DCHECK(read_cb_.is_null());
if (stop_signal_received_ || stopped_on_render_loop_) {
read_cb.Run(kReadError);
return;
}
read_cb_ = read_cb;
}
render_loop_->PostTask(FROM_HERE, base::Bind(
&BufferedDataSource::ReadTask, this, position, 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_;
}
/////////////////////////////////////////////////////////////////////////////
// Render thread tasks.
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_cb_.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_cb_| (which is cleared below).
stopped_on_render_loop_ = true;
if (!read_cb_.is_null())
DoneRead_Locked(kReadError);
}
// 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_cb_.is_null())
return;
}
loader_.reset(CreateResourceLoader(read_position_, kPositionNotSpecified));
loader_->Start(
base::Bind(&BufferedDataSource::PartialReadStartCallback, this),
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::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_);
// We never cache 200 responses, and don't want to get too far ahead of the
// read-head (and thus require a restart), so keep to the thresholds.
if (loader_.get() && !loader_->range_supported())
return BufferedResourceLoader::kThresholdDefer;
// If the page indicated preload=metadata, then load exactly what is needed
// needed for starting playback.
if (!media_has_played_ && preload_ == 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_.get());
// 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(),
base::Bind(&BufferedDataSource::ReadCallback, this));
}
void BufferedDataSource::DoneRead_Locked(int bytes_read) {
DVLOG(1) << "DoneRead: " << bytes_read << " bytes";
DCHECK(MessageLoop::current() == render_loop_);
DCHECK(!read_cb_.is_null());
DCHECK(bytes_read >= 0 || bytes_read == kReadError);
lock_.AssertAcquired();
read_cb_.Run(bytes_read);
read_cb_.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(
BufferedResourceLoader::Status status) {
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;
}
bool success = status == BufferedResourceLoader::kOK;
if (success) {
// TODO(hclam): Needs more thinking about supporting servers without range
// request or their partial response is not complete.
total_bytes_ = loader_->instance_size();
streaming_ = (total_bytes_ == kPositionNotSpecified) ||
!loader_->range_supported();
} else {
// TODO(hclam): In case of failure, we can retry several times.
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_)
return;
if (!success) {
DoneInitialization_Locked(media::PIPELINE_ERROR_NETWORK);
return;
}
UpdateHostState_Locked();
DoneInitialization_Locked(media::PIPELINE_OK);
}
}
void BufferedDataSource::NonHttpInitialStartCallback(
BufferedResourceLoader::Status status) {
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 = status == BufferedResourceLoader::kOK &&
instance_size != kPositionNotSpecified;
if (success) {
total_bytes_ = instance_size;
buffered_bytes_ = total_bytes_;
} 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(
BufferedResourceLoader::Status status) {
DCHECK(MessageLoop::current() == render_loop_);
DCHECK(loader_.get());
if (status == BufferedResourceLoader::kOK) {
// Once the request has started successfully, we can proceed with
// reading from it.
ReadInternal();
return;
}
// Stop the resource loader since we have received an error.
loader_->Stop();
// 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(kReadError);
}
void BufferedDataSource::ReadCallback(
BufferedResourceLoader::Status status,
int bytes_read) {
DCHECK(MessageLoop::current() == render_loop_);
if (status != BufferedResourceLoader::kOK) {
// Stop the resource load if it failed.
loader_->Stop();
if (status == BufferedResourceLoader::kCacheMiss &&
cache_miss_retries_left_ > 0) {
cache_miss_retries_left_--;
render_loop_->PostTask(FROM_HERE,
base::Bind(&BufferedDataSource::RestartLoadingTask, this));
return;
}
// Fall through to signal a read error.
bytes_read = kReadError;
}
// 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 (bytes_read > 0) {
memcpy(read_buffer_, intermediate_read_buffer_.get(), bytes_read);
} else if (bytes_read == 0 && total_bytes_ == kPositionNotSpecified) {
// We've reached the end of the file and we didn't know the total size
// before. Update the total size so Read()s past the end of the file will
// fail like they would if we had known the file size at the beginning.
total_bytes_ = loader_->instance_size();
if (host() && total_bytes_ != kPositionNotSpecified) {
host()->SetTotalBytes(total_bytes_);
host()->SetBufferedBytes(total_bytes_);
}
}
DoneRead_Locked(bytes_read);
}
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 (!url_.SchemeIs(kHttpScheme) && !url_.SchemeIs(kHttpsScheme))
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();
if (!host())
return;
if (total_bytes_ != kPositionNotSpecified)
host()->SetTotalBytes(total_bytes_);
host()->SetBufferedBytes(buffered_bytes_);
}
} // namespace webkit_media