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_live_rendition.h"
7 #include "base/task/bind_post_task.h"
8 #include "media/filters/hls_manifest_demuxer_engine.h"
12 HlsLiveRendition::~HlsLiveRendition() {
13 engine_host_->RemoveRole(role_);
16 HlsLiveRendition::HlsLiveRendition(ManifestDemuxerEngineHost* engine_host,
17 HlsRenditionHost* rendition_host,
19 scoped_refptr<hls::MediaPlaylist> playlist,
20 GURL media_playlist_uri)
21 : engine_host_(engine_host),
22 rendition_host_(rendition_host),
23 role_(std::move(role)),
24 media_playlist_uri_(std::move(media_playlist_uri)),
25 segment_duration_upper_limit_(playlist->GetTargetDuration()) {
26 AppendSegments(playlist.get());
29 absl::optional<base::TimeDelta> HlsLiveRendition::GetDuration() {
33 void HlsLiveRendition::CheckState(
34 base::TimeDelta media_time,
36 ManifestDemuxer::DelayCallback time_remaining_cb) {
37 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
38 if (is_stopped_for_shutdown_) {
39 std::move(time_remaining_cb).Run(kNoTimestamp);
43 if (playback_rate != 1 && playback_rate != 0) {
44 // TODO(crbug.com/1266991): What should be done about non-paused,
45 // non-real-time playback? Anything above 1 would hit the end and constantly
46 // be in a state of demuxer underflow, and anything slower than 1 would
47 // eventually have so much data buffered that it would OOM.
48 engine_host_->OnError(DEMUXER_ERROR_COULD_NOT_PARSE);
52 if (playback_rate != 0.0) {
53 has_ever_played_ = true;
56 if (playback_rate == 0.0 && has_ever_played_) {
57 require_seek_after_unpause_ = true;
59 std::move(time_remaining_cb).Run(kNoTimestamp);
63 auto loaded_ranges = engine_host_->GetBufferedRanges(role_);
64 if (require_seek_after_unpause_) {
65 if (loaded_ranges.empty() && segments_.empty()) {
66 // There should be no loaded ranges or segments after resuming from a
67 // pause, so fetch some.
68 FetchManifestUpdates(base::Seconds(0), std::move(time_remaining_cb));
72 if (loaded_ranges.empty()) {
73 // Now there are segments, but there is still no new content. Fetch and
74 // parse new content, before seeking.
75 ContinuePartialFetching(
76 base::BindOnce(std::move(time_remaining_cb), base::Seconds(0)));
80 require_seek_after_unpause_ = false;
81 engine_host_->RequestSeek(std::get<0>(loaded_ranges.back()));
83 // When the pipeline seeks, it should re-enter this state checking loop.
84 std::move(time_remaining_cb).Run(kNoTimestamp);
88 if (loaded_ranges.size() > 1) {
89 std::move(time_remaining_cb).Run(kNoTimestamp);
90 engine_host_->OnError(DEMUXER_ERROR_COULD_NOT_OPEN);
94 if (loaded_ranges.empty()) {
95 if (segments_.empty()) {
96 // This is likely to happen right after the player has started playing,
97 // which means we need a full update on the manifest.
98 // TODO(crbug/1266991): Use the manifest frequency polling logic here too.
99 MaybeFetchManifestUpdates(base::Seconds(0), std::move(time_remaining_cb));
103 ContinuePartialFetching(
104 base::BindOnce(std::move(time_remaining_cb), base::Seconds(0)));
108 auto actual_buffer_time = std::get<1>(loaded_ranges.back()) - media_time;
109 auto ideal_buffer_time = GetForwardBufferSize();
110 if (actual_buffer_time > ideal_buffer_time) {
111 // clear content older than the current media time.
112 ClearOldData(media_time);
114 // Finally, in this downtime, try to fetch manifest data. This has to be
115 // done periodically to get new segments, and since there is a good buffer
116 // currently, its a good time to do that fetch. Manifest updates are usually
117 // small, and shouldn't cause the buffer to run out.
118 auto delay_time = ideal_buffer_time / 1.5;
119 MaybeFetchManifestUpdates(delay_time, std::move(time_remaining_cb));
123 if (!partial_stream_ && segments_.empty()) {
124 // TODO(crbug/1266991) We've run out of segments, and will demuxer
125 // underflow shortly. This implies that the rendition should be
127 MaybeFetchManifestUpdates(base::Seconds(0), std::move(time_remaining_cb));
131 ContinuePartialFetching(
132 base::BindOnce(std::move(time_remaining_cb), base::Seconds(0)));
135 void HlsLiveRendition::ContinuePartialFetching(base::OnceClosure cb) {
136 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
137 CHECK(!is_stopped_for_shutdown_);
138 if (partial_stream_) {
139 FetchMoreDataFromPendingStream(std::move(cb));
142 if (!segments_.empty()) {
143 LoadSegment(*segments_.front(), std::move(cb));
148 ManifestDemuxer::SeekResponse HlsLiveRendition::Seek(base::TimeDelta time) {
149 return ManifestDemuxer::SeekState::kIsReady;
152 void HlsLiveRendition::StartWaitingForSeek() {
153 // Do nothing, seeking a live stream is not valid.
156 void HlsLiveRendition::Stop() {
157 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
158 partial_stream_ = nullptr;
159 is_stopped_for_shutdown_ = true;
162 base::TimeDelta HlsLiveRendition::GetForwardBufferSize() const {
163 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
164 CHECK(!is_stopped_for_shutdown_);
165 // Try to keep a buffer of at least 5x fetch time, or 3 seconds, whichever
166 // is longer. These numbers were picked based on trial and error to get a
168 if (fetch_time_.Count() == 0) {
169 return base::Seconds(10);
171 return std::max(base::Seconds(10), fetch_time_.Mean() * 5);
174 void HlsLiveRendition::LoadSegment(const hls::MediaSegment& segment,
175 base::OnceClosure cb) {
176 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
177 CHECK(!is_stopped_for_shutdown_);
178 rendition_host_->ReadFromUrl(
179 segment.GetUri(), /*read_chunked=*/true, segment.GetByteRange(),
180 base::BindOnce(&HlsLiveRendition::OnSegmentData,
181 weak_factory_.GetWeakPtr(), std::move(cb),
182 base::TimeTicks::Now()));
185 void HlsLiveRendition::FetchMoreDataFromPendingStream(base::OnceClosure cb) {
186 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
187 CHECK(!is_stopped_for_shutdown_);
188 CHECK(partial_stream_);
189 auto stream = std::move(partial_stream_);
190 rendition_host_->ReadStream(
191 std::move(stream), base::BindOnce(&HlsLiveRendition::OnSegmentData,
192 weak_factory_.GetWeakPtr(),
193 std::move(cb), base::TimeTicks::Now()));
196 void HlsLiveRendition::OnSegmentData(base::OnceClosure cb,
197 base::TimeTicks net_req_start,
198 HlsDataSourceProvider::ReadResult result) {
199 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
201 if (is_stopped_for_shutdown_) {
206 if (!result.has_value()) {
207 engine_host_->OnError(
208 {DEMUXER_ERROR_COULD_NOT_PARSE, std::move(result).error()});
212 // Always ensure we are parsing the entirety of the data chunk received.
213 auto sequences_seen = last_sequence_number_ - first_sequence_number_;
214 auto parse_end = segment_duration_upper_limit_ * (sequences_seen + 1);
215 auto stream = std::move(result).value();
217 if (!engine_host_->AppendAndParseData(role_, base::TimeDelta(), parse_end,
218 &parse_offset_, stream->raw_data(),
219 stream->buffer_size())) {
220 engine_host_->OnError(DEMUXER_ERROR_COULD_NOT_PARSE);
224 auto fetch_duration = base::TimeTicks::Now() - net_req_start;
225 // Adjust time based on a standard 4k download chunk.
226 auto scaled = (fetch_duration * stream->buffer_size()) / 4096;
227 fetch_time_.AddSample(scaled);
229 if (stream->CanReadMore()) {
231 partial_stream_ = std::move(stream);
237 void HlsLiveRendition::AppendSegments(hls::MediaPlaylist* playlist) {
238 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
239 CHECK(!is_stopped_for_shutdown_);
240 last_download_time_ = base::TimeTicks::Now();
241 for (const auto& segment : playlist->GetSegments()) {
242 if (first_sequence_number_ == 0) {
243 first_sequence_number_ = segment->GetMediaSequenceNumber();
245 if (segment->GetMediaSequenceNumber() <= last_sequence_number_) {
248 last_sequence_number_ = segment->GetMediaSequenceNumber();
249 segments_.push(segment);
253 void HlsLiveRendition::MaybeFetchManifestUpdates(
254 base::TimeDelta delay,
255 ManifestDemuxer::DelayCallback cb) {
256 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
257 CHECK(!is_stopped_for_shutdown_);
258 // Section 6.3.4 of the spec states that:
259 // the client MUST wait for at least the target duration before attempting
260 // to reload the Playlist file again, measured from the last time the client
261 // began loading the Playlist file.
262 auto since_last_manifest = base::TimeTicks::Now() - last_download_time_;
263 auto update_after = segment_duration_upper_limit_ * (segments_.size() + 1);
264 if (since_last_manifest > update_after) {
265 FetchManifestUpdates(delay, std::move(cb));
268 std::move(cb).Run(delay);
271 void HlsLiveRendition::FetchManifestUpdates(base::TimeDelta delay,
272 ManifestDemuxer::DelayCallback cb) {
273 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
274 CHECK(!is_stopped_for_shutdown_);
275 rendition_host_->ReadFromUrl(
276 media_playlist_uri_, /*read_chunked=*/false, absl::nullopt,
277 base::BindOnce(&HlsLiveRendition::OnManifestUpdates,
278 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), delay,
282 void HlsLiveRendition::OnManifestUpdates(
283 base::TimeTicks download_start_time,
284 base::TimeDelta delay_time,
285 ManifestDemuxer::DelayCallback cb,
286 HlsDataSourceProvider::ReadResult result) {
287 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
289 if (is_stopped_for_shutdown_) {
290 std::move(cb).Run(base::Seconds(0));
294 if (!result.has_value()) {
295 engine_host_->OnError(
296 {DEMUXER_ERROR_COULD_NOT_PARSE, std::move(result).error()});
300 auto stream = std::move(result).value();
301 if (stream->CanReadMore()) {
302 // TODO(crbug/1266991): Log a large manifest warning.
303 rendition_host_->ReadStream(
305 base::BindOnce(&HlsLiveRendition::OnManifestUpdates,
306 weak_factory_.GetWeakPtr(), download_start_time,
307 delay_time, std::move(cb)));
310 auto info = hls::Playlist::IdentifyPlaylist(stream->AsString());
311 if (!info.has_value()) {
312 engine_host_->OnError(
313 {DEMUXER_ERROR_COULD_NOT_PARSE, std::move(info).error()});
317 auto playlist = rendition_host_->ParseMediaPlaylistFromStringSource(
318 stream->AsString(), media_playlist_uri_, (*info).version);
319 if (!playlist.has_value()) {
320 engine_host_->OnError(
321 {DEMUXER_ERROR_COULD_NOT_PARSE, std::move(playlist).error()});
325 auto playlist_ptr = std::move(playlist).value();
326 AppendSegments(playlist_ptr.get());
328 base::TimeDelta fetch_time = base::TimeTicks::Now() - download_start_time;
329 std::move(cb).Run(std::max(base::Seconds(0), delay_time - fetch_time));
332 void HlsLiveRendition::ClearOldData(base::TimeDelta time) {
333 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
334 CHECK(!is_stopped_for_shutdown_);
335 // 5 seconds chosen mostly arbitrarily to keep some prior buffer while not
336 // keeping too much to cause memory issues.
337 if (time <= base::Seconds(5)) {
340 engine_host_->Remove(role_, base::TimeDelta(), time - base::Seconds(5));
343 void HlsLiveRendition::ResetForPause() {
344 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
345 CHECK(!is_stopped_for_shutdown_);
347 last_sequence_number_ = first_sequence_number_;
348 partial_stream_ = nullptr;
349 auto loaded_ranges = engine_host_->GetBufferedRanges(role_);
350 if (!loaded_ranges.empty()) {
351 auto end_time = std::get<1>(loaded_ranges.back());
352 engine_host_->Remove(role_, base::TimeDelta(), end_time);