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"
6 #include "base/test/gmock_callback_support.h"
7 #include "base/test/task_environment.h"
8 #include "media/base/test_helpers.h"
9 #include "media/filters/hls_test_helpers.h"
15 const std::string kInitialFetchLivePlaylist =
18 "#EXT-X-TARGETDURATION:2\n"
19 "#EXT-X-MEDIA-SEQUENCE:14551245\n"
21 "playlist_4500Kb_14551245.ts\n"
23 "playlist_4500Kb_14551246.ts\n"
25 "playlist_4500Kb_14551247.ts\n"
27 "playlist_4500Kb_14551248.ts\n"
29 "playlist_4500Kb_14551249.ts\n"
31 "playlist_4500Kb_14551250.ts\n"
33 "playlist_4500Kb_14551251.ts\n"
35 "playlist_4500Kb_14551252.ts\n"
37 "playlist_4500Kb_14551253.ts\n"
39 "playlist_4500Kb_14551254.ts\n";
41 const std::string kSecondFetchLivePlaylist =
44 "#EXT-X-TARGETDURATION:2\n"
45 "#EXT-X-MEDIA-SEQUENCE:14551249\n"
47 "playlist_4500Kb_14551249.ts\n"
49 "playlist_4500Kb_14551250.ts\n"
51 "playlist_4500Kb_14551251.ts\n"
53 "playlist_4500Kb_14551252.ts\n"
55 "playlist_4500Kb_14551253.ts\n"
57 "playlist_4500Kb_14551254.ts\n"
59 "playlist_4500Kb_14551255.ts\n"
61 "playlist_4500Kb_14551256.ts\n"
63 "playlist_4500Kb_14551257.ts\n"
65 "playlist_4500Kb_14551258.ts\n";
70 using testing::Return;
72 class HlsLiveRenditionUnittest : public testing::Test {
74 std::unique_ptr<MockManifestDemuxerEngineHost> mock_mdeh_;
75 std::unique_ptr<MockHlsRenditionHost> mock_hrh_;
76 base::test::TaskEnvironment task_environment_{
77 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
79 std::unique_ptr<HlsLiveRendition> MakeLiveRendition(
81 base::StringPiece content) {
82 constexpr hls::types::DecimalInteger version = 3;
83 auto parsed = hls::MediaPlaylist::Parse(content, uri, version, nullptr);
84 if (!parsed.has_value()) {
85 LOG(ERROR) << MediaSerialize(std::move(parsed).error());
88 return std::make_unique<HlsLiveRendition>(mock_mdeh_.get(), mock_hrh_.get(),
89 "test", std::move(parsed).value(),
93 MOCK_METHOD(void, CheckStateComplete, (base::TimeDelta delay), ());
95 ManifestDemuxer::DelayCallback BindCheckState(base::TimeDelta time) {
96 EXPECT_CALL(*this, CheckStateComplete(time));
97 return base::BindOnce(&HlsLiveRenditionUnittest::CheckStateComplete,
98 base::Unretained(this));
101 ManifestDemuxer::DelayCallback BindCheckStateNoExpect() {
102 return base::BindOnce(&HlsLiveRenditionUnittest::CheckStateComplete,
103 base::Unretained(this));
106 void RespondToUrl(std::string uri,
108 bool batching = true) {
109 EXPECT_CALL(*mock_hrh_, ReadFromUrl(GURL(uri), batching, _, _))
110 .WillOnce([content = std::move(content), host = mock_hrh_.get()](
111 GURL url, bool, absl::optional<hls::types::ByteRange>,
112 HlsDataSourceProvider::ReadCb cb) {
113 auto stream = StringHlsDataSourceStreamFactory::CreateStream(content);
114 std::move(cb).Run(std::move(stream));
119 HlsLiveRenditionUnittest()
120 : mock_mdeh_(std::make_unique<MockManifestDemuxerEngineHost>()),
121 mock_hrh_(std::make_unique<MockHlsRenditionHost>()) {}
124 TEST_F(HlsLiveRenditionUnittest, TestNonRealTimePlaybackRate) {
126 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
127 ASSERT_NE(rendition, nullptr);
128 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
130 // Any rate not 0.0 or 1.0 should error.
131 EXPECT_CALL(*mock_mdeh_, OnError(_));
132 rendition->CheckState(base::Seconds(0), 2.0, BindCheckStateNoExpect());
133 task_environment_.RunUntilIdle();
136 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
137 task_environment_.RunUntilIdle();
140 TEST_F(HlsLiveRenditionUnittest, TestCreateRenditionPaused) {
142 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
143 ASSERT_NE(rendition, nullptr);
144 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
146 // CheckState causes the rentidion to:
147 // Check buffered ranges first
148 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_));
149 // The first segment will be queried
150 RespondToUrl("http://example.com/playlist_4500Kb_14551245.ts", "tscontent");
152 EXPECT_CALL(*mock_mdeh_, AppendAndParseData(_, _, _, _, _, 9))
153 .WillOnce(Return(true));
154 // CheckState should in this case respond with a delay of zero seconds.
155 rendition->CheckState(base::Seconds(0), 0.0,
156 BindCheckState(base::Seconds(0)));
157 task_environment_.RunUntilIdle();
160 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
161 task_environment_.RunUntilIdle();
164 TEST_F(HlsLiveRenditionUnittest, TestPausedRenditionHasSomeData) {
166 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
167 ASSERT_NE(rendition, nullptr);
168 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
170 // CheckState causes the rentidion to:
171 // Check buffered ranges first. In this case, we've loaded a bunch of content
172 // already, and our loaded ranges are [0 - 8)
173 Ranges<base::TimeDelta> loaded_ranges;
174 loaded_ranges.Add(base::Seconds(0), base::Seconds(8));
175 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
176 .WillOnce(Return(loaded_ranges));
177 // The next unqueried segment will be queried
178 RespondToUrl("http://example.com/playlist_4500Kb_14551245.ts", "tscontent");
180 EXPECT_CALL(*mock_mdeh_, AppendAndParseData(_, _, _, _, _, 9))
181 .WillOnce(Return(true));
182 // CheckState should in this case respond with a delay of zero seconds.
183 rendition->CheckState(base::Seconds(0), 0.0,
184 BindCheckState(base::Seconds(0)));
185 task_environment_.RunUntilIdle();
188 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
189 task_environment_.RunUntilIdle();
192 TEST_F(HlsLiveRenditionUnittest, TestPausedRenditionHasEnoughBufferedData) {
194 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
195 ASSERT_NE(rendition, nullptr);
196 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
198 // CheckState causes the rentidion to:
199 // Check buffered ranges first. In this case, we've loaded a bunch of content
200 // already, and our loaded ranges are [0 - 12)
201 Ranges<base::TimeDelta> loaded_ranges;
202 loaded_ranges.Add(base::Seconds(0), base::Seconds(12));
203 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
204 .WillOnce(Return(loaded_ranges));
205 // Old data will try to be removed. Since media time is 0, there is nothing
206 // to do. Then there will be an attempt to fetch a new manifest, which won't
207 // have any work to do either, instead just posting the delay_cb back.
208 // CheckState should in this case respond with a delay of 10 / 1.5 seconds.
209 rendition->CheckState(base::Seconds(0), 0.0,
210 BindCheckState(base::Seconds(10.0 / 1.5)));
211 task_environment_.RunUntilIdle();
214 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
215 task_environment_.RunUntilIdle();
218 TEST_F(HlsLiveRenditionUnittest, TestRenditionHasEnoughDataFetchNewManifest) {
220 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
221 ASSERT_NE(rendition, nullptr);
222 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
224 // CheckState causes the rentidion to:
225 // Check buffered ranges first. In this case, we've loaded a bunch of content
226 // already, and our loaded ranges are [0 - 12)
227 Ranges<base::TimeDelta> loaded_ranges;
228 loaded_ranges.Add(base::Seconds(0), base::Seconds(12));
229 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
230 .WillOnce(Return(loaded_ranges));
231 // Old data will try to be removed. Since media time is 0, there is nothing
232 // to do. Then there will be an attempt to fetch a new manifest, which will
234 task_environment_.FastForwardBy(base::Seconds(23));
235 RespondToUrl("http://example.com", kSecondFetchLivePlaylist, false);
237 EXPECT_CALL(*mock_hrh_, ParseMediaPlaylistFromStringSource(_, _, _))
238 .WillOnce([](base::StringPiece source, GURL uri,
239 hls::types::DecimalInteger version) {
240 return hls::MediaPlaylist::Parse(source, uri, version, nullptr);
243 // CheckState should in this case respond with a delay of 10 / 1.5 seconds.
244 rendition->CheckState(base::Seconds(0), 0.0,
245 BindCheckState(base::Seconds(10.0 / 1.5)));
246 task_environment_.RunUntilIdle();
249 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
250 task_environment_.RunUntilIdle();
253 TEST_F(HlsLiveRenditionUnittest, TestRenditionHasEnoughDataDeleteOldContent) {
255 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
256 ASSERT_NE(rendition, nullptr);
257 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
259 // CheckState causes the rentidion to:
260 // Check buffered ranges first. In this case, we've loaded a bunch of content
261 // already, and our loaded ranges are [0 - 32)
262 Ranges<base::TimeDelta> loaded_ranges;
263 loaded_ranges.Add(base::Seconds(0), base::Seconds(32));
264 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
265 .WillOnce(Return(loaded_ranges));
266 // Old data will try to be removed. Since media time is 15, there are 10
267 // seconds of old data to delete. There will be no new fetch and parse for
269 EXPECT_CALL(*mock_mdeh_, Remove(_, base::Seconds(0), base::Seconds(10)));
270 task_environment_.FastForwardBy(base::Seconds(15));
272 // CheckState should in this case respond with a delay of 10 / 1.5 seconds.
273 rendition->CheckState(base::Seconds(15), 0.0,
274 BindCheckState(base::Seconds(10.0 / 1.5)));
275 task_environment_.RunUntilIdle();
278 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
279 task_environment_.RunUntilIdle();
282 TEST_F(HlsLiveRenditionUnittest, TestPauseAndUnpause) {
284 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
285 ASSERT_NE(rendition, nullptr);
286 ASSERT_EQ(rendition->GetDuration(), absl::nullopt);
288 ON_CALL(*mock_mdeh_, OnError(_)).WillByDefault([](PipelineStatus st) {
289 LOG(ERROR) << MediaSerialize(st);
292 // Load a bunch of data, check state, will set `has_ever_played_`
293 Ranges<base::TimeDelta> loaded_ranges;
294 loaded_ranges.Add(base::Seconds(0), base::Seconds(32));
295 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
297 .WillRepeatedly(Return(loaded_ranges));
298 rendition->CheckState(base::Seconds(4), 1.0,
299 BindCheckState(base::Seconds(10 / 1.5)));
300 task_environment_.RunUntilIdle();
302 // The pause should remove everything.
303 EXPECT_CALL(*mock_mdeh_, Remove(_, base::Seconds(0), base::Seconds(32)));
304 rendition->CheckState(base::Seconds(4), 0.0, BindCheckState(kNoTimestamp));
305 task_environment_.RunUntilIdle();
307 // Restarting playback should requery the manifest, respond with another
308 // event for 0 seconds, expecting to download more
309 Ranges<base::TimeDelta> post_seek_ranges;
310 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
311 .WillRepeatedly(Return(post_seek_ranges));
312 RespondToUrl("http://example.com", kSecondFetchLivePlaylist, false);
313 EXPECT_CALL(*mock_hrh_, ParseMediaPlaylistFromStringSource(_, _, _))
314 .WillOnce([](base::StringPiece source, GURL uri,
315 hls::types::DecimalInteger version) {
316 return hls::MediaPlaylist::Parse(source, uri, version, nullptr);
318 rendition->CheckState(base::Seconds(4), 1.0,
319 BindCheckState(base::Seconds(0)));
320 task_environment_.RunUntilIdle();
322 // It then gets called again (since it was scheduled for zero seconds),
323 // and this time tries to download data.
324 RespondToUrl("http://example.com/playlist_4500Kb_14551249.ts", "tscontent");
325 EXPECT_CALL(*mock_mdeh_, AppendAndParseData(_, _, _, _, _, 9))
326 .WillOnce(Return(true));
327 rendition->CheckState(base::Seconds(4), 1.0,
328 BindCheckState(base::Seconds(0)));
329 task_environment_.RunUntilIdle();
331 // Loading that content creates a buffered range somewhere in the future,
332 // which we then get a request to seek to.
333 post_seek_ranges.Add(base::Seconds(1000), base::Seconds(1032));
334 EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
335 .WillRepeatedly(Return(post_seek_ranges));
336 EXPECT_CALL(*mock_mdeh_, RequestSeek(base::Seconds(1000)));
337 rendition->CheckState(base::Seconds(4), 1.0, BindCheckState(kNoTimestamp));
340 EXPECT_CALL(*mock_mdeh_, RemoveRole(_));
341 task_environment_.RunUntilIdle();
344 TEST_F(HlsLiveRenditionUnittest, TestStop) {
346 MakeLiveRendition(GURL("http://example.com"), kInitialFetchLivePlaylist);
347 ASSERT_NE(rendition, nullptr);
351 // Should always be kNoTimestamp after `Stop()` and no network requests.
352 rendition->CheckState(base::Seconds(0), 1.0, BindCheckState(kNoTimestamp));