#include "base/memory/scoped_ptr.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
+#include "media/base/cdm_callback_promise.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_keys.h"
#include "media/base/media_switches.h"
#include "media/base/test_data_util.h"
#include "media/cdm/aes_decryptor.h"
+#include "media/cdm/json_web_key.h"
#include "media/filters/chunk_demuxer.h"
+#include "media/filters/renderer_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+using testing::_;
using testing::AnyNumber;
+using testing::AtLeast;
using testing::AtMost;
+using testing::SaveArg;
namespace media {
const char kSourceId[] = "SourceId";
+const char kCencInitDataType[] = "cenc";
const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 };
const char kWebM[] = "video/webm; codecs=\"vp8,vorbis\"";
const char kAudioOnlyWebM[] = "video/webm; codecs=\"vorbis\"";
const char kOpusAudioOnlyWebM[] = "video/webm; codecs=\"opus\"";
const char kVideoOnlyWebM[] = "video/webm; codecs=\"vp8\"";
-const char kMP4VideoType[] = "video/mp4";
-const char kMP4AudioType[] = "audio/mp4";
#if defined(USE_PROPRIETARY_CODECS)
+const char kADTS[] = "audio/aac";
const char kMP4[] = "video/mp4; codecs=\"avc1.4D4041,mp4a.40.2\"";
const char kMP4Video[] = "video/mp4; codecs=\"avc1.4D4041\"";
const char kMP4VideoAVC3[] = "video/mp4; codecs=\"avc3.64001f\"";
// Constants for the Media Source config change tests.
const int kAppendTimeSec = 1;
const int kAppendTimeMs = kAppendTimeSec * 1000;
-const int k320WebMFileDurationMs = 2737;
-const int k640WebMFileDurationMs = 2763;
-const int kOpusEndTrimmingWebMFileDurationMs = 2771;
-const uint32 kOpusEndTrimmingWebMFileAudioBytes = 528676;
-const int kVP9WebMFileDurationMs = 2735;
-const int kVP8AWebMFileDurationMs = 2700;
+const int k320WebMFileDurationMs = 2736;
+const int k640WebMFileDurationMs = 2749;
+const int kOpusEndTrimmingWebMFileDurationMs = 2741;
+const int kVP9WebMFileDurationMs = 2736;
+const int kVP8AWebMFileDurationMs = 2733;
#if defined(USE_PROPRIETARY_CODECS)
const int k640IsoFileDurationMs = 2737;
const int k640IsoCencFileDurationMs = 2736;
const int k1280IsoFileDurationMs = 2736;
-const int k1280IsoAVC3FileDurationMs = 2735;
+const int k1280IsoAVC3FileDurationMs = 2736;
#endif // defined(USE_PROPRIETARY_CODECS)
+// Return a timeline offset for bear-320x240-live.webm.
+static base::Time kLiveTimelineOffset() {
+ // The file contians the following UTC timeline offset:
+ // 2012-11-10 12:34:56.789123456
+ // Since base::Time only has a resolution of microseconds,
+ // construct a base::Time for 2012-11-10 12:34:56.789123.
+ base::Time::Exploded exploded_time;
+ exploded_time.year = 2012;
+ exploded_time.month = 11;
+ exploded_time.day_of_month = 10;
+ exploded_time.hour = 12;
+ exploded_time.minute = 34;
+ exploded_time.second = 56;
+ exploded_time.millisecond = 789;
+ base::Time timeline_offset = base::Time::FromUTCExploded(exploded_time);
+
+ timeline_offset += base::TimeDelta::FromMicroseconds(123);
+
+ return timeline_offset;
+}
+
+// FFmpeg only supports time a resolution of seconds so this
+// helper function truncates a base::Time to seconds resolution.
+static base::Time TruncateToFFmpegTimeResolution(base::Time t) {
+ base::Time::Exploded exploded_time;
+ t.UTCExplode(&exploded_time);
+ exploded_time.millisecond = 0;
+
+ return base::Time::FromUTCExploded(exploded_time);
+}
+
// Note: Tests using this class only exercise the DecryptingDemuxerStream path.
// They do not exercise the Decrypting{Audio|Video}Decoder path.
class FakeEncryptedMedia {
public:
virtual ~AppBase() {}
- virtual void KeyAdded(const std::string& session_id) = 0;
+ virtual void OnSessionMessage(const std::string& web_session_id,
+ const std::vector<uint8>& message,
+ const GURL& destination_url) = 0;
+
+ virtual void OnSessionClosed(const std::string& web_session_id) = 0;
+
+ virtual void OnSessionKeysChange(const std::string& web_session_id,
+ bool has_additional_usable_key) = 0;
// Errors are not expected unless overridden.
- virtual void KeyError(const std::string& session_id,
- MediaKeys::KeyError error_code,
- int system_code) {
+ virtual void OnSessionError(const std::string& web_session_id,
+ const std::string& error_name,
+ uint32 system_code,
+ const std::string& error_message) {
FAIL() << "Unexpected Key Error";
}
- virtual void KeyMessage(const std::string& session_id,
- const std::vector<uint8>& message,
- const std::string& default_url) = 0;
-
virtual void NeedKey(const std::string& type,
const std::vector<uint8>& init_data,
AesDecryptor* decryptor) = 0;
};
FakeEncryptedMedia(AppBase* app)
- : decryptor_(base::Bind(&FakeEncryptedMedia::KeyAdded,
+ : decryptor_(base::Bind(&FakeEncryptedMedia::OnSessionMessage,
base::Unretained(this)),
- base::Bind(&FakeEncryptedMedia::KeyError,
+ base::Bind(&FakeEncryptedMedia::OnSessionClosed,
base::Unretained(this)),
- base::Bind(&FakeEncryptedMedia::KeyMessage,
+ base::Bind(&FakeEncryptedMedia::OnSessionKeysChange,
base::Unretained(this))),
- app_(app) {
- }
+ app_(app) {}
AesDecryptor* decryptor() {
return &decryptor_;
}
- // Callbacks for firing key events. Delegate to |app_|.
- void KeyAdded(const std::string& session_id) {
- app_->KeyAdded(session_id);
+ // Callbacks for firing session events. Delegate to |app_|.
+ void OnSessionMessage(const std::string& web_session_id,
+ const std::vector<uint8>& message,
+ const GURL& destination_url) {
+ app_->OnSessionMessage(web_session_id, message, destination_url);
}
- void KeyError(const std::string& session_id,
- MediaKeys::KeyError error_code,
- int system_code) {
- app_->KeyError(session_id, error_code, system_code);
+ void OnSessionClosed(const std::string& web_session_id) {
+ app_->OnSessionClosed(web_session_id);
}
- void KeyMessage(const std::string& session_id,
- const std::vector<uint8>& message,
- const std::string& default_url) {
- app_->KeyMessage(session_id, message, default_url);
+ void OnSessionKeysChange(const std::string& web_session_id,
+ bool has_additional_usable_key) {
+ app_->OnSessionKeysChange(web_session_id, has_additional_usable_key);
+ }
+
+ void OnSessionError(const std::string& web_session_id,
+ const std::string& error_name,
+ uint32 system_code,
+ const std::string& error_message) {
+ app_->OnSessionError(
+ web_session_id, error_name, system_code, error_message);
}
void NeedKey(const std::string& type,
scoped_ptr<AppBase> app_;
};
+enum PromiseResult { RESOLVED, REJECTED };
+
// Provides |kSecretKey| in response to needkey.
class KeyProvidingApp : public FakeEncryptedMedia::AppBase {
public:
- virtual void KeyAdded(const std::string& session_id) OVERRIDE {
- EXPECT_FALSE(session_id.empty());
+ KeyProvidingApp() {}
+
+ void OnResolveWithSession(PromiseResult expected,
+ const std::string& web_session_id) {
+ EXPECT_EQ(expected, RESOLVED);
+ EXPECT_GT(web_session_id.length(), 0ul);
+ current_session_id_ = web_session_id;
+ }
+
+ void OnResolve(PromiseResult expected) {
+ EXPECT_EQ(expected, RESOLVED);
+ }
+
+ void OnReject(PromiseResult expected,
+ media::MediaKeys::Exception exception_code,
+ uint32 system_code,
+ const std::string& error_message) {
+ EXPECT_EQ(expected, REJECTED);
}
- virtual void KeyMessage(const std::string& session_id,
- const std::vector<uint8>& message,
- const std::string& default_url) OVERRIDE {
- EXPECT_FALSE(session_id.empty());
+ scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) {
+ scoped_ptr<media::SimpleCdmPromise> promise(new media::CdmCallbackPromise<>(
+ base::Bind(
+ &KeyProvidingApp::OnResolve, base::Unretained(this), expected),
+ base::Bind(
+ &KeyProvidingApp::OnReject, base::Unretained(this), expected)));
+ return promise.Pass();
+ }
+
+ scoped_ptr<NewSessionCdmPromise> CreateSessionPromise(
+ PromiseResult expected) {
+ scoped_ptr<media::NewSessionCdmPromise> promise(
+ new media::CdmCallbackPromise<std::string>(
+ base::Bind(&KeyProvidingApp::OnResolveWithSession,
+ base::Unretained(this),
+ expected),
+ base::Bind(
+ &KeyProvidingApp::OnReject, base::Unretained(this), expected)));
+ return promise.Pass();
+ }
+
+ void OnSessionMessage(const std::string& web_session_id,
+ const std::vector<uint8>& message,
+ const GURL& destination_url) override {
+ EXPECT_FALSE(web_session_id.empty());
EXPECT_FALSE(message.empty());
+ EXPECT_EQ(current_session_id_, web_session_id);
+ }
- current_session_id_ = session_id;
+ void OnSessionClosed(const std::string& web_session_id) override {
+ EXPECT_EQ(current_session_id_, web_session_id);
}
- virtual void NeedKey(const std::string& type,
- const std::vector<uint8>& init_data,
- AesDecryptor* decryptor) OVERRIDE {
+ void OnSessionKeysChange(const std::string& web_session_id,
+ bool has_additional_usable_key) override {
+ EXPECT_EQ(current_session_id_, web_session_id);
+ EXPECT_EQ(has_additional_usable_key, true);
+ }
+
+ void NeedKey(const std::string& type,
+ const std::vector<uint8>& init_data,
+ AesDecryptor* decryptor) override {
if (current_session_id_.empty()) {
- EXPECT_TRUE(decryptor->GenerateKeyRequest(type, kInitData,
- arraysize(kInitData)));
+ decryptor->CreateSession(type,
+ kInitData,
+ arraysize(kInitData),
+ MediaKeys::TEMPORARY_SESSION,
+ CreateSessionPromise(RESOLVED));
+ EXPECT_FALSE(current_session_id_.empty());
}
- EXPECT_FALSE(current_session_id_.empty());
-
// Clear Key really needs the key ID in |init_data|. For WebM, they are the
// same, but this is not the case for ISO CENC. Therefore, provide the
// correct key ID.
const uint8* key_id = init_data.empty() ? NULL : &init_data[0];
size_t key_id_length = init_data.size();
- if (type == kMP4AudioType || type == kMP4VideoType) {
+ if (type == kCencInitDataType) {
key_id = kKeyId;
key_id_length = arraysize(kKeyId);
}
- decryptor->AddKey(kSecretKey, arraysize(kSecretKey),
- key_id, key_id_length, current_session_id_);
+ // Convert key into a JSON structure and then add it.
+ std::string jwk = GenerateJWKSet(
+ kSecretKey, arraysize(kSecretKey), key_id, key_id_length);
+ decryptor->UpdateSession(current_session_id_,
+ reinterpret_cast<const uint8*>(jwk.data()),
+ jwk.size(),
+ CreatePromise(RESOLVED));
}
std::string current_session_id_;
};
+class RotatingKeyProvidingApp : public KeyProvidingApp {
+ public:
+ RotatingKeyProvidingApp() : num_distint_need_key_calls_(0) {}
+ ~RotatingKeyProvidingApp() override {
+ // Expect that NeedKey is fired multiple times with different |init_data|.
+ EXPECT_GT(num_distint_need_key_calls_, 1u);
+ }
+
+ void NeedKey(const std::string& type,
+ const std::vector<uint8>& init_data,
+ AesDecryptor* decryptor) override {
+ // Skip the request if the |init_data| has been seen.
+ if (init_data == prev_init_data_)
+ return;
+ prev_init_data_ = init_data;
+ ++num_distint_need_key_calls_;
+
+ decryptor->CreateSession(type,
+ vector_as_array(&init_data),
+ init_data.size(),
+ MediaKeys::TEMPORARY_SESSION,
+ CreateSessionPromise(RESOLVED));
+
+ std::vector<uint8> key_id;
+ std::vector<uint8> key;
+ EXPECT_TRUE(GetKeyAndKeyId(init_data, &key, &key_id));
+
+ // Convert key into a JSON structure and then add it.
+ std::string jwk = GenerateJWKSet(vector_as_array(&key),
+ key.size(),
+ vector_as_array(&key_id),
+ key_id.size());
+ decryptor->UpdateSession(current_session_id_,
+ reinterpret_cast<const uint8*>(jwk.data()),
+ jwk.size(),
+ CreatePromise(RESOLVED));
+ }
+
+ private:
+ bool GetKeyAndKeyId(std::vector<uint8> init_data,
+ std::vector<uint8>* key,
+ std::vector<uint8>* key_id) {
+ // For WebM, init_data is key_id; for ISO CENC, init_data should contain
+ // the key_id. We assume key_id is in the end of init_data here (that is
+ // only a reasonable assumption for WebM and clear key ISO CENC).
+ DCHECK_GE(init_data.size(), arraysize(kKeyId));
+ std::vector<uint8> key_id_from_init_data(
+ init_data.end() - arraysize(kKeyId), init_data.end());
+
+ key->assign(kSecretKey, kSecretKey + arraysize(kSecretKey));
+ key_id->assign(kKeyId, kKeyId + arraysize(kKeyId));
+
+ // The Key and KeyId for this testing key provider are created by left
+ // rotating kSecretKey and kKeyId. Note that this implementation is only
+ // intended for testing purpose. The actual key rotation algorithm can be
+ // much more complicated.
+ // Find out the rotating position from |key_id_from_init_data| and apply on
+ // |key|.
+ for (size_t pos = 0; pos < arraysize(kKeyId); ++pos) {
+ std::rotate(key_id->begin(), key_id->begin() + pos, key_id->end());
+ if (*key_id == key_id_from_init_data) {
+ std::rotate(key->begin(), key->begin() + pos, key->end());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::vector<uint8> prev_init_data_;
+ uint32 num_distint_need_key_calls_;
+};
+
// Ignores needkey and does not perform a license request
class NoResponseApp : public FakeEncryptedMedia::AppBase {
public:
- virtual void KeyAdded(const std::string& session_id) OVERRIDE {
- EXPECT_FALSE(session_id.empty());
- FAIL() << "Unexpected KeyAdded";
+ void OnSessionMessage(const std::string& web_session_id,
+ const std::vector<uint8>& message,
+ const GURL& default_url) override {
+ EXPECT_FALSE(web_session_id.empty());
+ EXPECT_FALSE(message.empty());
+ FAIL() << "Unexpected Message";
}
- virtual void KeyMessage(const std::string& session_id,
- const std::vector<uint8>& message,
- const std::string& default_url) OVERRIDE {
- EXPECT_FALSE(session_id.empty());
- EXPECT_FALSE(message.empty());
- FAIL() << "Unexpected KeyMessage";
+ void OnSessionClosed(const std::string& web_session_id) override {
+ EXPECT_FALSE(web_session_id.empty());
+ FAIL() << "Unexpected Closed";
}
- virtual void NeedKey(const std::string& type,
- const std::vector<uint8>& init_data,
- AesDecryptor* decryptor) OVERRIDE {
+ void OnSessionKeysChange(const std::string& web_session_id,
+ bool has_additional_usable_key) override {
+ EXPECT_FALSE(web_session_id.empty());
+ EXPECT_EQ(has_additional_usable_key, true);
}
+
+ void NeedKey(const std::string& type,
+ const std::vector<uint8>& init_data,
+ AesDecryptor* decryptor) override {}
};
// Helper class that emulates calls made on the ChunkDemuxer by the
// Media Source API.
class MockMediaSource {
public:
- MockMediaSource(const std::string& filename, const std::string& mimetype,
+ MockMediaSource(const std::string& filename,
+ const std::string& mimetype,
int initial_append_size)
: file_path_(GetTestDataFilePath(filename)),
current_position_(0),
initial_append_size_(initial_append_size),
mimetype_(mimetype),
chunk_demuxer_(new ChunkDemuxer(
- base::Bind(&MockMediaSource::DemuxerOpened,
- base::Unretained(this)),
+ base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)),
base::Bind(&MockMediaSource::DemuxerNeedKey,
base::Unretained(this)),
- base::Bind(&MockMediaSource::OnTextTrack,
- base::Unretained(this)),
- LogCB())),
+ LogCB(),
+ true)),
owned_chunk_demuxer_(chunk_demuxer_) {
file_data_ = ReadTestDataFile(filename);
void Seek(base::TimeDelta seek_time, int new_position, int seek_append_size) {
chunk_demuxer_->StartWaitingForSeek(seek_time);
- chunk_demuxer_->Abort(kSourceId);
+ chunk_demuxer_->Abort(
+ kSourceId,
+ base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_);
DCHECK_GE(new_position, 0);
DCHECK_LT(new_position, file_data_->data_size());
DCHECK(chunk_demuxer_);
DCHECK_LT(current_position_, file_data_->data_size());
DCHECK_LE(current_position_ + size, file_data_->data_size());
+
chunk_demuxer_->AppendData(
- kSourceId, file_data_->data() + current_position_, size);
+ kSourceId, file_data_->data() + current_position_, size,
+ base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_,
+ base::Bind(&MockMediaSource::InitSegmentReceived,
+ base::Unretained(this)));
current_position_ += size;
}
- void AppendAtTime(const base::TimeDelta& timestampOffset,
- const uint8* pData, int size) {
- CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, timestampOffset));
- chunk_demuxer_->AppendData(kSourceId, pData, size);
- CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, base::TimeDelta()));
+ void AppendAtTime(base::TimeDelta timestamp_offset,
+ const uint8* pData,
+ int size) {
+ CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId));
+ chunk_demuxer_->AppendData(kSourceId, pData, size,
+ base::TimeDelta(), kInfiniteDuration(),
+ ×tamp_offset,
+ base::Bind(&MockMediaSource::InitSegmentReceived,
+ base::Unretained(this)));
+ last_timestamp_offset_ = timestamp_offset;
+ }
+
+ void AppendAtTimeWithWindow(base::TimeDelta timestamp_offset,
+ base::TimeDelta append_window_start,
+ base::TimeDelta append_window_end,
+ const uint8* pData,
+ int size) {
+ CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId));
+ chunk_demuxer_->AppendData(kSourceId,
+ pData,
+ size,
+ append_window_start,
+ append_window_end,
+ ×tamp_offset,
+ base::Bind(&MockMediaSource::InitSegmentReceived,
+ base::Unretained(this)));
+ last_timestamp_offset_ = timestamp_offset;
}
void EndOfStream() {
}
CHECK_EQ(chunk_demuxer_->AddId(kSourceId, type, codecs), ChunkDemuxer::kOk);
+
AppendData(initial_append_size_);
}
need_key_cb_.Run(type, init_data);
}
- scoped_ptr<TextTrack> OnTextTrack(TextKind kind,
- const std::string& label,
- const std::string& language) {
- return scoped_ptr<TextTrack>();
+ base::TimeDelta last_timestamp_offset() const {
+ return last_timestamp_offset_;
}
+ MOCK_METHOD0(InitSegmentReceived, void(void));
+
private:
base::FilePath file_path_;
scoped_refptr<DecoderBuffer> file_data_;
ChunkDemuxer* chunk_demuxer_;
scoped_ptr<Demuxer> owned_chunk_demuxer_;
Demuxer::NeedKeyCB need_key_cb_;
+ base::TimeDelta last_timestamp_offset_;
};
class PipelineIntegrationTest
public PipelineIntegrationTestBase {
public:
void StartPipelineWithMediaSource(MockMediaSource* source) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
+ EXPECT_CALL(*source, InitSegmentReceived()).Times(AtLeast(1));
+ EXPECT_CALL(*this, OnMetadata(_))
+ .Times(AtMost(1))
+ .WillRepeatedly(SaveArg<0>(&metadata_));
+ EXPECT_CALL(*this, OnBufferingStateChanged(BUFFERING_HAVE_ENOUGH))
.Times(AtMost(1));
+ demuxer_ = source->GetDemuxer().Pass();
pipeline_->Start(
- CreateFilterCollection(source->GetDemuxer(), NULL),
+ demuxer_.get(),
+ CreateRenderer(NULL),
base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
QuitOnStatusCB(PIPELINE_OK),
- base::Bind(&PipelineIntegrationTest::OnBufferingState,
+ base::Bind(&PipelineIntegrationTest::OnMetadata,
base::Unretained(this)),
- base::Closure());
-
+ base::Bind(&PipelineIntegrationTest::OnBufferingStateChanged,
+ base::Unretained(this)),
+ base::Closure(),
+ base::Bind(&PipelineIntegrationTest::OnAddTextTrack,
+ base::Unretained(this)));
message_loop_.Run();
}
void StartPipelineWithEncryptedMedia(
MockMediaSource* source,
FakeEncryptedMedia* encrypted_media) {
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
- .Times(AtMost(1));
- EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
+ EXPECT_CALL(*source, InitSegmentReceived()).Times(AtLeast(1));
+ EXPECT_CALL(*this, OnMetadata(_))
+ .Times(AtMost(1))
+ .WillRepeatedly(SaveArg<0>(&metadata_));
+ EXPECT_CALL(*this, OnBufferingStateChanged(BUFFERING_HAVE_ENOUGH))
.Times(AtMost(1));
+ demuxer_ = source->GetDemuxer().Pass();
pipeline_->Start(
- CreateFilterCollection(source->GetDemuxer(),
- encrypted_media->decryptor()),
+ demuxer_.get(),
+ CreateRenderer(encrypted_media->decryptor()),
base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
QuitOnStatusCB(PIPELINE_OK),
- base::Bind(&PipelineIntegrationTest::OnBufferingState,
+ base::Bind(&PipelineIntegrationTest::OnMetadata,
+ base::Unretained(this)),
+ base::Bind(&PipelineIntegrationTest::OnBufferingStateChanged,
base::Unretained(this)),
- base::Closure());
+ base::Closure(),
+ base::Bind(&PipelineIntegrationTest::OnAddTextTrack,
+ base::Unretained(this)));
source->set_need_key_cb(base::Bind(&FakeEncryptedMedia::NeedKey,
base::Unretained(encrypted_media)));
ASSERT_TRUE(WaitUntilOnEnded());
}
+TEST_F(PipelineIntegrationTest, BasicPlaybackOpusOgg) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear-opus.ogg"), PIPELINE_OK));
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+}
+
TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) {
ASSERT_TRUE(Start(
GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK, kHashed));
EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash());
EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash());
+ EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null());
+}
+
+TEST_F(PipelineIntegrationTest, BasicPlaybackLive) {
+ ASSERT_TRUE(Start(
+ GetTestDataFilePath("bear-320x240-live.webm"), PIPELINE_OK, kHashed));
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+
+ EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash());
+ EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash());
+
+ // TODO: Fix FFmpeg code to return higher resolution time values so
+ // we don't have to truncate our expectations here.
+ EXPECT_EQ(TruncateToFFmpegTimeResolution(kLiveTimelineOffset()),
+ demuxer_->GetTimelineOffset());
}
TEST_F(PipelineIntegrationTest, F32PlaybackHashed) {
Play();
ASSERT_TRUE(WaitUntilOnEnded());
+
+ EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null());
source.Abort();
Stop();
}
-// TODO(fgalligan): Enable after new vp9 files are landed.
-// http://crbug.com/259116
-TEST_F(PipelineIntegrationTest,
- DISABLED_BasicPlayback_MediaSource_VideoOnly_VP9_WebM) {
- MockMediaSource source("bear-vp9.webm", kWebMVP9, 32393);
+TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Live) {
+ MockMediaSource source("bear-320x240-live.webm", kWebM, 219221);
+ StartPipelineWithMediaSource(&source);
+ source.EndOfStream();
+
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(k320WebMFileDurationMs,
+ pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+
+ EXPECT_EQ(kLiveTimelineOffset(),
+ demuxer_->GetTimelineOffset());
+ source.Abort();
+ Stop();
+}
+
+TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP9_WebM) {
+ MockMediaSource source("bear-vp9.webm", kWebMVP9, 67504);
StartPipelineWithMediaSource(&source);
source.EndOfStream();
}
TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP8A_WebM) {
- EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber());
MockMediaSource source("bear-vp8a.webm", kVideoOnlyWebM, kAppendWholeFile);
StartPipelineWithMediaSource(&source);
source.EndOfStream();
}
TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Opus_WebM) {
- EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber());
MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM,
kAppendWholeFile);
StartPipelineWithMediaSource(&source);
Play();
ASSERT_TRUE(WaitUntilOnEnded());
- EXPECT_EQ(kOpusEndTrimmingWebMFileAudioBytes,
- pipeline_->GetStatistics().audio_bytes_decoded);
source.Abort();
Stop();
}
// Flaky. http://crbug.com/304776
TEST_F(PipelineIntegrationTest, DISABLED_MediaSource_Opus_Seeking_WebM) {
- EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber());
MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM,
kAppendWholeFile);
StartHashedPipelineWithMediaSource(&source);
-
EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
EXPECT_EQ(kOpusEndTrimmingWebMFileDurationMs,
}
#if defined(USE_PROPRIETARY_CODECS)
+TEST_F(PipelineIntegrationTest, MediaSource_ADTS) {
+ MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile);
+ StartPipelineWithMediaSource(&source);
+ source.EndOfStream();
+
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(325, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
+ Play();
+
+ EXPECT_TRUE(WaitUntilOnEnded());
+}
+
+TEST_F(PipelineIntegrationTest, MediaSource_ADTS_TimestampOffset) {
+ MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile);
+ StartHashedPipelineWithMediaSource(&source);
+ EXPECT_EQ(325, source.last_timestamp_offset().InMilliseconds());
+
+ // Trim off multiple frames off the beginning of the segment which will cause
+ // the first decoded frame to be incorrect if preroll isn't implemented.
+ const base::TimeDelta adts_preroll_duration =
+ base::TimeDelta::FromSecondsD(2.5 * 1024 / 44100);
+ const base::TimeDelta append_time =
+ source.last_timestamp_offset() - adts_preroll_duration;
+
+ scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.adts");
+ source.AppendAtTimeWithWindow(append_time,
+ append_time + adts_preroll_duration,
+ kInfiniteDuration(),
+ second_file->data(),
+ second_file->data_size());
+ source.EndOfStream();
+
+ EXPECT_EQ(592, source.last_timestamp_offset().InMilliseconds());
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(592, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
+ Play();
+
+ EXPECT_TRUE(WaitUntilOnEnded());
+
+ // Verify preroll is stripped.
+ EXPECT_EQ("-0.06,0.97,-0.90,-0.70,-0.53,-0.34,", GetAudioHash());
+}
+
+TEST_F(PipelineIntegrationTest, BasicPlaybackHashed_MP3) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("sfx.mp3"), PIPELINE_OK, kHashed));
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+
+ // Verify codec delay and preroll are stripped.
+ EXPECT_EQ("3.05,2.87,3.00,3.32,3.58,4.08,", GetAudioHash());
+}
+
TEST_F(PipelineIntegrationTest, MediaSource_MP3) {
MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile);
- StartPipelineWithMediaSource(&source);
+ StartHashedPipelineWithMediaSource(&source);
source.EndOfStream();
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(313, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
Play();
EXPECT_TRUE(WaitUntilOnEnded());
+
+ // Verify that codec delay was stripped.
+ EXPECT_EQ("1.01,2.71,4.18,4.32,3.04,1.12,", GetAudioHash());
}
+TEST_F(PipelineIntegrationTest, MediaSource_MP3_TimestampOffset) {
+ MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile);
+ StartPipelineWithMediaSource(&source);
+ EXPECT_EQ(313, source.last_timestamp_offset().InMilliseconds());
+
+ // There are 576 silent frames at the start of this mp3. The second append
+ // should trim them off.
+ const base::TimeDelta mp3_preroll_duration =
+ base::TimeDelta::FromSecondsD(576.0 / 44100);
+ const base::TimeDelta append_time =
+ source.last_timestamp_offset() - mp3_preroll_duration;
+
+ scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.mp3");
+ source.AppendAtTimeWithWindow(append_time,
+ append_time + mp3_preroll_duration,
+ kInfiniteDuration(),
+ second_file->data(),
+ second_file->data_size());
+ source.EndOfStream();
+
+ EXPECT_EQ(613, source.last_timestamp_offset().InMilliseconds());
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(613, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
+ Play();
+
+ EXPECT_TRUE(WaitUntilOnEnded());
+}
TEST_F(PipelineIntegrationTest, MediaSource_MP3_Icecast) {
MockMediaSource source("icy_sfx.mp3", kMP3, kAppendWholeFile);
TEST_F(PipelineIntegrationTest,
MediaSource_ConfigChange_Encrypted_MP4_CENC_VideoOnly) {
- MockMediaSource source("bear-640x360-v_frag-cenc.mp4",
- kMP4Video, kAppendWholeFile);
+ MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
Stop();
}
+TEST_F(PipelineIntegrationTest,
+ MediaSource_ConfigChange_Encrypted_MP4_CENC_KeyRotation_VideoOnly) {
+ MockMediaSource source("bear-640x360-v_frag-cenc-key_rotation.mp4", kMP4Video,
+ kAppendWholeFile);
+ FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
+ StartPipelineWithEncryptedMedia(&source, &encrypted_media);
+
+ scoped_refptr<DecoderBuffer> second_file =
+ ReadTestDataFile("bear-1280x720-v_frag-cenc-key_rotation.mp4");
+
+ source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec),
+ second_file->data(), second_file->data_size());
+
+ source.EndOfStream();
+
+ EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
+ EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds());
+ EXPECT_EQ(kAppendTimeMs + k1280IsoFileDurationMs,
+ pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds());
+
+ Play();
+
+ EXPECT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
+
// Config changes from clear to encrypted are not currently supported.
// TODO(ddorwin): Figure out why this CHECKs in AppendAtTime().
TEST_F(PipelineIntegrationTest,
// Config changes from encrypted to clear are not currently supported.
TEST_F(PipelineIntegrationTest,
MediaSource_ConfigChange_EncryptedThenClear_MP4_CENC) {
- MockMediaSource source("bear-640x360-v_frag-cenc.mp4",
- kMP4Video, kAppendWholeFile);
+ MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
}
TEST_F(PipelineIntegrationTest, EncryptedPlayback_ClearStart_WebM) {
- MockMediaSource source("bear-320x240-av_enc-av_clear-1s.webm",
- kWebM, kAppendWholeFile);
+ MockMediaSource source("bear-320x240-av_enc-av_clear-1s.webm", kWebM,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
}
TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_WebM) {
- MockMediaSource source("bear-320x240-av_enc-av_clear-all.webm",
- kWebM, kAppendWholeFile);
+ MockMediaSource source("bear-320x240-av_enc-av_clear-all.webm", kWebM,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new NoResponseApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
#if defined(USE_PROPRIETARY_CODECS)
TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_VideoOnly) {
- MockMediaSource source("bear-1280x720-v_frag-cenc.mp4",
- kMP4Video, kAppendWholeFile);
+ MockMediaSource source("bear-1280x720-v_frag-cenc.mp4", kMP4Video,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
}
TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_AudioOnly) {
- MockMediaSource source("bear-1280x720-a_frag-cenc.mp4",
- kMP4Audio, kAppendWholeFile);
+ MockMediaSource source("bear-1280x720-a_frag-cenc.mp4", kMP4Audio,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
TEST_F(PipelineIntegrationTest,
EncryptedPlayback_NoEncryptedFrames_MP4_CENC_VideoOnly) {
- MockMediaSource source("bear-1280x720-v_frag-cenc_clear-all.mp4",
- kMP4Video, kAppendWholeFile);
+ MockMediaSource source("bear-1280x720-v_frag-cenc_clear-all.mp4", kMP4Video,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new NoResponseApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
TEST_F(PipelineIntegrationTest,
EncryptedPlayback_NoEncryptedFrames_MP4_CENC_AudioOnly) {
- MockMediaSource source("bear-1280x720-a_frag-cenc_clear-all.mp4",
- kMP4Audio, kAppendWholeFile);
+ MockMediaSource source("bear-1280x720-a_frag-cenc_clear-all.mp4", kMP4Audio,
+ kAppendWholeFile);
FakeEncryptedMedia encrypted_media(new NoResponseApp());
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
Stop();
}
+TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Video) {
+ MockMediaSource source("bear-1280x720-v_frag-cenc-key_rotation.mp4",
+ kMP4Video, kAppendWholeFile);
+ FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
+ StartPipelineWithEncryptedMedia(&source, &encrypted_media);
+
+ source.EndOfStream();
+ ASSERT_EQ(PIPELINE_OK, pipeline_status_);
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
+
+TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Audio) {
+ MockMediaSource source("bear-1280x720-a_frag-cenc-key_rotation.mp4",
+ kMP4Audio, kAppendWholeFile);
+ FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp());
+ StartPipelineWithEncryptedMedia(&source, &encrypted_media);
+
+ source.EndOfStream();
+ ASSERT_EQ(PIPELINE_OK, pipeline_status_);
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
#endif
// TODO(acolwell): Fix flakiness http://crbug.com/117921
ASSERT_TRUE(WaitUntilOnEnded());
}
+#if defined(USE_PROPRIETARY_CODECS)
+TEST_F(PipelineIntegrationTest, Rotated_Metadata_0) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_rotate_0.mp4"), PIPELINE_OK));
+ ASSERT_EQ(VIDEO_ROTATION_0, metadata_.video_rotation);
+}
+
+TEST_F(PipelineIntegrationTest, Rotated_Metadata_90) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_rotate_90.mp4"), PIPELINE_OK));
+ ASSERT_EQ(VIDEO_ROTATION_90, metadata_.video_rotation);
+}
+
+TEST_F(PipelineIntegrationTest, Rotated_Metadata_180) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_rotate_180.mp4"), PIPELINE_OK));
+ ASSERT_EQ(VIDEO_ROTATION_180, metadata_.video_rotation);
+}
+
+TEST_F(PipelineIntegrationTest, Rotated_Metadata_270) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_rotate_270.mp4"), PIPELINE_OK));
+ ASSERT_EQ(VIDEO_ROTATION_270, metadata_.video_rotation);
+}
+#endif
+
// Verify audio decoder & renderer can handle aborted demuxer reads.
TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) {
ASSERT_TRUE(TestSeekDuringRead("bear-320x240-audio-only.webm", kAudioOnlyWebM,
TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) {
ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", kVideoOnlyWebM,
32768,
- base::TimeDelta::FromMilliseconds(200),
+ base::TimeDelta::FromMilliseconds(167),
base::TimeDelta::FromMilliseconds(1668),
0x1C896, 65536));
}
PIPELINE_OK));
Play();
ASSERT_TRUE(WaitUntilOnEnded());
- EXPECT_EQ(kOpusEndTrimmingWebMFileAudioBytes,
- pipeline_->GetStatistics().audio_bytes_decoded);
}
// Verify that VP9 video in WebM containers can be played back.
-// TODO(fgalligan): Enable after new vp9 files are landed.
-// http://crbug.com/259116
-TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VideoOnly_VP9_WebM) {
+TEST_F(PipelineIntegrationTest, BasicPlayback_VideoOnly_VP9_WebM) {
ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9.webm"),
PIPELINE_OK));
Play();
// Verify that VP9 video and Opus audio in the same WebM container can be played
// back.
-// TODO(fgalligan): Enable after new vp9 files are landed.
-// http://crbug.com/259116
-TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VP9_Opus_WebM) {
+TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Opus_WebM) {
ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9-opus.webm"),
PIPELINE_OK));
Play();
// Verify that VP8 video with alpha channel can be played back.
TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_WebM) {
- EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber());
ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a.webm"),
PIPELINE_OK));
Play();
EXPECT_EQ(last_video_frame_format_, VideoFrame::YV12A);
}
+// Verify that VP8A video with odd width/height can be played back.
+TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_Odd_WebM) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a-odd-dimensions.webm"),
+ PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ EXPECT_EQ(last_video_frame_format_, VideoFrame::YV12A);
+}
+
+// Verify that VP9 video with odd width/height can be played back.
+TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Odd_WebM) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9-odd-dimensions.webm"),
+ PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+}
+
// Verify that VP8 video with inband text track can be played back.
TEST_F(PipelineIntegrationTest, BasicPlayback_VP8_WebVTT_WebM) {
+ EXPECT_CALL(*this, OnAddTextTrack(_, _));
ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8-webvtt.webm"),
PIPELINE_OK));
Play();
ASSERT_TRUE(WaitUntilOnEnded());
}
+// Verify that VP9 video with 4:4:4 subsampling can be played back.
+TEST_F(PipelineIntegrationTest, P444_VP9_WebM) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-P444.webm"),
+ PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ EXPECT_EQ(last_video_frame_format_, VideoFrame::YV24);
+}
+
+// Verify that videos with an odd frame size playback successfully.
+TEST_F(PipelineIntegrationTest, BasicPlayback_OddVideoSize) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("butterfly-853x480.webm"),
+ PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+}
+
+// Verify that OPUS audio in a webm which reports a 44.1kHz sample rate plays
+// correctly at 48kHz
+TEST_F(PipelineIntegrationTest, BasicPlayback_Opus441kHz) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("sfx-opus-441.webm"), PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ EXPECT_EQ(48000,
+ demuxer_->GetStream(DemuxerStream::AUDIO)
+ ->audio_decoder_config()
+ .samples_per_second());
+}
+
+// Same as above but using MediaSource.
+TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Opus441kHz) {
+ MockMediaSource source(
+ "sfx-opus-441.webm", kOpusAudioOnlyWebM, kAppendWholeFile);
+ StartPipelineWithMediaSource(&source);
+ source.EndOfStream();
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+ EXPECT_EQ(48000,
+ demuxer_->GetStream(DemuxerStream::AUDIO)
+ ->audio_decoder_config()
+ .samples_per_second());
+}
+
+// Ensures audio-only playback with missing or negative timestamps works. Tests
+// the common live-streaming case for chained ogg. See http://crbug.com/396864.
+TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOgg) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("double-sfx.ogg"), PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ ASSERT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
+}
+
+// Ensures audio-video playback with missing or negative timestamps fails softly
+// instead of crashing. See http://crbug.com/396864.
+TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOggVideo) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("double-bear.ogv"), PIPELINE_OK));
+ Play();
+ EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError());
+ ASSERT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
+}
+
+// Tests that we signal ended even when audio runs longer than video track.
+TEST_F(PipelineIntegrationTest, BasicPlaybackAudioLongerThanVideo) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_audio_longer_than_video.ogv"),
+ PIPELINE_OK));
+ // Audio track is 2000ms. Video track is 1001ms. Duration should be higher
+ // of the two.
+ EXPECT_EQ(2000, pipeline_->GetMediaDuration().InMilliseconds());
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+}
+
+// Tests that we signal ended even when audio runs shorter than video track.
+TEST_F(PipelineIntegrationTest, BasicPlaybackAudioShorterThanVideo) {
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear_audio_shorter_than_video.ogv"),
+ PIPELINE_OK));
+ // Audio track is 500ms. Video track is 1001ms. Duration should be higher of
+ // the two.
+ EXPECT_EQ(1001, pipeline_->GetMediaDuration().InMilliseconds());
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+}
+
+TEST_F(PipelineIntegrationTest, BasicPlaybackPositiveStartTime) {
+ ASSERT_TRUE(
+ Start(GetTestDataFilePath("nonzero-start-time.webm"), PIPELINE_OK));
+ Play();
+ ASSERT_TRUE(WaitUntilOnEnded());
+ ASSERT_EQ(base::TimeDelta::FromMicroseconds(396000),
+ demuxer_->GetStartTime());
+}
+
} // namespace media