// 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/callback_helpers.h" #include "base/message_loop.h" #include "media/base/media_log.h" #include "net/base/net_errors.h" using WebKit::WebFrame; namespace { // BufferedDataSource has an intermediate buffer, this value governs the initial // size of that buffer. It is set to 32KB because this is a typical read size // of FFmpeg. const int kInitialReadBufferSize = 32768; // Number of cache misses we allow for a single Read() before signaling an // error. const int kNumCacheMissRetries = 3; } // namespace namespace webkit_media { BufferedDataSource::BufferedDataSource( MessageLoop* render_loop, WebFrame* frame, media::MediaLog* media_log, const DownloadingCB& downloading_cb) : cors_mode_(BufferedResourceLoader::kUnspecified), total_bytes_(kPositionNotSpecified), assume_fully_buffered_(false), streaming_(false), frame_(frame), read_size_(0), read_buffer_(NULL), last_read_start_(0), 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_has_played_(false), preload_(AUTO), cache_miss_retries_left_(kNumCacheMissRetries), bitrate_(0), playback_rate_(0.0), media_log_(media_log), downloading_cb_(downloading_cb) { DCHECK(!downloading_cb_.is_null()); } BufferedDataSource::~BufferedDataSource() {} // A factory method to create BufferedResourceLoader using the read parameters. // This method can be overridden to inject mock BufferedResourceLoader object // for testing purpose. BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( int64 first_byte_position, int64 last_byte_position) { DCHECK(MessageLoop::current() == render_loop_); BufferedResourceLoader::DeferStrategy strategy = preload_ == METADATA ? BufferedResourceLoader::kReadThenDefer : BufferedResourceLoader::kCapacityDefer; return new BufferedResourceLoader(url_, cors_mode_, first_byte_position, last_byte_position, strategy, bitrate_, playback_rate_, media_log_); } 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, BufferedResourceLoader::CORSMode cors_mode, const InitializeCB& init_cb) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(!init_cb.is_null()); DCHECK(!loader_.get()); url_ = url; cors_mode_ = cors_mode; init_cb_ = init_cb; if (url_.SchemeIs(kHttpScheme) || url_.SchemeIs(kHttpsScheme)) { // Do an unbounded range request starting at the beginning. If the server // responds with 200 instead of 206 we'll fall back into a streaming mode. loader_.reset(CreateResourceLoader(0, kPositionNotSpecified)); } else { // For all other protocols, assume they support range request. We fetch // the full range of the resource to obtain the instance size because // we won't be served HTTP headers. loader_.reset(CreateResourceLoader(kPositionNotSpecified, kPositionNotSpecified)); assume_fully_buffered_ = true; } loader_->Start( base::Bind(&BufferedDataSource::StartCallback, this), base::Bind(&BufferedDataSource::LoadingStateChangedCallback, this), base::Bind(&BufferedDataSource::ProgressCallback, this), frame_); } void BufferedDataSource::SetPreload(Preload preload) { DCHECK(MessageLoop::current() == render_loop_); preload_ = preload; } bool BufferedDataSource::HasSingleOrigin() { DCHECK(MessageLoop::current() == render_loop_); DCHECK(init_cb_.is_null() && loader_.get()) << "Initialize() must complete before calling HasSingleOrigin()"; return loader_->HasSingleOrigin(); } bool BufferedDataSource::DidPassCORSAccessCheck() const { return loader_.get() && loader_->DidPassCORSAccessCheck(); } void BufferedDataSource::Abort() { DCHECK(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. last_read_start_ = 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_); init_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_size_ = 0; read_buffer_ = 0; } void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); if (playback_rate != 0) media_has_played_ = true; playback_rate_ = playback_rate; loader_->SetPlaybackRate(playback_rate); if (!loader_->range_supported()) { // 200 responses end up not being reused to satisfy future range requests, // and we don't want to get too far ahead of the read-head (and thus require // a restart), so keep to the thresholds. loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); } else if (media_has_played_ && playback_rate == 0) { // If the playback has started (at which point the preload value is ignored) // and we're paused, then try to load as much as possible (the loader will // fall back to kCapacityDefer if it knows the current response won't be // useful from the cache in the future). loader_->UpdateDeferStrategy(BufferedResourceLoader::kNeverDefer); } else { // If media is currently playing or the page indicated preload=auto, // use threshold strategy to enable/disable deferring when the buffer // is full/depleted. loader_->UpdateDeferStrategy(BufferedResourceLoader::kCapacityDefer); } } void BufferedDataSource::SetBitrateTask(int bitrate) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); bitrate_ = bitrate; loader_->SetBitrate(bitrate); } // This method is the place where actual read happens, |loader_| must be valid // prior to make this method call. void BufferedDataSource::ReadInternal() { DCHECK(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( last_read_start_, 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_size_ = 0; read_buffer_ = 0; } ///////////////////////////////////////////////////////////////////////////// // BufferedResourceLoader callback methods. void BufferedDataSource::StartCallback( BufferedResourceLoader::Status status) { DCHECK(MessageLoop::current() == render_loop_); DCHECK(loader_.get()); bool init_cb_is_null = false; { base::AutoLock auto_lock(lock_); init_cb_is_null = init_cb_.is_null(); } if (init_cb_is_null) { loader_->Stop(); return; } // All responses must be successful. Resources that are assumed to be fully // buffered must have a known content length. bool success = status == BufferedResourceLoader::kOk && (!assume_fully_buffered_ || loader_->instance_size() != kPositionNotSpecified); if (success) { total_bytes_ = loader_->instance_size(); streaming_ = !assume_fully_buffered_ && (total_bytes_ == kPositionNotSpecified || !loader_->range_supported()); } else { loader_->Stop(); } // TODO(scherkus): we shouldn't have to lock to signal host(), see // http://crbug.com/113712 for details. scoped_refptr destruction_guard(this); { base::AutoLock auto_lock(lock_); if (stop_signal_received_) return; if (success) UpdateHostState_Locked(); base::ResetAndReturn(&init_cb_).Run(success); } } void BufferedDataSource::PartialReadStartCallback( BufferedResourceLoader::Status status) { DCHECK(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(); // TODO(scherkus): we shouldn't have to lock to signal host(), see // http://crbug.com/113712 for details. base::AutoLock auto_lock(lock_); if (stop_signal_received_) return; 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_--; // Recreate a loader starting from where we last left off until the // end of the resource. loader_.reset(CreateResourceLoader( last_read_start_, kPositionNotSpecified)); loader_->Start( base::Bind(&BufferedDataSource::PartialReadStartCallback, this), base::Bind(&BufferedDataSource::LoadingStateChangedCallback, this), base::Bind(&BufferedDataSource::ProgressCallback, this), frame_); return; } // Fall through to signal a read error. bytes_read = kReadError; } // TODO(scherkus): we shouldn't have to lock to signal host(), see // http://crbug.com/113712 for details. base::AutoLock auto_lock(lock_); if (stop_signal_received_) return; if (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()->AddBufferedByteRange(loader_->first_byte_position(), total_bytes_); } } DoneRead_Locked(bytes_read); } void BufferedDataSource::LoadingStateChangedCallback( BufferedResourceLoader::LoadingState state) { DCHECK(MessageLoop::current() == render_loop_); if (assume_fully_buffered_) return; bool is_downloading_data; switch (state) { case BufferedResourceLoader::kLoading: is_downloading_data = true; break; case BufferedResourceLoader::kLoadingDeferred: case BufferedResourceLoader::kLoadingFinished: is_downloading_data = false; break; // TODO(scherkus): we don't signal network activity changes when loads // fail to preserve existing behaviour when deferring is toggled, however // we should consider changing DownloadingCB to also propagate loading // state. For example there isn't any signal today to notify the client that // loading has failed (we only get errors on subsequent reads). case BufferedResourceLoader::kLoadingFailed: return; } downloading_cb_.Run(is_downloading_data); } void BufferedDataSource::ProgressCallback(int64 position) { DCHECK(MessageLoop::current() == render_loop_); if (assume_fully_buffered_) return; // TODO(scherkus): we shouldn't have to lock to signal host(), see // http://crbug.com/113712 for details. base::AutoLock auto_lock(lock_); if (stop_signal_received_) return; if (position > last_read_start_) ReportOrQueueBufferedBytes(last_read_start_, position); } void BufferedDataSource::ReportOrQueueBufferedBytes(int64 start, int64 end) { if (host()) host()->AddBufferedByteRange(start, end); else queued_buffered_byte_ranges_.Add(start, end); } void BufferedDataSource::UpdateHostState_Locked() { lock_.AssertAcquired(); if (!host()) return; for (size_t i = 0; i < queued_buffered_byte_ranges_.size(); ++i) { host()->AddBufferedByteRange(queued_buffered_byte_ranges_.start(i), queued_buffered_byte_ranges_.end(i)); } queued_buffered_byte_ranges_.clear(); if (total_bytes_ == kPositionNotSpecified) return; host()->SetTotalBytes(total_bytes_); if (assume_fully_buffered_) host()->AddBufferedByteRange(0, total_bytes_); } } // namespace webkit_media