1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/hls_vod_rendition.h"
7 #include "base/task/bind_post_task.h"
8 #include "media/filters/hls_manifest_demuxer_engine.h"
12 // Chosen mostly arbitrarily.
13 constexpr size_t kChunkSize = 1024 * 32;
15 constexpr base::TimeDelta kBufferDuration = base::Seconds(10);
17 // A nice round number, chosen to make sure that we get a good average network
19 constexpr size_t kMovingAverageSampleSize = 128;
21 HlsVodRendition::SegmentInfo::SegmentInfo() {}
22 HlsVodRendition::SegmentInfo::SegmentInfo(const HlsVodRendition::SegmentInfo&) =
24 HlsVodRendition::SegmentInfo::~SegmentInfo() {}
26 HlsVodRendition::PendingSegment::~PendingSegment() = default;
27 HlsVodRendition::PendingSegment::PendingSegment(
28 std::unique_ptr<HlsDataSourceStream> stream,
30 : stream(std::move(stream)), index(index) {}
32 HlsVodRendition::~HlsVodRendition() {
33 pending_stream_fetch_ = absl::nullopt;
34 engine_host_->RemoveRole(role_);
37 HlsVodRendition::HlsVodRendition(ManifestDemuxerEngineHost* engine_host,
38 HlsRenditionHost* rendition_host,
40 scoped_refptr<hls::MediaPlaylist> playlist,
41 base::TimeDelta duration)
42 : engine_host_(engine_host),
43 rendition_host_(rendition_host),
45 segment_duration_upper_limit_(playlist->GetTargetDuration()),
47 fetch_time_(kMovingAverageSampleSize) {
49 for (const auto& segment : playlist->GetSegments()) {
51 info.index = segments_.size();
52 info.segment = segment;
53 info.absolute_start = time;
54 time += segment->GetDuration();
55 info.absolute_end = time;
56 segments_.push_back(info);
59 fetch_queue_ = segments_.begin();
62 absl::optional<base::TimeDelta> HlsVodRendition::GetDuration() {
66 void HlsVodRendition::CheckState(
67 base::TimeDelta media_time,
69 ManifestDemuxer::DelayCallback time_remaining_cb) {
70 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
71 if (is_stopped_for_shutdown_ || segments_.empty()) {
72 std::move(time_remaining_cb).Run(kNoTimestamp);
76 auto ranges = engine_host_->GetBufferedRanges(role_);
78 // Ensure that we don't have a half-fetched stream when there's nothing
79 // loaded, since this case implies playback has just started or just
81 CHECK(!pending_stream_fetch_.has_value());
83 if (fetch_queue_ == segments_.end()) {
84 std::move(time_remaining_cb).Run(kNoTimestamp);
88 FetchNext(base::BindOnce(std::move(time_remaining_cb), base::Seconds(0)),
93 auto time_until_underflow = base::Seconds(0);
94 if (ranges.start(ranges.size() - 1) > media_time) {
95 // If media time comes before the last loaded range, then a seek probably
96 // failed, and this should be an error.
97 PipelineStatus error = DEMUXER_ERROR_COULD_NOT_PARSE;
98 engine_host_->OnError(std::move(error)
99 .WithData("timestamp", media_time)
100 .WithData("range_start", ranges.back().first)
101 .WithData("range_end", ranges.back().second));
105 if (ranges.contains(ranges.size() - 1, media_time)) {
106 // If media time is inside the last range, then we might have time to
107 // recalculate and clear buffers.
108 time_until_underflow = ranges.back().second - media_time;
109 time_until_underflow /= playback_rate ? playback_rate : 1;
112 // If the remaining time is large enough, then there is time to clear old
113 // data and delay for a bit. "Large enough" is calculated to be at least 10
114 // seconds, chosen based on the "slow 3g" connection setting in devtools for
115 // a 1080p h264 video stream. "Large enough" must also be much more than the
116 // amount of time it would take to fetch the next segment, and the 6x
117 // multiplier was again chosen after messing about with the devtools network
118 // speed panel. The delay is then calculated such that roughly half the buffer
119 // has been rendered by the time another state check happens.
120 if (time_until_underflow > kBufferDuration &&
121 time_until_underflow > fetch_time_.Mean() * 6) {
122 // We have buffered enough to have time to clear old segments and delay.
123 base::TimeDelta time_to_clear = ClearOldSegments(media_time);
124 auto delay_time = kNoTimestamp;
126 delay_time = time_until_underflow - (fetch_time_.Mean() * 10) -
127 time_to_clear - base::Seconds(5);
128 if (delay_time < base::TimeDelta()) {
129 delay_time = base::TimeDelta();
132 std::move(time_remaining_cb).Run(delay_time);
136 // If there is nothing more to fetch, then playback should just continue until
138 if (!pending_stream_fetch_.has_value() && fetch_queue_ == segments_.end()) {
139 if (!set_stream_end_) {
140 engine_host_->SetEndOfStream();
141 set_stream_end_ = true;
143 std::move(time_remaining_cb).Run(kNoTimestamp);
147 FetchNext(base::BindOnce(std::move(time_remaining_cb), base::Seconds(0)),
151 ManifestDemuxer::SeekResponse HlsVodRendition::Seek(base::TimeDelta seek_time) {
152 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
154 if (is_stopped_for_shutdown_) {
155 return PIPELINE_ERROR_ABORT;
158 if (set_stream_end_) {
159 set_stream_end_ = false;
160 engine_host_->UnsetEndOfStream();
163 auto ranges = engine_host_->GetBufferedRanges(role_);
164 if (!ranges.empty() && ranges.contains(ranges.size() - 1, seek_time)) {
165 // If the seek time is in the last loaded range, then there is no need to
166 // update the pending fetch state or clear/flush any buffers.
167 return ManifestDemuxer::SeekState::kIsReady;
170 // If we seek anywhere else, we should evict everything in order to avoid
171 // fragmented loaded sections and large memory consumption.
172 engine_host_->EvictCodedFrames(role_, base::Seconds(0), 0);
173 engine_host_->RemoveAndReset(role_, base::TimeDelta(), duration_,
176 // reset the queue of segments to the current seek time.
177 pending_stream_fetch_ = absl::nullopt;
179 std::lower_bound(segments_.begin(), segments_.end(), seek_time,
180 [](const SegmentInfo& segment, base::TimeDelta time) {
181 return segment.absolute_end < time;
184 // If we havent seeked to the end, we can then reset the sequence modes.
185 if (fetch_queue_ != segments_.end()) {
186 engine_host_->SetGroupStartIfParsingAndSequenceMode(
187 role_, (*fetch_queue_).absolute_start);
190 return ManifestDemuxer::SeekState::kNeedsData;
193 void HlsVodRendition::StartWaitingForSeek() {
194 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
195 pending_stream_fetch_ = absl::nullopt;
198 void HlsVodRendition::Stop() {
199 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
200 pending_stream_fetch_ = absl::nullopt;
201 is_stopped_for_shutdown_ = true;
204 base::TimeDelta HlsVodRendition::ClearOldSegments(base::TimeDelta media_time) {
205 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
206 CHECK(!is_stopped_for_shutdown_);
207 base::TimeTicks removal_start = base::TimeTicks::Now();
208 // Keep 10 seconds of content before the current media time. `media_time` is
209 // more accurately described as the highest timestamp yet seen in the current
210 // continual stream of playback, and depending on framerate, might be slightly
211 // ahead of where the actual currently-being-rendered frame is. Additionally,
212 // the ten seconds allows a small backwards-seek to not totally reset the
213 // buffer and redownload old content.
214 auto it = std::lower_bound(
215 segments_.begin(), segments_.end(), media_time - kBufferDuration,
216 [](const SegmentInfo& segment, base::TimeDelta time) {
217 return segment.absolute_end < time;
219 if (it != segments_.end()) {
220 if ((*it).absolute_start > base::TimeDelta()) {
221 engine_host_->Remove(role_, base::TimeDelta(), (*it).absolute_start);
224 return base::TimeTicks::Now() - removal_start;
227 void HlsVodRendition::FetchNext(base::OnceClosure cb, base::TimeDelta time) {
228 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
229 CHECK(!is_stopped_for_shutdown_);
230 CHECK(pending_stream_fetch_.has_value() || fetch_queue_ != segments_.end());
231 if (pending_stream_fetch_.has_value()) {
232 FetchMoreDataFromPendingStream(std::move(cb), time);
236 SegmentInfo* segment = &*fetch_queue_;
237 std::advance(fetch_queue_, 1);
238 LoadSegment(segment, time, std::move(cb));
241 void HlsVodRendition::LoadSegment(SegmentInfo* segment,
242 base::TimeDelta fetch_required_time,
243 base::OnceClosure cb) {
244 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
245 CHECK(!is_stopped_for_shutdown_);
247 rendition_host_->ReadFromUrl(
248 segment->segment->GetUri(), true, segment->segment->GetByteRange(),
249 base::BindPostTaskToCurrentDefault(base::BindOnce(
250 &HlsVodRendition::OnSegmentData, weak_factory_.GetWeakPtr(),
251 std::move(cb), std::move(fetch_required_time), segment->index,
252 base::TimeTicks::Now())));
255 void HlsVodRendition::FetchMoreDataFromPendingStream(
256 base::OnceClosure cb,
257 base::TimeDelta fetch_required_time) {
258 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
259 CHECK(!is_stopped_for_shutdown_);
260 CHECK(pending_stream_fetch_.has_value());
261 const SegmentInfo& segment = segments_[pending_stream_fetch_->index];
262 auto stream = std::move(pending_stream_fetch_->stream);
263 pending_stream_fetch_.reset();
264 rendition_host_->ReadStream(
266 base::BindOnce(&HlsVodRendition::OnSegmentData,
267 weak_factory_.GetWeakPtr(), std::move(cb),
268 std::move(fetch_required_time), segment.index,
269 base::TimeTicks::Now()));
272 void HlsVodRendition::OnSegmentData(base::OnceClosure cb,
273 base::TimeDelta required_time,
274 size_t segment_index,
275 base::TimeTicks net_req_start,
276 HlsDataSourceProvider::ReadResult result) {
277 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
278 if (is_stopped_for_shutdown_) {
283 if (!result.has_value()) {
284 // Drop |cb| here, and let the abort handler pick up the pieces.
285 return engine_host_->OnError(
286 {DEMUXER_ERROR_COULD_NOT_PARSE, std::move(result).error()});
288 CHECK_LT(segment_index, segments_.size());
290 base::TimeDelta end =
291 segments_[segment_index].absolute_start + segment_duration_upper_limit_;
293 std::unique_ptr<HlsDataSourceStream> stream = std::move(result).value();
295 if (!engine_host_->AppendAndParseData(
296 role_, base::TimeDelta(), end + base::Seconds(1), &parse_offset_,
297 stream->raw_data(), stream->buffer_size())) {
298 return engine_host_->OnError(DEMUXER_ERROR_COULD_NOT_PARSE);
301 auto fetch_duration = base::TimeTicks::Now() - net_req_start;
302 // Store the time it took to download this chunk. The time should be scaled
303 // for situations where we only have a few bytes left to download.
304 auto scaled = (fetch_duration * stream->buffer_size()) / kChunkSize;
305 fetch_time_.AddSample(scaled);
307 if (stream->CanReadMore()) {
309 pending_stream_fetch_.emplace(std::move(stream), segment_index);
312 // After a seek especially, we will start loading content that comes
313 // potentially much earlier than the seek time, and it's possible that the
314 // loaded ranges won't yet contain the timestamp that is required to be loaded
315 // for the seek to complete. In this case, we just keep fetching until
316 // the seek time is loaded.
318 auto ranges = engine_host_->GetBufferedRanges(role_);
319 if (ranges.size() && ranges.contains(ranges.size() - 1, required_time)) {
324 // If the last range doesn't contain the timestamp, keep parsing until it
325 // does. If there is nothing left to download, then we can return.
326 if (!pending_stream_fetch_.has_value() && fetch_queue_ == segments_.end()) {
331 FetchNext(std::move(cb), required_time);