blob: fea879edea26383c56026dd75e6fa1d4a4d00f5f [file] [log] [blame]
// 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 download_time = now - download_history_[i].first;
if (download_time <= base::TimeDelta())
continue;
download_rate =
std::min(download_rate, downloaded_bytes / download_time.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;
const int64_t byte_pos =
std::max<int64_t>(total_bytes_ * (current_position / media_duration), 0);
const int64_t unloaded_bytes =
UnloadedBytesInInterval(Interval<int64_t>(byte_pos, total_bytes_));
if (unloaded_bytes == 0)
return true;
double download_rate = DownloadRate();
return (download_rate > 0) &&
((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