summaryrefslogtreecommitdiffstats
path: root/media/base
diff options
context:
space:
mode:
authorvrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-02 02:52:41 +0000
committervrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-02-02 02:52:41 +0000
commitb0aba07d55920c2b54f9eb6840ff36664f401a8e (patch)
treebf0058abeab0062f2b618ec27be18553fa6a0597 /media/base
parent8f549e16bef7423f465164970b31b04b84db45b7 (diff)
downloadchromium_src-b0aba07d55920c2b54f9eb6840ff36664f401a8e.zip
chromium_src-b0aba07d55920c2b54f9eb6840ff36664f401a8e.tar.gz
chromium_src-b0aba07d55920c2b54f9eb6840ff36664f401a8e.tar.bz2
Fix media timeline so that thumb never exceeds buffered data
Caps PipelineImpl's current time to the time of the end of the last frame buffered so that the thumb no longer continues progress when stalled. BUG=99915 TEST=Playing video with limited bandwidth. Review URL: http://codereview.chromium.org/9155003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@120151 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/base')
-rw-r--r--media/base/clock.cc109
-rw-r--r--media/base/clock.h55
-rw-r--r--media/base/clock_unittest.cc320
-rw-r--r--media/base/composite_filter.cc5
-rw-r--r--media/base/filter_host.h4
-rw-r--r--media/base/filters.h15
-rw-r--r--media/base/mock_filter_host.h10
-rw-r--r--media/base/mock_filters.cc5
-rw-r--r--media/base/mock_filters.h14
-rw-r--r--media/base/pipeline.cc73
-rw-r--r--media/base/pipeline.h10
-rw-r--r--media/base/pipeline_unittest.cc20
12 files changed, 414 insertions, 226 deletions
diff --git a/media/base/clock.cc b/media/base/clock.cc
index 4cdc021..2517d1d 100644
--- a/media/base/clock.cc
+++ b/media/base/clock.cc
@@ -10,9 +10,8 @@
namespace media {
Clock::Clock(TimeProvider* time_provider)
- : time_provider_(time_provider),
- playing_(false),
- playback_rate_(1.0f) {
+ : time_provider_(time_provider) {
+ Reset();
}
Clock::~Clock() {}
@@ -23,44 +22,64 @@ bool Clock::IsPlaying() const {
base::TimeDelta Clock::Play() {
DCHECK(!playing_);
- reference_ = GetTimeFromProvider();
+ UpdateReferencePoints();
playing_ = true;
return media_time_;
}
base::TimeDelta Clock::Pause() {
DCHECK(playing_);
- // Save our new accumulated amount of media time.
- media_time_ = Elapsed();
+ UpdateReferencePoints();
playing_ = false;
return media_time_;
}
void Clock::SetPlaybackRate(float playback_rate) {
- if (playing_) {
- base::Time time = GetTimeFromProvider();
- media_time_ = ElapsedViaProvidedTime(time);
- reference_ = time;
- }
+ UpdateReferencePoints();
playback_rate_ = playback_rate;
}
-void Clock::SetTime(const base::TimeDelta& time) {
- if (time == kNoTimestamp()) {
- NOTREACHED();
- return;
- }
- if (playing_) {
- reference_ = GetTimeFromProvider();
- }
- media_time_ = time;
+void Clock::SetTime(base::TimeDelta current_time, base::TimeDelta max_time) {
+ DCHECK(current_time <= max_time);
+ DCHECK(current_time != kNoTimestamp());
+
+ UpdateReferencePoints(current_time);
+ max_time_ = ClampToValidTimeRange(max_time);
+ underflow_ = false;
}
-base::TimeDelta Clock::Elapsed() const {
- if (!playing_) {
+base::TimeDelta Clock::Elapsed() {
+ if (duration_ == kNoTimestamp())
+ return base::TimeDelta();
+
+ // The clock is not advancing, so return the last recorded time.
+ if (!playing_ || underflow_)
return media_time_;
+
+ base::TimeDelta elapsed = EstimatedElapsedTime();
+ if (max_time_ != kNoTimestamp() && elapsed > max_time_) {
+ UpdateReferencePoints(max_time_);
+ underflow_ = true;
+ elapsed = max_time_;
}
- return ElapsedViaProvidedTime(GetTimeFromProvider());
+
+ return elapsed;
+}
+
+void Clock::SetMaxTime(base::TimeDelta max_time) {
+ DCHECK(max_time != kNoTimestamp());
+
+ UpdateReferencePoints();
+ max_time_ = ClampToValidTimeRange(max_time);
+
+ DCHECK(media_time_ <= max_time_);
+ underflow_ = false;
+}
+
+void Clock::SetDuration(base::TimeDelta duration) {
+ DCHECK(duration_ == kNoTimestamp());
+ DCHECK(duration > base::TimeDelta());
+ duration_ = duration;
}
base::TimeDelta Clock::ElapsedViaProvidedTime(const base::Time& time) const {
@@ -71,10 +90,50 @@ base::TimeDelta Clock::ElapsedViaProvidedTime(const base::Time& time) const {
}
base::Time Clock::GetTimeFromProvider() const {
- if (time_provider_) {
+ if (time_provider_)
return time_provider_();
- }
return base::Time();
}
+base::TimeDelta Clock::ClampToValidTimeRange(base::TimeDelta time) const {
+ if (duration_ == kNoTimestamp())
+ return base::TimeDelta();
+ return std::max(std::min(time, duration_), base::TimeDelta());
+}
+
+void Clock::EndOfStream() {
+ Pause();
+ SetTime(Duration(), Duration());
+}
+
+base::TimeDelta Clock::Duration() const {
+ if (duration_ == kNoTimestamp())
+ return base::TimeDelta();
+ return duration_;
+}
+
+void Clock::UpdateReferencePoints() {
+ UpdateReferencePoints(Elapsed());
+}
+
+void Clock::UpdateReferencePoints(base::TimeDelta current_time) {
+ media_time_ = ClampToValidTimeRange(current_time);
+ reference_ = GetTimeFromProvider();
+}
+
+base::TimeDelta Clock::EstimatedElapsedTime() {
+ return ClampToValidTimeRange(
+ ElapsedViaProvidedTime(GetTimeFromProvider()));
+}
+
+void Clock::Reset() {
+ playing_ = false;
+ playback_rate_ = 1.0f;
+ max_time_ = kNoTimestamp();
+ duration_ = kNoTimestamp();
+ media_time_ = base::TimeDelta();
+ reference_ = base::Time();
+ underflow_ = false;
+}
+
} // namespace media
diff --git a/media/base/clock.h b/media/base/clock.h
index fc1be6d..2854cb8 100644
--- a/media/base/clock.h
+++ b/media/base/clock.h
@@ -48,26 +48,65 @@ class MEDIA_EXPORT Clock {
// will now change.
void SetPlaybackRate(float playback_rate);
- // Forcefully sets the media time to the given time. This should only be used
- // where a discontinuity in the media is found (i.e., seeking).
- void SetTime(const base::TimeDelta& time);
+ // Forcefully sets the media time to |current_time|. The second parameter is
+ // the |max_time| that the clock should progress after a call to Play(). This
+ // value is often the time of the end of the last frame buffered and decoded.
+ //
+ // These values are clamped to the duration of the video, which is initially
+ // set to 0 (before SetDuration() is called).
+ void SetTime(base::TimeDelta current_time, base::TimeDelta max_time);
- // Returns the current elapsed media time.
- base::TimeDelta Elapsed() const;
+ // Sets the |max_time| to be returned by a call to Elapsed(). |max_time| must
+ // be greater than or equal to the current Elapsed() time.
+ void SetMaxTime(base::TimeDelta max_time);
+
+ // Returns the current elapsed media time. Returns 0 if SetDuration() has
+ // never been called.
+ base::TimeDelta Elapsed();
+
+ // Sets the duration of the video. Clock expects the duration will be set
+ // exactly once.
+ void SetDuration(base::TimeDelta duration);
+
+ // Resets clock to an uninitialized state.
+ void Reset();
+
+ // Notifies the clock that the end of stream has been reached. The clock state
+ // is updated accordingly.
+ void EndOfStream();
+
+ // Returns the duration of the clock, or 0 if not set.
+ base::TimeDelta Duration() const;
private:
+ // Updates the reference points based on the current calculated time.
+ void UpdateReferencePoints();
+
+ // Updates the reference points based on the given |current_time|.
+ void UpdateReferencePoints(base::TimeDelta current_time);
+
+ // Returns the time elapsed based on the current reference points, ignoring
+ // the |max_time_| cap.
+ base::TimeDelta EstimatedElapsedTime();
+
// Returns the current media time treating the given time as the latest
// value as returned by |time_provider_|.
base::TimeDelta ElapsedViaProvidedTime(const base::Time& time) const;
base::Time GetTimeFromProvider() const;
+ base::TimeDelta ClampToValidTimeRange(base::TimeDelta time) const;
+
// Function returning current time in base::Time units.
TimeProvider* time_provider_;
// Whether the clock is running.
bool playing_;
+ // Whether the clock is stalled because it has reached the |max_time_|
+ // allowed.
+ bool underflow_;
+
// The system clock time when this clock last starting playing or had its
// time set via SetTime().
base::Time reference_;
@@ -79,6 +118,12 @@ class MEDIA_EXPORT Clock {
// Current playback rate.
float playback_rate_;
+ // The maximum time that can be returned by calls to GetCurrentTime.
+ base::TimeDelta max_time_;
+
+ // Duration of the media.
+ base::TimeDelta duration_;
+
DISALLOW_COPY_AND_ASSIGN(Clock);
};
diff --git a/media/base/clock_unittest.cc b/media/base/clock_unittest.cc
index dcf39ae..6773a5b 100644
--- a/media/base/clock_unittest.cc
+++ b/media/base/clock_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -24,167 +24,233 @@ static std::ostream& operator<<(std::ostream& stream, const TimeDelta& time) {
namespace media {
-TEST(ClockTest, Created) {
- StrictMock<base::MockTimeProvider> mock_time;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(0);
+static const int kDurationInSeconds = 120;
+
+class ClockTest : public ::testing::Test {
+ public:
+ ClockTest()
+ : clock_(&base::MockTimeProvider::StaticNow) {
+ SetDuration();
+ EXPECT_CALL(mock_time_, Now())
+ .WillRepeatedly(Return(base::Time::UnixEpoch()));
+ }
+
+ protected:
+ void SetDuration() {
+ const base::TimeDelta kDuration =
+ base::TimeDelta::FromSeconds(kDurationInSeconds);
+ clock_.SetDuration(kDuration);
+ EXPECT_EQ(kDuration, clock_.Duration());
+ }
+
+ void AdvanceSystemTime(base::TimeDelta delta) {
+ time_elapsed_ += delta;
+ EXPECT_CALL(mock_time_, Now())
+ .WillRepeatedly(Return(base::Time::UnixEpoch() + time_elapsed_));
+ }
+
+ Clock clock_;
+ StrictMock<base::MockTimeProvider> mock_time_;
+ base::TimeDelta time_elapsed_;
+};
- Clock clock(&base::MockTimeProvider::StaticNow);
- EXPECT_EQ(kExpected, clock.Elapsed());
+TEST_F(ClockTest, Created) {
+ const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(kExpected, clock_.Elapsed());
}
-TEST(ClockTest, Play_NormalSpeed) {
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(6)));
+TEST_F(ClockTest, Play_NormalSpeed) {
const base::TimeDelta kZero;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(2);
+ const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(2);
- Clock clock(&base::MockTimeProvider::StaticNow);
- EXPECT_EQ(kZero, clock.Play());
- EXPECT_EQ(kExpected, clock.Elapsed());
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kTimeToAdvance);
+ EXPECT_EQ(kTimeToAdvance, clock_.Elapsed());
}
-TEST(ClockTest, Play_DoubleSpeed) {
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(9)));
+TEST_F(ClockTest, Play_DoubleSpeed) {
const base::TimeDelta kZero;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(10);
+ const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(5);
- Clock clock(&base::MockTimeProvider::StaticNow);
- clock.SetPlaybackRate(2.0f);
- EXPECT_EQ(kZero, clock.Play());
- EXPECT_EQ(kExpected, clock.Elapsed());
+ clock_.SetPlaybackRate(2.0f);
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kTimeToAdvance);
+ EXPECT_EQ(2 * kTimeToAdvance, clock_.Elapsed());
}
-TEST(ClockTest, Play_HalfSpeed) {
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(8)));
+TEST_F(ClockTest, Play_HalfSpeed) {
const base::TimeDelta kZero;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(2);
+ const base::TimeDelta kTimeToAdvance = base::TimeDelta::FromSeconds(4);
- Clock clock(&base::MockTimeProvider::StaticNow);
- clock.SetPlaybackRate(0.5f);
- EXPECT_EQ(kZero, clock.Play());
- EXPECT_EQ(kExpected, clock.Elapsed());
+ clock_.SetPlaybackRate(0.5f);
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kTimeToAdvance);
+ EXPECT_EQ(kTimeToAdvance / 2, clock_.Elapsed());
}
-TEST(ClockTest, Play_ZeroSpeed) {
+TEST_F(ClockTest, Play_ZeroSpeed) {
// We'll play for 2 seconds at normal speed, 4 seconds at zero speed, and 8
- // seconds at normal speed:
- // (1.0 x 2) + (0.0 x 4) + (1.0 x 8) = 10
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(6)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(10)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(18)));
+ // seconds at normal speed.
const base::TimeDelta kZero;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(10);
+ const base::TimeDelta kPlayDuration1 = base::TimeDelta::FromSeconds(2);
+ const base::TimeDelta kPlayDuration2 = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kPlayDuration3 = base::TimeDelta::FromSeconds(8);
+ const base::TimeDelta kExpected = kPlayDuration1 + kPlayDuration3;
+
+ EXPECT_EQ(kZero, clock_.Play());
+
+ AdvanceSystemTime(kPlayDuration1);
+ clock_.SetPlaybackRate(0.0f);
+ AdvanceSystemTime(kPlayDuration2);
+ clock_.SetPlaybackRate(1.0f);
+ AdvanceSystemTime(kPlayDuration3);
- Clock clock(&base::MockTimeProvider::StaticNow);
- EXPECT_EQ(kZero, clock.Play());
- clock.SetPlaybackRate(0.0f);
- clock.SetPlaybackRate(1.0f);
- EXPECT_EQ(kExpected, clock.Elapsed());
+ EXPECT_EQ(kExpected, clock_.Elapsed());
}
-TEST(ClockTest, Play_MultiSpeed) {
+TEST_F(ClockTest, Play_MultiSpeed) {
// We'll play for 2 seconds at half speed, 4 seconds at normal speed, and 8
- // seconds at double speed:
- // (0.5 x 2) + (1.0 x 4) + (2.0 x 8) = 21
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(6)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(10)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(18)));
+ // seconds at double speed.
const base::TimeDelta kZero;
- const base::TimeDelta kExpected = base::TimeDelta::FromSeconds(21);
-
- Clock clock(&base::MockTimeProvider::StaticNow);
- clock.SetPlaybackRate(0.5f);
- EXPECT_EQ(kZero, clock.Play());
- clock.SetPlaybackRate(1.0f);
- clock.SetPlaybackRate(2.0f);
- EXPECT_EQ(kExpected, clock.Elapsed());
+ const base::TimeDelta kPlayDuration1 = base::TimeDelta::FromSeconds(2);
+ const base::TimeDelta kPlayDuration2 = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kPlayDuration3 = base::TimeDelta::FromSeconds(8);
+ const base::TimeDelta kExpected =
+ kPlayDuration1 / 2 + kPlayDuration2 + 2 * kPlayDuration3;
+
+ clock_.SetPlaybackRate(0.5f);
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kPlayDuration1);
+
+ clock_.SetPlaybackRate(1.0f);
+ AdvanceSystemTime(kPlayDuration2);
+
+ clock_.SetPlaybackRate(2.0f);
+ AdvanceSystemTime(kPlayDuration3);
+ EXPECT_EQ(kExpected, clock_.Elapsed());
}
-TEST(ClockTest, Pause) {
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(8)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(12)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(16)));
+TEST_F(ClockTest, Pause) {
const base::TimeDelta kZero;
- const base::TimeDelta kFirstPause = base::TimeDelta::FromSeconds(4);
- const base::TimeDelta kSecondPause = base::TimeDelta::FromSeconds(8);
-
- Clock clock(&base::MockTimeProvider::StaticNow);
- EXPECT_EQ(kZero, clock.Play());
- EXPECT_EQ(kFirstPause, clock.Pause());
- EXPECT_EQ(kFirstPause, clock.Elapsed());
- EXPECT_EQ(kFirstPause, clock.Play());
- EXPECT_EQ(kSecondPause, clock.Pause());
- EXPECT_EQ(kSecondPause, clock.Elapsed());
+ const base::TimeDelta kPlayDuration = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kPauseDuration = base::TimeDelta::FromSeconds(20);
+ const base::TimeDelta kExpectedFirstPause = kPlayDuration;
+ const base::TimeDelta kExpectedSecondPause = 2 * kPlayDuration;
+
+ // Play for 4 seconds.
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kPlayDuration);
+
+ // Pause for 20 seconds.
+ EXPECT_EQ(kExpectedFirstPause, clock_.Pause());
+ EXPECT_EQ(kExpectedFirstPause, clock_.Elapsed());
+ AdvanceSystemTime(kPauseDuration);
+ EXPECT_EQ(kExpectedFirstPause, clock_.Elapsed());
+
+ // Play again for 4 more seconds.
+ EXPECT_EQ(kExpectedFirstPause, clock_.Play());
+ AdvanceSystemTime(kPlayDuration);
+ EXPECT_EQ(kExpectedSecondPause, clock_.Pause());
+ EXPECT_EQ(kExpectedSecondPause, clock_.Elapsed());
}
-TEST(ClockTest, SetTime_Paused) {
- // We'll remain paused while we set the time. The time should be simply
- // updated without accessing the time provider.
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
+TEST_F(ClockTest, SetTime_Paused) {
const base::TimeDelta kFirstTime = base::TimeDelta::FromSeconds(4);
const base::TimeDelta kSecondTime = base::TimeDelta::FromSeconds(16);
- Clock clock(&base::MockTimeProvider::StaticNow);
- clock.SetTime(kFirstTime);
- EXPECT_EQ(kFirstTime, clock.Elapsed());
- clock.SetTime(kSecondTime);
- EXPECT_EQ(kSecondTime, clock.Elapsed());
+ clock_.SetTime(kFirstTime, clock_.Duration());
+ EXPECT_EQ(kFirstTime, clock_.Elapsed());
+ clock_.SetTime(kSecondTime, clock_.Duration());
+ EXPECT_EQ(kSecondTime, clock_.Elapsed());
}
-TEST(ClockTest, SetTime_Playing) {
+TEST_F(ClockTest, SetTime_Playing) {
// We'll play for 4 seconds, then set the time to 12, then play for 4 more
- // seconds. We'll expect a media time of 16.
- InSequence s;
- StrictMock<base::MockTimeProvider> mock_time;
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(4)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(8)));
- EXPECT_CALL(mock_time, Now())
- .WillOnce(Return(base::Time::FromDoubleT(12)));
+ // seconds.
+ const base::TimeDelta kZero;
+ const base::TimeDelta kPlayDuration = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kUpdatedTime = base::TimeDelta::FromSeconds(12);
+ const base::TimeDelta kExpected = kUpdatedTime + kPlayDuration;
+
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(kPlayDuration);
+
+ clock_.SetTime(kUpdatedTime, clock_.Duration());
+ AdvanceSystemTime(kPlayDuration);
+ EXPECT_EQ(kExpected, clock_.Elapsed());
+}
+
+TEST_F(ClockTest, CapAtMediaDuration_Paused) {
+ const base::TimeDelta kDuration =
+ base::TimeDelta::FromSeconds(kDurationInSeconds);
+ const base::TimeDelta kTimeOverDuration =
+ base::TimeDelta::FromSeconds(kDurationInSeconds + 4);
+
+ // Elapsed time should always be capped at the duration of the media.
+ clock_.SetTime(kTimeOverDuration, kTimeOverDuration);
+ EXPECT_EQ(kDuration, clock_.Elapsed());
+}
+
+TEST_F(ClockTest, CapAtMediaDuration_Playing) {
+ const base::TimeDelta kZero;
+ const base::TimeDelta kDuration =
+ base::TimeDelta::FromSeconds(kDurationInSeconds);
+ const base::TimeDelta kTimeOverDuration =
+ base::TimeDelta::FromSeconds(kDurationInSeconds + 4);
+
+ // Play for twice as long as the duration of the media.
+ EXPECT_EQ(kZero, clock_.Play());
+ AdvanceSystemTime(2 * kDuration);
+ EXPECT_EQ(kDuration, clock_.Elapsed());
+
+ // Manually set the time past the duration.
+ clock_.SetTime(kTimeOverDuration, kTimeOverDuration);
+ EXPECT_EQ(kDuration, clock_.Elapsed());
+}
+
+TEST_F(ClockTest, SetMaxTime) {
const base::TimeDelta kZero;
- const base::TimeDelta kExepected = base::TimeDelta::FromSeconds(16);
+ const base::TimeDelta kTimeInterval = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kMaxTime = base::TimeDelta::FromSeconds(6);
+
+ EXPECT_EQ(kZero, clock_.Play());
+ clock_.SetMaxTime(kMaxTime);
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kTimeInterval, clock_.Elapsed());
+
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime, clock_.Elapsed());
+
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime, clock_.Elapsed());
+}
+
+TEST_F(ClockTest, SetMaxTime_MultipleTimes) {
+ const base::TimeDelta kZero;
+ const base::TimeDelta kTimeInterval = base::TimeDelta::FromSeconds(4);
+ const base::TimeDelta kMaxTime1 = base::TimeDelta::FromSeconds(6);
+ const base::TimeDelta kMaxTime2 = base::TimeDelta::FromSeconds(12);
+
+ EXPECT_EQ(kZero, clock_.Play());
+ clock_.SetMaxTime(clock_.Duration());
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kTimeInterval, clock_.Elapsed());
+
+ clock_.SetMaxTime(kMaxTime1);
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime1, clock_.Elapsed());
+
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime1, clock_.Elapsed());
+
+ clock_.SetMaxTime(kMaxTime2);
+ EXPECT_EQ(kMaxTime1, clock_.Elapsed());
+
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime1 + kTimeInterval, clock_.Elapsed());
- Clock clock(&base::MockTimeProvider::StaticNow);
- EXPECT_EQ(kZero, clock.Play());
- clock.SetTime(base::TimeDelta::FromSeconds(12));
- EXPECT_EQ(kExepected, clock.Elapsed());
+ AdvanceSystemTime(kTimeInterval);
+ EXPECT_EQ(kMaxTime2, clock_.Elapsed());
}
} // namespace media
diff --git a/media/base/composite_filter.cc b/media/base/composite_filter.cc
index 5089815..235a810 100644
--- a/media/base/composite_filter.cc
+++ b/media/base/composite_filter.cc
@@ -21,7 +21,6 @@ class CompositeFilter::FilterHostImpl : public FilterHost {
virtual void SetError(PipelineStatus error) OVERRIDE;
virtual base::TimeDelta GetTime() const OVERRIDE;
virtual base::TimeDelta GetDuration() const OVERRIDE;
- virtual void SetTime(base::TimeDelta time) OVERRIDE;
virtual void SetNaturalVideoSize(const gfx::Size& size) OVERRIDE;
virtual void NotifyEnded() OVERRIDE;
virtual void DisableAudioRenderer() OVERRIDE;
@@ -486,10 +485,6 @@ base::TimeDelta CompositeFilter::FilterHostImpl::GetDuration() const {
return host_->GetDuration();
}
-void CompositeFilter::FilterHostImpl::SetTime(base::TimeDelta time) {
- host_->SetTime(time);
-}
-
void CompositeFilter::FilterHostImpl::SetNaturalVideoSize(
const gfx::Size& size) {
host_->SetNaturalVideoSize(size);
diff --git a/media/base/filter_host.h b/media/base/filter_host.h
index 4719618..361550e 100644
--- a/media/base/filter_host.h
+++ b/media/base/filter_host.h
@@ -36,10 +36,6 @@ class MEDIA_EXPORT FilterHost {
// Gets the duration.
virtual base::TimeDelta GetDuration() const = 0;
- // Updates the current time. Other filters should poll to examine the updated
- // time.
- virtual void SetTime(base::TimeDelta time) = 0;
-
// Sets the natural size of the video output in pixel units.
virtual void SetNaturalVideoSize(const gfx::Size& size) = 0;
diff --git a/media/base/filters.h b/media/base/filters.h
index 4992723..5600eda 100644
--- a/media/base/filters.h
+++ b/media/base/filters.h
@@ -204,11 +204,16 @@ class MEDIA_EXPORT AudioDecoder : public Filter {
class MEDIA_EXPORT VideoRenderer : public Filter {
public:
+ // Used to update the pipeline's clock time. The parameter is the time that
+ // the clock should not exceed.
+ typedef base::Callback<void(base::TimeDelta)> VideoTimeCB;
+
// Initialize a VideoRenderer with the given VideoDecoder, executing the
// callback upon completion.
virtual void Initialize(VideoDecoder* decoder,
const PipelineStatusCB& callback,
- const StatisticsCallback& stats_callback) = 0;
+ const StatisticsCallback& stats_callback,
+ const VideoTimeCB& time_cb) = 0;
// Returns true if this filter has received and processed an end-of-stream
// buffer.
@@ -218,6 +223,11 @@ class MEDIA_EXPORT VideoRenderer : public Filter {
class MEDIA_EXPORT AudioRenderer : public Filter {
public:
+ // Used to update the pipeline's clock time. The first parameter is the
+ // current time, and the second parameter is the time that the clock must not
+ // exceed.
+ typedef base::Callback<void(base::TimeDelta, base::TimeDelta)> AudioTimeCB;
+
// Initialize a AudioRenderer with the given AudioDecoder, executing the
// |init_callback| upon completion. |underflow_callback| is called when the
// renderer runs out of data to pass to the audio card during playback.
@@ -226,7 +236,8 @@ class MEDIA_EXPORT AudioRenderer : public Filter {
// condition.
virtual void Initialize(AudioDecoder* decoder,
const PipelineStatusCB& init_callback,
- const base::Closure& underflow_callback) = 0;
+ const base::Closure& underflow_callback,
+ const AudioTimeCB& time_cb) = 0;
// Returns true if this filter has received and processed an end-of-stream
// buffer.
diff --git a/media/base/mock_filter_host.h b/media/base/mock_filter_host.h
index 82ab0ac..67f91af 100644
--- a/media/base/mock_filter_host.h
+++ b/media/base/mock_filter_host.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
//
@@ -27,17 +27,9 @@ class MockFilterHost : public FilterHost {
MOCK_METHOD1(SetError, void(PipelineStatus error));
MOCK_CONST_METHOD0(GetDuration, base::TimeDelta());
MOCK_CONST_METHOD0(GetTime, base::TimeDelta());
- MOCK_METHOD1(SetTime, void(base::TimeDelta time));
- MOCK_METHOD1(SetDuration, void(base::TimeDelta duration));
- MOCK_METHOD1(SetBufferedTime, void(base::TimeDelta buffered_time));
- MOCK_METHOD1(SetTotalBytes, void(int64 total_bytes));
- MOCK_METHOD1(SetBufferedBytes, void(int64 buffered_bytes));
MOCK_METHOD1(SetNaturalVideoSize, void(const gfx::Size& size));
- MOCK_METHOD1(SetNetworkActivity, void(bool network_activity));
MOCK_METHOD0(NotifyEnded, void());
MOCK_METHOD0(DisableAudioRenderer, void());
- MOCK_METHOD1(SetCurrentReadPosition, void(int64 offset));
- MOCK_METHOD0(GetCurrentReadPosition, int64());
private:
DISALLOW_COPY_AND_ASSIGN(MockFilterHost);
diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc
index 484fe8d..9c116b7 100644
--- a/media/base/mock_filters.cc
+++ b/media/base/mock_filters.cc
@@ -176,6 +176,11 @@ void RunPipelineStatusCB3(::testing::Unused, const PipelineStatusCB& callback,
callback.Run(PIPELINE_OK);
}
+void RunPipelineStatusCB4(::testing::Unused, const PipelineStatusCB& callback,
+ ::testing::Unused, ::testing::Unused) {
+ callback.Run(PIPELINE_OK);
+}
+
void RunStopFilterCallback(const base::Closure& callback) {
callback.Run();
}
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index b2b12c2..30e0973 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -237,9 +237,11 @@ class MockVideoRenderer : public VideoRenderer {
MOCK_METHOD0(OnAudioRendererDisabled, void());
// VideoRenderer implementation.
- MOCK_METHOD3(Initialize, void(VideoDecoder* decoder,
+ MOCK_METHOD4(Initialize, void(VideoDecoder* decoder,
const PipelineStatusCB& callback,
- const StatisticsCallback& stats_callback));
+ const StatisticsCallback& stats_callback,
+ const VideoTimeCB& time_cb));
+
MOCK_METHOD0(HasEnded, bool());
// TODO(scherkus): although VideoRendererBase defines this method, this really
@@ -264,9 +266,10 @@ class MockAudioRenderer : public AudioRenderer {
MOCK_METHOD0(OnAudioRendererDisabled, void());
// AudioRenderer implementation.
- MOCK_METHOD3(Initialize, void(AudioDecoder* decoder,
+ MOCK_METHOD4(Initialize, void(AudioDecoder* decoder,
const PipelineStatusCB& init_callback,
- const base::Closure& underflow_callback));
+ const base::Closure& underflow_callback,
+ const AudioTimeCB& time_cb));
MOCK_METHOD0(HasEnded, bool());
MOCK_METHOD1(SetVolume, void(float volume));
@@ -319,7 +322,8 @@ void RunFilterStatusCB(::testing::Unused, const FilterStatusCB& cb);
void RunPipelineStatusCB(PipelineStatus status, const PipelineStatusCB& cb);
void RunPipelineStatusCB3(::testing::Unused, const PipelineStatusCB& callback,
::testing::Unused);
-
+void RunPipelineStatusCB4(::testing::Unused, const PipelineStatusCB& callback,
+ ::testing::Unused, ::testing::Unused);
// Helper gmock function that immediately executes the Closure on behalf of the
// provided filter. Can be used when mocking the Stop() method.
void RunStopFilterCallback(const base::Closure& callback);
diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc
index 359472f..0f268c9 100644
--- a/media/base/pipeline.cc
+++ b/media/base/pipeline.cc
@@ -214,11 +214,7 @@ base::TimeDelta Pipeline::GetCurrentTime() const {
base::TimeDelta Pipeline::GetCurrentTime_Locked() const {
lock_.AssertAcquired();
- base::TimeDelta elapsed = clock_->Elapsed();
- if (elapsed > duration_)
- return duration_;
-
- return elapsed;
+ return clock_->Elapsed();
}
base::TimeDelta Pipeline::GetBufferedTime() {
@@ -226,8 +222,8 @@ base::TimeDelta Pipeline::GetBufferedTime() {
// If media is fully loaded, then return duration.
if (local_source_ || total_bytes_ == buffered_bytes_) {
- max_buffered_time_ = duration_;
- return duration_;
+ max_buffered_time_ = clock_->Duration();
+ return max_buffered_time_;
}
base::TimeDelta current_time = GetCurrentTime_Locked();
@@ -241,7 +237,8 @@ base::TimeDelta Pipeline::GetBufferedTime() {
// If buffered time was not set, we use current time, current bytes, and
// buffered bytes to estimate the buffered time.
- double estimated_rate = duration_.InMillisecondsF() / total_bytes_;
+ double estimated_rate =
+ clock_->Duration().InMillisecondsF() / total_bytes_;
double estimated_current_time = estimated_rate * current_bytes_;
DCHECK_GE(buffered_bytes_, current_bytes_);
base::TimeDelta buffered_time = base::TimeDelta::FromMilliseconds(
@@ -249,7 +246,7 @@ base::TimeDelta Pipeline::GetBufferedTime() {
estimated_current_time));
// Cap approximated buffered time at the length of the video.
- buffered_time = std::min(buffered_time, duration_);
+ buffered_time = std::min(buffered_time, clock_->Duration());
// Make sure buffered_time is at least the current time
buffered_time = std::max(buffered_time, current_time);
@@ -262,7 +259,7 @@ base::TimeDelta Pipeline::GetBufferedTime() {
base::TimeDelta Pipeline::GetMediaDuration() const {
base::AutoLock auto_lock(lock_);
- return duration_;
+ return clock_->Duration();
}
int64 Pipeline::GetBufferedBytes() const {
@@ -323,7 +320,6 @@ void Pipeline::ResetState() {
tearing_down_ = false;
error_caused_teardown_ = false;
playback_rate_change_pending_ = false;
- duration_ = kZero;
buffered_time_ = kZero;
buffered_bytes_ = 0;
streaming_ = false;
@@ -339,7 +335,7 @@ void Pipeline::ResetState() {
has_video_ = false;
waiting_for_clock_update_ = false;
audio_disabled_ = false;
- clock_->SetTime(kZero);
+ clock_->Reset();
download_rate_monitor_.Reset();
}
@@ -451,20 +447,31 @@ base::TimeDelta Pipeline::GetDuration() const {
return GetMediaDuration();
}
-void Pipeline::SetTime(base::TimeDelta time) {
+void Pipeline::OnAudioTimeUpdate(base::TimeDelta time,
+ base::TimeDelta max_time) {
+ DCHECK(time <= max_time);
DCHECK(IsRunning());
base::AutoLock auto_lock(lock_);
- // If we were waiting for a valid timestamp and such timestamp arrives, we
- // need to clear the flag for waiting and start the clock.
- if (waiting_for_clock_update_) {
- if (time < clock_->Elapsed())
- return;
- clock_->SetTime(time);
- StartClockIfWaitingForTimeUpdate_Locked();
+ if (!has_audio_)
return;
- }
- clock_->SetTime(time);
+ if (waiting_for_clock_update_ && time < clock_->Elapsed())
+ return;
+
+ clock_->SetTime(time, max_time);
+ StartClockIfWaitingForTimeUpdate_Locked();
+}
+
+void Pipeline::OnVideoTimeUpdate(base::TimeDelta max_time) {
+ DCHECK(IsRunning());
+ base::AutoLock auto_lock(lock_);
+
+ if (has_audio_)
+ return;
+
+ DCHECK(!waiting_for_clock_update_);
+ DCHECK(clock_->Elapsed() <= max_time);
+ clock_->SetMaxTime(max_time);
}
void Pipeline::SetDuration(base::TimeDelta duration) {
@@ -475,7 +482,7 @@ void Pipeline::SetDuration(base::TimeDelta duration) {
UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration);
base::AutoLock auto_lock(lock_);
- duration_ = duration;
+ clock_->SetDuration(duration);
}
void Pipeline::SetBufferedTime(base::TimeDelta buffered_time) {
@@ -903,6 +910,7 @@ void Pipeline::NotifyEndedTask() {
// Start clock since there is no more audio to
// trigger clock updates.
base::AutoLock auto_lock(lock_);
+ clock_->SetMaxTime(clock_->Duration());
StartClockIfWaitingForTimeUpdate_Locked();
}
@@ -914,8 +922,7 @@ void Pipeline::NotifyEndedTask() {
SetState(kEnded);
{
base::AutoLock auto_lock(lock_);
- clock_->Pause();
- clock_->SetTime(duration_);
+ clock_->EndOfStream();
}
if (!ended_callback_.is_null()) {
@@ -947,6 +954,7 @@ void Pipeline::DisableAudioRendererTask() {
// Start clock since there is no more audio to
// trigger clock updates.
+ clock_->SetMaxTime(clock_->Duration());
StartClockIfWaitingForTimeUpdate_Locked();
}
@@ -975,7 +983,7 @@ void Pipeline::FilterStateTransitionTask() {
SetState(FindNextState(state_));
if (state_ == kSeeking) {
base::AutoLock auto_lock(lock_);
- clock_->SetTime(seek_timestamp_);
+ clock_->SetTime(seek_timestamp_, seek_timestamp_);
}
// Carry out the action for the current state.
@@ -1014,8 +1022,10 @@ void Pipeline::FilterStateTransitionTask() {
// We use audio stream to update the clock. So if there is such a stream,
// we pause the clock until we receive a valid timestamp.
waiting_for_clock_update_ = true;
- if (!has_audio_)
+ if (!has_audio_) {
+ clock_->SetMaxTime(clock_->Duration());
StartClockIfWaitingForTimeUpdate_Locked();
+ }
// Start monitoring rate of downloading.
int bitrate = 0;
@@ -1144,7 +1154,7 @@ void Pipeline::OnDemuxerBuilt(PipelineStatus status, Demuxer* demuxer) {
base::AutoLock auto_lock(lock_);
// We do not want to start the clock running. We only want to set the base
// media time so our timestamp calculations will be correct.
- clock_->SetTime(demuxer_->GetStartTime());
+ clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime());
}
OnFilterInitialize(PIPELINE_OK);
@@ -1232,7 +1242,9 @@ bool Pipeline::InitializeAudioRenderer(
audio_renderer_->Initialize(
decoder,
base::Bind(&Pipeline::OnFilterInitialize, this),
- base::Bind(&Pipeline::OnAudioUnderflow, this));
+ base::Bind(&Pipeline::OnAudioUnderflow, this),
+ base::Bind(&Pipeline::OnAudioTimeUpdate, this));
+
return true;
}
@@ -1256,7 +1268,8 @@ bool Pipeline::InitializeVideoRenderer(
video_renderer_->Initialize(
decoder,
base::Bind(&Pipeline::OnFilterInitialize, this),
- base::Bind(&Pipeline::OnUpdateStatistics, this));
+ base::Bind(&Pipeline::OnUpdateStatistics, this),
+ base::Bind(&Pipeline::OnVideoTimeUpdate, this));
return true;
}
diff --git a/media/base/pipeline.h b/media/base/pipeline.h
index 2e6318d..771ccdf 100644
--- a/media/base/pipeline.h
+++ b/media/base/pipeline.h
@@ -338,7 +338,6 @@ class MEDIA_EXPORT Pipeline
virtual void SetError(PipelineStatus error) OVERRIDE;
virtual base::TimeDelta GetTime() const OVERRIDE;
virtual base::TimeDelta GetDuration() const OVERRIDE;
- virtual void SetTime(base::TimeDelta time) OVERRIDE;
virtual void SetNaturalVideoSize(const gfx::Size& size) OVERRIDE;
virtual void NotifyEnded() OVERRIDE;
virtual void DisableAudioRenderer() OVERRIDE;
@@ -358,6 +357,12 @@ class MEDIA_EXPORT Pipeline
// Callback executed by filters to update statistics.
void OnUpdateStatistics(const PipelineStatistics& stats);
+ // Callback executed by audio renderer to update clock time.
+ void OnAudioTimeUpdate(base::TimeDelta time, base::TimeDelta max_time);
+
+ // Callback executed by video renderer to update clock time.
+ void OnVideoTimeUpdate(base::TimeDelta max_time);
+
// The following "task" methods correspond to the public methods, but these
// methods are run as the result of posting a task to the PipelineInternal's
// message loop.
@@ -511,9 +516,6 @@ class MEDIA_EXPORT Pipeline
// Whether or not a playback rate change should be done once seeking is done.
bool playback_rate_change_pending_;
- // Duration of the media in microseconds. Set by filters.
- base::TimeDelta duration_;
-
// Amount of available buffered data in microseconds. Set by filters.
base::TimeDelta buffered_time_;
diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc
index 58d1aa8..ead6d82 100644
--- a/media/base/pipeline_unittest.cc
+++ b/media/base/pipeline_unittest.cc
@@ -153,8 +153,8 @@ class PipelineTest : public ::testing::Test {
// Sets up expectations to allow the video renderer to initialize.
void InitializeVideoRenderer() {
EXPECT_CALL(*mocks_->video_renderer(),
- Initialize(mocks_->video_decoder(), _, _))
- .WillOnce(Invoke(&RunPipelineStatusCB3));
+ Initialize(mocks_->video_decoder(), _, _, _))
+ .WillOnce(Invoke(&RunPipelineStatusCB4));
EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f));
EXPECT_CALL(*mocks_->video_renderer(),
Seek(mocks_->demuxer()->GetStartTime(), _))
@@ -167,13 +167,13 @@ class PipelineTest : public ::testing::Test {
void InitializeAudioRenderer(bool disable_after_init_callback = false) {
if (disable_after_init_callback) {
EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(mocks_->audio_decoder(), _, _))
- .WillOnce(DoAll(Invoke(&RunPipelineStatusCB3),
+ Initialize(mocks_->audio_decoder(), _, _, _))
+ .WillOnce(DoAll(Invoke(&RunPipelineStatusCB4),
DisableAudioRenderer(mocks_->audio_renderer())));
} else {
EXPECT_CALL(*mocks_->audio_renderer(),
- Initialize(mocks_->audio_decoder(), _, _))
- .WillOnce(Invoke(&RunPipelineStatusCB3));
+ Initialize(mocks_->audio_decoder(), _, _, _))
+ .WillOnce(Invoke(&RunPipelineStatusCB4));
}
EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
@@ -681,6 +681,10 @@ TEST_F(PipelineTest, AudioStreamShorterThanVideo) {
streams.push_back(audio_stream());
streams.push_back(video_stream());
+ // Replace the clock so we can simulate wallclock time advancing w/o using
+ // Sleep().
+ pipeline_->SetClockForTesting(new Clock(&StaticClockFunction));
+
InitializeDemuxer(&streams, duration);
InitializeAudioDecoder(audio_stream());
InitializeAudioRenderer();
@@ -691,10 +695,6 @@ TEST_F(PipelineTest, AudioStreamShorterThanVideo) {
// For convenience to simulate filters calling the methods.
FilterHost* host = pipeline_;
- // Replace the clock so we can simulate wallclock time advancing w/o using
- // Sleep().
- pipeline_->SetClockForTesting(new Clock(&StaticClockFunction));
-
EXPECT_EQ(0, host->GetTime().ToInternalValue());
float playback_rate = 1.0f;