• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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/pipeline_integration_test_base.h"
6 
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "build/build_config.h"
12 #include "media/base/cdm_promise.h"
13 #include "media/base/decoder_buffer.h"
14 #include "media/base/media_keys.h"
15 #include "media/base/media_switches.h"
16 #include "media/base/test_data_util.h"
17 #include "media/cdm/aes_decryptor.h"
18 #include "media/cdm/json_web_key.h"
19 #include "media/filters/chunk_demuxer.h"
20 
21 using testing::_;
22 using testing::AnyNumber;
23 using testing::AtMost;
24 using testing::SaveArg;
25 
26 namespace media {
27 
28 const char kSourceId[] = "SourceId";
29 const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 };
30 
31 const char kWebM[] = "video/webm; codecs=\"vp8,vorbis\"";
32 const char kWebMVP9[] = "video/webm; codecs=\"vp9\"";
33 const char kAudioOnlyWebM[] = "video/webm; codecs=\"vorbis\"";
34 const char kOpusAudioOnlyWebM[] = "video/webm; codecs=\"opus\"";
35 const char kVideoOnlyWebM[] = "video/webm; codecs=\"vp8\"";
36 const char kMP4VideoType[] = "video/mp4";
37 const char kMP4AudioType[] = "audio/mp4";
38 #if defined(USE_PROPRIETARY_CODECS)
39 const char kADTS[] = "audio/aac";
40 const char kMP4[] = "video/mp4; codecs=\"avc1.4D4041,mp4a.40.2\"";
41 const char kMP4Video[] = "video/mp4; codecs=\"avc1.4D4041\"";
42 const char kMP4VideoAVC3[] = "video/mp4; codecs=\"avc3.64001f\"";
43 const char kMP4Audio[] = "audio/mp4; codecs=\"mp4a.40.2\"";
44 const char kMP3[] = "audio/mpeg";
45 #endif  // defined(USE_PROPRIETARY_CODECS)
46 
47 // Key used to encrypt test files.
48 const uint8 kSecretKey[] = {
49   0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
50   0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
51 };
52 
53 // The key ID for all encrypted files.
54 const uint8 kKeyId[] = {
55   0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
56   0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35
57 };
58 
59 const int kAppendWholeFile = -1;
60 
61 // Constants for the Media Source config change tests.
62 const int kAppendTimeSec = 1;
63 const int kAppendTimeMs = kAppendTimeSec * 1000;
64 const int k320WebMFileDurationMs = 2736;
65 const int k640WebMFileDurationMs = 2749;
66 const int kOpusEndTrimmingWebMFileDurationMs = 2741;
67 const int kVP9WebMFileDurationMs = 2736;
68 const int kVP8AWebMFileDurationMs = 2733;
69 
70 #if defined(USE_PROPRIETARY_CODECS)
71 const int k640IsoFileDurationMs = 2737;
72 const int k640IsoCencFileDurationMs = 2736;
73 const int k1280IsoFileDurationMs = 2736;
74 const int k1280IsoAVC3FileDurationMs = 2736;
75 #endif  // defined(USE_PROPRIETARY_CODECS)
76 
77 // Return a timeline offset for bear-320x240-live.webm.
kLiveTimelineOffset()78 static base::Time kLiveTimelineOffset() {
79   // The file contians the following UTC timeline offset:
80   // 2012-11-10 12:34:56.789123456
81   // Since base::Time only has a resolution of microseconds,
82   // construct a base::Time for 2012-11-10 12:34:56.789123.
83   base::Time::Exploded exploded_time;
84   exploded_time.year = 2012;
85   exploded_time.month = 11;
86   exploded_time.day_of_month = 10;
87   exploded_time.hour = 12;
88   exploded_time.minute = 34;
89   exploded_time.second = 56;
90   exploded_time.millisecond = 789;
91   base::Time timeline_offset = base::Time::FromUTCExploded(exploded_time);
92 
93   timeline_offset += base::TimeDelta::FromMicroseconds(123);
94 
95   return timeline_offset;
96 }
97 
98 // FFmpeg only supports time a resolution of seconds so this
99 // helper function truncates a base::Time to seconds resolution.
TruncateToFFmpegTimeResolution(base::Time t)100 static base::Time TruncateToFFmpegTimeResolution(base::Time t) {
101   base::Time::Exploded exploded_time;
102   t.UTCExplode(&exploded_time);
103   exploded_time.millisecond = 0;
104 
105   return base::Time::FromUTCExploded(exploded_time);
106 }
107 
108 // Note: Tests using this class only exercise the DecryptingDemuxerStream path.
109 // They do not exercise the Decrypting{Audio|Video}Decoder path.
110 class FakeEncryptedMedia {
111  public:
112   // Defines the behavior of the "app" that responds to EME events.
113   class AppBase {
114    public:
~AppBase()115     virtual ~AppBase() {}
116 
117     virtual void OnSessionMessage(const std::string& web_session_id,
118                                   const std::vector<uint8>& message,
119                                   const GURL& destination_url) = 0;
120 
121     virtual void OnSessionReady(const std::string& web_session_id) = 0;
122 
123     virtual void OnSessionClosed(const std::string& web_session_id) = 0;
124 
125     // Errors are not expected unless overridden.
OnSessionError(const std::string & web_session_id,const std::string & error_name,uint32 system_code,const std::string & error_message)126     virtual void OnSessionError(const std::string& web_session_id,
127                                 const std::string& error_name,
128                                 uint32 system_code,
129                                 const std::string& error_message) {
130       FAIL() << "Unexpected Key Error";
131     }
132 
133     virtual void NeedKey(const std::string& type,
134                          const std::vector<uint8>& init_data,
135                          AesDecryptor* decryptor) = 0;
136   };
137 
FakeEncryptedMedia(AppBase * app)138   FakeEncryptedMedia(AppBase* app)
139       : decryptor_(base::Bind(&FakeEncryptedMedia::OnSessionMessage,
140                               base::Unretained(this)),
141                    base::Bind(&FakeEncryptedMedia::OnSessionClosed,
142                               base::Unretained(this))),
143         app_(app) {}
144 
decryptor()145   AesDecryptor* decryptor() {
146     return &decryptor_;
147   }
148 
149   // Callbacks for firing session events. Delegate to |app_|.
OnSessionMessage(const std::string & web_session_id,const std::vector<uint8> & message,const GURL & destination_url)150   void OnSessionMessage(const std::string& web_session_id,
151                         const std::vector<uint8>& message,
152                         const GURL& destination_url) {
153     app_->OnSessionMessage(web_session_id, message, destination_url);
154   }
155 
OnSessionReady(const std::string & web_session_id)156   void OnSessionReady(const std::string& web_session_id) {
157     app_->OnSessionReady(web_session_id);
158   }
159 
OnSessionClosed(const std::string & web_session_id)160   void OnSessionClosed(const std::string& web_session_id) {
161     app_->OnSessionClosed(web_session_id);
162   }
163 
OnSessionError(const std::string & web_session_id,const std::string & error_name,uint32 system_code,const std::string & error_message)164   void OnSessionError(const std::string& web_session_id,
165                       const std::string& error_name,
166                       uint32 system_code,
167                       const std::string& error_message) {
168     app_->OnSessionError(
169         web_session_id, error_name, system_code, error_message);
170   }
171 
NeedKey(const std::string & type,const std::vector<uint8> & init_data)172   void NeedKey(const std::string& type,
173                const std::vector<uint8>& init_data) {
174     app_->NeedKey(type, init_data, &decryptor_);
175   }
176 
177  private:
178   AesDecryptor decryptor_;
179   scoped_ptr<AppBase> app_;
180 };
181 
182 enum PromiseResult { RESOLVED, REJECTED };
183 
184 // Provides |kSecretKey| in response to needkey.
185 class KeyProvidingApp : public FakeEncryptedMedia::AppBase {
186  public:
KeyProvidingApp()187   KeyProvidingApp() {}
188 
OnResolveWithSession(PromiseResult expected,const std::string & web_session_id)189   void OnResolveWithSession(PromiseResult expected,
190                             const std::string& web_session_id) {
191     EXPECT_EQ(expected, RESOLVED);
192     EXPECT_GT(web_session_id.length(), 0ul);
193     current_session_id_ = web_session_id;
194   }
195 
OnResolve(PromiseResult expected)196   void OnResolve(PromiseResult expected) {
197     EXPECT_EQ(expected, RESOLVED);
198   }
199 
OnReject(PromiseResult expected,media::MediaKeys::Exception exception_code,uint32 system_code,const std::string & error_message)200   void OnReject(PromiseResult expected,
201                 media::MediaKeys::Exception exception_code,
202                 uint32 system_code,
203                 const std::string& error_message) {
204     EXPECT_EQ(expected, REJECTED);
205   }
206 
CreatePromise(PromiseResult expected)207   scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) {
208     scoped_ptr<media::SimpleCdmPromise> promise(new media::SimpleCdmPromise(
209         base::Bind(
210             &KeyProvidingApp::OnResolve, base::Unretained(this), expected),
211         base::Bind(
212             &KeyProvidingApp::OnReject, base::Unretained(this), expected)));
213     return promise.Pass();
214   }
215 
CreateSessionPromise(PromiseResult expected)216   scoped_ptr<NewSessionCdmPromise> CreateSessionPromise(
217       PromiseResult expected) {
218     scoped_ptr<media::NewSessionCdmPromise> promise(
219         new media::NewSessionCdmPromise(
220             base::Bind(&KeyProvidingApp::OnResolveWithSession,
221                        base::Unretained(this),
222                        expected),
223             base::Bind(
224                 &KeyProvidingApp::OnReject, base::Unretained(this), expected)));
225     return promise.Pass();
226   }
227 
OnSessionMessage(const std::string & web_session_id,const std::vector<uint8> & message,const GURL & destination_url)228   virtual void OnSessionMessage(const std::string& web_session_id,
229                                 const std::vector<uint8>& message,
230                                 const GURL& destination_url) OVERRIDE {
231     EXPECT_FALSE(web_session_id.empty());
232     EXPECT_FALSE(message.empty());
233     EXPECT_EQ(current_session_id_, web_session_id);
234   }
235 
OnSessionReady(const std::string & web_session_id)236   virtual void OnSessionReady(const std::string& web_session_id) OVERRIDE {
237     EXPECT_EQ(current_session_id_, web_session_id);
238   }
239 
OnSessionClosed(const std::string & web_session_id)240   virtual void OnSessionClosed(const std::string& web_session_id) OVERRIDE {
241     EXPECT_EQ(current_session_id_, web_session_id);
242   }
243 
NeedKey(const std::string & type,const std::vector<uint8> & init_data,AesDecryptor * decryptor)244   virtual void NeedKey(const std::string& type,
245                        const std::vector<uint8>& init_data,
246                        AesDecryptor* decryptor) OVERRIDE {
247     if (current_session_id_.empty()) {
248       decryptor->CreateSession(type,
249                                kInitData,
250                                arraysize(kInitData),
251                                MediaKeys::TEMPORARY_SESSION,
252                                CreateSessionPromise(RESOLVED));
253       EXPECT_FALSE(current_session_id_.empty());
254     }
255 
256     // Clear Key really needs the key ID in |init_data|. For WebM, they are the
257     // same, but this is not the case for ISO CENC. Therefore, provide the
258     // correct key ID.
259     const uint8* key_id = init_data.empty() ? NULL : &init_data[0];
260     size_t key_id_length = init_data.size();
261     if (type == kMP4AudioType || type == kMP4VideoType) {
262       key_id = kKeyId;
263       key_id_length = arraysize(kKeyId);
264     }
265 
266     // Convert key into a JSON structure and then add it.
267     std::string jwk = GenerateJWKSet(
268         kSecretKey, arraysize(kSecretKey), key_id, key_id_length);
269     decryptor->UpdateSession(current_session_id_,
270                              reinterpret_cast<const uint8*>(jwk.data()),
271                              jwk.size(),
272                              CreatePromise(RESOLVED));
273   }
274 
275   std::string current_session_id_;
276 };
277 
278 class RotatingKeyProvidingApp : public KeyProvidingApp {
279  public:
RotatingKeyProvidingApp()280   RotatingKeyProvidingApp() : num_distint_need_key_calls_(0) {}
~RotatingKeyProvidingApp()281   virtual ~RotatingKeyProvidingApp() {
282     // Expect that NeedKey is fired multiple times with different |init_data|.
283     EXPECT_GT(num_distint_need_key_calls_, 1u);
284   }
285 
NeedKey(const std::string & type,const std::vector<uint8> & init_data,AesDecryptor * decryptor)286   virtual void NeedKey(const std::string& type,
287                        const std::vector<uint8>& init_data,
288                        AesDecryptor* decryptor) OVERRIDE {
289     // Skip the request if the |init_data| has been seen.
290     if (init_data == prev_init_data_)
291       return;
292     prev_init_data_ = init_data;
293     ++num_distint_need_key_calls_;
294 
295     decryptor->CreateSession(type,
296                              vector_as_array(&init_data),
297                              init_data.size(),
298                              MediaKeys::TEMPORARY_SESSION,
299                              CreateSessionPromise(RESOLVED));
300 
301     std::vector<uint8> key_id;
302     std::vector<uint8> key;
303     EXPECT_TRUE(GetKeyAndKeyId(init_data, &key, &key_id));
304 
305     // Convert key into a JSON structure and then add it.
306     std::string jwk = GenerateJWKSet(vector_as_array(&key),
307                                      key.size(),
308                                      vector_as_array(&key_id),
309                                      key_id.size());
310     decryptor->UpdateSession(current_session_id_,
311                              reinterpret_cast<const uint8*>(jwk.data()),
312                              jwk.size(),
313                              CreatePromise(RESOLVED));
314   }
315 
316  private:
GetKeyAndKeyId(std::vector<uint8> init_data,std::vector<uint8> * key,std::vector<uint8> * key_id)317   bool GetKeyAndKeyId(std::vector<uint8> init_data,
318                       std::vector<uint8>* key,
319                       std::vector<uint8>* key_id) {
320     // For WebM, init_data is key_id; for ISO CENC, init_data should contain
321     // the key_id. We assume key_id is in the end of init_data here (that is
322     // only a reasonable assumption for WebM and clear key ISO CENC).
323     DCHECK_GE(init_data.size(), arraysize(kKeyId));
324     std::vector<uint8> key_id_from_init_data(
325         init_data.end() - arraysize(kKeyId), init_data.end());
326 
327     key->assign(kSecretKey, kSecretKey + arraysize(kSecretKey));
328     key_id->assign(kKeyId, kKeyId + arraysize(kKeyId));
329 
330     // The Key and KeyId for this testing key provider are created by left
331     // rotating kSecretKey and kKeyId. Note that this implementation is only
332     // intended for testing purpose. The actual key rotation algorithm can be
333     // much more complicated.
334     // Find out the rotating position from |key_id_from_init_data| and apply on
335     // |key|.
336     for (size_t pos = 0; pos < arraysize(kKeyId); ++pos) {
337       std::rotate(key_id->begin(), key_id->begin() + pos, key_id->end());
338       if (*key_id == key_id_from_init_data) {
339         std::rotate(key->begin(), key->begin() + pos, key->end());
340         return true;
341       }
342     }
343     return false;
344   }
345 
346   std::vector<uint8> prev_init_data_;
347   uint32 num_distint_need_key_calls_;
348 };
349 
350 // Ignores needkey and does not perform a license request
351 class NoResponseApp : public FakeEncryptedMedia::AppBase {
352  public:
OnSessionMessage(const std::string & web_session_id,const std::vector<uint8> & message,const GURL & default_url)353   virtual void OnSessionMessage(const std::string& web_session_id,
354                                 const std::vector<uint8>& message,
355                                 const GURL& default_url) OVERRIDE {
356     EXPECT_FALSE(web_session_id.empty());
357     EXPECT_FALSE(message.empty());
358     FAIL() << "Unexpected Message";
359   }
360 
OnSessionReady(const std::string & web_session_id)361   virtual void OnSessionReady(const std::string& web_session_id) OVERRIDE {
362     EXPECT_FALSE(web_session_id.empty());
363     FAIL() << "Unexpected Ready";
364   }
365 
OnSessionClosed(const std::string & web_session_id)366   virtual void OnSessionClosed(const std::string& web_session_id) OVERRIDE {
367     EXPECT_FALSE(web_session_id.empty());
368     FAIL() << "Unexpected Closed";
369   }
370 
NeedKey(const std::string & type,const std::vector<uint8> & init_data,AesDecryptor * decryptor)371   virtual void NeedKey(const std::string& type,
372                        const std::vector<uint8>& init_data,
373                        AesDecryptor* decryptor) OVERRIDE {
374   }
375 };
376 
377 // Helper class that emulates calls made on the ChunkDemuxer by the
378 // Media Source API.
379 class MockMediaSource {
380  public:
MockMediaSource(const std::string & filename,const std::string & mimetype,int initial_append_size)381   MockMediaSource(const std::string& filename,
382                   const std::string& mimetype,
383                   int initial_append_size)
384       : file_path_(GetTestDataFilePath(filename)),
385         current_position_(0),
386         initial_append_size_(initial_append_size),
387         mimetype_(mimetype),
388         chunk_demuxer_(new ChunkDemuxer(
389             base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)),
390             base::Bind(&MockMediaSource::DemuxerNeedKey,
391                        base::Unretained(this)),
392             LogCB(),
393             true)),
394         owned_chunk_demuxer_(chunk_demuxer_) {
395 
396     file_data_ = ReadTestDataFile(filename);
397 
398     if (initial_append_size_ == kAppendWholeFile)
399       initial_append_size_ = file_data_->data_size();
400 
401     DCHECK_GT(initial_append_size_, 0);
402     DCHECK_LE(initial_append_size_, file_data_->data_size());
403   }
404 
~MockMediaSource()405   virtual ~MockMediaSource() {}
406 
GetDemuxer()407   scoped_ptr<Demuxer> GetDemuxer() { return owned_chunk_demuxer_.Pass(); }
408 
set_need_key_cb(const Demuxer::NeedKeyCB & need_key_cb)409   void set_need_key_cb(const Demuxer::NeedKeyCB& need_key_cb) {
410     need_key_cb_ = need_key_cb;
411   }
412 
Seek(base::TimeDelta seek_time,int new_position,int seek_append_size)413   void Seek(base::TimeDelta seek_time, int new_position, int seek_append_size) {
414     chunk_demuxer_->StartWaitingForSeek(seek_time);
415 
416     chunk_demuxer_->Abort(
417         kSourceId,
418         base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_);
419 
420     DCHECK_GE(new_position, 0);
421     DCHECK_LT(new_position, file_data_->data_size());
422     current_position_ = new_position;
423 
424     AppendData(seek_append_size);
425   }
426 
AppendData(int size)427   void AppendData(int size) {
428     DCHECK(chunk_demuxer_);
429     DCHECK_LT(current_position_, file_data_->data_size());
430     DCHECK_LE(current_position_ + size, file_data_->data_size());
431 
432     chunk_demuxer_->AppendData(
433         kSourceId, file_data_->data() + current_position_, size,
434         base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_);
435     current_position_ += size;
436   }
437 
AppendAtTime(base::TimeDelta timestamp_offset,const uint8 * pData,int size)438   void AppendAtTime(base::TimeDelta timestamp_offset,
439                     const uint8* pData,
440                     int size) {
441     CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId));
442     chunk_demuxer_->AppendData(kSourceId, pData, size,
443                                base::TimeDelta(), kInfiniteDuration(),
444                                &timestamp_offset);
445     last_timestamp_offset_ = timestamp_offset;
446   }
447 
AppendAtTimeWithWindow(base::TimeDelta timestamp_offset,base::TimeDelta append_window_start,base::TimeDelta append_window_end,const uint8 * pData,int size)448   void AppendAtTimeWithWindow(base::TimeDelta timestamp_offset,
449                               base::TimeDelta append_window_start,
450                               base::TimeDelta append_window_end,
451                               const uint8* pData,
452                               int size) {
453     CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId));
454     chunk_demuxer_->AppendData(kSourceId,
455                                pData,
456                                size,
457                                append_window_start,
458                                append_window_end,
459                                &timestamp_offset);
460     last_timestamp_offset_ = timestamp_offset;
461   }
462 
EndOfStream()463   void EndOfStream() {
464     chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
465   }
466 
Abort()467   void Abort() {
468     if (!chunk_demuxer_)
469       return;
470     chunk_demuxer_->Shutdown();
471     chunk_demuxer_ = NULL;
472   }
473 
DemuxerOpened()474   void DemuxerOpened() {
475     base::MessageLoop::current()->PostTask(
476         FROM_HERE, base::Bind(&MockMediaSource::DemuxerOpenedTask,
477                               base::Unretained(this)));
478   }
479 
DemuxerOpenedTask()480   void DemuxerOpenedTask() {
481     // This code assumes that |mimetype_| is one of the following forms.
482     // 1. audio/mpeg
483     // 2. video/webm;codec="vorbis,vp8".
484     size_t semicolon = mimetype_.find(";");
485     std::string type = mimetype_;
486     std::vector<std::string> codecs;
487     if (semicolon != std::string::npos) {
488       type = mimetype_.substr(0, semicolon);
489       size_t codecs_param_start = mimetype_.find("codecs=\"", semicolon);
490 
491       CHECK_NE(codecs_param_start, std::string::npos);
492 
493       codecs_param_start += 8; // Skip over the codecs=".
494 
495       size_t codecs_param_end = mimetype_.find("\"", codecs_param_start);
496 
497       CHECK_NE(codecs_param_end, std::string::npos);
498 
499       std::string codecs_param =
500           mimetype_.substr(codecs_param_start,
501                            codecs_param_end - codecs_param_start);
502       Tokenize(codecs_param, ",", &codecs);
503     }
504 
505     CHECK_EQ(chunk_demuxer_->AddId(kSourceId, type, codecs), ChunkDemuxer::kOk);
506 
507     AppendData(initial_append_size_);
508   }
509 
DemuxerNeedKey(const std::string & type,const std::vector<uint8> & init_data)510   void DemuxerNeedKey(const std::string& type,
511                       const std::vector<uint8>& init_data) {
512     DCHECK(!init_data.empty());
513     CHECK(!need_key_cb_.is_null());
514     need_key_cb_.Run(type, init_data);
515   }
516 
last_timestamp_offset() const517   base::TimeDelta last_timestamp_offset() const {
518     return last_timestamp_offset_;
519   }
520 
521  private:
522   base::FilePath file_path_;
523   scoped_refptr<DecoderBuffer> file_data_;
524   int current_position_;
525   int initial_append_size_;
526   std::string mimetype_;
527   ChunkDemuxer* chunk_demuxer_;
528   scoped_ptr<Demuxer> owned_chunk_demuxer_;
529   Demuxer::NeedKeyCB need_key_cb_;
530   base::TimeDelta last_timestamp_offset_;
531 };
532 
533 class PipelineIntegrationTest
534     : public testing::Test,
535       public PipelineIntegrationTestBase {
536  public:
StartPipelineWithMediaSource(MockMediaSource * source)537   void StartPipelineWithMediaSource(MockMediaSource* source) {
538     EXPECT_CALL(*this, OnMetadata(_)).Times(AtMost(1))
539         .WillRepeatedly(SaveArg<0>(&metadata_));
540     EXPECT_CALL(*this, OnPrerollCompleted()).Times(AtMost(1));
541     pipeline_->Start(
542         CreateFilterCollection(source->GetDemuxer(), NULL),
543         base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
544         base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
545         QuitOnStatusCB(PIPELINE_OK),
546         base::Bind(&PipelineIntegrationTest::OnMetadata,
547                    base::Unretained(this)),
548         base::Bind(&PipelineIntegrationTest::OnPrerollCompleted,
549                    base::Unretained(this)),
550         base::Closure());
551 
552     message_loop_.Run();
553   }
554 
StartHashedPipelineWithMediaSource(MockMediaSource * source)555   void StartHashedPipelineWithMediaSource(MockMediaSource* source) {
556     hashing_enabled_ = true;
557     StartPipelineWithMediaSource(source);
558   }
559 
StartPipelineWithEncryptedMedia(MockMediaSource * source,FakeEncryptedMedia * encrypted_media)560   void StartPipelineWithEncryptedMedia(
561       MockMediaSource* source,
562       FakeEncryptedMedia* encrypted_media) {
563     EXPECT_CALL(*this, OnMetadata(_)).Times(AtMost(1))
564         .WillRepeatedly(SaveArg<0>(&metadata_));
565     EXPECT_CALL(*this, OnPrerollCompleted()).Times(AtMost(1));
566     pipeline_->Start(
567         CreateFilterCollection(source->GetDemuxer(),
568                                encrypted_media->decryptor()),
569         base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
570         base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
571         QuitOnStatusCB(PIPELINE_OK),
572         base::Bind(&PipelineIntegrationTest::OnMetadata,
573                    base::Unretained(this)),
574         base::Bind(&PipelineIntegrationTest::OnPrerollCompleted,
575                    base::Unretained(this)),
576         base::Closure());
577 
578     source->set_need_key_cb(base::Bind(&FakeEncryptedMedia::NeedKey,
579                                        base::Unretained(encrypted_media)));
580 
581     message_loop_.Run();
582   }
583 
584   // Verifies that seeking works properly for ChunkDemuxer when the
585   // seek happens while there is a pending read on the ChunkDemuxer
586   // and no data is available.
TestSeekDuringRead(const std::string & filename,const std::string & mimetype,int initial_append_size,base::TimeDelta start_seek_time,base::TimeDelta seek_time,int seek_file_position,int seek_append_size)587   bool TestSeekDuringRead(const std::string& filename,
588                           const std::string& mimetype,
589                           int initial_append_size,
590                           base::TimeDelta start_seek_time,
591                           base::TimeDelta seek_time,
592                           int seek_file_position,
593                           int seek_append_size) {
594     MockMediaSource source(filename, mimetype, initial_append_size);
595     StartPipelineWithMediaSource(&source);
596 
597     if (pipeline_status_ != PIPELINE_OK)
598       return false;
599 
600     Play();
601     if (!WaitUntilCurrentTimeIsAfter(start_seek_time))
602       return false;
603 
604     source.Seek(seek_time, seek_file_position, seek_append_size);
605     if (!Seek(seek_time))
606       return false;
607 
608     source.EndOfStream();
609 
610     source.Abort();
611     Stop();
612     return true;
613   }
614 };
615 
TEST_F(PipelineIntegrationTest,BasicPlayback)616 TEST_F(PipelineIntegrationTest, BasicPlayback) {
617   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
618 
619   Play();
620 
621   ASSERT_TRUE(WaitUntilOnEnded());
622 }
623 
TEST_F(PipelineIntegrationTest,BasicPlaybackOpusOgg)624 TEST_F(PipelineIntegrationTest, BasicPlaybackOpusOgg) {
625   ASSERT_TRUE(Start(GetTestDataFilePath("bear-opus.ogg"), PIPELINE_OK));
626 
627   Play();
628 
629   ASSERT_TRUE(WaitUntilOnEnded());
630 }
631 
TEST_F(PipelineIntegrationTest,BasicPlaybackHashed)632 TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) {
633   ASSERT_TRUE(Start(
634       GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK, kHashed));
635 
636   Play();
637 
638   ASSERT_TRUE(WaitUntilOnEnded());
639 
640   EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash());
641   EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash());
642   EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null());
643 }
644 
TEST_F(PipelineIntegrationTest,BasicPlaybackLive)645 TEST_F(PipelineIntegrationTest, BasicPlaybackLive) {
646   ASSERT_TRUE(Start(
647       GetTestDataFilePath("bear-320x240-live.webm"), PIPELINE_OK, kHashed));
648 
649   Play();
650 
651   ASSERT_TRUE(WaitUntilOnEnded());
652 
653   EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash());
654   EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash());
655 
656   // TODO: Fix FFmpeg code to return higher resolution time values so
657   // we don't have to truncate our expectations here.
658   EXPECT_EQ(TruncateToFFmpegTimeResolution(kLiveTimelineOffset()),
659             demuxer_->GetTimelineOffset());
660 }
661 
TEST_F(PipelineIntegrationTest,F32PlaybackHashed)662 TEST_F(PipelineIntegrationTest, F32PlaybackHashed) {
663   ASSERT_TRUE(
664       Start(GetTestDataFilePath("sfx_f32le.wav"), PIPELINE_OK, kHashed));
665   Play();
666   ASSERT_TRUE(WaitUntilOnEnded());
667   EXPECT_EQ(std::string(kNullVideoHash), GetVideoHash());
668   EXPECT_EQ("3.03,2.86,2.99,3.31,3.57,4.06,", GetAudioHash());
669 }
670 
TEST_F(PipelineIntegrationTest,BasicPlaybackEncrypted)671 TEST_F(PipelineIntegrationTest, BasicPlaybackEncrypted) {
672   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
673   set_need_key_cb(base::Bind(&FakeEncryptedMedia::NeedKey,
674                              base::Unretained(&encrypted_media)));
675 
676   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-av_enc-av.webm"),
677                     encrypted_media.decryptor()));
678 
679   Play();
680 
681   ASSERT_TRUE(WaitUntilOnEnded());
682   Stop();
683 }
684 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource)685 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) {
686   MockMediaSource source("bear-320x240.webm", kWebM, 219229);
687   StartPipelineWithMediaSource(&source);
688   source.EndOfStream();
689 
690   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
691   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
692   EXPECT_EQ(k320WebMFileDurationMs,
693             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
694 
695   Play();
696 
697   ASSERT_TRUE(WaitUntilOnEnded());
698 
699   EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null());
700   source.Abort();
701   Stop();
702 }
703 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource_Live)704 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Live) {
705   MockMediaSource source("bear-320x240-live.webm", kWebM, 219221);
706   StartPipelineWithMediaSource(&source);
707   source.EndOfStream();
708 
709   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
710   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
711   EXPECT_EQ(k320WebMFileDurationMs,
712             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
713 
714   Play();
715 
716   ASSERT_TRUE(WaitUntilOnEnded());
717 
718   EXPECT_EQ(kLiveTimelineOffset(),
719             demuxer_->GetTimelineOffset());
720   source.Abort();
721   Stop();
722 }
723 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource_VP9_WebM)724 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP9_WebM) {
725   MockMediaSource source("bear-vp9.webm", kWebMVP9, 67504);
726   StartPipelineWithMediaSource(&source);
727   source.EndOfStream();
728 
729   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
730   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
731   EXPECT_EQ(kVP9WebMFileDurationMs,
732             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
733 
734   Play();
735 
736   ASSERT_TRUE(WaitUntilOnEnded());
737   source.Abort();
738   Stop();
739 }
740 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource_VP8A_WebM)741 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP8A_WebM) {
742   MockMediaSource source("bear-vp8a.webm", kVideoOnlyWebM, kAppendWholeFile);
743   StartPipelineWithMediaSource(&source);
744   source.EndOfStream();
745 
746   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
747   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
748   EXPECT_EQ(kVP8AWebMFileDurationMs,
749             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
750 
751   Play();
752 
753   ASSERT_TRUE(WaitUntilOnEnded());
754   source.Abort();
755   Stop();
756 }
757 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource_Opus_WebM)758 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Opus_WebM) {
759   MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM,
760                          kAppendWholeFile);
761   StartPipelineWithMediaSource(&source);
762   source.EndOfStream();
763 
764   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
765   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
766   EXPECT_EQ(kOpusEndTrimmingWebMFileDurationMs,
767             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
768   Play();
769 
770   ASSERT_TRUE(WaitUntilOnEnded());
771   source.Abort();
772   Stop();
773 }
774 
775 // Flaky. http://crbug.com/304776
TEST_F(PipelineIntegrationTest,DISABLED_MediaSource_Opus_Seeking_WebM)776 TEST_F(PipelineIntegrationTest, DISABLED_MediaSource_Opus_Seeking_WebM) {
777   MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM,
778                          kAppendWholeFile);
779   StartHashedPipelineWithMediaSource(&source);
780 
781   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
782   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
783   EXPECT_EQ(kOpusEndTrimmingWebMFileDurationMs,
784             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
785 
786   base::TimeDelta start_seek_time = base::TimeDelta::FromMilliseconds(1000);
787   base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(2000);
788 
789   Play();
790   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(start_seek_time));
791   source.Seek(seek_time, 0x1D5, 34017);
792   source.EndOfStream();
793   ASSERT_TRUE(Seek(seek_time));
794 
795   ASSERT_TRUE(WaitUntilOnEnded());
796 
797   EXPECT_EQ("0.76,0.20,-0.82,-0.58,-1.29,-0.29,", GetAudioHash());
798 
799   source.Abort();
800   Stop();
801 }
802 
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_WebM)803 TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_WebM) {
804   MockMediaSource source("bear-320x240-16x9-aspect.webm", kWebM,
805                          kAppendWholeFile);
806   StartPipelineWithMediaSource(&source);
807 
808   scoped_refptr<DecoderBuffer> second_file =
809       ReadTestDataFile("bear-640x360.webm");
810 
811   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
812                       second_file->data(), second_file->data_size());
813 
814   source.EndOfStream();
815 
816   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
817   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
818   EXPECT_EQ(kAppendTimeMs + k640WebMFileDurationMs,
819             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
820 
821   Play();
822 
823   EXPECT_TRUE(WaitUntilOnEnded());
824   source.Abort();
825   Stop();
826 }
827 
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_Encrypted_WebM)828 TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_Encrypted_WebM) {
829   MockMediaSource source("bear-320x240-16x9-aspect-av_enc-av.webm", kWebM,
830                          kAppendWholeFile);
831   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
832   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
833 
834   scoped_refptr<DecoderBuffer> second_file =
835       ReadTestDataFile("bear-640x360-av_enc-av.webm");
836 
837   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
838                       second_file->data(), second_file->data_size());
839 
840   source.EndOfStream();
841 
842   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
843   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
844   EXPECT_EQ(kAppendTimeMs + k640WebMFileDurationMs,
845             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
846 
847   Play();
848 
849   EXPECT_TRUE(WaitUntilOnEnded());
850   source.Abort();
851   Stop();
852 }
853 
854 // Config changes from encrypted to clear are not currently supported.
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_ClearThenEncrypted_WebM)855 TEST_F(PipelineIntegrationTest,
856        MediaSource_ConfigChange_ClearThenEncrypted_WebM) {
857   MockMediaSource source("bear-320x240-16x9-aspect.webm", kWebM,
858                          kAppendWholeFile);
859   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
860   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
861 
862   scoped_refptr<DecoderBuffer> second_file =
863       ReadTestDataFile("bear-640x360-av_enc-av.webm");
864 
865   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
866                       second_file->data(), second_file->data_size());
867 
868   source.EndOfStream();
869 
870   message_loop_.Run();
871   EXPECT_EQ(PIPELINE_ERROR_DECODE, pipeline_status_);
872 
873   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
874   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
875   // The second video was not added, so its time has not been added.
876   EXPECT_EQ(k320WebMFileDurationMs,
877             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
878 
879   Play();
880 
881   EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
882   source.Abort();
883 }
884 
885 // Config changes from clear to encrypted are not currently supported.
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_EncryptedThenClear_WebM)886 TEST_F(PipelineIntegrationTest,
887        MediaSource_ConfigChange_EncryptedThenClear_WebM) {
888   MockMediaSource source("bear-320x240-16x9-aspect-av_enc-av.webm", kWebM,
889                          kAppendWholeFile);
890   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
891   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
892 
893   scoped_refptr<DecoderBuffer> second_file =
894       ReadTestDataFile("bear-640x360.webm");
895 
896   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
897                       second_file->data(), second_file->data_size());
898 
899   source.EndOfStream();
900 
901   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
902   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
903   // The second video was not added, so its time has not been added.
904   EXPECT_EQ(k320WebMFileDurationMs,
905             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
906 
907   Play();
908 
909   EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
910   source.Abort();
911 }
912 
913 #if defined(USE_PROPRIETARY_CODECS)
TEST_F(PipelineIntegrationTest,MediaSource_ADTS)914 TEST_F(PipelineIntegrationTest, MediaSource_ADTS) {
915   MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile);
916   StartPipelineWithMediaSource(&source);
917   source.EndOfStream();
918 
919   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
920   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
921   EXPECT_EQ(325, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
922 
923   Play();
924 
925   EXPECT_TRUE(WaitUntilOnEnded());
926 }
927 
TEST_F(PipelineIntegrationTest,MediaSource_ADTS_TimestampOffset)928 TEST_F(PipelineIntegrationTest, MediaSource_ADTS_TimestampOffset) {
929   MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile);
930   StartHashedPipelineWithMediaSource(&source);
931   EXPECT_EQ(325, source.last_timestamp_offset().InMilliseconds());
932 
933   // Trim off multiple frames off the beginning of the segment which will cause
934   // the first decoded frame to be incorrect if preroll isn't implemented.
935   const base::TimeDelta adts_preroll_duration =
936       base::TimeDelta::FromSecondsD(2.5 * 1024 / 44100);
937   const base::TimeDelta append_time =
938       source.last_timestamp_offset() - adts_preroll_duration;
939 
940   scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.adts");
941   source.AppendAtTimeWithWindow(append_time,
942                                 append_time + adts_preroll_duration,
943                                 kInfiniteDuration(),
944                                 second_file->data(),
945                                 second_file->data_size());
946   source.EndOfStream();
947 
948   EXPECT_EQ(592, source.last_timestamp_offset().InMilliseconds());
949   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
950   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
951   EXPECT_EQ(592, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
952 
953   Play();
954 
955   EXPECT_TRUE(WaitUntilOnEnded());
956 
957   // Verify preroll is stripped.
958   EXPECT_EQ("-0.06,0.97,-0.90,-0.70,-0.53,-0.34,", GetAudioHash());
959 }
960 
TEST_F(PipelineIntegrationTest,BasicPlaybackHashed_MP3)961 TEST_F(PipelineIntegrationTest, BasicPlaybackHashed_MP3) {
962   ASSERT_TRUE(Start(GetTestDataFilePath("sfx.mp3"), PIPELINE_OK, kHashed));
963 
964   Play();
965 
966   ASSERT_TRUE(WaitUntilOnEnded());
967 
968   // Verify codec delay and preroll are stripped.
969   EXPECT_EQ("3.05,2.87,3.00,3.32,3.58,4.08,", GetAudioHash());
970 }
971 
TEST_F(PipelineIntegrationTest,MediaSource_MP3)972 TEST_F(PipelineIntegrationTest, MediaSource_MP3) {
973   MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile);
974   StartHashedPipelineWithMediaSource(&source);
975   source.EndOfStream();
976 
977   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
978   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
979   EXPECT_EQ(313, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
980 
981   Play();
982 
983   EXPECT_TRUE(WaitUntilOnEnded());
984 
985   // Verify that codec delay was stripped.
986   EXPECT_EQ("1.01,2.71,4.18,4.32,3.04,1.12,", GetAudioHash());
987 }
988 
TEST_F(PipelineIntegrationTest,MediaSource_MP3_TimestampOffset)989 TEST_F(PipelineIntegrationTest, MediaSource_MP3_TimestampOffset) {
990   MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile);
991   StartPipelineWithMediaSource(&source);
992   EXPECT_EQ(313, source.last_timestamp_offset().InMilliseconds());
993 
994   // There are 576 silent frames at the start of this mp3.  The second append
995   // should trim them off.
996   const base::TimeDelta mp3_preroll_duration =
997       base::TimeDelta::FromSecondsD(576.0 / 44100);
998   const base::TimeDelta append_time =
999       source.last_timestamp_offset() - mp3_preroll_duration;
1000 
1001   scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.mp3");
1002   source.AppendAtTimeWithWindow(append_time,
1003                                 append_time + mp3_preroll_duration,
1004                                 kInfiniteDuration(),
1005                                 second_file->data(),
1006                                 second_file->data_size());
1007   source.EndOfStream();
1008 
1009   EXPECT_EQ(613, source.last_timestamp_offset().InMilliseconds());
1010   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1011   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1012   EXPECT_EQ(613, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1013 
1014   Play();
1015 
1016   EXPECT_TRUE(WaitUntilOnEnded());
1017 }
1018 
TEST_F(PipelineIntegrationTest,MediaSource_MP3_Icecast)1019 TEST_F(PipelineIntegrationTest, MediaSource_MP3_Icecast) {
1020   MockMediaSource source("icy_sfx.mp3", kMP3, kAppendWholeFile);
1021   StartPipelineWithMediaSource(&source);
1022   source.EndOfStream();
1023 
1024   Play();
1025 
1026   EXPECT_TRUE(WaitUntilOnEnded());
1027 }
1028 
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_MP4)1029 TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_MP4) {
1030   MockMediaSource source("bear-640x360-av_frag.mp4", kMP4, kAppendWholeFile);
1031   StartPipelineWithMediaSource(&source);
1032 
1033   scoped_refptr<DecoderBuffer> second_file =
1034       ReadTestDataFile("bear-1280x720-av_frag.mp4");
1035 
1036   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
1037                       second_file->data(), second_file->data_size());
1038 
1039   source.EndOfStream();
1040 
1041   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1042   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1043   EXPECT_EQ(kAppendTimeMs + k1280IsoFileDurationMs,
1044             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1045 
1046   Play();
1047 
1048   EXPECT_TRUE(WaitUntilOnEnded());
1049   source.Abort();
1050   Stop();
1051 }
1052 
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_Encrypted_MP4_CENC_VideoOnly)1053 TEST_F(PipelineIntegrationTest,
1054        MediaSource_ConfigChange_Encrypted_MP4_CENC_VideoOnly) {
1055   MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video,
1056                          kAppendWholeFile);
1057   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1058   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1059 
1060   scoped_refptr<DecoderBuffer> second_file =
1061       ReadTestDataFile("bear-1280x720-v_frag-cenc.mp4");
1062 
1063   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
1064                       second_file->data(), second_file->data_size());
1065 
1066   source.EndOfStream();
1067 
1068   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1069   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1070   EXPECT_EQ(kAppendTimeMs + k1280IsoFileDurationMs,
1071             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1072 
1073   Play();
1074 
1075   EXPECT_TRUE(WaitUntilOnEnded());
1076   source.Abort();
1077   Stop();
1078 }
1079 
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_Encrypted_MP4_CENC_KeyRotation_VideoOnly)1080 TEST_F(PipelineIntegrationTest,
1081        MediaSource_ConfigChange_Encrypted_MP4_CENC_KeyRotation_VideoOnly) {
1082   MockMediaSource source("bear-640x360-v_frag-cenc-key_rotation.mp4", kMP4Video,
1083                          kAppendWholeFile);
1084   FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
1085   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1086 
1087   scoped_refptr<DecoderBuffer> second_file =
1088       ReadTestDataFile("bear-1280x720-v_frag-cenc-key_rotation.mp4");
1089 
1090   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
1091                       second_file->data(), second_file->data_size());
1092 
1093   source.EndOfStream();
1094 
1095   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1096   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1097   EXPECT_EQ(kAppendTimeMs + k1280IsoFileDurationMs,
1098             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1099 
1100   Play();
1101 
1102   EXPECT_TRUE(WaitUntilOnEnded());
1103   source.Abort();
1104   Stop();
1105 }
1106 
1107 // Config changes from clear to encrypted are not currently supported.
1108 // TODO(ddorwin): Figure out why this CHECKs in AppendAtTime().
TEST_F(PipelineIntegrationTest,DISABLED_MediaSource_ConfigChange_ClearThenEncrypted_MP4_CENC)1109 TEST_F(PipelineIntegrationTest,
1110        DISABLED_MediaSource_ConfigChange_ClearThenEncrypted_MP4_CENC) {
1111   MockMediaSource source("bear-640x360-av_frag.mp4", kMP4Video,
1112                          kAppendWholeFile);
1113   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1114   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1115 
1116   scoped_refptr<DecoderBuffer> second_file =
1117       ReadTestDataFile("bear-1280x720-v_frag-cenc.mp4");
1118 
1119   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
1120                       second_file->data(), second_file->data_size());
1121 
1122   source.EndOfStream();
1123 
1124   message_loop_.Run();
1125   EXPECT_EQ(PIPELINE_ERROR_DECODE, pipeline_status_);
1126 
1127   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1128   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1129   // The second video was not added, so its time has not been added.
1130   EXPECT_EQ(k640IsoFileDurationMs,
1131             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1132 
1133   Play();
1134 
1135   EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
1136   source.Abort();
1137 }
1138 
1139 // Config changes from encrypted to clear are not currently supported.
TEST_F(PipelineIntegrationTest,MediaSource_ConfigChange_EncryptedThenClear_MP4_CENC)1140 TEST_F(PipelineIntegrationTest,
1141        MediaSource_ConfigChange_EncryptedThenClear_MP4_CENC) {
1142   MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video,
1143                          kAppendWholeFile);
1144   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1145   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1146 
1147   scoped_refptr<DecoderBuffer> second_file =
1148       ReadTestDataFile("bear-1280x720-av_frag.mp4");
1149 
1150   source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
1151                       second_file->data(), second_file->data_size());
1152 
1153   source.EndOfStream();
1154 
1155   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1156   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1157   // The second video was not added, so its time has not been added.
1158   EXPECT_EQ(k640IsoCencFileDurationMs,
1159             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1160 
1161   Play();
1162 
1163   EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
1164   source.Abort();
1165 }
1166 
1167 // Verify files which change configuration midstream fail gracefully.
TEST_F(PipelineIntegrationTest,MidStreamConfigChangesFail)1168 TEST_F(PipelineIntegrationTest, MidStreamConfigChangesFail) {
1169   ASSERT_TRUE(Start(
1170       GetTestDataFilePath("midstream_config_change.mp3"), PIPELINE_OK));
1171   Play();
1172   ASSERT_EQ(WaitUntilEndedOrError(), PIPELINE_ERROR_DECODE);
1173 }
1174 
1175 #endif
1176 
TEST_F(PipelineIntegrationTest,BasicPlayback_16x9AspectRatio)1177 TEST_F(PipelineIntegrationTest, BasicPlayback_16x9AspectRatio) {
1178   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-16x9-aspect.webm"),
1179                     PIPELINE_OK));
1180   Play();
1181   ASSERT_TRUE(WaitUntilOnEnded());
1182 }
1183 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_WebM)1184 TEST_F(PipelineIntegrationTest, EncryptedPlayback_WebM) {
1185   MockMediaSource source("bear-320x240-av_enc-av.webm", kWebM, 219816);
1186   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1187   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1188 
1189   source.EndOfStream();
1190   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1191 
1192   Play();
1193 
1194   ASSERT_TRUE(WaitUntilOnEnded());
1195   source.Abort();
1196   Stop();
1197 }
1198 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_ClearStart_WebM)1199 TEST_F(PipelineIntegrationTest, EncryptedPlayback_ClearStart_WebM) {
1200   MockMediaSource source("bear-320x240-av_enc-av_clear-1s.webm", kWebM,
1201                          kAppendWholeFile);
1202   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1203   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1204 
1205   source.EndOfStream();
1206   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1207 
1208   Play();
1209 
1210   ASSERT_TRUE(WaitUntilOnEnded());
1211   source.Abort();
1212   Stop();
1213 }
1214 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_NoEncryptedFrames_WebM)1215 TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_WebM) {
1216   MockMediaSource source("bear-320x240-av_enc-av_clear-all.webm", kWebM,
1217                          kAppendWholeFile);
1218   FakeEncryptedMedia encrypted_media(new NoResponseApp());
1219   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1220 
1221   source.EndOfStream();
1222   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1223 
1224   Play();
1225 
1226   ASSERT_TRUE(WaitUntilOnEnded());
1227   source.Abort();
1228   Stop();
1229 }
1230 
1231 #if defined(USE_PROPRIETARY_CODECS)
TEST_F(PipelineIntegrationTest,EncryptedPlayback_MP4_CENC_VideoOnly)1232 TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_VideoOnly) {
1233   MockMediaSource source("bear-1280x720-v_frag-cenc.mp4", kMP4Video,
1234                          kAppendWholeFile);
1235   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1236   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1237 
1238   source.EndOfStream();
1239   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1240 
1241   Play();
1242 
1243   ASSERT_TRUE(WaitUntilOnEnded());
1244   source.Abort();
1245   Stop();
1246 }
1247 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_MP4_CENC_AudioOnly)1248 TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_AudioOnly) {
1249   MockMediaSource source("bear-1280x720-a_frag-cenc.mp4", kMP4Audio,
1250                          kAppendWholeFile);
1251   FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
1252   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1253 
1254   source.EndOfStream();
1255   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1256 
1257   Play();
1258 
1259   ASSERT_TRUE(WaitUntilOnEnded());
1260   source.Abort();
1261   Stop();
1262 }
1263 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_NoEncryptedFrames_MP4_CENC_VideoOnly)1264 TEST_F(PipelineIntegrationTest,
1265        EncryptedPlayback_NoEncryptedFrames_MP4_CENC_VideoOnly) {
1266   MockMediaSource source("bear-1280x720-v_frag-cenc_clear-all.mp4", kMP4Video,
1267                          kAppendWholeFile);
1268   FakeEncryptedMedia encrypted_media(new NoResponseApp());
1269   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1270 
1271   source.EndOfStream();
1272   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1273 
1274   Play();
1275 
1276   ASSERT_TRUE(WaitUntilOnEnded());
1277   source.Abort();
1278   Stop();
1279 }
1280 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_NoEncryptedFrames_MP4_CENC_AudioOnly)1281 TEST_F(PipelineIntegrationTest,
1282        EncryptedPlayback_NoEncryptedFrames_MP4_CENC_AudioOnly) {
1283   MockMediaSource source("bear-1280x720-a_frag-cenc_clear-all.mp4", kMP4Audio,
1284                          kAppendWholeFile);
1285   FakeEncryptedMedia encrypted_media(new NoResponseApp());
1286   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1287 
1288   source.EndOfStream();
1289   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1290 
1291   Play();
1292 
1293   ASSERT_TRUE(WaitUntilOnEnded());
1294   source.Abort();
1295   Stop();
1296 }
1297 
TEST_F(PipelineIntegrationTest,BasicPlayback_MediaSource_VideoOnly_MP4_AVC3)1298 TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VideoOnly_MP4_AVC3) {
1299   MockMediaSource source("bear-1280x720-v_frag-avc3.mp4", kMP4VideoAVC3,
1300                          kAppendWholeFile);
1301   StartPipelineWithMediaSource(&source);
1302   source.EndOfStream();
1303 
1304   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
1305   EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
1306   EXPECT_EQ(k1280IsoAVC3FileDurationMs,
1307             pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
1308 
1309   Play();
1310 
1311   ASSERT_TRUE(WaitUntilOnEnded());
1312   source.Abort();
1313   Stop();
1314 }
1315 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_MP4_CENC_KeyRotation_Video)1316 TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Video) {
1317   MockMediaSource source("bear-1280x720-v_frag-cenc-key_rotation.mp4",
1318                          kMP4Video, kAppendWholeFile);
1319   FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
1320   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1321 
1322   source.EndOfStream();
1323   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1324 
1325   Play();
1326 
1327   ASSERT_TRUE(WaitUntilOnEnded());
1328   source.Abort();
1329   Stop();
1330 }
1331 
TEST_F(PipelineIntegrationTest,EncryptedPlayback_MP4_CENC_KeyRotation_Audio)1332 TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Audio) {
1333   MockMediaSource source("bear-1280x720-a_frag-cenc-key_rotation.mp4",
1334                          kMP4Audio, kAppendWholeFile);
1335   FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
1336   StartPipelineWithEncryptedMedia(&source, &encrypted_media);
1337 
1338   source.EndOfStream();
1339   ASSERT_EQ(PIPELINE_OK, pipeline_status_);
1340 
1341   Play();
1342 
1343   ASSERT_TRUE(WaitUntilOnEnded());
1344   source.Abort();
1345   Stop();
1346 }
1347 #endif
1348 
1349 // TODO(acolwell): Fix flakiness http://crbug.com/117921
TEST_F(PipelineIntegrationTest,DISABLED_SeekWhilePaused)1350 TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePaused) {
1351   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
1352 
1353   base::TimeDelta duration(pipeline_->GetMediaDuration());
1354   base::TimeDelta start_seek_time(duration / 4);
1355   base::TimeDelta seek_time(duration * 3 / 4);
1356 
1357   Play();
1358   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(start_seek_time));
1359   Pause();
1360   ASSERT_TRUE(Seek(seek_time));
1361   EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);
1362   Play();
1363   ASSERT_TRUE(WaitUntilOnEnded());
1364 
1365   // Make sure seeking after reaching the end works as expected.
1366   Pause();
1367   ASSERT_TRUE(Seek(seek_time));
1368   EXPECT_EQ(pipeline_->GetMediaTime(), seek_time);
1369   Play();
1370   ASSERT_TRUE(WaitUntilOnEnded());
1371 }
1372 
1373 // TODO(acolwell): Fix flakiness http://crbug.com/117921
TEST_F(PipelineIntegrationTest,DISABLED_SeekWhilePlaying)1374 TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePlaying) {
1375   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK));
1376 
1377   base::TimeDelta duration(pipeline_->GetMediaDuration());
1378   base::TimeDelta start_seek_time(duration / 4);
1379   base::TimeDelta seek_time(duration * 3 / 4);
1380 
1381   Play();
1382   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(start_seek_time));
1383   ASSERT_TRUE(Seek(seek_time));
1384   EXPECT_GE(pipeline_->GetMediaTime(), seek_time);
1385   ASSERT_TRUE(WaitUntilOnEnded());
1386 
1387   // Make sure seeking after reaching the end works as expected.
1388   ASSERT_TRUE(Seek(seek_time));
1389   EXPECT_GE(pipeline_->GetMediaTime(), seek_time);
1390   ASSERT_TRUE(WaitUntilOnEnded());
1391 }
1392 
1393 // Verify audio decoder & renderer can handle aborted demuxer reads.
TEST_F(PipelineIntegrationTest,ChunkDemuxerAbortRead_AudioOnly)1394 TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) {
1395   ASSERT_TRUE(TestSeekDuringRead("bear-320x240-audio-only.webm", kAudioOnlyWebM,
1396                                  8192,
1397                                  base::TimeDelta::FromMilliseconds(464),
1398                                  base::TimeDelta::FromMilliseconds(617),
1399                                  0x10CA, 19730));
1400 }
1401 
1402 // Verify video decoder & renderer can handle aborted demuxer reads.
TEST_F(PipelineIntegrationTest,ChunkDemuxerAbortRead_VideoOnly)1403 TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) {
1404   ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", kVideoOnlyWebM,
1405                                  32768,
1406                                  base::TimeDelta::FromMilliseconds(167),
1407                                  base::TimeDelta::FromMilliseconds(1668),
1408                                  0x1C896, 65536));
1409 }
1410 
1411 // Verify that Opus audio in WebM containers can be played back.
TEST_F(PipelineIntegrationTest,BasicPlayback_AudioOnly_Opus_WebM)1412 TEST_F(PipelineIntegrationTest, BasicPlayback_AudioOnly_Opus_WebM) {
1413   ASSERT_TRUE(Start(GetTestDataFilePath("bear-opus-end-trimming.webm"),
1414                     PIPELINE_OK));
1415   Play();
1416   ASSERT_TRUE(WaitUntilOnEnded());
1417 }
1418 
1419 // Verify that VP9 video in WebM containers can be played back.
TEST_F(PipelineIntegrationTest,BasicPlayback_VideoOnly_VP9_WebM)1420 TEST_F(PipelineIntegrationTest, BasicPlayback_VideoOnly_VP9_WebM) {
1421   ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9.webm"),
1422                     PIPELINE_OK));
1423   Play();
1424   ASSERT_TRUE(WaitUntilOnEnded());
1425 }
1426 
1427 // Verify that VP9 video and Opus audio in the same WebM container can be played
1428 // back.
TEST_F(PipelineIntegrationTest,BasicPlayback_VP9_Opus_WebM)1429 TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Opus_WebM) {
1430   ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9-opus.webm"),
1431                     PIPELINE_OK));
1432   Play();
1433   ASSERT_TRUE(WaitUntilOnEnded());
1434 }
1435 
1436 // Verify that VP8 video with alpha channel can be played back.
TEST_F(PipelineIntegrationTest,BasicPlayback_VP8A_WebM)1437 TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_WebM) {
1438   ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a.webm"),
1439                     PIPELINE_OK));
1440   Play();
1441   ASSERT_TRUE(WaitUntilOnEnded());
1442   EXPECT_EQ(last_video_frame_format_, VideoFrame::YV12A);
1443 }
1444 
1445 // Verify that VP8A video with odd width/height can be played back.
TEST_F(PipelineIntegrationTest,BasicPlayback_VP8A_Odd_WebM)1446 TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_Odd_WebM) {
1447   ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a-odd-dimensions.webm"),
1448                     PIPELINE_OK));
1449   Play();
1450   ASSERT_TRUE(WaitUntilOnEnded());
1451   EXPECT_EQ(last_video_frame_format_, VideoFrame::YV12A);
1452 }
1453 
1454 // Verify that VP8 video with inband text track can be played back.
TEST_F(PipelineIntegrationTest,BasicPlayback_VP8_WebVTT_WebM)1455 TEST_F(PipelineIntegrationTest,
1456        BasicPlayback_VP8_WebVTT_WebM) {
1457   ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8-webvtt.webm"),
1458                     PIPELINE_OK));
1459   Play();
1460   ASSERT_TRUE(WaitUntilOnEnded());
1461 }
1462 
1463 // Verify that VP9 video with 4:4:4 subsampling can be played back.
TEST_F(PipelineIntegrationTest,P444_VP9_WebM)1464 TEST_F(PipelineIntegrationTest, P444_VP9_WebM) {
1465   ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-P444.webm"),
1466                     PIPELINE_OK));
1467   Play();
1468   ASSERT_TRUE(WaitUntilOnEnded());
1469   EXPECT_EQ(last_video_frame_format_, VideoFrame::YV24);
1470 }
1471 
1472 // Verify that videos with an odd frame size playback successfully.
TEST_F(PipelineIntegrationTest,BasicPlayback_OddVideoSize)1473 TEST_F(PipelineIntegrationTest, BasicPlayback_OddVideoSize) {
1474   ASSERT_TRUE(Start(GetTestDataFilePath("butterfly-853x480.webm"),
1475                     PIPELINE_OK));
1476   Play();
1477   ASSERT_TRUE(WaitUntilOnEnded());
1478 }
1479 
1480 }  // namespace media
1481