Commit b9ec078c authored by dalecurtis's avatar dalecurtis Committed by Commit bot

Initialize media timeline correctly for positive start times.

The previous fix for positive start times http://crrev.com/93c37f1b is
incomplete. It fixed seeking, but did not fix rendering. As such, the
media timeline will start playback from 0 and advance in real time to
any positive start time... even if the actual start is hours away.

When testing I used a test clip with a short positive start time so
did not notice the issue.  In attempting to write a layout test, I
retested with a larger start time which revealed the deficiency in my
first solution.

This change reverts the removal of Demuxer::StartTime() with the added
restriction that it must always be positive.  Pipeline uses this time
like it did before, during initialization and as a minimum bound for
seeking.

BUG=413292
TEST=four new tests! layout test forthcoming.

Review URL: https://codereview.chromium.org/575643002

Cr-Commit-Position: refs/heads/master@{#295159}
parent 8de1135a
...@@ -73,6 +73,9 @@ class MEDIA_EXPORT Demuxer : public DemuxerStreamProvider { ...@@ -73,6 +73,9 @@ class MEDIA_EXPORT Demuxer : public DemuxerStreamProvider {
// method (including Stop()) after a demuxer has stopped. // method (including Stop()) after a demuxer has stopped.
virtual void Stop() = 0; virtual void Stop() = 0;
// Returns the starting time for the media file; it's always positive.
virtual base::TimeDelta GetStartTime() const = 0;
// Returns Time represented by presentation timestamp 0. // Returns Time represented by presentation timestamp 0.
// If the timstamps are not associated with a Time, then // If the timstamps are not associated with a Time, then
// a null Time is returned. // a null Time is returned.
......
...@@ -39,6 +39,7 @@ class MockDemuxer : public Demuxer { ...@@ -39,6 +39,7 @@ class MockDemuxer : public Demuxer {
MOCK_METHOD0(Stop, void()); MOCK_METHOD0(Stop, void());
MOCK_METHOD0(OnAudioRendererDisabled, void()); MOCK_METHOD0(OnAudioRendererDisabled, void());
MOCK_METHOD1(GetStream, DemuxerStream*(DemuxerStream::Type)); MOCK_METHOD1(GetStream, DemuxerStream*(DemuxerStream::Type));
MOCK_CONST_METHOD0(GetStartTime, base::TimeDelta());
MOCK_CONST_METHOD0(GetTimelineOffset, base::Time()); MOCK_CONST_METHOD0(GetTimelineOffset, base::Time());
MOCK_CONST_METHOD0(GetLiveness, Liveness()); MOCK_CONST_METHOD0(GetLiveness, Liveness());
......
...@@ -343,10 +343,12 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { ...@@ -343,10 +343,12 @@ void Pipeline::StateTransitionTask(PipelineStatus status) {
if (!is_initialized_) { if (!is_initialized_) {
is_initialized_ = true; is_initialized_ = true;
ReportMetadata(); ReportMetadata();
start_timestamp_ = demuxer_->GetStartTime();
} }
base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
DCHECK(start_timestamp_ >= base::TimeDelta());
renderer_->StartPlayingFrom(start_timestamp_); renderer_->StartPlayingFrom(start_timestamp_);
if (text_renderer_) if (text_renderer_)
...@@ -584,13 +586,16 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { ...@@ -584,13 +586,16 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) {
DCHECK(seek_cb_.is_null()); DCHECK(seek_cb_.is_null());
const base::TimeDelta seek_timestamp =
std::max(time, demuxer_->GetStartTime());
SetState(kSeeking); SetState(kSeeking);
seek_cb_ = seek_cb; seek_cb_ = seek_cb;
renderer_ended_ = false; renderer_ended_ = false;
text_renderer_ended_ = false; text_renderer_ended_ = false;
start_timestamp_ = time; start_timestamp_ = seek_timestamp;
DoSeek(time, DoSeek(seek_timestamp,
base::Bind(&Pipeline::OnStateTransition, weak_factory_.GetWeakPtr())); base::Bind(&Pipeline::OnStateTransition, weak_factory_.GetWeakPtr()));
} }
......
...@@ -103,6 +103,8 @@ class PipelineTest : public ::testing::Test { ...@@ -103,6 +103,8 @@ class PipelineTest : public ::testing::Test {
EXPECT_CALL(*renderer_, GetMediaTime()) EXPECT_CALL(*renderer_, GetMediaTime())
.WillRepeatedly(Return(base::TimeDelta())); .WillRepeatedly(Return(base::TimeDelta()));
EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_));
} }
virtual ~PipelineTest() { virtual ~PipelineTest() {
...@@ -203,7 +205,7 @@ class PipelineTest : public ::testing::Test { ...@@ -203,7 +205,7 @@ class PipelineTest : public ::testing::Test {
EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_)); EXPECT_CALL(callbacks_, OnMetadata(_)).WillOnce(SaveArg<0>(&metadata_));
EXPECT_CALL(*renderer_, SetPlaybackRate(0.0f)); EXPECT_CALL(*renderer_, SetPlaybackRate(0.0f));
EXPECT_CALL(*renderer_, SetVolume(1.0f)); EXPECT_CALL(*renderer_, SetVolume(1.0f));
EXPECT_CALL(*renderer_, StartPlayingFrom(base::TimeDelta())) EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_))
.WillOnce(SetBufferingState(&buffering_state_cb_, .WillOnce(SetBufferingState(&buffering_state_cb_,
BUFFERING_HAVE_ENOUGH)); BUFFERING_HAVE_ENOUGH));
EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
...@@ -313,6 +315,7 @@ class PipelineTest : public ::testing::Test { ...@@ -313,6 +315,7 @@ class PipelineTest : public ::testing::Test {
base::Closure ended_cb_; base::Closure ended_cb_;
VideoDecoderConfig video_decoder_config_; VideoDecoderConfig video_decoder_config_;
PipelineMetadata metadata_; PipelineMetadata metadata_;
base::TimeDelta start_time_;
private: private:
DISALLOW_COPY_AND_ASSIGN(PipelineTest); DISALLOW_COPY_AND_ASSIGN(PipelineTest);
...@@ -742,6 +745,22 @@ TEST_F(PipelineTest, Underflow) { ...@@ -742,6 +745,22 @@ TEST_F(PipelineTest, Underflow) {
DoSeek(expected); DoSeek(expected);
} }
TEST_F(PipelineTest, PositiveStartTime) {
start_time_ = base::TimeDelta::FromSeconds(1);
EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_));
CreateAudioStream();
MockDemuxerStreamVector streams;
streams.push_back(audio_stream());
SetDemuxerExpectations(&streams);
SetRendererExpectations();
StartPipelineAndExpect(PIPELINE_OK);
ExpectDemuxerStop();
ExpectPipelineStopAndDestroyPipeline();
pipeline_->Stop(
base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)));
message_loop_.RunUntilIdle();
}
class PipelineTeardownTest : public PipelineTest { class PipelineTeardownTest : public PipelineTest {
public: public:
enum TeardownState { enum TeardownState {
......
...@@ -1141,6 +1141,10 @@ DemuxerStream* ChunkDemuxer::GetStream(DemuxerStream::Type type) { ...@@ -1141,6 +1141,10 @@ DemuxerStream* ChunkDemuxer::GetStream(DemuxerStream::Type type) {
return NULL; return NULL;
} }
TimeDelta ChunkDemuxer::GetStartTime() const {
return TimeDelta();
}
Demuxer::Liveness ChunkDemuxer::GetLiveness() const { Demuxer::Liveness ChunkDemuxer::GetLiveness() const {
return liveness_; return liveness_;
} }
...@@ -1642,7 +1646,7 @@ void ChunkDemuxer::OnSourceInitDone( ...@@ -1642,7 +1646,7 @@ void ChunkDemuxer::OnSourceInitDone(
return; return;
} }
SeekAllSources(base::TimeDelta()); SeekAllSources(GetStartTime());
StartReturningData(); StartReturningData();
if (duration_ == kNoTimestamp()) if (duration_ == kNoTimestamp())
......
...@@ -164,6 +164,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { ...@@ -164,6 +164,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE; virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE;
virtual base::Time GetTimelineOffset() const OVERRIDE; virtual base::Time GetTimelineOffset() const OVERRIDE;
virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE; virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE;
virtual base::TimeDelta GetStartTime() const OVERRIDE;
virtual Liveness GetLiveness() const OVERRIDE; virtual Liveness GetLiveness() const OVERRIDE;
// Methods used by an external object to control this demuxer. // Methods used by an external object to control this demuxer.
......
...@@ -686,6 +686,10 @@ FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream( ...@@ -686,6 +686,10 @@ FFmpegDemuxerStream* FFmpegDemuxer::GetFFmpegStream(
return NULL; return NULL;
} }
base::TimeDelta FFmpegDemuxer::GetStartTime() const {
return std::max(start_time_, base::TimeDelta());
}
Demuxer::Liveness FFmpegDemuxer::GetLiveness() const { Demuxer::Liveness FFmpegDemuxer::GetLiveness() const {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
return liveness_; return liveness_;
...@@ -959,7 +963,7 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, ...@@ -959,7 +963,7 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
// Since we're shifting the externally visible start time to zero, we need to // Since we're shifting the externally visible start time to zero, we need to
// adjust the timeline offset to compensate. // adjust the timeline offset to compensate.
if (!timeline_offset_.is_null()) if (!timeline_offset_.is_null() && start_time_ < base::TimeDelta())
timeline_offset_ += start_time_; timeline_offset_ += start_time_;
if (max_duration == kInfiniteDuration() && !timeline_offset_.is_null()) { if (max_duration == kInfiniteDuration() && !timeline_offset_.is_null()) {
......
...@@ -168,6 +168,7 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer { ...@@ -168,6 +168,7 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE; virtual void Seek(base::TimeDelta time, const PipelineStatusCB& cb) OVERRIDE;
virtual base::Time GetTimelineOffset() const OVERRIDE; virtual base::Time GetTimelineOffset() const OVERRIDE;
virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE; virtual DemuxerStream* GetStream(DemuxerStream::Type type) OVERRIDE;
virtual base::TimeDelta GetStartTime() const OVERRIDE;
virtual Liveness GetLiveness() const OVERRIDE; virtual Liveness GetLiveness() const OVERRIDE;
// Calls |need_key_cb_| with the initialization data encountered in the file. // Calls |need_key_cb_| with the initialization data encountered in the file.
...@@ -179,8 +180,9 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer { ...@@ -179,8 +180,9 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
void NotifyCapacityAvailable(); void NotifyCapacityAvailable();
void NotifyBufferingChanged(); void NotifyBufferingChanged();
// The lowest demuxed timestamp. DemuxerStream's must use this to adjust // The lowest demuxed timestamp. If negative, DemuxerStreams must use this to
// packet timestamps such that external clients see a zero-based timeline. // adjust packet timestamps such that external clients see a zero-based
// timeline.
base::TimeDelta start_time() const { return start_time_; } base::TimeDelta start_time() const { return start_time_; }
private: private:
......
...@@ -447,9 +447,8 @@ TEST_F(FFmpegDemuxerTest, Read_VideoPositiveStartTime) { ...@@ -447,9 +447,8 @@ TEST_F(FFmpegDemuxerTest, Read_VideoPositiveStartTime) {
// audio). // audio).
EXPECT_EQ(audio_start_time, demuxer_->start_time()); EXPECT_EQ(audio_start_time, demuxer_->start_time());
// Verify that the timeline offset has been adjusted by the start time. // Verify that the timeline offset has not been adjusted by the start time.
EXPECT_EQ(kTimelineOffsetMs + audio_start_time.InMilliseconds(), EXPECT_EQ(kTimelineOffsetMs, demuxer_->GetTimelineOffset().ToJavaTime());
demuxer_->GetTimelineOffset().ToJavaTime());
// Seek back to the beginning and repeat the test. // Seek back to the beginning and repeat the test.
WaitableMessageLoopEvent event; WaitableMessageLoopEvent event;
...@@ -549,6 +548,10 @@ TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOggDiscard_Sync) { ...@@ -549,6 +548,10 @@ TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOggDiscard_Sync) {
EXPECT_EQ(base::TimeDelta::FromMicroseconds(-2902), EXPECT_EQ(base::TimeDelta::FromMicroseconds(-2902),
demuxer_->start_time()); demuxer_->start_time());
// Though the internal start time may be below zero, the exposed media time
// must always be greater than zero.
EXPECT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
video->Read(NewReadCB(FROM_HERE, 9997, 0)); video->Read(NewReadCB(FROM_HERE, 9997, 0));
message_loop_.Run(); message_loop_.Run();
......
...@@ -1563,6 +1563,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOgg) { ...@@ -1563,6 +1563,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOgg) {
ASSERT_TRUE(Start(GetTestDataFilePath("double-sfx.ogg"), PIPELINE_OK)); ASSERT_TRUE(Start(GetTestDataFilePath("double-sfx.ogg"), PIPELINE_OK));
Play(); Play();
ASSERT_TRUE(WaitUntilOnEnded()); ASSERT_TRUE(WaitUntilOnEnded());
ASSERT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
} }
// Ensures audio-video playback with missing or negative timestamps fails softly // Ensures audio-video playback with missing or negative timestamps fails softly
...@@ -1571,6 +1572,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOggVideo) { ...@@ -1571,6 +1572,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackChainedOggVideo) {
ASSERT_TRUE(Start(GetTestDataFilePath("double-bear.ogv"), PIPELINE_OK)); ASSERT_TRUE(Start(GetTestDataFilePath("double-bear.ogv"), PIPELINE_OK));
Play(); Play();
EXPECT_EQ(PIPELINE_ERROR_DECODE, WaitUntilEndedOrError()); 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. // Tests that we signal ended even when audio runs longer than video track.
...@@ -1595,4 +1597,13 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackAudioShorterThanVideo) { ...@@ -1595,4 +1597,13 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackAudioShorterThanVideo) {
ASSERT_TRUE(WaitUntilOnEnded()); 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 } // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment