| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/blink/buffered_data_source_host_impl.h" |
| |
| #include "media/base/timestamp_constants.h" |
| |
| namespace media { |
| |
| // We want a relatively small window for estimating bandwidth, |
| // that way we don't need to worry too much about seeks and pause |
| // throwing off the estimates. |
| constexpr base::TimeDelta kDownloadHistoryWindowSeconds = |
| base::TimeDelta::FromSecondsD(10.0); |
| |
| // Limit the number of entries in the rate estimator queue. |
| // 1024 entries should be more than enough. |
| constexpr size_t kDownloadHistoryMaxEntries = 1024; |
| |
| // Just in case someone gives progress one byte at a time, |
| // let's aggregate progress updates together until we reach |
| // at least this many bytes. |
| constexpr int64_t kDownloadHistoryMinBytesPerEntry = 1000; |
| |
| BufferedDataSourceHostImpl::BufferedDataSourceHostImpl( |
| base::RepeatingClosure progress_cb, |
| const base::TickClock* tick_clock) |
| : total_bytes_(0), |
| did_loading_progress_(false), |
| progress_cb_(std::move(progress_cb)), |
| tick_clock_(tick_clock) {} |
| |
| BufferedDataSourceHostImpl::~BufferedDataSourceHostImpl() = default; |
| |
| void BufferedDataSourceHostImpl::SetTotalBytes(int64_t total_bytes) { |
| total_bytes_ = total_bytes; |
| } |
| |
| int64_t BufferedDataSourceHostImpl::UnloadedBytesInInterval( |
| const Interval<int64_t>& interval) const { |
| int64_t bytes = 0; |
| auto i = buffered_byte_ranges_.find(interval.begin); |
| while (i != buffered_byte_ranges_.end()) { |
| if (i.interval_begin() >= interval.end) |
| break; |
| if (!i.value()) { |
| Interval<int64_t> intersection = i.interval().Intersect(interval); |
| if (!intersection.Empty()) |
| bytes += intersection.end - intersection.begin; |
| } |
| ++i; |
| } |
| return bytes; |
| } |
| |
| void BufferedDataSourceHostImpl::AddBufferedByteRange(int64_t start, |
| int64_t end) { |
| int64_t new_bytes = UnloadedBytesInInterval(Interval<int64_t>(start, end)); |
| if (new_bytes > 0) |
| did_loading_progress_ = true; |
| buffered_byte_ranges_.SetInterval(start, end, 1); |
| |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| int64_t bytes_so_far = 0; |
| if (!download_history_.empty()) |
| bytes_so_far = download_history_.back().second; |
| bytes_so_far += new_bytes; |
| |
| // If the difference between the last entry and the second to last entry is |
| // less than kDownloadHistoryMinBytesPerEntry, just overwrite the last entry. |
| if (download_history_.size() > 1 && |
| download_history_.back().second - (download_history_.end() - 2)->second < |
| kDownloadHistoryMinBytesPerEntry) { |
| download_history_.back() = std::make_pair(now, bytes_so_far); |
| } else { |
| download_history_.emplace_back(now, bytes_so_far); |
| } |
| DCHECK_GE(download_history_.size(), 1u); |
| // Drop entries that are too old. |
| while (download_history_.size() > kDownloadHistoryMaxEntries || |
| download_history_.back().first - download_history_.front().first > |
| kDownloadHistoryWindowSeconds) { |
| download_history_.pop_front(); |
| } |
| progress_cb_.Run(); |
| } |
| |
| static base::TimeDelta TimeForByteOffset(int64_t byte_offset, |
| int64_t total_bytes, |
| base::TimeDelta duration) { |
| double position = static_cast<double>(byte_offset) / total_bytes; |
| // Snap to the beginning/end where the approximation can look especially bad. |
| if (position < 0.01) |
| return base::TimeDelta(); |
| if (position > 0.99) |
| return duration; |
| return base::TimeDelta::FromMilliseconds( |
| static_cast<int64_t>(position * duration.InMilliseconds())); |
| } |
| |
| void BufferedDataSourceHostImpl::AddBufferedTimeRanges( |
| Ranges<base::TimeDelta>* buffered_time_ranges, |
| base::TimeDelta media_duration) const { |
| DCHECK(media_duration != kNoTimestamp); |
| DCHECK(media_duration != kInfiniteDuration); |
| if (total_bytes_ && !buffered_byte_ranges_.empty()) { |
| for (const auto i : buffered_byte_ranges_) { |
| if (i.second) { |
| int64_t start = i.first.begin; |
| int64_t end = i.first.end; |
| buffered_time_ranges->Add( |
| TimeForByteOffset(start, total_bytes_, media_duration), |
| TimeForByteOffset(end, total_bytes_, media_duration)); |
| } |
| } |
| } |
| } |
| |
| bool BufferedDataSourceHostImpl::DidLoadingProgress() { |
| bool ret = did_loading_progress_; |
| did_loading_progress_ = false; |
| return ret; |
| } |
| |
| double BufferedDataSourceHostImpl::DownloadRate() const { |
| // If the download history is really small, any estimate we make is going to |
| // be wildly inaccurate, so let's not make any estimates until we have more |
| // data. |
| if (download_history_.size() < 5) |
| return 0.0; |
| |
| // The data we get is bursty, so we get multiple measuring points very close |
| // together. These bursts will often lead us to over-estimate the download |
| // rate. By iterating over the beginning of the time series and picking the |
| // data point that has the lowest download rate, we avoid over-estimating. |
| const double kVeryLargeRate = 1.0E20; |
| double download_rate = kVeryLargeRate; |
| for (int i = 0; i < std::min<int>(20, download_history_.size() - 3); i++) { |
| int64_t downloaded_bytes = |
| download_history_.back().second - download_history_[i].second; |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| base::TimeDelta downloaded_seconds = now - download_history_[i].first; |
| if (downloaded_seconds <= base::TimeDelta()) |
| continue; |
| download_rate = std::min( |
| download_rate, downloaded_bytes / downloaded_seconds.InSecondsF()); |
| } |
| return download_rate == kVeryLargeRate ? 0.0 : download_rate; |
| } |
| |
| bool BufferedDataSourceHostImpl::CanPlayThrough( |
| base::TimeDelta current_position, |
| base::TimeDelta media_duration, |
| double playback_rate) const { |
| DCHECK_GE(playback_rate, 0); |
| if (!total_bytes_ || media_duration <= base::TimeDelta() || |
| media_duration == kInfiniteDuration) { |
| return false; |
| } |
| if (current_position > media_duration) |
| return true; |
| double fraction = current_position.InSecondsF() / media_duration.InSecondsF(); |
| int64_t byte_pos = total_bytes_ * fraction; |
| if (byte_pos < 0) |
| byte_pos = 0; |
| |
| int64_t unloaded_bytes = |
| UnloadedBytesInInterval(Interval<int64_t>(byte_pos, total_bytes_)); |
| |
| if (unloaded_bytes == 0) |
| return true; |
| |
| double download_rate = DownloadRate(); |
| if (download_rate <= 0.0) |
| return false; |
| |
| return unloaded_bytes / download_rate < |
| (media_duration - current_position).InSecondsF() / playback_rate; |
| } |
| |
| void BufferedDataSourceHostImpl::SetTickClockForTest( |
| const base::TickClock* tick_clock) { |
| tick_clock_ = tick_clock; |
| } |
| |
| } // namespace media |