Chromium Code Reviews| Index: media/blink/multibuffer_data_source.cc |
| diff --git a/media/blink/multibuffer_data_source.cc b/media/blink/multibuffer_data_source.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c8c8bdbb7def6976ecd6eb146eeff5f7e0eba728 |
| --- /dev/null |
| +++ b/media/blink/multibuffer_data_source.cc |
| @@ -0,0 +1,553 @@ |
| +// Copyright 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "media/blink/multibuffer_data_source.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/callback_helpers.h" |
| +#include "base/location.h" |
| +#include "base/single_thread_task_runner.h" |
| +#include "media/base/media_log.h" |
| +#include "media/blink/multibuffer_reader.h" |
| +#include "net/base/net_errors.h" |
| + |
| +using blink::WebFrame; |
| + |
| +namespace { |
| + |
| +// Minimum preload buffer. |
| +const int64 kMinBufferPreload = 2 << 20; // 2 Mb |
| +// Maxmimum preload buffer. |
| +const int64 kMaxBufferPreload = 20 << 20; // 20 Mb |
| + |
| +// Preload this much extra, then stop preloading until we fall below the |
| +// kTargetSecondsBufferedAhead. |
| +const int64 kPreloadHighExtra = 1 << 20; // 1 Mb |
| + |
| +// Total size of the pinned region in the cache. |
| +const int64 kMaxBufferSize = 25 << 20; // 25 Mb |
| + |
| +// If bitrate is not known, use this. |
| +const int64 kDefaultBitrate = 200 * 8 << 10; // 200 Kbps. |
| + |
| +// Maximum bitrate for buffer calculations. |
| +const int64 kMaxBitrate = 20 * 8 << 20; // 20 Mbps. |
| + |
| +// Maximum playback rate for buffer calculations. |
| +const double kMaxPlaybackRate = 25.0; |
| + |
| +// Preload this many seconds of data by default. |
| +const int64 kTargetSecondsBufferedAhead = 10; |
| + |
| +// Keep this many seconds of data for going back by default. |
| +const int64 kTargetSecondsBufferedBehind = 2; |
| + |
| +} // namespace |
| + |
| +namespace media { |
| + |
| +template<typename T> |
| +T clamp(T value, T min, T max) { |
| + return std::max(std::min(value, max), min); |
| +} |
| + |
| +class MultibufferDataSource::ReadOperation { |
| + public: |
| + ReadOperation(int64 position, int size, uint8* data, |
| + const DataSource::ReadCB& callback); |
| + ~ReadOperation(); |
| + |
| + // Runs |callback_| with the given |result|, deleting the operation |
| + // afterwards. |
| + static void Run(scoped_ptr<ReadOperation> read_op, int result); |
| + |
| + int64 position() { return position_; } |
| + int size() { return size_; } |
| + uint8* data() { return data_; } |
| + |
| + private: |
| + const int64 position_; |
| + const int size_; |
| + uint8* data_; |
| + DataSource::ReadCB callback_; |
| + |
| + DISALLOW_IMPLICIT_CONSTRUCTORS(ReadOperation); |
| +}; |
| + |
| +MultibufferDataSource::ReadOperation::ReadOperation( |
| + int64 position, int size, uint8* data, |
| + const DataSource::ReadCB& callback) |
| + : position_(position), |
| + size_(size), |
| + data_(data), |
| + callback_(callback) { |
| + DCHECK(!callback_.is_null()); |
| +} |
| + |
| +MultibufferDataSource::ReadOperation::~ReadOperation() { |
| + DCHECK(callback_.is_null()); |
| +} |
| + |
| +// static |
| +void MultibufferDataSource::ReadOperation::Run( |
| + scoped_ptr<ReadOperation> read_op, int result) { |
| + base::ResetAndReturn(&read_op->callback_).Run(result); |
| +} |
| + |
| +MultibufferDataSource::MultibufferDataSource( |
| + const GURL& url, |
| + UrlData::CORSMode cors_mode, |
| + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| + ResourceMultiBuffer* multibuffer, |
| + WebFrame* frame, |
| + MediaLog* media_log, |
| + BufferedDataSourceHost* host, |
| + const DownloadingCB& downloading_cb) |
| + : cors_mode_(cors_mode), |
| + total_bytes_(kPositionNotSpecified), |
| + streaming_(false), |
| + loading_(false), |
| + render_task_runner_(task_runner), |
| + multibuffer_(multibuffer), |
| + frame_(frame), |
| + stop_signal_received_(false), |
| + media_has_played_(false), |
| + single_origin_(true), |
| + cancel_on_defer_(false), |
| + preload_(AUTO), |
| + bitrate_(0), |
| + playback_rate_(0.0), |
| + media_log_(media_log), |
| + host_(host), |
| + downloading_cb_(downloading_cb), |
| + weak_factory_(this) { |
| + weak_ptr_ = weak_factory_.GetWeakPtr(); |
| + DCHECK(host_); |
| + DCHECK(!downloading_cb_.is_null()); |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + url_data_ = multibuffer_->url_index()->GetByUrl(url, cors_mode_); |
| + url_data_->Use(); |
| + DCHECK(url_data_); |
| +} |
| + |
| +MultibufferDataSource::~MultibufferDataSource() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| +} |
| + |
| +bool MultibufferDataSource::media_has_played() const { |
| + return media_has_played_; |
| +} |
| + |
| +bool MultibufferDataSource::assume_fully_buffered() { |
| + return !url_data_->url().SchemeIsHTTPOrHTTPS(); |
| +} |
| + |
| +// A factory method to create BufferedResourceLoader using the read parameters. |
|
liberato (no reviews please)
2015/10/16 21:50:36
comment is out of date. also, it doesn't create a
hubbe
2015/10/16 23:47:05
Removed the comment.
|
| +void MultibufferDataSource::CreateResourceLoader( |
| + int64 first_byte_position, int64 last_byte_position) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + |
| + base::WeakPtr<MultibufferDataSource> weak_this = weak_factory_.GetWeakPtr(); |
| + loader_.reset(new MultiBufferReader( |
| + multibuffer_, |
| + destination_url_data_ ? destination_url_data_ : url_data_, |
| + first_byte_position, |
| + last_byte_position, |
| + base::Bind(&MultibufferDataSource::ProgressCallback, weak_this))); |
| + UpdateBufferSizes(); |
| +} |
| + |
| +void MultibufferDataSource::Initialize(const InitializeCB& init_cb) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + DCHECK(!init_cb.is_null()); |
| + DCHECK(!loader_.get()); |
| + |
| + init_cb_ = init_cb; |
| + |
| + CreateResourceLoader(0, kPositionNotSpecified); |
| + |
| + base::WeakPtr<MultibufferDataSource> weak_this = weak_factory_.GetWeakPtr(); |
| + |
| + // We're not allowed to call Wait() if data is already available. |
| + if (loader_->Available()) { |
| + render_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&MultibufferDataSource::StartCallback, weak_this)); |
| + } else { |
| + loader_->Wait(1, base::Bind( |
| + &MultibufferDataSource::StartCallback, weak_this)); |
| + } |
| + UpdateLoadingState(); |
| +} |
| + |
| +void MultibufferDataSource::SetPreload(Preload preload) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + preload_ = preload; |
| + UpdateBufferSizes(); |
| +} |
| + |
| +bool MultibufferDataSource::HasSingleOrigin() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + DCHECK(init_cb_.is_null() && loader_.get()) |
| + << "Initialize() must complete before calling HasSingleOrigin()"; |
| + return single_origin_; |
| +} |
| + |
| +bool MultibufferDataSource::DidPassCORSAccessCheck() const { |
| + if (cors_mode_ == UrlData::kUnspecified) |
| + return false; |
| + // If init_cb is set, we initialization is not finished yet. |
| + if (!init_cb_.is_null()) |
| + return false; |
| + // Loader will be false if there was a failure. |
| + if (!loader_) |
| + return false; |
| + return true; |
| +} |
| + |
| +void MultibufferDataSource::Abort() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + StopInternal_Locked(); |
| + } |
| + StopLoader(); |
| + frame_ = NULL; |
| +} |
| + |
| +void MultibufferDataSource::MediaPlaybackRateChanged(double playback_rate) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + DCHECK(loader_.get()); |
| + |
| + if (playback_rate < 0.0) |
| + return; |
| + |
| + playback_rate_ = playback_rate; |
| + cancel_on_defer_ = false; |
| + UpdateBufferSizes(); |
| +} |
| + |
| +void MultibufferDataSource::MediaIsPlaying() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + media_has_played_ = true; |
| + cancel_on_defer_ = false; |
| + paused_ = false; |
| + preload_ = AUTO; |
| + UpdateBufferSizes(); |
| +} |
| + |
| +void MultibufferDataSource::MediaIsPaused() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + paused_ = true; |
| + UpdateBufferSizes(); |
| +} |
| + |
| +///////////////////////////////////////////////////////////////////////////// |
| +// DataSource implementation. |
| +void MultibufferDataSource::Stop() { |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + StopInternal_Locked(); |
| + } |
| + |
| + render_task_runner_->PostTask(FROM_HERE, |
| + base::Bind(&MultibufferDataSource::StopLoader, |
| + weak_factory_.GetWeakPtr())); |
| +} |
| + |
| +void MultibufferDataSource::SetBitrate(int bitrate) { |
| + render_task_runner_->PostTask(FROM_HERE, |
| + base::Bind(&MultibufferDataSource::SetBitrateTask, |
| + weak_factory_.GetWeakPtr(), |
| + bitrate)); |
| +} |
| + |
| +void MultibufferDataSource::OnBufferingHaveEnough() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + if (loader_ && preload_ == METADATA && !media_has_played_ && !IsStreaming()) { |
| + cancel_on_defer_ = true; |
| + if (!loading_) |
| + loader_.reset(nullptr); |
| + } |
| +} |
| + |
| +void MultibufferDataSource::Read( |
| + int64 position, int size, uint8* data, |
| + const DataSource::ReadCB& read_cb) { |
| + DVLOG(1) << "Read: " << position << " offset, " << size << " bytes"; |
| + // Reading is not allowed until after initialization. |
| + DCHECK(init_cb_.is_null()); |
| + DCHECK(!read_cb.is_null()); |
| + |
| + { |
| + base::AutoLock auto_lock(lock_); |
| + DCHECK(!read_op_); |
| + |
| + if (stop_signal_received_) { |
| + read_cb.Run(kReadError); |
| + return; |
| + } |
| + |
| + read_op_.reset(new ReadOperation(position, size, data, read_cb)); |
| + } |
| + |
| + render_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&MultibufferDataSource::ReadTask, weak_factory_.GetWeakPtr())); |
| +} |
| + |
| +bool MultibufferDataSource::GetSize(int64* size_out) { |
| + if (destination_url_data_) { |
| + *size_out = destination_url_data_->length(); |
| + if (*size_out != kPositionNotSpecified) { |
| + return true; |
| + } |
| + } |
| + *size_out = 0; |
| + return false; |
| +} |
| + |
| +bool MultibufferDataSource::IsStreaming() { |
| + return streaming_; |
| +} |
| + |
| +///////////////////////////////////////////////////////////////////////////// |
| +// This method is the place where actual read happens, |
| +void MultibufferDataSource::ReadTask() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + |
| + base::AutoLock auto_lock(lock_); |
| + int bytes_read = 0; |
| + if (stop_signal_received_) |
| + return; |
| + DCHECK(read_op_); |
| + DCHECK(read_op_->size()); |
| + |
| + if (!loader_) { |
| + CreateResourceLoader(read_op_->position(), kPositionNotSpecified); |
| + } |
|
liberato (no reviews please)
2015/10/16 21:50:36
else?
hubbe
2015/10/16 23:47:05
Sure, why not?
|
| + |
| + loader_->Seek(read_op_->position()); |
| + |
| + int64_t available = loader_->Available(); |
| + if (available < 0) { |
| + // A failure has occured. |
| + ReadOperation::Run(read_op_.Pass(), kReadError); |
| + return; |
| + } |
| + if (available) { |
| + bytes_read = static_cast<int>( |
| + std::min<int64_t>(available, read_op_->size())); |
| + bytes_read = loader_->TryRead(read_op_->data(), bytes_read); |
| + ReadOperation::Run(read_op_.Pass(), bytes_read); |
| + } else { |
| + loader_->Wait(1, base::Bind(&MultibufferDataSource::ReadTask, |
| + weak_factory_.GetWeakPtr())); |
| + UpdateLoadingState(); |
| + } |
| +} |
| + |
| +void MultibufferDataSource::StopInternal_Locked() { |
| + lock_.AssertAcquired(); |
| + if (stop_signal_received_) |
| + return; |
| + |
| + stop_signal_received_ = true; |
| + |
| + // Initialize() isn't part of the DataSource interface so don't call it in |
| + // response to Stop(). |
| + init_cb_.Reset(); |
| + |
| + if (read_op_) |
| + ReadOperation::Run(read_op_.Pass(), kReadError); |
| +} |
| + |
| +void MultibufferDataSource::StopLoader() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + loader_.reset(nullptr); |
| + UpdateLoadingState(); |
| +} |
| + |
| +void MultibufferDataSource::SetBitrateTask(int bitrate) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + DCHECK(loader_.get()); |
| + |
| + bitrate_ = bitrate; |
| + UpdateBufferSizes(); |
| +} |
| + |
| +///////////////////////////////////////////////////////////////////////////// |
| +// BufferedResourceLoader callback methods. |
| +void MultibufferDataSource::StartCallback() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + DCHECK(loader_); |
| + |
| + 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_.reset(); |
| + return; |
| + } |
| + |
| + destination_url_data_ = loader_->GetUrlData(); |
| + |
| + // All responses must be successful. Resources that are assumed to be fully |
| + // buffered must have a known content length. |
| + bool success = |
| + loader_->Available() > 0 && |
| + destination_url_data_ && |
| + (!assume_fully_buffered() || |
| + destination_url_data_->length() != kPositionNotSpecified); |
| + |
| + if (success) { |
| + total_bytes_ = destination_url_data_->length(); |
| + streaming_ = |
| + !assume_fully_buffered() && |
| + (total_bytes_ == kPositionNotSpecified || |
| + !destination_url_data_->range_supported()); |
| + |
| + media_log_->SetDoubleProperty("total_bytes", |
| + static_cast<double>(total_bytes_)); |
| + media_log_->SetBooleanProperty("streaming", streaming_); |
| + } else { |
| + loader_.reset(nullptr); |
| + } |
| + |
| + // TODO(scherkus): we shouldn't have to lock to signal host(), see |
| + // http://crbug.com/113712 for details. |
| + base::AutoLock auto_lock(lock_); |
| + if (stop_signal_received_) |
| + return; |
| + |
| + if (success) { |
| + if (total_bytes_ != kPositionNotSpecified) { |
| + host_->SetTotalBytes(total_bytes_); |
| + if (assume_fully_buffered()) |
| + host_->AddBufferedByteRange(0, total_bytes_); |
| + } |
| + |
| + // Progress callback might be called after the start callback, |
| + // make sure that we update has_single_origin_ now. |
|
liberato (no reviews please)
2015/10/16 21:50:36
single_origin_
hubbe
2015/10/16 23:47:05
Done.
|
| + UpdateSingleOrigin(); |
| + |
| + media_log_->SetBooleanProperty("single_origin", single_origin_); |
| + media_log_->SetBooleanProperty("passed_cors_access_check", |
| + DidPassCORSAccessCheck()); |
| + media_log_->SetBooleanProperty("range_header_supported", |
| + destination_url_data_->range_supported()); |
| + } |
| + |
| + UpdateLoadingState(); |
| + render_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(base::ResetAndReturn(&init_cb_), success)); |
| +} |
| + |
| +void MultibufferDataSource::UpdateSingleOrigin() { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + if (loader_ && destination_url_data_) { |
| + scoped_refptr<UrlData> new_url_data = loader_->GetUrlData(); |
| + if (new_url_data && new_url_data != destination_url_data_) { |
| + // A redirect has happened. |
| + // Check if origin has changed. |
| + if (destination_url_data_->url().GetOrigin() != |
| + new_url_data->url().GetOrigin()) { |
| + single_origin_ = false; |
| + } |
| + } |
| + } |
| +} |
| + |
| +void MultibufferDataSource::ProgressCallback(int64 begin, int64 end) { |
| + DCHECK(render_task_runner_->BelongsToCurrentThread()); |
| + |
| + UpdateSingleOrigin(); |
| + if (assume_fully_buffered()) |
| + return; |
| + |
| + if (end > begin) { |
| + // TODO(scherkus): we shouldn't have to lock to signal host(), see |
| + // http://crbug.com/113712 for details. |
| + base::AutoLock auto_lock(lock_); |
| + if (stop_signal_received_) |
| + return; |
| + |
| + host_->AddBufferedByteRange(begin, end); |
| + } |
| + |
| + UpdateLoadingState(); |
| +} |
| + |
| +void MultibufferDataSource::UpdateLoadingState() { |
| + // Update loading state. |
| + if ((!!loader_ && loader_->IsLoading()) != loading_) { |
|
liberato (no reviews please)
2015/10/16 21:50:36
extra points to use s/!=/^ :)
hubbe
2015/10/16 23:47:05
Acknowledged.
|
| + loading_ = !loading_; |
| + |
| + if (!loading_ && cancel_on_defer_) { |
| + loader_.reset(nullptr); |
| + } |
| + |
| + // Callback could kill us, be sure to call it last. |
| + downloading_cb_.Run(loading_); |
| + } |
| +} |
| + |
| +void MultibufferDataSource::UpdateBufferSizes() { |
| + if (!loader_) |
| + return; |
| + |
| + if (!assume_fully_buffered()) { |
| + // If the playback has started and we're paused, then try to load as much as |
| + // possible, assuming that the file is cacheable. (If not, why bother?) |
| + if (media_has_played_ && paused_ && |
| + destination_url_data_ && |
| + destination_url_data_->range_supported() && |
| + destination_url_data_->cacheable()) { |
| + loader_->SetPreload(1LL << 40, 1LL << 40); // 1 Tb |
| + return; |
| + } |
| + } |
| + |
| + // Use a default bit rate if unknown and clamp to prevent overflow. |
| + int64 bitrate = clamp<int64>(bitrate_, 0, kMaxBitrate); |
| + if (bitrate == 0) |
| + bitrate = kDefaultBitrate; |
| + |
| + // Only scale the buffer window for playback rates greater than 1.0 in |
| + // magnitude and clamp to prevent overflow. |
| + bool backward_playback = false; |
| + double playback_rate = playback_rate_; |
| + if (playback_rate < 0.0) { |
| + backward_playback = true; |
| + playback_rate *= -1.0; |
| + } |
| + |
| + playback_rate = std::max(playback_rate, 1.0); |
| + playback_rate = std::min(playback_rate, kMaxPlaybackRate); |
| + |
| + int64 bytes_per_second = (bitrate / 8.0) * playback_rate; |
| + |
| + int64 preload = clamp(kTargetSecondsBufferedAhead * bytes_per_second, |
| + kMinBufferPreload, |
| + kMaxBufferPreload); |
| + int64 back_buffer = clamp(kTargetSecondsBufferedBehind * bytes_per_second, |
| + kMinBufferPreload, |
| + kMaxBufferPreload); |
| + if (backward_playback) |
| + std::swap(preload, back_buffer); |
| + |
| + int64 pin_forwards = kMaxBufferSize - back_buffer; |
| + DCHECK_LE(preload_ + kPreloadHighExtra, pin_forwards); |
| + loader_->SetMaxBuffer(back_buffer, pin_forwards); |
| + |
| + if (preload_ == METADATA) { |
| + loader_->SetPreload(0, 0); |
| + } else { |
| + loader_->SetPreload(preload + kPreloadHighExtra, preload); |
| + } |
| +} |
| + |
| +} // namespace media |