Fix buffered range updates in media::Pipeline

Currently the buffered ranges reported by
media::Pipeline::GetBufferedTimeRanges may be out of sync with the
actually buffered ranges in the demuxer, since there's no way for a
demuxer to indicate that some time range got evicted (e.g. in MSE case).
This CL replaces media::Pipeline::AddBufferedTimeRange with
OnBufferedTimeRangesChanged method that allows demuxer to report
arbitrary changes in buffered ranges.

BUG=570514

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

Cr-Commit-Position: refs/heads/master@{#369585}
diff --git a/chromecast/media/cma/test/frame_segmenter_for_test.cc b/chromecast/media/cma/test/frame_segmenter_for_test.cc
index e3c72d6d..7d4d06d 100644
--- a/chromecast/media/cma/test/frame_segmenter_for_test.cc
+++ b/chromecast/media/cma/test/frame_segmenter_for_test.cc
@@ -283,8 +283,8 @@
 class FakeDemuxerHost : public ::media::DemuxerHost {
  public:
   // DemuxerHost implementation.
-  void AddBufferedTimeRange(base::TimeDelta start,
-                            base::TimeDelta end) override {}
+  void OnBufferedTimeRangesChanged(
+      const ::media::Ranges<base::TimeDelta>& ranges) override {}
   void SetDuration(base::TimeDelta duration) override {}
   void OnDemuxerError(::media::PipelineStatus error) override {
     LOG(FATAL) << "OnDemuxerError: " << error;
diff --git a/content/renderer/media/android/media_source_delegate.cc b/content/renderer/media/android/media_source_delegate.cc
index e29125d..b9ffb03d 100644
--- a/content/renderer/media/android/media_source_delegate.cc
+++ b/content/renderer/media/android/media_source_delegate.cc
@@ -307,9 +307,9 @@
       media_weak_factory_.GetWeakPtr()));
 }
 
