[M120 Migration][hbbtv] Audio tracks count notification
[platform/framework/web/chromium-efl.git] / media / filters / hls_manifest_demuxer_engine_unittest.cc
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.
4
5 #include "media/filters/hls_manifest_demuxer_engine.h"
6 #include "media/filters/manifest_demuxer.h"
7
8 #include <memory>
9 #include <string>
10 #include <vector>
11
12 #include "base/memory/scoped_refptr.h"
13 #include "base/run_loop.h"
14 #include "base/test/gmock_callback_support.h"
15 #include "base/test/task_environment.h"
16 #include "media/base/mock_media_log.h"
17 #include "media/base/pipeline_status.h"
18 #include "media/base/test_helpers.h"
19 #include "media/filters/hls_data_source_provider.h"
20 #include "media/filters/hls_test_helpers.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 namespace media {
25
26 const std::string kInvalidMediaPlaylist =
27     "#This Wont Parse!\n"
28     "#EXT-X-ENDLIST\n";
29
30 const std::string kShortMediaPlaylist =
31     "#EXTM3U\n"
32     "#EXT-X-TARGETDURATION:10\n"
33     "#EXT-X-VERSION:3\n"
34     "#EXTINF:9.009,\n"
35     "http://media.example.com/first.ts\n"
36     "#EXT-X-ENDLIST\n";
37
38 const std::string kSimpleMediaPlaylist =
39     "#EXTM3U\n"
40     "#EXT-X-TARGETDURATION:10\n"
41     "#EXT-X-VERSION:3\n"
42     "#EXTINF:9.009,\n"
43     "http://media.example.com/first.ts\n"
44     "#EXTINF:9.009,\n"
45     "http://media.example.com/second.ts\n"
46     "#EXTINF:3.003,\n"
47     "http://media.example.com/third.ts\n"
48     "#EXT-X-ENDLIST\n";
49
50 const std::string kSimpleLiveMediaPlaylist =
51     "#EXTM3U\n"
52     "#EXT-X-TARGETDURATION:10\n"
53     "#EXT-X-VERSION:3\n"
54     "#EXT-X-MEDIA-SEQUENCE:18698597\n"
55     "#EXTINF:9.009,\n"
56     "http://media.example.com/first.ts\n"
57     "#EXTINF:9.009,\n"
58     "http://media.example.com/second.ts\n"
59     "#EXTINF:3.003,\n"
60     "http://media.example.com/third.ts\n";
61
62 const std::string kSingleInfoMediaPlaylist =
63     "#EXTM3U\n"
64     "#EXT-X-TARGETDURATION:10\n"
65     "#EXT-X-VERSION:3\n"
66     "#EXTINF:9.009,\n"
67     "http://media.example.com/only.ts\n";
68
69 const std::string kUnsupportedCodecs =
70     "#EXTM3U\n"
71     "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"vvc1.00.00\"\n"
72     "http://example.com/audio-only.m3u8\n"
73     "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"sheet.music\"\n"
74     "http://example.com/audio-only.m3u8\n"
75     "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"av02.00.00\"\n"
76     "http://example.com/audio-only.m3u8\n";
77
78 const std::string kSimpleMultivariantPlaylist =
79     "#EXTM3U\n"
80     "#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000\n"
81     "http://example.com/low.m3u8\n"
82     "#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000\n"
83     "http://example.com/mid.m3u8\n"
84     "#EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000\n"
85     "http://example.com/hi.m3u8\n"
86     "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
87     "http://example.com/audio-only.m3u8\n";
88
89 const std::string kMultivariantPlaylistWithAlts =
90     "#EXTM3U\n"
91     "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Eng\",DEFAULT=YES,"
92     "AUTOSELECT=YES,LANGUAGE=\"en\",URI=\"eng-audio.m3u8\"\n"
93     "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Ger\",DEFAULT=NO,"
94     "AUTOSELECT=YES,LANGUAGE=\"en\",URI=\"ger-audio.m3u8\"\n"
95     "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Com\",DEFAULT=NO,"
96     "AUTOSELECT=NO,LANGUAGE=\"en\",URI=\"eng-comments.m3u8\"\n"
97     "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"avc1.420000\",AUDIO=\"aac\"\n"
98     "low/video-only.m3u8\n"
99     "#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS=\"avc1.420000\",AUDIO=\"aac\"\n"
100     "mid/video-only.m3u8\n"
101     "#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"avc1.420000\",AUDIO=\"aac\"\n"
102     "hi/video-only.m3u8\n"
103     "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.05\",AUDIO=\"aac\"\n"
104     "main/english-audio.m3u8\n";
105
106 using ::base::test::RunOnceCallback;
107 using ::base::test::RunOnceClosure;
108 using testing::_;
109 using testing::AtLeast;
110 using testing::ByMove;
111 using testing::DoAll;
112 using testing::Eq;
113 using testing::Invoke;
114 using testing::NiceMock;
115 using testing::NotNull;
116 using testing::Ref;
117 using testing::Return;
118 using testing::SaveArg;
119 using testing::SetArgPointee;
120 using testing::StrictMock;
121
122 MATCHER_P2(CloseTo,
123            Target,
124            Radius,
125            std::string(negation ? "isn't" : "is") + " within " +
126                testing::PrintToString(Radius) + " of " +
127                testing::PrintToString(Target)) {
128   return (arg - Target <= Radius) || (Target - arg <= Radius);
129 }
130
131
132 class FakeHlsDataSourceProvider : public HlsDataSourceProvider {
133  private:
134   raw_ptr<HlsDataSourceProvider> mock_;
135
136  public:
137   FakeHlsDataSourceProvider(HlsDataSourceProvider* mock) : mock_(mock) {}
138
139   void ReadFromUrl(GURL url,
140                    absl::optional<hls::types::ByteRange> range,
141                    HlsDataSourceProvider::ReadCb request) override {
142     mock_->ReadFromUrl(url, range, std::move(request));
143   }
144
145   void ReadFromExistingStream(std::unique_ptr<HlsDataSourceStream> stream,
146                               HlsDataSourceProvider::ReadCb cb) override {
147     CHECK(!stream->CanReadMore());
148     std::move(cb).Run(std::move(stream));
149   }
150
151   void AbortPendingReads(base::OnceClosure callback) override {
152     mock_->AbortPendingReads(std::move(callback));
153   }
154 };
155
156 class HlsManifestDemuxerEngineTest : public testing::Test {
157  protected:
158   std::unique_ptr<MediaLog> media_log_;
159   std::unique_ptr<MockManifestDemuxerEngineHost> mock_mdeh_;
160   std::unique_ptr<MockHlsDataSourceProvider> mock_dsp_;
161   base::test::TaskEnvironment task_environment_;
162   std::unique_ptr<HlsManifestDemuxerEngine> engine_;
163   std::unique_ptr<MockCodecDetector> mock_detector_;
164
165   MOCK_METHOD(void, MockInitComplete, (PipelineStatus status), ());
166
167   template <typename T>
168   void BindUrlToDataSource(std::string url, std::string value) {
169     EXPECT_CALL(*mock_dsp_, ReadFromUrl(GURL(url), _, _))
170         .Times(1)
171         .WillOnce(RunOnceCallback<2>(T::CreateStream(value)));
172   }
173
174  public:
175   HlsManifestDemuxerEngineTest()
176       : media_log_(std::make_unique<NiceMock<media::MockMediaLog>>()),
177         mock_mdeh_(std::make_unique<NiceMock<MockManifestDemuxerEngineHost>>()),
178         mock_dsp_(std::make_unique<StrictMock<MockHlsDataSourceProvider>>()) {
179     ON_CALL(*mock_mdeh_, AddRole(_, _, _)).WillByDefault(Return(true));
180     ON_CALL(*mock_mdeh_, GetBufferedRanges(_))
181         .WillByDefault(Return(Ranges<base::TimeDelta>()));
182
183     EXPECT_CALL(*mock_dsp_, ReadFromExistingStream(_, _)).Times(0);
184
185     base::SequenceBound<FakeHlsDataSourceProvider> dsp(
186         task_environment_.GetMainThreadTaskRunner(), mock_dsp_.get());
187
188     engine_ = std::make_unique<HlsManifestDemuxerEngine>(
189         std::move(dsp), base::SingleThreadTaskRunner::GetCurrentDefault(),
190         GURL("http://media.example.com/manifest.m3u8"), media_log_.get());
191
192     mock_detector_ = std::make_unique<StrictMock<MockCodecDetector>>();
193   }
194
195   void InitializeEngine() {
196     engine_->Initialize(
197         mock_mdeh_.get(),
198         base::BindOnce(&HlsManifestDemuxerEngineTest::MockInitComplete,
199                        base::Unretained(this)));
200   }
201
202   void InitializeEngineWithMockDetector() {
203     engine_->InitializeWithMockCodecDetectorForTesting(
204         mock_mdeh_.get(),
205         base::BindOnce(&HlsManifestDemuxerEngineTest::MockInitComplete,
206                        base::Unretained(this)),
207         std::move(mock_detector_));
208   }
209
210   ~HlsManifestDemuxerEngineTest() override {
211     engine_->Stop();
212     base::RunLoop().RunUntilIdle();
213   }
214 };
215
216 TEST_F(HlsManifestDemuxerEngineTest, TestInitFailure) {
217   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
218       "http://media.example.com/manifest.m3u8", kInvalidMediaPlaylist);
219   EXPECT_CALL(*mock_mdeh_,
220               OnError(HasStatusCode(DEMUXER_ERROR_COULD_NOT_PARSE)));
221   EXPECT_CALL(*this, MockInitComplete(_)).Times(0);
222   InitializeEngine();
223   task_environment_.RunUntilIdle();
224   ASSERT_TRUE(engine_->IsSeekable());
225 }
226
227 TEST_F(HlsManifestDemuxerEngineTest, TestSimpleConfigAddsOnePrimaryRole) {
228   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(base::StringPiece("primary"), true));
229   EXPECT_CALL(*mock_mdeh_, SetDuration(21.021));
230   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("primary"), "video/mp2t",
231                                    "avc1.420000, mp4a.40.05"));
232   EXPECT_CALL(*mock_mdeh_, RemoveRole(base::StringPiece("primary")));
233   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
234       "http://media.example.com/manifest.m3u8", kSimpleMediaPlaylist);
235   BindUrlToDataSource<FileHlsDataSourceStreamFactory>(
236       "http://media.example.com/first.ts", "bear-1280x720-hls.ts");
237   EXPECT_CALL(*this, MockInitComplete(HasStatusCode(PIPELINE_OK)));
238   InitializeEngine();
239   task_environment_.RunUntilIdle();
240   ASSERT_TRUE(engine_->IsSeekable());
241 }
242
243 TEST_F(HlsManifestDemuxerEngineTest, TestSimpleLiveConfigAddsOnePrimaryRole) {
244   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(base::StringPiece("primary"), true));
245   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("primary"), "video/mp2t",
246                                    "avc1.420000, mp4a.40.05"));
247   EXPECT_CALL(*mock_mdeh_, RemoveRole(base::StringPiece("primary")));
248   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
249       "http://media.example.com/manifest.m3u8", kSimpleLiveMediaPlaylist);
250   BindUrlToDataSource<FileHlsDataSourceStreamFactory>(
251       "http://media.example.com/first.ts", "bear-1280x720-hls.ts");
252   EXPECT_CALL(*this, MockInitComplete(HasStatusCode(PIPELINE_OK)));
253   InitializeEngine();
254   task_environment_.RunUntilIdle();
255   ASSERT_FALSE(engine_->IsSeekable());
256 }
257
258 TEST_F(HlsManifestDemuxerEngineTest, TestMultivariantPlaylistNoAlternates) {
259   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(base::StringPiece("primary"), true));
260   EXPECT_CALL(*mock_mdeh_, SetDuration(21.021));
261   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("primary"), "video/mp2t",
262                                    "avc1.420000, mp4a.40.05"));
263   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
264       "http://media.example.com/manifest.m3u8", kSimpleMultivariantPlaylist);
265   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
266       "http://example.com/hi.m3u8", kSimpleMediaPlaylist);
267   BindUrlToDataSource<FileHlsDataSourceStreamFactory>(
268       "http://media.example.com/first.ts", "bear-1280x720-hls.ts");
269   EXPECT_CALL(*this, MockInitComplete(HasStatusCode(PIPELINE_OK)));
270   InitializeEngine();
271   task_environment_.RunUntilIdle();
272 }
273
274 TEST_F(HlsManifestDemuxerEngineTest, TestMultivariantPlaylistWithAlternates) {
275   EXPECT_CALL(*mock_mdeh_,
276               SetSequenceMode(base::StringPiece("audio-override"), true));
277   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(base::StringPiece("primary"), true));
278   EXPECT_CALL(*mock_mdeh_, SetDuration(21.021));
279   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("audio-override"),
280                                    "video/mp2t", "avc1.420000"));
281   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("primary"), "video/mp2t",
282                                    "avc1.420000"));
283
284   // URL queries in order:
285   //  - manifest.m3u8: root manifest
286   //  - eng-audio.m3u8: audio override rendition playlist
287   //  - only.ts: check the container/codecs for the audio override rendition
288   //  - video-only.m3u8: primary rendition
289   //  - first.ts: check container/codecs for the primary rendition
290   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
291       "http://media.example.com/manifest.m3u8", kMultivariantPlaylistWithAlts);
292   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
293       "http://media.example.com/eng-audio.m3u8", kSingleInfoMediaPlaylist);
294   BindUrlToDataSource<FileHlsDataSourceStreamFactory>(
295       "http://media.example.com/only.ts", "bear-1280x720-aac_he.ts");
296   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
297       "http://media.example.com/hi/video-only.m3u8", kSimpleMediaPlaylist);
298   BindUrlToDataSource<FileHlsDataSourceStreamFactory>(
299       "http://media.example.com/first.ts", "bear-1280x720-hls.ts");
300   EXPECT_CALL(*this, MockInitComplete(HasStatusCode(PIPELINE_OK)));
301   InitializeEngine();
302   task_environment_.RunUntilIdle();
303 }
304
305 TEST_F(HlsManifestDemuxerEngineTest, TestMultivariantWithNoSupportedCodecs) {
306   EXPECT_CALL(*mock_mdeh_, AddRole(_, _, _)).Times(0);
307   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(_, _)).Times(0);
308   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
309       "http://media.example.com/manifest.m3u8", kUnsupportedCodecs);
310   EXPECT_CALL(*mock_mdeh_,
311               OnError(HasStatusCode(DEMUXER_ERROR_COULD_NOT_PARSE)));
312   InitializeEngine();
313   task_environment_.RunUntilIdle();
314 }
315
316 TEST_F(HlsManifestDemuxerEngineTest, TestAsyncSeek) {
317   auto rendition = std::make_unique<StrictMock<MockHlsRendition>>();
318   EXPECT_CALL(*rendition, GetDuration()).WillOnce(Return(base::Seconds(30)));
319   auto* rendition_ptr = rendition.get();
320   engine_->AddRenditionForTesting(std::move(rendition));
321   // Set up rendition state and run, expecting no other callbacks.
322   task_environment_.RunUntilIdle();
323
324   // When seeking, indicate that we do not need to load more buffers.
325   EXPECT_CALL(*rendition_ptr, StartWaitingForSeek());
326   engine_->StartWaitingForSeek();
327   task_environment_.RunUntilIdle();
328
329   EXPECT_CALL(*rendition_ptr, Seek(_))
330       .WillOnce(Return(ManifestDemuxer::SeekState::kIsReady));
331   EXPECT_CALL(*mock_dsp_, AbortPendingReads(_)).WillOnce(RunOnceClosure<0>());
332   engine_->Seek(base::Seconds(10),
333                 base::BindOnce([](ManifestDemuxer::SeekResponse resp) {
334                   ASSERT_TRUE(resp.has_value());
335                   ASSERT_EQ(std::move(resp).value(),
336                             ManifestDemuxer::SeekState::kIsReady);
337                 }));
338   task_environment_.RunUntilIdle();
339
340   // Destruction should call stop.
341   EXPECT_CALL(*rendition_ptr, Stop());
342   task_environment_.RunUntilIdle();
343 }
344
345 TEST_F(HlsManifestDemuxerEngineTest, TestMultiRenditionCheckState) {
346   auto rendition1 = std::make_unique<MockHlsRendition>();
347   auto rendition2 = std::make_unique<MockHlsRendition>();
348   EXPECT_CALL(*rendition1, GetDuration()).WillOnce(Return(absl::nullopt));
349   EXPECT_CALL(*rendition2, GetDuration()).WillOnce(Return(absl::nullopt));
350
351   auto* rend1 = rendition1.get();
352   auto* rend2 = rendition2.get();
353   engine_->AddRenditionForTesting(std::move(rendition1));
354
355   // While there is only one rendition, the response from |OnTimeUpdate| is
356   // whatever that rendition wants.
357   EXPECT_CALL(*rend1, CheckState(_, _, _))
358       .WillOnce(RunOnceCallback<2>(base::Seconds(7)));
359   engine_->OnTimeUpdate(base::Seconds(0), 0.0,
360                         base::BindOnce([](base::TimeDelta r) {
361                           ASSERT_EQ(r, base::Seconds(7));
362                         }));
363
364   EXPECT_CALL(*rend1, CheckState(_, _, _))
365       .WillOnce(RunOnceCallback<2>(kNoTimestamp));
366   engine_->OnTimeUpdate(
367       base::Seconds(0), 0.0,
368       base::BindOnce([](base::TimeDelta r) { ASSERT_EQ(r, kNoTimestamp); }));
369
370   // After adding the second rendition, the response from OnTimeUpdate is now
371   // the lesser of (rend1.response - (calc time of rend2)) and
372   // (rend2.response)
373   engine_->AddRenditionForTesting(std::move(rendition2));
374
375   // Both renditions request time, so pick the lesser.
376   EXPECT_CALL(*rend1, CheckState(_, _, _))
377       .WillOnce(RunOnceCallback<2>(base::Seconds(7)));
378   EXPECT_CALL(*rend2, CheckState(_, _, _))
379       .WillOnce(RunOnceCallback<2>(base::Seconds(3)));
380   engine_->OnTimeUpdate(
381       base::Seconds(0), 0.0, base::BindOnce([](base::TimeDelta r) {
382         EXPECT_THAT(r, CloseTo(base::Seconds(3), base::Milliseconds(1)));
383       }));
384
385   // When one rendition provides kNoTimestamp and another does not, use the
386   // non-kNoTimestamp value.
387   EXPECT_CALL(*rend1, CheckState(_, _, _))
388       .WillOnce(RunOnceCallback<2>(kNoTimestamp));
389   EXPECT_CALL(*rend2, CheckState(_, _, _))
390       .WillOnce(RunOnceCallback<2>(base::Seconds(3)));
391   engine_->OnTimeUpdate(
392       base::Seconds(0), 0.0, base::BindOnce([](base::TimeDelta r) {
393         EXPECT_THAT(r, CloseTo(base::Seconds(3), base::Milliseconds(1)));
394       }));
395
396   EXPECT_CALL(*rend1, CheckState(_, _, _))
397       .WillOnce(RunOnceCallback<2>(base::Seconds(7)));
398   EXPECT_CALL(*rend2, CheckState(_, _, _))
399       .WillOnce(RunOnceCallback<2>(kNoTimestamp));
400   engine_->OnTimeUpdate(
401       base::Seconds(0), 0.0, base::BindOnce([](base::TimeDelta r) {
402         EXPECT_THAT(r, CloseTo(base::Seconds(7), base::Milliseconds(1)));
403       }));
404 }
405
406 TEST_F(HlsManifestDemuxerEngineTest, SeekAfterErrorFails) {
407   BindUrlToDataSource<StringHlsDataSourceStreamFactory>(
408       "http://media.example.com/manifest.m3u8", kInvalidMediaPlaylist);
409   EXPECT_CALL(*mock_mdeh_,
410               OnError(HasStatusCode(DEMUXER_ERROR_COULD_NOT_PARSE)));
411   EXPECT_CALL(*this, MockInitComplete(_)).Times(0);
412   InitializeEngine();
413   task_environment_.RunUntilIdle();
414
415   // When one of the renditions surfaces an error, ManifestDemuxer will request
416   // that the engine stop. Mimic that here.
417   engine_->Stop();
418   task_environment_.RunUntilIdle();
419
420   // Now if we try to seek, the response should be an instant aborted error.
421   engine_->Seek(base::Seconds(10),
422                 base::BindOnce([](ManifestDemuxer::SeekResponse resp) {
423                   ASSERT_FALSE(resp.has_value());
424                   ASSERT_EQ(std::move(resp).error(), PIPELINE_ERROR_ABORT);
425                 }));
426   task_environment_.RunUntilIdle();
427 }
428
429 TEST_F(HlsManifestDemuxerEngineTest, TestEndOfStreamAfterAllFetched) {
430   // All the expectations set during the initialization process.
431   EXPECT_CALL(*mock_mdeh_, SetSequenceMode(base::StringPiece("primary"), true));
432   EXPECT_CALL(*mock_mdeh_, AddRole(base::StringPiece("primary"), "video/mp2t",
433                                    "avc1.420000, mp4a.40.05"));
434   EXPECT_CALL(*mock_mdeh_, SetDuration(9.009));
435   HlsCodecDetector::ContainerAndCodecs mock_response = {
436       "video/mp2t", "avc1.420000, mp4a.40.05"};
437   EXPECT_CALL(*mock_detector_, DetermineContainerAndCodec(_, _))
438       .WillOnce(RunOnceCallback<1>(mock_response));
439   EXPECT_CALL(*this, MockInitComplete(HasStatusCode(PIPELINE_OK)));
440
441   // We can't use `BindUrlToDataSource` here, since it can't re-create streams
442   // like we need it to. The network requests are in order:
443   // - manifest.m3u8 - main manifest
444   // - first.ts      - request for the first few bytes to do codec detection
445   // - first.ts      - request for chunks of data to add to ChunkDemuxer
446   EXPECT_CALL(*mock_dsp_,
447               ReadFromUrl(GURL("http://media.example.com/manifest.m3u8"), _, _))
448       .WillOnce(RunOnceCallback<2>(
449           StringHlsDataSourceStreamFactory::CreateStream(kShortMediaPlaylist)));
450   EXPECT_CALL(*mock_dsp_,
451               ReadFromUrl(GURL("http://media.example.com/first.ts"), _, _))
452       .WillOnce(
453           RunOnceCallback<2>(StringHlsDataSourceStreamFactory::CreateStream(
454               "hey, this isn't a bitstream!")))
455       .WillOnce(
456           RunOnceCallback<2>(StringHlsDataSourceStreamFactory::CreateStream(
457               "do I look like a video to you?")));
458
459   // `GetBufferedRanges` gets called many times during this process:
460   // - HlsVodRendition::CheckState (1) => empty ranges, nothing loaded.
461   // - HlsVodRendition::OnSegmentData (1) => populated by AppendAndParseData
462   // - HlsVodRendition::CheckState (2) => still has data
463   Ranges<base::TimeDelta> populated_ranges;
464   populated_ranges.Add(base::Seconds(0), base::Seconds(5));
465   EXPECT_CALL(*mock_mdeh_, GetBufferedRanges(_))
466       .WillOnce(Return(Ranges<base::TimeDelta>()))
467       .WillOnce(Return(populated_ranges))
468       .WillOnce(Return(populated_ranges));
469
470   // The first call to `OnTimeUpdate` should trigger the append function,
471   // and our data was 30 characters long.
472   EXPECT_CALL(*mock_mdeh_,
473               AppendAndParseData("primary", base::Seconds(0), _, _, _, 30))
474       .WillOnce(Return(true));
475
476   // Finally, and EndOfStream call happens:
477   EXPECT_CALL(*mock_mdeh_, SetEndOfStream());
478
479   // And then teardown:
480   EXPECT_CALL(*mock_mdeh_, RemoveRole(base::StringPiece("primary")));
481
482   // Setup with a mock codec detector - this will set all the roles, duration,
483   // modes, and also make a request for the manifest and the first segment.
484   InitializeEngineWithMockDetector();
485   task_environment_.RunUntilIdle();
486
487   // For the first state check, there should be empty ranges, which triggers
488   // `HlsVodRendition::FetchNext`, which should request the data from first.ts
489   // add its content, and then return.
490   engine_->OnTimeUpdate(base::Seconds(0), 1.0, base::DoNothing());
491   task_environment_.RunUntilIdle();
492
493   // For the second state check, there are no more segments, no pending segment,
494   // and there are loaded ranges, so HlsVodRendition will report an EndOfStream.
495   engine_->OnTimeUpdate(base::Seconds(6), 1.0, base::DoNothing());
496   task_environment_.RunUntilIdle();
497
498   // Expectations on teardown.
499   ASSERT_TRUE(engine_->IsSeekable());
500   task_environment_.RunUntilIdle();
501 }
502
503 }  // namespace media