blob: 6a7d2e998416089ea1177b21e49163c6d7542e6a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/filters/pipeline_controller.h"
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "media/base/mock_filters.h"
#include "media/base/pipeline.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
class PipelineControllerTest : public ::testing::Test, public Pipeline::Client {
public:
PipelineControllerTest()
: pipeline_controller_(&pipeline_,
base::Bind(&PipelineControllerTest::CreateRenderer,
base::Unretained(this)),
base::Bind(&PipelineControllerTest::OnSeeked,
base::Unretained(this)),
base::Bind(&PipelineControllerTest::OnSuspended,
base::Unretained(this)),
base::Bind(&PipelineControllerTest::OnError,
base::Unretained(this))) {}
~PipelineControllerTest() override {}
PipelineStatusCB StartPipeline(bool is_streaming, bool is_static) {
EXPECT_FALSE(pipeline_controller_.IsStable());
PipelineStatusCB start_cb;
EXPECT_CALL(pipeline_, Start(_, _, _, _)).WillOnce(SaveArg<3>(&start_cb));
pipeline_controller_.Start(&demuxer_, this, is_streaming, is_static);
Mock::VerifyAndClear(&pipeline_);
EXPECT_FALSE(pipeline_controller_.IsStable());
return start_cb;
}
PipelineStatusCB StartPipeline() { return StartPipeline(false, true); }
PipelineStatusCB StartPipeline_WithDynamicData() {
return StartPipeline(false, false);
}
PipelineStatusCB StartPipeline_WithStreamingData() {
return StartPipeline(true, false);
}
PipelineStatusCB SeekPipeline(base::TimeDelta time) {
EXPECT_TRUE(pipeline_controller_.IsStable());
PipelineStatusCB seek_cb;
EXPECT_CALL(pipeline_, Seek(time, _)).WillOnce(SaveArg<1>(&seek_cb));
pipeline_controller_.Seek(time, true);
Mock::VerifyAndClear(&pipeline_);
EXPECT_FALSE(pipeline_controller_.IsStable());
return seek_cb;
}
PipelineStatusCB SuspendPipeline() {
EXPECT_TRUE(pipeline_controller_.IsStable());
PipelineStatusCB suspend_cb;
EXPECT_CALL(pipeline_, Suspend(_)).WillOnce(SaveArg<0>(&suspend_cb));
pipeline_controller_.Suspend();
Mock::VerifyAndClear(&pipeline_);
EXPECT_TRUE(pipeline_controller_.IsSuspended());
EXPECT_FALSE(pipeline_controller_.IsStable());
EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended());
return suspend_cb;
}
PipelineStatusCB ResumePipeline() {
EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended());
PipelineStatusCB resume_cb;
EXPECT_CALL(pipeline_, Resume(_, _, _))
.WillOnce(
DoAll(SaveArg<1>(&last_resume_time_), SaveArg<2>(&resume_cb)));
EXPECT_CALL(pipeline_, GetMediaTime())
.WillRepeatedly(Return(base::TimeDelta()));
pipeline_controller_.Resume();
Mock::VerifyAndClear(&pipeline_);
EXPECT_FALSE(pipeline_controller_.IsSuspended());
EXPECT_FALSE(pipeline_controller_.IsStable());
EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended());
return resume_cb;
}
void Complete(const PipelineStatusCB& cb) {
cb.Run(PIPELINE_OK);
base::RunLoop().RunUntilIdle();
}
protected:
std::unique_ptr<Renderer> CreateRenderer() {
return std::unique_ptr<Renderer>();
}
void OnSeeked(bool time_updated) {
was_seeked_ = true;
last_seeked_time_updated_ = time_updated;
}
void OnSuspended() { was_suspended_ = true; }
// Pipeline::Client overrides
void OnError(PipelineStatus status) override { NOTREACHED(); }
void OnEnded() override {}
void OnMetadata(PipelineMetadata metadata) override {}
void OnBufferingStateChange(BufferingState state) override {}
void OnDurationChange() override {}
void OnAddTextTrack(const TextTrackConfig& config,
const AddTextTrackDoneCB& done_cb) override {}
void OnWaitingForDecryptionKey() override {}
void OnVideoNaturalSizeChange(const gfx::Size& size) override {}
void OnVideoOpacityChange(bool opaque) override {}
base::MessageLoop message_loop_;
NiceMock<MockDemuxer> demuxer_;
StrictMock<MockPipeline> pipeline_;
PipelineController pipeline_controller_;
bool was_seeked_ = false;
bool last_seeked_time_updated_ = false;
bool was_suspended_ = false;
base::TimeDelta last_resume_time_;
DISALLOW_COPY_AND_ASSIGN(PipelineControllerTest);
};
TEST_F(PipelineControllerTest, Startup) {
PipelineStatusCB start_cb = StartPipeline();
EXPECT_FALSE(was_seeked_);
Complete(start_cb);
EXPECT_TRUE(was_seeked_);
EXPECT_FALSE(last_seeked_time_updated_);
EXPECT_FALSE(was_suspended_);
EXPECT_TRUE(pipeline_controller_.IsStable());
}
TEST_F(PipelineControllerTest, SuspendResume) {
Complete(StartPipeline());
EXPECT_TRUE(was_seeked_);
was_seeked_ = false;
Complete(SuspendPipeline());
EXPECT_TRUE(was_suspended_);
EXPECT_FALSE(pipeline_controller_.IsStable());
Complete(ResumePipeline());
EXPECT_TRUE(pipeline_controller_.IsStable());
// |was_seeked_| should not be affected by Suspend()/Resume() at all.
EXPECT_FALSE(was_seeked_);
}
TEST_F(PipelineControllerTest, Seek) {
// Normal seeking should not result in a cancel.
EXPECT_CALL(demuxer_, CancelPendingSeek(_)).Times(0);
Complete(StartPipeline());
was_seeked_ = false;
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time));
PipelineStatusCB seek_cb = SeekPipeline(seek_time);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(was_seeked_);
Complete(seek_cb);
EXPECT_TRUE(was_seeked_);
EXPECT_TRUE(pipeline_controller_.IsStable());
}
TEST_F(PipelineControllerTest, SuspendResumeTime) {
Complete(StartPipeline());
Complete(SuspendPipeline());
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
pipeline_controller_.Seek(seek_time, true);
base::RunLoop().RunUntilIdle();
Complete(ResumePipeline());
EXPECT_EQ(seek_time, last_resume_time_);
}
TEST_F(PipelineControllerTest, SuspendResumeTime_WithStreamingData) {
Complete(StartPipeline_WithStreamingData());
Complete(SuspendPipeline());
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
pipeline_controller_.Seek(seek_time, true);
base::RunLoop().RunUntilIdle();
Complete(ResumePipeline());
EXPECT_EQ(base::TimeDelta(), last_resume_time_);
}
TEST_F(PipelineControllerTest, SeekAborted) {
Complete(StartPipeline());
// Create a first pending seek.
base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5);
EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_1));
PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&demuxer_);
// Create a second seek; the first should be aborted.
base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10);
EXPECT_CALL(demuxer_, CancelPendingSeek(seek_time_2));
pipeline_controller_.Seek(seek_time_2, true);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&demuxer_);
// When the first seek is completed (or aborted) the second should be issued.
EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_2));
EXPECT_CALL(pipeline_, Seek(seek_time_2, _));
Complete(seek_cb_1);
}
TEST_F(PipelineControllerTest, PendingSuspend) {
Complete(StartPipeline());
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
PipelineStatusCB seek_cb = SeekPipeline(seek_time);
base::RunLoop().RunUntilIdle();
// While the seek is ongoing, request a suspend.
// It will be a mock failure if pipeline_.Suspend() is called.
pipeline_controller_.Suspend();
base::RunLoop().RunUntilIdle();
// Expect the suspend to trigger when the seek is completed.
EXPECT_CALL(pipeline_, Suspend(_));
Complete(seek_cb);
}
TEST_F(PipelineControllerTest, SeekMergesWithResume) {
Complete(StartPipeline());
Complete(SuspendPipeline());
// Request a seek while suspended.
// It will be a mock failure if pipeline_.Seek() is called.
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
pipeline_controller_.Seek(seek_time, true);
base::RunLoop().RunUntilIdle();
// Resume and verify the resume time includes the seek.
Complete(ResumePipeline());
EXPECT_EQ(seek_time, last_resume_time_);
EXPECT_TRUE(last_seeked_time_updated_);
}
TEST_F(PipelineControllerTest, SeekMergesWithSeek) {
Complete(StartPipeline());
base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5);
PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time_1);
base::RunLoop().RunUntilIdle();
// Request another seek while the first is ongoing.
base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10);
pipeline_controller_.Seek(seek_time_2, true);
base::RunLoop().RunUntilIdle();
// Request a third seek. (It should replace the second.)
base::TimeDelta seek_time_3 = base::TimeDelta::FromSeconds(15);
pipeline_controller_.Seek(seek_time_3, true);
base::RunLoop().RunUntilIdle();
// Expect the third seek to trigger when the first seek completes.
EXPECT_CALL(pipeline_, Seek(seek_time_3, _));
Complete(seek_cb_1);
}
TEST_F(PipelineControllerTest, SeekToSeekTimeElided) {
Complete(StartPipeline());
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time);
base::RunLoop().RunUntilIdle();
// Request a seek to the same time again.
pipeline_controller_.Seek(seek_time, true);
base::RunLoop().RunUntilIdle();
// Complete the first seek.
// It would be a mock error if the second seek was dispatched here.
Complete(seek_cb_1);
EXPECT_TRUE(pipeline_controller_.IsStable());
}
TEST_F(PipelineControllerTest, SeekToSeekTimeNotElided) {
Complete(StartPipeline_WithDynamicData());
base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);
PipelineStatusCB seek_cb_1 = SeekPipeline(seek_time);
base::RunLoop().RunUntilIdle();
// Request a seek to the same time again.
pipeline_controller_.Seek(seek_time, true);
base::RunLoop().RunUntilIdle();
// Expect the second seek to trigger when the first seek completes.
EXPECT_CALL(pipeline_, Seek(seek_time, _));
Complete(seek_cb_1);
}
} // namespace media