-void MediaSourceDelegate::AddBufferedTimeRange(base::TimeDelta start,
-                                               base::TimeDelta end) {
-  buffered_time_ranges_.Add(start, end);
+void MediaSourceDelegate::OnBufferedTimeRangesChanged(
+    const media::Ranges<base::TimeDelta>& ranges) {
+  buffered_time_ranges_ = ranges;
 }
 
 void MediaSourceDelegate::SetDuration(base::TimeDelta duration) {
diff --git a/content/renderer/media/android/media_source_delegate.h b/content/renderer/media/android/media_source_delegate.h
index 9571d5a7..6d9719c 100644
--- a/content/renderer/media/android/media_source_delegate.h
+++ b/content/renderer/media/android/media_source_delegate.h
@@ -110,8 +110,8 @@
 
  private:
   // Methods inherited from DemuxerHost.
-  void AddBufferedTimeRange(base::TimeDelta start,
-                            base::TimeDelta end) override;
+  void OnBufferedTimeRangesChanged(
+      const media::Ranges<base::TimeDelta>& ranges) override;
   void SetDuration(base::TimeDelta duration) override;
   void OnDemuxerError(media::PipelineStatus status) override;
   void AddTextStream(media::DemuxerStream* text_stream,
diff --git a/media/base/demuxer.h b/media/base/demuxer.h
index cc9f665..5a2218a 100644
--- a/media/base/demuxer.h
+++ b/media/base/demuxer.h
@@ -17,6 +17,7 @@
 #include "media/base/eme_constants.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
+#include "media/base/ranges.h"
 
 namespace media {
 
@@ -24,9 +25,13 @@
 
 class MEDIA_EXPORT DemuxerHost {
  public:
-  // Notify the host that time range [start,end] has been buffered.
-  virtual void AddBufferedTimeRange(base::TimeDelta start,
-                                    base::TimeDelta end) = 0;
+  // Notify the host that buffered time ranges have changed. Note that buffered
+  // time ranges can grow (when new media data is appended), but they can also
+  // shrink (when buffering reaches limit capacity and some buffered data
+  // becomes evicted, e.g. due to MSE GC algorithm, or by explicit removal of
+  // ranges directed by MSE web app).
+  virtual void OnBufferedTimeRangesChanged(
+      const Ranges<base::TimeDelta>& ranges) = 0;
 
   // Sets the duration of the media in microseconds.
   // Duration may be kInfiniteDuration() if the duration is not known.
diff --git a/media/base/demuxer_perftest.cc b/media/base/demuxer_perftest.cc
index 6743ac6c..7d8a10ba 100644
--- a/media/base/demuxer_perftest.cc
+++ b/media/base/demuxer_perftest.cc
@@ -31,8 +31,8 @@
   ~DemuxerHostImpl() override {}
 
   // DemuxerHost implementation.
-  void AddBufferedTimeRange(base::TimeDelta start,
-                            base::TimeDelta end) override {}
+  void OnBufferedTimeRangesChanged(
+      const Ranges<base::TimeDelta>& ranges) override {}
   void SetDuration(base::TimeDelta duration) override {}
   void OnDemuxerError(media::PipelineStatus error) override {}
   void AddTextStream(media::DemuxerStream* text_stream,
diff --git a/media/base/mock_demuxer_host.h b/media/base/mock_demuxer_host.h
index 614f971d..6ab7814 100644
--- a/media/base/mock_demuxer_host.h
+++ b/media/base/mock_demuxer_host.h
@@ -17,8 +17,8 @@
   MockDemuxerHost();
   virtual ~MockDemuxerHost();
 
-  MOCK_METHOD2(AddBufferedTimeRange, void(base::TimeDelta start,
-                                          base::TimeDelta end));
+  MOCK_METHOD1(OnBufferedTimeRangesChanged,
+               void(const Ranges<base::TimeDelta>&));
   MOCK_METHOD1(SetDuration, void(base::TimeDelta duration));
   MOCK_METHOD1(OnDemuxerError, void(PipelineStatus error));
   MOCK_METHOD2(AddTextStream, void(DemuxerStream*,
diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc
index ce49c5d..867d630 100644
--- a/media/base/pipeline.cc
+++ b/media/base/pipeline.cc
@@ -507,10 +507,11 @@
   }
 }
 
-void Pipeline::AddBufferedTimeRange(TimeDelta start, TimeDelta end) {
+void Pipeline::OnBufferedTimeRangesChanged(
+    const Ranges<base::TimeDelta>& ranges) {
   DCHECK(IsRunning());
   base::AutoLock auto_lock(lock_);
-  buffered_time_ranges_.Add(start, end);
+  buffered_time_ranges_ = ranges;
   did_loading_progress_ = true;
 }
 
diff --git a/media/base/pipeline.h b/media/base/pipeline.h
index ba99348..5eae2e09 100644
--- a/media/base/pipeline.h
+++ b/media/base/pipeline.h
@@ -246,8 +246,8 @@
   void FinishSeek();
 
   // DemuxerHost implementaion.
-  void AddBufferedTimeRange(base::TimeDelta start,
-                            base::TimeDelta end) override;
+  void OnBufferedTimeRangesChanged(
+      const Ranges<base::TimeDelta>& ranges) override;
   void SetDuration(base::TimeDelta duration) override;
   void OnDemuxerError(PipelineStatus error) override;
   void AddTextStream(DemuxerStream* text_stream,
@@ -350,7 +350,7 @@
   // Amount of available buffered data as reported by |demuxer_|.
   Ranges<base::TimeDelta> buffered_time_ranges_;
 
-  // True when AddBufferedTimeRange() has been called more recently than
+  // True when OnBufferedTimeRangesChanged() has been called more recently than
   // DidLoadingProgress().
   bool did_loading_progress_;
 
diff --git a/media/base/pipeline_unittest.cc b/media/base/pipeline_unittest.cc
index 5406278..8f43d60 100644
--- a/media/base/pipeline_unittest.cc
+++ b/media/base/pipeline_unittest.cc
@@ -661,7 +661,9 @@
   EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size());
 
   EXPECT_FALSE(pipeline_->DidLoadingProgress());
-  pipeline_->AddBufferedTimeRange(base::TimeDelta(), kDuration / 8);
+  Ranges<base::TimeDelta> ranges;
+  ranges.Add(base::TimeDelta(), kDuration / 8);
+  pipeline_->OnBufferedTimeRangesChanged(ranges);
   EXPECT_TRUE(pipeline_->DidLoadingProgress());
   EXPECT_FALSE(pipeline_->DidLoadingProgress());
   EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size());
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index f8bffcd..374d618c 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -659,8 +659,7 @@
     ranges = GetBufferedRanges_Locked();
   }
 
-  for (size_t i = 0; i < ranges.size(); ++i)
-    host_->AddBufferedTimeRange(ranges.start(i), ranges.end(i));
+  host_->OnBufferedTimeRangesChanged(ranges);
 }
 
 void ChunkDemuxer::ResetParserState(const std::string& id,
@@ -702,6 +701,7 @@
     return;
 
   source_state_map_[id]->Remove(start, end, duration_);
+  host_->OnBufferedTimeRangesChanged(GetBufferedRanges_Locked());
 }
 
 double ChunkDemuxer::GetDuration() {
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index 5e1f777..308f9eb 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -584,7 +584,7 @@
   void AppendData(const std::string& source_id,
                   const uint8_t* data,
                   size_t length) {
-    EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
+    EXPECT_CALL(host_, OnBufferedTimeRangesChanged(_)).Times(AnyNumber());
 
     demuxer_->AppendData(source_id, data, length,
                          append_window_start_for_next_append_,
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 7769c92..18863e6 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -1514,8 +1514,7 @@
   } else if (video) {
     buffered = video->GetBufferedRanges();
   }
-  for (size_t i = 0; i < buffered.size(); ++i)
-    host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i));
+  host_->OnBufferedTimeRangesChanged(buffered);
 }
 
 void FFmpegDemuxer::OnDataSourceError() {
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index 1a7acab..e22e454 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -84,7 +84,7 @@
   void CreateDemuxer(const std::string& name) {
     CHECK(!demuxer_);
 
-    EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
+    EXPECT_CALL(host_, OnBufferedTimeRangesChanged(_)).Times(AnyNumber());
 
     CreateDataSource(name);
 
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index 47fc08d..43da5c1d 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -561,6 +561,19 @@
     last_timestamp_offset_ = timestamp_offset;
   }
 
+  void SetMemoryLimits(size_t limit_bytes) {
+    chunk_demuxer_->SetMemoryLimits(DemuxerStream::AUDIO, limit_bytes);
+    chunk_demuxer_->SetMemoryLimits(DemuxerStream::VIDEO, limit_bytes);
+  }
+
+  void EvictCodedFrames(base::TimeDelta currentMediaTime, size_t newDataSize) {
+    chunk_demuxer_->EvictCodedFrames(kSourceId, currentMediaTime, newDataSize);
+  }
+
+  void RemoveRange(base::TimeDelta start, base::TimeDelta end) {
+    chunk_demuxer_->Remove(kSourceId, start, end);
+  }
+
   void EndOfStream() {
     chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
   }
@@ -1089,6 +1102,60 @@
   Stop();
 }
 
+TEST_F(PipelineIntegrationTest, MediaSource_Remove_Updates_BufferedRanges) {
+  const char* input_filename = "bear-320x240.webm";
+  MockMediaSource source(input_filename, kWebM, kAppendWholeFile);
+  StartPipelineWithMediaSource(&source);
+
+  auto buffered_ranges = pipeline_->GetBufferedTimeRanges();
+  EXPECT_EQ(1u, buffered_ranges.size());
+  EXPECT_EQ(0, buffered_ranges.start(0).InMilliseconds());
+  EXPECT_EQ(k320WebMFileDurationMs, buffered_ranges.end(0).InMilliseconds());
+
+  source.RemoveRange(base::TimeDelta::FromMilliseconds(1000),
+                     base::TimeDelta::FromMilliseconds(k320WebMFileDurationMs));
+  buffered_ranges = pipeline_->GetBufferedTimeRanges();
+  EXPECT_EQ(1u, buffered_ranges.size());
+  EXPECT_EQ(0, buffered_ranges.start(0).InMilliseconds());
+  EXPECT_EQ(1001, buffered_ranges.end(0).InMilliseconds());
+
+  source.Shutdown();
+  Stop();
+}
+
+// This test case imitates media playback with advancing media_time and
+// continuously adding new data. At some point we should reach the buffering
+// limit, after that MediaSource should evict some buffered data and that
+// evicted data shold be reflected in the change of media::Pipeline buffered
+// ranges (returned by GetBufferedTimeRanges). At that point the buffered ranges
+// will no longer start at 0.
+TEST_F(PipelineIntegrationTest, MediaSource_FillUp_Buffer) {
+  const char* input_filename = "bear-320x240.webm";
+  MockMediaSource source(input_filename, kWebM, kAppendWholeFile);
+  StartPipelineWithMediaSource(&source);
+  source.SetMemoryLimits(1048576);
+
+  scoped_refptr<DecoderBuffer> file = ReadTestDataFile(input_filename);
+
+  auto buffered_ranges = pipeline_->GetBufferedTimeRanges();
+  EXPECT_EQ(1u, buffered_ranges.size());
+  do {
+    // Advance media_time to the end of the currently buffered data
+    base::TimeDelta media_time = buffered_ranges.end(0);
+    source.Seek(media_time);
+    // Ask MediaSource to evict buffered data if buffering limit has been
+    // reached (the data will be evicted from the front of the buffered range).
+    source.EvictCodedFrames(media_time, file->data_size());
+    source.AppendAtTime(media_time, file->data(), file->data_size());
+    buffered_ranges = pipeline_->GetBufferedTimeRanges();
+  } while (buffered_ranges.size() == 1 &&
+           buffered_ranges.start(0) == base::TimeDelta::FromSeconds(0));
+
+  EXPECT_EQ(1u, buffered_ranges.size());
+  source.Shutdown();
+  Stop();
+}
+
 #if !defined(DISABLE_EME_TESTS)
 TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_Encrypted_WebM) {
   MockMediaSource source("bear-320x240-16x9-aspect-av_enc-av.webm", kWebM,