[go: nahoru, domu]

Replace StreamStatusChanged callback with async track switching

Allow the pipeline to control the entire track switch process without
    the media source communicating directly with the renderer.

    WMPL | pipeline | demuxer | demuxer_stream | renderer | video/audio_renderer
    ───────────────────────────Old Control Flow─────────────────────────────────
         |          |         |                |          |
     switch_track   |         |                |          |
      --------->    |         |                |          |
         |    switch track    |                |          |
         |     --------->     |                |          |
         |          | disable/enable stream    |          |
         |          |      ----------->        |          |
    ───────────────────────Sometime in the future───────────────────────────────
         |          |         |               read from stream
         |          |         |        <-----------------------------
         |          |         |                |End of stream
         |          |         |        ----------------------------->
         |          |         |                |      OnStreamStatus
         |          |         |                |    <----------------
         |          |         |                |   Flush/Restart/Reset
         |          |         |                |    ---------------->
         |          |         |                | OnBufferingStateChange
         |          |         |                |    <----------------
         |          | OnBufferingStateChange   |          |
         |    <--------------------------------------     |
    OnBufferingStateChange    |                |          |
    <----------     |         |                |          |

    ───────────────────────────New Control Flow─────────────────────────────────
    WMPL | pipeline | demuxer | demuxer_stream | renderer | video/audio_renderer
         |          |         |                |          |
     switch_track   |         |                |          |
      --------->    |         |                |          |
         |    switch track    |                |          |
         |     --------->     |                |          |
         |          | disable/enable stream    |          |
         |          |      ----------->        |          |
         |   active streams   |                |          |
         |     <---------     |                |          |
         |          |        switch track      |          |
         |     -------------------------------------->    |
         |          |         |                |    Flush/Restart/Reset
         |          |         |                |     --------------->
         |          |  Notify pipeline of completed track change
         |     <-----------------------------------------------------
    ───────────────────────Sometime in the future───────────────────────────────
         |          |         |                | OnBufferingStateChange
         |          |         |                |    <----------------
         |          | OnBufferingStateChange   |          |
         |    <--------------------------------------     |
    OnBufferingStateChange    |                |          |
    <----------     |         |                |          |

    In the new control flow the pipeline is able to control both the demuxer
    and the renderer, instead of controlling the demuxer and waiting for the
    renderer to act on it's own.


Bug: 709302
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: Iafc862e79f5afc1d3a6ea3400c03decd0ce91d27
Reviewed-on: https://chromium-review.googlesource.com/899843
Commit-Queue: Ted Meyer <tmathmeyer@chromium.org>
Reviewed-by: Sergey Volk <servolk@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Dan Sanders <sandersd@chromium.org>
Reviewed-by: Xiangjun Zhang <xjz@chromium.org>
Reviewed-by: Yuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551901}
diff --git a/media/base/demuxer.h b/media/base/demuxer.h
index 7994083..60e46c8 100644
--- a/media/base/demuxer.h
+++ b/media/base/demuxer.h
@@ -74,6 +74,12 @@
   using MediaTracksUpdatedCB =
       base::Callback<void(std::unique_ptr<MediaTracks>)>;
 
+  // Called once the demuxer has finished enabling or disabling tracks. The type
+  // argument is required because the vector may be empty.
+  using TrackChangeCB =
+      base::OnceCallback<void(DemuxerStream::Type type,
+                              const std::vector<DemuxerStream*>&)>;
+
   Demuxer();
   ~Demuxer() override;
 
@@ -142,15 +148,19 @@
   // Returns the memory usage in bytes for the demuxer.
   virtual int64_t GetMemoryUsage() const = 0;
 
+  // The |track_ids| vector has either 1 track, or is empty, indicating that
+  // all tracks should be disabled. |change_completed_cb| is fired after the
+  // demuxer streams are disabled, however this callback should then notify
+  // the appropriate renderer in order for tracks to be switched fully.
   virtual void OnEnabledAudioTracksChanged(
       const std::vector<MediaTrack::Id>& track_ids,
-      base::TimeDelta curr_time) = 0;
+      base::TimeDelta curr_time,
+      TrackChangeCB change_completed_cb) = 0;
 
-  // |track_id| either contains the selected video track id or is null,
-  // indicating that all video tracks are deselected/disabled.
   virtual void OnSelectedVideoTrackChanged(
-      base::Optional<MediaTrack::Id> track_id,
-      base::TimeDelta curr_time) = 0;
+      const std::vector<MediaTrack::Id>& track_ids,
+      base::TimeDelta curr_time,
+      TrackChangeCB change_completed_cb) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(Demuxer);
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index b97effa..501c46d8 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -220,9 +220,4 @@
   return result;
 }
 
-void FakeMediaResource::SetStreamStatusChangeCB(
-    const StreamStatusChangeCB& cb) {
-  NOTIMPLEMENTED();
-}
-
 }  // namespace media
diff --git a/media/base/fake_demuxer_stream.h b/media/base/fake_demuxer_stream.h
index d4f8350..42f3bb5 100644
--- a/media/base/fake_demuxer_stream.h
+++ b/media/base/fake_demuxer_stream.h
@@ -113,7 +113,6 @@
 
   // MediaResource implementation.
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
 
  private:
   FakeDemuxerStream fake_video_stream_;
diff --git a/media/base/media_resource.h b/media/base/media_resource.h
index 3b65dae..37ac330 100644
--- a/media/base/media_resource.h
+++ b/media/base/media_resource.h
@@ -17,14 +17,6 @@
 
 namespace media {
 
-// The callback that is used to notify clients about streams being enabled and
-// disabled. The first parameter is the DemuxerStream whose status changed. The
-// second parameter is a bool indicating whether the stream got enabled or
-// disabled. The third parameter specifies the media playback position at the
-// time the status change happened.
-using StreamStatusChangeCB =
-    base::RepeatingCallback<void(DemuxerStream*, bool, base::TimeDelta)>;
-
 // Abstract class that defines how to retrieve "media resources" in
 // DemuxerStream form (for most cases) or URL form (for the MediaPlayerRenderer
 // case).
@@ -58,10 +50,6 @@
   // exists or a null pointer if there is no streams of that type.
   DemuxerStream* GetFirstStream(DemuxerStream::Type type);
 
-  // The StreamStatusChangeCB allows clients to receive notifications about one
-  // of the streams being disabled or enabled.
-  virtual void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) = 0;
-
   // For Type::URL:
   //   Returns the URL parameters of the media to play. Empty URLs are legal,
   //   and should be handled appropriately by the caller.
diff --git a/media/base/media_url_demuxer.cc b/media/base/media_url_demuxer.cc
index afdd25c..d832135 100644
--- a/media/base/media_url_demuxer.cc
+++ b/media/base/media_url_demuxer.cc
@@ -23,11 +23,6 @@
   return std::vector<DemuxerStream*>();
 }
 
-// Should never be called since MediaResource::Type is URL.
-void MediaUrlDemuxer::SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) {
-  NOTREACHED();
-}
-
 MediaUrlParams MediaUrlDemuxer::GetMediaUrlParams() const {
   return params_;
 }
@@ -75,9 +70,22 @@
 
 void MediaUrlDemuxer::OnEnabledAudioTracksChanged(
     const std::vector<MediaTrack::Id>& track_ids,
-    base::TimeDelta curr_time) {}
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  // TODO(tmathmeyer): potentially support track changes for this renderer.
+  std::vector<DemuxerStream*> streams;
+  std::move(change_completed_cb).Run(DemuxerStream::AUDIO, streams);
+  DLOG(WARNING) << "Track changes are not supported.";
+}
+
 void MediaUrlDemuxer::OnSelectedVideoTrackChanged(
-    base::Optional<MediaTrack::Id> selected_track_id,
-    base::TimeDelta curr_time) {}
+    const std::vector<MediaTrack::Id>& track_ids,
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  // TODO(tmathmeyer): potentially support track changes for this renderer.
+  std::vector<DemuxerStream*> streams;
+  std::move(change_completed_cb).Run(DemuxerStream::VIDEO, streams);
+  DLOG(WARNING) << "Track changes are not supported.";
+}
 
 }  // namespace media
diff --git a/media/base/media_url_demuxer.h b/media/base/media_url_demuxer.h
index 15173cad..38527f2 100644
--- a/media/base/media_url_demuxer.h
+++ b/media/base/media_url_demuxer.h
@@ -40,7 +40,6 @@
 
   // MediaResource interface.
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
   MediaUrlParams GetMediaUrlParams() const override;
   MediaResource::Type GetType() const override;
 
@@ -58,10 +57,11 @@
   base::Time GetTimelineOffset() const override;
   int64_t GetMemoryUsage() const override;
   void OnEnabledAudioTracksChanged(const std::vector<MediaTrack::Id>& track_ids,
-                                   base::TimeDelta curr_time) override;
-  void OnSelectedVideoTrackChanged(
-      base::Optional<MediaTrack::Id> selected_track_id,
-      base::TimeDelta curr_time) override;
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
+  void OnSelectedVideoTrackChanged(const std::vector<MediaTrack::Id>& track_ids,
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
 
  private:
   MediaUrlParams params_;
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index 4cb961d..ba1aa367 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -88,10 +88,19 @@
                     base::TimeDelta,
                     const PipelineStatusCB&));
 
-  MOCK_METHOD1(OnEnabledAudioTracksChanged,
-               void(const std::vector<MediaTrack::Id>&));
-  MOCK_METHOD1(OnSelectedVideoTrackChanged,
-               void(base::Optional<MediaTrack::Id>));
+  void OnEnabledAudioTracksChanged(const std::vector<MediaTrack::Id>& id,
+                                   base::OnceClosure callback) {
+    MockOnEnabledAudioTracksChanged(id, callback);
+  }
+  void OnSelectedVideoTrackChanged(base::Optional<MediaTrack::Id> id,
+                                   base::OnceClosure callback) {
+    MockOnSelectedVideoTrackChanged(id, callback);
+  }
+
+  MOCK_METHOD2(MockOnEnabledAudioTracksChanged,
+               void(const std::vector<MediaTrack::Id>&, base::OnceClosure&));
+  MOCK_METHOD2(MockOnSelectedVideoTrackChanged,
+               void(base::Optional<MediaTrack::Id>, base::OnceClosure&));
 
   // TODO(sandersd): This should automatically return true between Start() and
   // Stop(). (Or better, remove it from the interface entirely.)
@@ -143,15 +152,30 @@
   MOCK_METHOD0(Stop, void());
   MOCK_METHOD0(AbortPendingReads, void());
   MOCK_METHOD0(GetAllStreams, std::vector<DemuxerStream*>());
-  MOCK_METHOD1(SetStreamStatusChangeCB, void(const StreamStatusChangeCB& cb));
 
   MOCK_CONST_METHOD0(GetStartTime, base::TimeDelta());
   MOCK_CONST_METHOD0(GetTimelineOffset, base::Time());
   MOCK_CONST_METHOD0(GetMemoryUsage, int64_t());
-  MOCK_METHOD2(OnEnabledAudioTracksChanged,
-               void(const std::vector<MediaTrack::Id>&, base::TimeDelta));
-  MOCK_METHOD2(OnSelectedVideoTrackChanged,
-               void(base::Optional<MediaTrack::Id>, base::TimeDelta));
+
+  void OnEnabledAudioTracksChanged(const std::vector<MediaTrack::Id>& id,
+                                   base::TimeDelta time,
+                                   TrackChangeCB cb) {
+    MockOnEnabledAudioTracksChanged(id, time, cb);
+  }
+  void OnSelectedVideoTrackChanged(const std::vector<MediaTrack::Id>& id,
+                                   base::TimeDelta time,
+                                   TrackChangeCB cb) {
+    MockOnSelectedVideoTrackChanged(id, time, cb);
+  }
+
+  MOCK_METHOD3(MockOnEnabledAudioTracksChanged,
+               void(const std::vector<MediaTrack::Id>&,
+                    base::TimeDelta,
+                    TrackChangeCB&));
+  MOCK_METHOD3(MockOnSelectedVideoTrackChanged,
+               void(const std::vector<MediaTrack::Id>&,
+                    base::TimeDelta,
+                    TrackChangeCB&));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockDemuxer);
@@ -315,6 +339,22 @@
                void(CdmContext* cdm_context,
                     const CdmAttachedCB& cdm_attached_cb));
 
+  void OnSelectedVideoTracksChanged(const std::vector<DemuxerStream*>& id,
+                                    base::OnceClosure cb) {
+    MockOnSelectedVideoTrackChanged(id, cb);
+  }
+
+  void OnSelectedAudioTracksChanged(const std::vector<DemuxerStream*>& id,
+                                    base::OnceClosure cb) {
+    MockOnSelectedAudioTracksChanged(id, cb);
+  }
+
+  MOCK_METHOD2(MockOnSelectedVideoTrackChanged,
+               void(std::vector<DemuxerStream*>, base::OnceClosure&));
+
+  MOCK_METHOD2(MockOnSelectedAudioTracksChanged,
+               void(std::vector<DemuxerStream*>, base::OnceClosure&));
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockRenderer);
 };
diff --git a/media/base/pipeline.h b/media/base/pipeline.h
index 91332ef..0df4066 100644
--- a/media/base/pipeline.h
+++ b/media/base/pipeline.h
@@ -106,14 +106,43 @@
                      Client* client,
                      const PipelineStatusCB& seek_cb) = 0;
 
+  // Track switching works similarly for both audio and video. Callbacks are
+  // used to notify when it is time to procede to the next step, since many of
+  // the operations are asynchronous.
+  // ──────────────────── Track Switch Control Flow ───────────────────────
+  //  pipeline | demuxer | demuxer_stream | renderer | video/audio_renderer
+  //           |         |                |          |
+  //           |         |                |          |
+  //           |         |                |          |
+  //     switch track    |                |          |
+  //      --------->     |                |          |
+  //           | disable/enable stream    |          |
+  //           |      ----------->        |          |
+  //    active streams   |                |          |
+  //      <---------     |                |          |
+  //           |        switch track      |          |
+  //      -------------------------------------->    |
+  //           |         |                |    Flush/Restart/Reset
+  //           |         |                |     --------------->
+  //     Notify pipeline of completed track change (via callback)
+  //      <-----------------------------------------------------
+  // ──────────────────── Sometime in the future ──────────────────────────
+  //           |         |                | OnBufferingStateChange
+  //           |         |                |    <----------------
+  //           | OnBufferingStateChange   |          |
+  //     <--------------------------------------     |
+  //           |         |                |          |
+  //           |         |                |          |
   // |enabled_track_ids| contains track ids of enabled audio tracks.
   virtual void OnEnabledAudioTracksChanged(
-      const std::vector<MediaTrack::Id>& enabled_track_ids) = 0;
+      const std::vector<MediaTrack::Id>& enabled_track_ids,
+      base::OnceClosure change_completed_cb) = 0;
 
   // |selected_track_id| is either empty, which means no video track is
   // selected, or contains the selected video track id.
   virtual void OnSelectedVideoTrackChanged(
-      base::Optional<MediaTrack::Id> selected_track_id) = 0;
+      base::Optional<MediaTrack::Id> selected_track_id,
+      base::OnceClosure change_completed_cb) = 0;
 
   // Stops the pipeline. This is a blocking function.
   // If the pipeline is started, it must be stopped before destroying it.
diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc
index 2194a60..5776cc4 100644
--- a/media/base/pipeline_impl.cc
+++ b/media/base/pipeline_impl.cc
@@ -71,12 +71,14 @@
 
   // |enabled_track_ids| contains track ids of enabled audio tracks.
   void OnEnabledAudioTracksChanged(
-      const std::vector<MediaTrack::Id>& enabled_track_ids);
+      const std::vector<MediaTrack::Id>& enabled_track_ids,
+      base::OnceClosure change_completed_cb);
 
   // |selected_track_id| is either empty, which means no video track is
   // selected, or contains the selected video track id.
   void OnSelectedVideoTrackChanged(
-      base::Optional<MediaTrack::Id> selected_track_id);
+      base::Optional<MediaTrack::Id> selected_track_id,
+      base::OnceClosure change_completed_cb);
 
  private:
   // Contains state shared between main and media thread.
@@ -109,6 +111,13 @@
     base::TimeDelta suspend_timestamp = kNoTimestamp;
   };
 
+  base::TimeDelta GetCurrentTimestamp();
+
+  void OnDemuxerCompletedTrackChange(
+      base::OnceClosure change_completed_cb,
+      DemuxerStream::Type stream_type,
+      const std::vector<DemuxerStream*>& streams);
+
   // DemuxerHost implementaion.
   void OnBufferedTimeRangesChanged(const Ranges<base::TimeDelta>& ranges) final;
   void SetDuration(base::TimeDelta duration) final;
@@ -577,26 +586,30 @@
   CheckPlaybackEnded();
 }
 
-void PipelineImpl::OnEnabledAudioTracksChanged(
-    const std::vector<MediaTrack::Id>& enabled_track_ids) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  media_task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&RendererWrapper::OnEnabledAudioTracksChanged,
-                 base::Unretained(renderer_wrapper_.get()), enabled_track_ids));
+// TODO(crbug/817089): Combine this functionality into renderer->GetMediaTime().
+base::TimeDelta PipelineImpl::RendererWrapper::GetCurrentTimestamp() {
+  DCHECK(demuxer_);
+  DCHECK(shared_state_.renderer || state_ != kPlaying);
+
+  return state_ == kPlaying ? shared_state_.renderer->GetMediaTime()
+                            : demuxer_->GetStartTime();
 }
 
-void PipelineImpl::OnSelectedVideoTrackChanged(
-    base::Optional<MediaTrack::Id> selected_track_id) {
+void PipelineImpl::OnEnabledAudioTracksChanged(
+    const std::vector<MediaTrack::Id>& enabled_track_ids,
+    base::OnceClosure change_completed_cb) {
   DCHECK(thread_checker_.CalledOnValidThread());
   media_task_runner_->PostTask(
       FROM_HERE,
-      base::Bind(&RendererWrapper::OnSelectedVideoTrackChanged,
-                 base::Unretained(renderer_wrapper_.get()), selected_track_id));
+      base::BindOnce(&RendererWrapper::OnEnabledAudioTracksChanged,
+                     base::Unretained(renderer_wrapper_.get()),
+                     enabled_track_ids,
+                     BindToCurrentLoop(std::move(change_completed_cb))));
 }
 
 void PipelineImpl::RendererWrapper::OnEnabledAudioTracksChanged(
-    const std::vector<MediaTrack::Id>& enabled_track_ids) {
+    const std::vector<MediaTrack::Id>& enabled_track_ids,
+    base::OnceClosure change_completed_cb) {
   DCHECK(media_task_runner_->BelongsToCurrentThread());
 
   // If the pipeline has been created, but not started yet, we may still receive
@@ -606,51 +619,86 @@
   // status is in sync with blink after pipeline is started.
   if (state_ == kCreated) {
     DCHECK(!demuxer_);
+    std::move(change_completed_cb).Run();
     return;
   }
 
   // Track status notifications might be delivered asynchronously. If we receive
   // a notification when pipeline is stopped/shut down, it's safe to ignore it.
   if (state_ == kStopping || state_ == kStopped) {
+    std::move(change_completed_cb).Run();
     return;
   }
+  demuxer_->OnEnabledAudioTracksChanged(
+      enabled_track_ids, GetCurrentTimestamp(),
+      base::BindOnce(&RendererWrapper::OnDemuxerCompletedTrackChange,
+                     weak_this_, base::Passed(&change_completed_cb)));
+}
 
-  DCHECK(demuxer_);
-  DCHECK(shared_state_.renderer || (state_ != kPlaying));
-
-  base::TimeDelta curr_time = (state_ == kPlaying)
-                                  ? shared_state_.renderer->GetMediaTime()
-                                  : demuxer_->GetStartTime();
-  demuxer_->OnEnabledAudioTracksChanged(enabled_track_ids, curr_time);
+void PipelineImpl::OnSelectedVideoTrackChanged(
+    base::Optional<MediaTrack::Id> selected_track_id,
+    base::OnceClosure change_completed_cb) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  media_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&RendererWrapper::OnSelectedVideoTrackChanged,
+                     base::Unretained(renderer_wrapper_.get()),
+                     selected_track_id,
+                     BindToCurrentLoop(std::move(change_completed_cb))));
 }
 
 void PipelineImpl::RendererWrapper::OnSelectedVideoTrackChanged(
-    base::Optional<MediaTrack::Id> selected_track_id) {
+    base::Optional<MediaTrack::Id> selected_track_id,
+    base::OnceClosure change_completed_cb) {
   DCHECK(media_task_runner_->BelongsToCurrentThread());
 
-  // If the pipeline has been created, but not started yet, we may still receive
-  // track notifications from blink level (e.g. when video track gets deselected
-  // due to player/pipeline belonging to a background tab). We can safely ignore
-  // these, since WebMediaPlayerImpl will ensure that demuxer stream / track
-  // status is in sync with blink after pipeline is started.
+  // See RenderWrapper::OnEnabledAudioTracksChanged.
   if (state_ == kCreated) {
     DCHECK(!demuxer_);
+    std::move(change_completed_cb).Run();
     return;
   }
 
-  // Track status notifications might be delivered asynchronously. If we receive
-  // a notification when pipeline is stopped/shut down, it's safe to ignore it.
   if (state_ == kStopping || state_ == kStopped) {
+    std::move(change_completed_cb).Run();
     return;
   }
 
-  DCHECK(demuxer_);
-  DCHECK(shared_state_.renderer || (state_ != kPlaying));
+  std::vector<MediaTrack::Id> tracks;
+  if (selected_track_id)
+    tracks.push_back(*selected_track_id);
 
-  base::TimeDelta curr_time = (state_ == kPlaying)
-                                  ? shared_state_.renderer->GetMediaTime()
-                                  : demuxer_->GetStartTime();
-  demuxer_->OnSelectedVideoTrackChanged(selected_track_id, curr_time);
+  demuxer_->OnSelectedVideoTrackChanged(
+      tracks, GetCurrentTimestamp(),
+      base::BindOnce(&RendererWrapper::OnDemuxerCompletedTrackChange,
+                     weak_this_, base::Passed(&change_completed_cb)));
+}
+
+void PipelineImpl::RendererWrapper::OnDemuxerCompletedTrackChange(
+    base::OnceClosure change_completed_cb,
+    DemuxerStream::Type stream_type,
+    const std::vector<DemuxerStream*>& streams) {
+  DCHECK(media_task_runner_->BelongsToCurrentThread());
+  if (!shared_state_.renderer) {
+    // This can happen if the pipeline has been suspended.
+    std::move(change_completed_cb).Run();
+    return;
+  }
+
+  switch (stream_type) {
+    case DemuxerStream::AUDIO:
+      shared_state_.renderer->OnEnabledAudioTracksChanged(
+          streams, std::move(change_completed_cb));
+      break;
+    case DemuxerStream::VIDEO:
+      shared_state_.renderer->OnSelectedVideoTracksChanged(
+          streams, std::move(change_completed_cb));
+      break;
+    // TODO(tmathmeyer): Look into text track switching.
+    case DemuxerStream::TEXT:
+    case DemuxerStream::UNKNOWN:  // Fail on unknown type.
+      NOTREACHED();
+  }
 }
 
 void PipelineImpl::RendererWrapper::OnStatisticsUpdate(
diff --git a/media/base/pipeline_impl.h b/media/base/pipeline_impl.h
index 9171ef5..543f768 100644
--- a/media/base/pipeline_impl.h
+++ b/media/base/pipeline_impl.h
@@ -35,9 +35,13 @@
 //         |                                 |
 //         V                                 V
 //   [ Playing ] <---------.            [ Stopped ]
-//     |     | Seek()      |
-//     |     V             |
-//     |   [ Seeking ] ----'
+//     |  |  | Seek()      |
+//     |  |  V             |
+//     |  | [ Seeking ] ---'
+//     |  |                ^
+//     |  | *TrackChange() |
+//     |  V                |
+//     | [ Switching ] ----'
 //     |                   ^
 //     | Suspend()         |
 //     V                   |
@@ -99,12 +103,14 @@
 
   // |enabled_track_ids| contains track ids of enabled audio tracks.
   void OnEnabledAudioTracksChanged(
-      const std::vector<MediaTrack::Id>& enabled_track_ids) override;
+      const std::vector<MediaTrack::Id>& enabled_track_ids,
+      base::OnceClosure change_completed_cb) override;
 
   // |selected_track_id| is either empty, which means no video track is
   // selected, or contains the selected video track id.
   void OnSelectedVideoTrackChanged(
-      base::Optional<MediaTrack::Id> selected_track_id) override;
+      base::Optional<MediaTrack::Id> selected_track_id,
+      base::OnceClosure change_completed_cb) override;
 
  private:
   friend class MediaLog;
diff --git a/media/base/renderer.cc b/media/base/renderer.cc
index f348d75..c2e1736 100644
--- a/media/base/renderer.cc
+++ b/media/base/renderer.cc
@@ -10,4 +10,18 @@
 
 Renderer::~Renderer() = default;
 
+void Renderer::OnSelectedVideoTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  std::move(change_completed_cb).Run();
+  DLOG(WARNING) << "Track changes are not supported.";
+}
+
+void Renderer::OnEnabledAudioTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  std::move(change_completed_cb).Run();
+  DLOG(WARNING) << "Track changes are not supported.";
+}
+
 }  // namespace media
diff --git a/media/base/renderer.h b/media/base/renderer.h
index 7b460d9..075ae48f 100644
--- a/media/base/renderer.h
+++ b/media/base/renderer.h
@@ -11,6 +11,7 @@
 #include "base/time/time.h"
 #include "media/base/buffering_state.h"
 #include "media/base/cdm_context.h"
+#include "media/base/demuxer_stream.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
 
@@ -57,6 +58,18 @@
   // Returns the current media time.
   virtual base::TimeDelta GetMediaTime() = 0;
 
+  // Provides a list of DemuxerStreams correlating to the tracks which should
+  // be played. An empty list would mean that any playing track of the same
+  // type should be flushed and disabled. Any provided Streams should be played
+  // by whatever mechanism the subclass of Renderer choses for managing it's AV
+  // playback.
+  virtual void OnSelectedVideoTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb);
+  virtual void OnEnabledAudioTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(Renderer);
 };
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 8bb2151..e630715 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -351,14 +351,6 @@
     base::ResetAndReturn(&read_cb_).Run(kOk,
                                         StreamParserBuffer::CreateEOSBuffer());
   }
-  if (!stream_status_change_cb_.is_null())
-    stream_status_change_cb_.Run(this, is_enabled_, timestamp);
-}
-
-void ChunkDemuxerStream::SetStreamStatusChangeCB(
-    const StreamStatusChangeCB& cb) {
-  DCHECK(!cb.is_null());
-  stream_status_change_cb_ = BindToCurrentLoop(cb);
 }
 
 TextTrackConfig ChunkDemuxerStream::text_track_config() {
@@ -567,15 +559,6 @@
   return result;
 }
 
-void ChunkDemuxer::SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) {
-  base::AutoLock auto_lock(lock_);
-  DCHECK(!cb.is_null());
-  for (const auto& stream : audio_streams_)
-    stream->SetStreamStatusChangeCB(cb);
-  for (const auto& stream : video_streams_)
-    stream->SetStreamStatusChangeCB(cb);
-}
-
 TimeDelta ChunkDemuxer::GetStartTime() const {
   return TimeDelta();
 }
@@ -782,68 +765,59 @@
   return itr->second->GetHighestPresentationTimestamp();
 }
 
-void ChunkDemuxer::OnEnabledAudioTracksChanged(
+void ChunkDemuxer::FindAndEnableProperTracks(
     const std::vector<MediaTrack::Id>& track_ids,
-    base::TimeDelta curr_time) {
+    base::TimeDelta curr_time,
+    DemuxerStream::Type track_type,
+    TrackChangeCB change_completed_cb) {
   base::AutoLock auto_lock(lock_);
+
   std::set<ChunkDemuxerStream*> enabled_streams;
   for (const auto& id : track_ids) {
     auto it = track_id_to_demux_stream_map_.find(id);
     if (it == track_id_to_demux_stream_map_.end())
       continue;
     ChunkDemuxerStream* stream = it->second;
-    DCHECK_EQ(DemuxerStream::AUDIO, stream->type());
+    DCHECK(stream);
+    DCHECK_EQ(track_type, stream->type());
     // TODO(servolk): Remove after multiple enabled audio tracks are supported
     // by the media::RendererImpl.
     if (!enabled_streams.empty()) {
       MEDIA_LOG(INFO, media_log_)
-          << "Only one enabled audio track is supported, ignoring track " << id;
+          << "Only one enabled track is supported, ignoring track " << id;
       continue;
     }
     enabled_streams.insert(stream);
+    stream->SetEnabled(true, curr_time);
   }
 
-  // First disable all streams that need to be disabled and then enable streams
-  // that are enabled.
-  for (const auto& stream : audio_streams_) {
-    if (enabled_streams.find(stream.get()) == enabled_streams.end()) {
+  bool is_audio = track_type == DemuxerStream::AUDIO;
+  for (const auto& stream : is_audio ? audio_streams_ : video_streams_) {
+    if (stream && enabled_streams.find(stream.get()) == enabled_streams.end()) {
       DVLOG(1) << __func__ << ": disabling stream " << stream.get();
       stream->SetEnabled(false, curr_time);
     }
   }
-  for (auto* stream : enabled_streams) {
-    DVLOG(1) << __func__ << ": enabling stream " << stream;
-    stream->SetEnabled(true, curr_time);
-  }
+
+  std::vector<DemuxerStream*> streams(enabled_streams.begin(),
+                                      enabled_streams.end());
+  std::move(change_completed_cb).Run(track_type, streams);
+}
+
+void ChunkDemuxer::OnEnabledAudioTracksChanged(
+    const std::vector<MediaTrack::Id>& track_ids,
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::AUDIO,
+                            std::move(change_completed_cb));
 }
 
 void ChunkDemuxer::OnSelectedVideoTrackChanged(
-    base::Optional<MediaTrack::Id> track_id,
-    base::TimeDelta curr_time) {
-  base::AutoLock auto_lock(lock_);
-  ChunkDemuxerStream* selected_stream = nullptr;
-  if (track_id) {
-    auto it = track_id_to_demux_stream_map_.find(*track_id);
-    if (it != track_id_to_demux_stream_map_.end()) {
-      selected_stream = it->second;
-      DCHECK(selected_stream);
-      DCHECK_EQ(DemuxerStream::VIDEO, selected_stream->type());
-    }
-  }
-
-  // First disable all streams that need to be disabled and then enable the
-  // stream that needs to be enabled (if any).
-  for (const auto& stream : video_streams_) {
-    if (stream.get() != selected_stream) {
-      DVLOG(1) << __func__ << ": disabling stream " << stream.get();
-      DCHECK_EQ(DemuxerStream::VIDEO, stream->type());
-      stream->SetEnabled(false, curr_time);
-    }
-  }
-  if (selected_stream) {
-    DVLOG(1) << __func__ << ": enabling stream " << selected_stream;
-    selected_stream->SetEnabled(true, curr_time);
-  }
+    const std::vector<MediaTrack::Id>& track_ids,
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::VIDEO,
+                            std::move(change_completed_cb));
 }
 
 void ChunkDemuxer::OnMemoryPressure(
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index d3fa05c..9253d2d5 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -123,8 +123,6 @@
   bool IsEnabled() const;
   void SetEnabled(bool enabled, base::TimeDelta timestamp);
 
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb);
-
   // Returns the text track configuration.  It is an error to call this method
   // if type() != TEXT.
   TextTrackConfig text_track_config();
@@ -171,7 +169,6 @@
   ReadCB read_cb_;
   bool partial_append_window_trimming_enabled_;
   bool is_enabled_;
-  StreamStatusChangeCB stream_status_change_cb_;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
 };
@@ -210,7 +207,6 @@
   void Seek(base::TimeDelta time, const PipelineStatusCB& cb) override;
   base::Time GetTimelineOffset() const override;
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
   base::TimeDelta GetStartTime() const override;
   int64_t GetMemoryUsage() const override;
   void AbortPendingReads() override;
@@ -255,11 +251,12 @@
   base::TimeDelta GetHighestPresentationTimestamp(const std::string& id) const;
 
   void OnEnabledAudioTracksChanged(const std::vector<MediaTrack::Id>& track_ids,
-                                   base::TimeDelta curr_time) override;
-  // |track_id| either contains the selected video track id or is null,
-  // indicating that all video tracks are deselected/disabled.
-  void OnSelectedVideoTrackChanged(base::Optional<MediaTrack::Id> track_id,
-                                   base::TimeDelta curr_time) override;
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
+
+  void OnSelectedVideoTrackChanged(const std::vector<MediaTrack::Id>& track_ids,
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
 
   // Appends media data to the source buffer associated with |id|, applying
   // and possibly updating |*timestamp_offset| during coded frame processing.
@@ -357,6 +354,12 @@
     SHUTDOWN,
   };
 
+  // Helper for vide and audio track changing.
+  void FindAndEnableProperTracks(const std::vector<MediaTrack::Id>& track_ids,
+                                 base::TimeDelta curr_time,
+                                 DemuxerStream::Type track_type,
+                                 TrackChangeCB change_completed_cb);
+
   void ChangeState_Locked(State new_state);
 
   // Reports an error and puts the demuxer in a state where it won't accept more
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index 8c340ef..53adadf 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -4773,30 +4773,49 @@
   CheckExpectedBuffers(video_stream, "71K 81");
 }
 
-void OnStreamStatusChanged(base::WaitableEvent* event,
-                           DemuxerStream* stream,
-                           bool enabled,
-                           base::TimeDelta) {
-  event->Signal();
+namespace {
+void QuitLoop(base::Closure quit_closure,
+              DemuxerStream::Type type,
+              const std::vector<DemuxerStream*>& streams) {
+  quit_closure.Run();
 }
 
-void CheckStreamStatusNotifications(MediaResource* media_resource,
-                                    ChunkDemuxerStream* stream) {
+void DisableAndEnableDemuxerTracks(
+    ChunkDemuxer* demuxer,
+    base::test::ScopedTaskEnvironment* scoped_task_environment) {
   base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                             base::WaitableEvent::InitialState::NOT_SIGNALED);
+  std::vector<MediaTrack::Id> audio_tracks;
+  std::vector<MediaTrack::Id> video_tracks;
 
-  ASSERT_TRUE(stream->IsEnabled());
-  media_resource->SetStreamStatusChangeCB(
-      base::Bind(&OnStreamStatusChanged, base::Unretained(&event)));
+  base::RunLoop disable_video;
+  demuxer->OnSelectedVideoTrackChanged(
+      video_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(disable_video.QuitClosure())));
+  disable_video.Run();
 
-  stream->SetEnabled(false, base::TimeDelta());
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(event.IsSignaled());
+  base::RunLoop disable_audio;
+  demuxer->OnEnabledAudioTracksChanged(
+      audio_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(disable_audio.QuitClosure())));
+  disable_audio.Run();
 
-  event.Reset();
-  stream->SetEnabled(true, base::TimeDelta());
-  base::RunLoop().RunUntilIdle();
-  ASSERT_TRUE(event.IsSignaled());
+  base::RunLoop enable_video;
+  video_tracks.push_back(MediaTrack::Id("1"));
+  demuxer->OnSelectedVideoTrackChanged(
+      video_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(enable_video.QuitClosure())));
+  enable_video.Run();
+
+  base::RunLoop enable_audio;
+  audio_tracks.push_back(MediaTrack::Id("2"));
+  demuxer->OnEnabledAudioTracksChanged(
+      audio_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(enable_audio.QuitClosure())));
+  enable_audio.Run();
+
+  scoped_task_environment->RunUntilIdle();
+}
 }
 
 TEST_P(ChunkDemuxerTest, StreamStatusNotifications) {
@@ -4809,17 +4828,16 @@
   EXPECT_NE(nullptr, video_stream);
 
   // Verify stream status changes without pending read.
-  CheckStreamStatusNotifications(demuxer_.get(), audio_stream);
-  CheckStreamStatusNotifications(demuxer_.get(), video_stream);
+  DisableAndEnableDemuxerTracks(demuxer_.get(), &scoped_task_environment_);
 
   // Verify stream status changes with pending read.
   bool read_done = false;
   audio_stream->Read(base::Bind(&OnReadDone_EOSExpected, &read_done));
-  CheckStreamStatusNotifications(demuxer_.get(), audio_stream);
+  DisableAndEnableDemuxerTracks(demuxer_.get(), &scoped_task_environment_);
   EXPECT_TRUE(read_done);
   read_done = false;
   video_stream->Read(base::Bind(&OnReadDone_EOSExpected, &read_done));
-  CheckStreamStatusNotifications(demuxer_.get(), video_stream);
+  DisableAndEnableDemuxerTracks(demuxer_.get(), &scoped_task_environment_);
   EXPECT_TRUE(read_done);
 }
 
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 3dde789..70f7a23 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -798,14 +798,6 @@
     DVLOG(1) << "Read from disabled stream, returning EOS";
     base::ResetAndReturn(&read_cb_).Run(kOk, DecoderBuffer::CreateEOSBuffer());
   }
-  if (!stream_status_change_cb_.is_null())
-    stream_status_change_cb_.Run(this, is_enabled_, timestamp);
-}
-
-void FFmpegDemuxerStream::SetStreamStatusChangeCB(
-    const StreamStatusChangeCB& cb) {
-  DCHECK(!cb.is_null());
-  stream_status_change_cb_ = cb;
 }
 
 void FFmpegDemuxerStream::SetLiveness(Liveness liveness) {
@@ -1103,13 +1095,6 @@
   return result;
 }
 
-void FFmpegDemuxer::SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) {
-  for (const auto& stream : streams_) {
-    if (stream)
-      stream->SetStreamStatusChangeCB(cb);
-  }
-}
-
 FFmpegDemuxerStream* FFmpegDemuxer::GetFirstEnabledFFmpegStream(
     DemuxerStream::Type type) const {
   for (const auto& stream : streams_) {
@@ -1737,9 +1722,11 @@
   base::ResetAndReturn(&pending_seek_cb_).Run(PIPELINE_OK);
 }
 
-void FFmpegDemuxer::OnEnabledAudioTracksChanged(
+void FFmpegDemuxer::FindAndEnableProperTracks(
     const std::vector<MediaTrack::Id>& track_ids,
-    base::TimeDelta curr_time) {
+    base::TimeDelta curr_time,
+    DemuxerStream::Type track_type,
+    TrackChangeCB change_completed_cb) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
   std::set<FFmpegDemuxerStream*> enabled_streams;
@@ -1748,7 +1735,7 @@
     if (it == track_id_to_demux_stream_map_.end())
       continue;
     FFmpegDemuxerStream* stream = it->second;
-    DCHECK_EQ(DemuxerStream::AUDIO, stream->type());
+    DCHECK_EQ(track_type, stream->type());
     // TODO(servolk): Remove after multiple enabled audio tracks are supported
     // by the media::RendererImpl.
     if (!enabled_streams.empty()) {
@@ -1757,52 +1744,38 @@
       continue;
     }
     enabled_streams.insert(stream);
+    stream->SetEnabled(true, curr_time);
   }
 
   // First disable all streams that need to be disabled and then enable streams
   // that are enabled.
   for (const auto& stream : streams_) {
-    if (stream && stream->type() == DemuxerStream::AUDIO &&
+    if (stream && stream->type() == track_type &&
         enabled_streams.find(stream.get()) == enabled_streams.end()) {
       DVLOG(1) << __func__ << ": disabling stream " << stream.get();
       stream->SetEnabled(false, curr_time);
     }
   }
-  for (auto* stream : enabled_streams) {
-    DCHECK(stream);
-    DVLOG(1) << __func__ << ": enabling stream " << stream;
-    stream->SetEnabled(true, curr_time);
-  }
+
+  std::vector<DemuxerStream*> streams(enabled_streams.begin(),
+                                      enabled_streams.end());
+  std::move(change_completed_cb).Run(track_type, streams);
+}
+
+void FFmpegDemuxer::OnEnabledAudioTracksChanged(
+    const std::vector<MediaTrack::Id>& track_ids,
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::AUDIO,
+                            std::move(change_completed_cb));
 }
 
 void FFmpegDemuxer::OnSelectedVideoTrackChanged(
-    base::Optional<MediaTrack::Id> track_id,
-    base::TimeDelta curr_time) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-
-  FFmpegDemuxerStream* selected_stream = nullptr;
-  if (track_id) {
-    auto it = track_id_to_demux_stream_map_.find(*track_id);
-    if (it != track_id_to_demux_stream_map_.end()) {
-      selected_stream = it->second;
-      DCHECK(selected_stream);
-      DCHECK_EQ(DemuxerStream::VIDEO, selected_stream->type());
-    }
-  }
-
-  // First disable all streams that need to be disabled and then enable the
-  // stream that needs to be enabled (if any).
-  for (const auto& stream : streams_) {
-    if (stream && stream->type() == DemuxerStream::VIDEO &&
-        stream.get() != selected_stream) {
-      DVLOG(1) << __func__ << ": disabling stream " << stream.get();
-      stream->SetEnabled(false, curr_time);
-    }
-  }
-  if (selected_stream) {
-    DVLOG(1) << __func__ << ": enabling stream " << selected_stream;
-    selected_stream->SetEnabled(true, curr_time);
-  }
+    const std::vector<MediaTrack::Id>& track_ids,
+    base::TimeDelta curr_time,
+    TrackChangeCB change_completed_cb) {
+  FindAndEnableProperTracks(track_ids, curr_time, DemuxerStream::VIDEO,
+                            std::move(change_completed_cb));
 }
 
 void FFmpegDemuxer::ReadFrameIfNeeded() {
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index a847f6a..425958d0 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -119,8 +119,6 @@
   bool IsEnabled() const;
   void SetEnabled(bool enabled, base::TimeDelta timestamp);
 
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb);
-
   void SetLiveness(Liveness liveness);
 
   // Returns the range of buffered data in this stream.
@@ -190,7 +188,6 @@
 
   DecoderBufferQueue buffer_queue_;
   ReadCB read_cb_;
-  StreamStatusChangeCB stream_status_change_cb_;
 
 #if BUILDFLAG(USE_PROPRIETARY_CODECS)
   std::unique_ptr<FFmpegBitstreamConverter> bitstream_converter_;
@@ -226,7 +223,6 @@
   void Seek(base::TimeDelta time, const PipelineStatusCB& cb) override;
   base::Time GetTimelineOffset() const override;
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
   base::TimeDelta GetStartTime() const override;
   int64_t GetMemoryUsage() const override;
 
@@ -244,11 +240,12 @@
   void NotifyDemuxerError(PipelineStatus error);
 
   void OnEnabledAudioTracksChanged(const std::vector<MediaTrack::Id>& track_ids,
-                                   base::TimeDelta curr_time) override;
-  // |track_id| either contains the selected video track id or is null,
-  // indicating that all video tracks are deselected/disabled.
-  void OnSelectedVideoTrackChanged(base::Optional<MediaTrack::Id> track_id,
-                                   base::TimeDelta curr_time) override;
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
+
+  void OnSelectedVideoTrackChanged(const std::vector<MediaTrack::Id>& track_ids,
+                                   base::TimeDelta curr_time,
+                                   TrackChangeCB change_completed_cb) override;
 
   // The lowest demuxed timestamp.  If negative, DemuxerStreams must use this to
   // adjust packet timestamps such that external clients see a zero-based
@@ -268,6 +265,12 @@
   // To allow tests access to privates.
   friend class FFmpegDemuxerTest;
 
+  // Helper for vide and audio track changing.
+  void FindAndEnableProperTracks(const std::vector<MediaTrack::Id>& track_ids,
+                                 base::TimeDelta curr_time,
+                                 DemuxerStream::Type track_type,
+                                 TrackChangeCB change_completed_cb);
+
   // FFmpeg callbacks during initialization.
   void OnOpenContextDone(const PipelineStatusCB& status_cb, bool result);
   void OnFindStreamInfoDone(const PipelineStatusCB& status_cb, int result);
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index f10ff9d..905f356 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -77,42 +77,6 @@
                std::string(stream_type) + " track");
 }
 
-namespace {
-void OnStreamStatusChanged(base::WaitableEvent* event,
-                           DemuxerStream* stream,
-                           bool enabled,
-                           base::TimeDelta) {
-  event->Signal();
-}
-
-void CheckStreamStatusNotifications(
-    MediaResource* media_resource,
-    FFmpegDemuxerStream* stream,
-    base::test::ScopedTaskEnvironment* scoped_task_environment) {
-  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
-                            base::WaitableEvent::InitialState::NOT_SIGNALED);
-
-  ASSERT_TRUE(stream->IsEnabled());
-  media_resource->SetStreamStatusChangeCB(
-      base::Bind(&OnStreamStatusChanged, base::Unretained(&event)));
-
-  stream->SetEnabled(false, base::TimeDelta());
-  scoped_task_environment->RunUntilIdle();
-  ASSERT_TRUE(event.IsSignaled());
-
-  event.Reset();
-  stream->SetEnabled(true, base::TimeDelta());
-  scoped_task_environment->RunUntilIdle();
-  ASSERT_TRUE(event.IsSignaled());
-}
-
-void OnReadDone_ExpectEos(DemuxerStream::Status status,
-                          scoped_refptr<DecoderBuffer> buffer) {
-  EXPECT_EQ(status, DemuxerStream::kOk);
-  EXPECT_TRUE(buffer->end_of_stream());
-}
-}
-
 const uint8_t kEncryptedMediaInitData[] = {
     0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
     0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
@@ -1806,6 +1770,57 @@
   EXPECT_EQ(astream, preferred_seeking_stream(base::TimeDelta()));
 }
 
+namespace {
+void QuitLoop(base::Closure quit_closure,
+              DemuxerStream::Type type,
+              const std::vector<DemuxerStream*>& streams) {
+  quit_closure.Run();
+}
+
+void DisableAndEnableDemuxerTracks(
+    FFmpegDemuxer* demuxer,
+    base::test::ScopedTaskEnvironment* scoped_task_environment) {
+  base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  std::vector<MediaTrack::Id> audio_tracks;
+  std::vector<MediaTrack::Id> video_tracks;
+
+  base::RunLoop disable_video;
+  demuxer->OnSelectedVideoTrackChanged(
+      video_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(disable_video.QuitClosure())));
+  disable_video.Run();
+
+  base::RunLoop disable_audio;
+  demuxer->OnEnabledAudioTracksChanged(
+      audio_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(disable_audio.QuitClosure())));
+  disable_audio.Run();
+
+  base::RunLoop enable_video;
+  video_tracks.push_back(MediaTrack::Id("1"));
+  demuxer->OnSelectedVideoTrackChanged(
+      video_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(enable_video.QuitClosure())));
+  enable_video.Run();
+
+  base::RunLoop enable_audio;
+  audio_tracks.push_back(MediaTrack::Id("2"));
+  demuxer->OnEnabledAudioTracksChanged(
+      audio_tracks, base::TimeDelta(),
+      base::BindOnce(QuitLoop, base::Passed(enable_audio.QuitClosure())));
+  enable_audio.Run();
+
+  scoped_task_environment->RunUntilIdle();
+}
+
+void OnReadDoneExpectEos(DemuxerStream::Status status,
+                         const scoped_refptr<DecoderBuffer> buffer) {
+  EXPECT_EQ(status, DemuxerStream::kOk);
+  EXPECT_TRUE(buffer->end_of_stream());
+}
+}  // namespace
+
 TEST_F(FFmpegDemuxerTest, StreamStatusNotifications) {
   CreateDemuxer("bear-320x240.webm");
   InitializeDemuxer();
@@ -1817,23 +1832,19 @@
   EXPECT_NE(nullptr, video_stream);
 
   // Verify stream status notifications delivery without pending read first.
-  CheckStreamStatusNotifications(demuxer_.get(), audio_stream,
-                                 &scoped_task_environment_);
-  CheckStreamStatusNotifications(demuxer_.get(), video_stream,
-                                 &scoped_task_environment_);
+  DisableAndEnableDemuxerTracks(demuxer_.get(), &scoped_task_environment_);
 
   // Verify that stream notifications are delivered properly when stream status
   // changes with a pending read. Call FlushBuffers before reading, to ensure
   // there is no buffers ready to be returned by the Read right away, thus
   // ensuring that status changes occur while an async read is pending.
+
   audio_stream->FlushBuffers();
-  audio_stream->Read(base::Bind(&media::OnReadDone_ExpectEos));
-  CheckStreamStatusNotifications(demuxer_.get(), audio_stream,
-                                 &scoped_task_environment_);
   video_stream->FlushBuffers();
-  video_stream->Read(base::Bind(&media::OnReadDone_ExpectEos));
-  CheckStreamStatusNotifications(demuxer_.get(), video_stream,
-                                 &scoped_task_environment_);
+  audio_stream->Read(base::Bind(&OnReadDoneExpectEos));
+  video_stream->Read(base::Bind(&OnReadDoneExpectEos));
+
+  DisableAndEnableDemuxerTracks(demuxer_.get(), &scoped_task_environment_);
 }
 
 TEST_F(FFmpegDemuxerTest, MultitrackMemoryUsage) {
diff --git a/media/filters/pipeline_controller.cc b/media/filters/pipeline_controller.cc
index b4cb152..706b581 100644
--- a/media/filters/pipeline_controller.cc
+++ b/media/filters/pipeline_controller.cc
@@ -215,7 +215,9 @@
   }
 
   // If we have pending operations, and a seek is ongoing, abort it.
-  if ((pending_seek_ || pending_suspend_) && waiting_for_seek_) {
+  if ((pending_seek_ || pending_suspend_ || pending_audio_track_change_ ||
+       pending_video_track_change_) &&
+      waiting_for_seek_) {
     // If there is no pending seek, return the current seek to pending status.
     if (!pending_seek_) {
       pending_seek_time_ = seek_time_;
@@ -229,6 +231,34 @@
     return;
   }
 
+  // We can only switch tracks if we are not in a transitioning state already.
+  if ((pending_audio_track_change_ || pending_video_track_change_) &&
+      (state_ == State::PLAYING || state_ == State::SUSPENDED)) {
+    State old_state = state_;
+    state_ = State::SWITCHING_TRACKS;
+
+    // Attempt to do a track change _before_ attempting a seek operation,
+    // otherwise the seek will apply to the old tracks instead of the new
+    // one(s). Also attempt audio before video.
+    if (pending_audio_track_change_) {
+      pending_audio_track_change_ = false;
+      pipeline_->OnEnabledAudioTracksChanged(
+          pending_audio_track_change_ids_,
+          base::BindOnce(&PipelineController::OnTrackChangeComplete,
+                         weak_factory_.GetWeakPtr(), old_state));
+      return;
+    }
+
+    if (pending_video_track_change_) {
+      pending_video_track_change_ = false;
+      pipeline_->OnSelectedVideoTrackChanged(
+          pending_video_track_change_id_,
+          base::BindOnce(&PipelineController::OnTrackChangeComplete,
+                         weak_factory_.GetWeakPtr(), old_state));
+      return;
+    }
+  }
+
   // Ordinary seeking.
   if (pending_seek_ && state_ == State::PLAYING) {
     seek_time_ = pending_seek_time_;
@@ -275,6 +305,8 @@
   pending_seek_ = false;
   pending_suspend_ = false;
   pending_resume_ = false;
+  pending_audio_track_change_ = false;
+  pending_video_track_change_ = false;
   state_ = State::STOPPED;
 
   pipeline_->Stop();
@@ -326,13 +358,37 @@
 }
 
 void PipelineController::OnEnabledAudioTracksChanged(
-    const std::vector<MediaTrack::Id>& enabledTrackIds) {
-  pipeline_->OnEnabledAudioTracksChanged(enabledTrackIds);
+    const std::vector<MediaTrack::Id>& enabled_track_ids) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  pending_audio_track_change_ = true;
+  pending_audio_track_change_ids_ = enabled_track_ids;
+
+  Dispatch();
 }
 
 void PipelineController::OnSelectedVideoTrackChanged(
     base::Optional<MediaTrack::Id> selected_track_id) {
-  pipeline_->OnSelectedVideoTrackChanged(selected_track_id);
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  pending_video_track_change_ = true;
+  pending_video_track_change_id_ = selected_track_id;
+
+  Dispatch();
+}
+
+void PipelineController::FireOnTrackChangeCompleteForTesting(State set_to) {
+  OnTrackChangeComplete(set_to);
+}
+
+void PipelineController::OnTrackChangeComplete(State previous_state) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  if (state_ == State::SWITCHING_TRACKS)
+    state_ = previous_state;
+
+  // Other track changed or seek/suspend/resume, etc may be waiting.
+  Dispatch();
 }
 
 }  // namespace media
diff --git a/media/filters/pipeline_controller.h b/media/filters/pipeline_controller.h
index ea2adb8..bf80dbb 100644
--- a/media/filters/pipeline_controller.h
+++ b/media/filters/pipeline_controller.h
@@ -35,6 +35,7 @@
     PLAYING,
     PLAYING_OR_SUSPENDED,
     SEEKING,
+    SWITCHING_TRACKS,
     SUSPENDING,
     SUSPENDED,
     RESUMING,
@@ -130,10 +131,14 @@
   PipelineStatistics GetStatistics() const;
   void SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb);
   void OnEnabledAudioTracksChanged(
-      const std::vector<MediaTrack::Id>& enabledTrackIds);
+      const std::vector<MediaTrack::Id>& enabled_track_ids);
   void OnSelectedVideoTrackChanged(
       base::Optional<MediaTrack::Id> selected_track_id);
 
+  // Used to fire the OnTrackChangeComplete function which is captured in a
+  // OnceCallback, and doesn't play nicely with gmock.
+  void FireOnTrackChangeCompleteForTesting(State set_to);
+
  private:
   // Attempts to make progress from the current state to the target state.
   void Dispatch();
@@ -141,6 +146,8 @@
   // PipelineStaus callback that also carries the target state.
   void OnPipelineStatus(State state, PipelineStatus pipeline_status);
 
+  void OnTrackChangeComplete(State previous_state);
+
   // The Pipeline we are managing state for.
   std::unique_ptr<Pipeline> pipeline_;
 
@@ -189,12 +196,22 @@
   // The target time of the active seek; valid while SEEKING or RESUMING.
   base::TimeDelta seek_time_;
 
-  // Target state which we will work to achieve. |pending_seek_time_| is only
-  // valid when |pending_seek_| is true.
+  // Target state which we will work to achieve.
   bool pending_seek_ = false;
-  base::TimeDelta pending_seek_time_;
   bool pending_suspend_ = false;
   bool pending_resume_ = false;
+  bool pending_audio_track_change_ = false;
+  bool pending_video_track_change_ = false;
+
+  // |pending_seek_time_| is only valid when |pending_seek_| is true.
+  // |pending_track_change_type_| is only valid when |pending_track_change_|.
+  // |pending_audio_track_change_ids_| is only valid when
+  //   |pending_audio_track_change_|.
+  // |pending_video_track_change_id_| is only valid when
+  //   |pending_video_track_change_|.
+  base::TimeDelta pending_seek_time_;
+  std::vector<MediaTrack::Id> pending_audio_track_change_ids_;
+  base::Optional<MediaTrack::Id> pending_video_track_change_id_;
 
   // Set to true during Start(). Indicates that |seeked_cb_| must be fired once
   // we've completed startup.
diff --git a/media/filters/pipeline_controller_unittest.cc b/media/filters/pipeline_controller_unittest.cc
index bf72dc1d..1d033fe 100644
--- a/media/filters/pipeline_controller_unittest.cc
+++ b/media/filters/pipeline_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/time/time.h"
+#include "media/base/gmock_callback_support.h"
 #include "media/base/mock_filters.h"
 #include "media/base/pipeline.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -392,4 +393,72 @@
   Complete(seek_cb_1);
 }
 
+TEST_F(PipelineControllerTest, VideoTrackChangeWhileSuspending) {
+  Complete(StartPipeline());
+  EXPECT_CALL(*pipeline_, Suspend(_));
+  EXPECT_CALL(*pipeline_, MockOnSelectedVideoTrackChanged(_, _)).Times(0);
+  pipeline_controller_.Suspend();
+  pipeline_controller_.OnSelectedVideoTrackChanged({});
+}
+
+TEST_F(PipelineControllerTest, AudioTrackChangeWhileSuspending) {
+  Complete(StartPipeline());
+  EXPECT_CALL(*pipeline_, Suspend(_));
+  EXPECT_CALL(*pipeline_, MockOnEnabledAudioTracksChanged(_, _)).Times(0);
+  pipeline_controller_.Suspend();
+  pipeline_controller_.OnEnabledAudioTracksChanged({});
+}
+
+TEST_F(PipelineControllerTest, AudioTrackChangeDuringVideoTrackChange) {
+  Complete(StartPipeline());
+
+  EXPECT_CALL(*pipeline_, MockOnSelectedVideoTrackChanged(_, _));
+  pipeline_controller_.OnSelectedVideoTrackChanged({});
+  pipeline_controller_.OnEnabledAudioTracksChanged({});
+  EXPECT_CALL(*pipeline_, MockOnEnabledAudioTracksChanged(_, _));
+
+  pipeline_controller_.FireOnTrackChangeCompleteForTesting(
+      PipelineController::State::PLAYING);
+
+  pipeline_controller_.FireOnTrackChangeCompleteForTesting(
+      PipelineController::State::PLAYING);
+}
+
+TEST_F(PipelineControllerTest, SuspendDuringVideoTrackChange) {
+  Complete(StartPipeline());
+  EXPECT_CALL(*pipeline_, MockOnSelectedVideoTrackChanged(_, _));
+  was_resumed_ = false;
+  pipeline_controller_.OnSelectedVideoTrackChanged({});
+  pipeline_controller_.Suspend();
+
+  base::RunLoop loop;
+  EXPECT_CALL(*pipeline_, Suspend(_))
+      .WillOnce(RunOnceClosure(loop.QuitClosure()));
+
+  pipeline_controller_.FireOnTrackChangeCompleteForTesting(
+      PipelineController::State::PLAYING);
+
+  loop.Run();
+  EXPECT_FALSE(was_resumed_);
+}
+
+TEST_F(PipelineControllerTest, SuspendDuringAudioTrackChange) {
+  Complete(StartPipeline());
+  EXPECT_CALL(*pipeline_, MockOnEnabledAudioTracksChanged(_, _));
+  was_resumed_ = false;
+
+  pipeline_controller_.OnEnabledAudioTracksChanged({});
+  pipeline_controller_.Suspend();
+
+  base::RunLoop loop;
+  EXPECT_CALL(*pipeline_, Suspend(_))
+      .WillOnce(RunOnceClosure(loop.QuitClosure()));
+
+  pipeline_controller_.FireOnTrackChangeCompleteForTesting(
+      PipelineController::State::PLAYING);
+
+  loop.Run();
+  EXPECT_FALSE(was_resumed_);
+}
+
 }  // namespace media
diff --git a/media/mojo/services/media_resource_shim.cc b/media/mojo/services/media_resource_shim.cc
index ace3f01..184f2a3 100644
--- a/media/mojo/services/media_resource_shim.cc
+++ b/media/mojo/services/media_resource_shim.cc
@@ -39,10 +39,6 @@
   return result;
 }
 
-void MediaResourceShim::SetStreamStatusChangeCB(
-    const StreamStatusChangeCB& cb) {
-}
-
 void MediaResourceShim::OnStreamReady() {
   if (++streams_ready_ == streams_.size())
     base::ResetAndReturn(&demuxer_ready_cb_).Run();
diff --git a/media/mojo/services/media_resource_shim.h b/media/mojo/services/media_resource_shim.h
index fe54ef7..fab9eecd 100644
--- a/media/mojo/services/media_resource_shim.h
+++ b/media/mojo/services/media_resource_shim.h
@@ -27,7 +27,6 @@
 
   // MediaResource interface.
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
 
  private:
   // Called as each mojom::DemuxerStream becomes ready.  Once all streams
diff --git a/media/remoting/courier_renderer.h b/media/remoting/courier_renderer.h
index df01dfd4..cef22a0 100644
--- a/media/remoting/courier_renderer.h
+++ b/media/remoting/courier_renderer.h
@@ -14,7 +14,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "base/timer/timer.h"
diff --git a/media/remoting/end2end_test_renderer.cc b/media/remoting/end2end_test_renderer.cc
index 87065b7..b227ec9 100644
--- a/media/remoting/end2end_test_renderer.cc
+++ b/media/remoting/end2end_test_renderer.cc
@@ -218,5 +218,19 @@
   controller_->OnMessageFromSink(*message);
 }
 
+void End2EndTestRenderer::OnSelectedVideoTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  courier_renderer_->OnSelectedVideoTracksChanged(
+      enabled_tracks, std::move(change_completed_cb));
+}
+
+void End2EndTestRenderer::OnEnabledAudioTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  courier_renderer_->OnEnabledAudioTracksChanged(
+      enabled_tracks, std::move(change_completed_cb));
+}
+
 }  // namespace remoting
 }  // namespace media
diff --git a/media/remoting/end2end_test_renderer.h b/media/remoting/end2end_test_renderer.h
index 8fa9f6c..da119fde 100644
--- a/media/remoting/end2end_test_renderer.h
+++ b/media/remoting/end2end_test_renderer.h
@@ -37,6 +37,14 @@
   void SetVolume(float volume) override;
   base::TimeDelta GetMediaTime() override;
 
+  void OnSelectedVideoTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb) override;
+
+  void OnEnabledAudioTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb) override;
+
  private:
   // Called to send RPC messages to |receiver_|.
   void SendMessageToSink(const std::vector<uint8_t>& message);
diff --git a/media/remoting/fake_media_resource.cc b/media/remoting/fake_media_resource.cc
index 14e21e1..e1789f8 100644
--- a/media/remoting/fake_media_resource.cc
+++ b/media/remoting/fake_media_resource.cc
@@ -105,10 +105,5 @@
   return streams;
 }
 
-void FakeMediaResource::SetStreamStatusChangeCB(
-    const StreamStatusChangeCB& cb) {
-  NOTIMPLEMENTED();
-}
-
 }  // namespace remoting
 }  // namespace media
diff --git a/media/remoting/fake_media_resource.h b/media/remoting/fake_media_resource.h
index 3c6772e..5d4f9795 100644
--- a/media/remoting/fake_media_resource.h
+++ b/media/remoting/fake_media_resource.h
@@ -51,7 +51,6 @@
 
   // MediaResource implementation.
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override;
 
  private:
   std::unique_ptr<FakeDemuxerStream> demuxer_stream_;
diff --git a/media/remoting/stream_provider.h b/media/remoting/stream_provider.h
index a794ed40..8dc5003 100644
--- a/media/remoting/stream_provider.h
+++ b/media/remoting/stream_provider.h
@@ -26,7 +26,6 @@
 
   // MediaResource implemenation.
   std::vector<DemuxerStream*> GetAllStreams() override;
-  void SetStreamStatusChangeCB(const StreamStatusChangeCB& cb) override {}
 
   void Initialize(int remote_audio_handle,
                   int remote_video_handle,
diff --git a/media/renderers/renderer_impl.cc b/media/renderers/renderer_impl.cc
index 88ff00ca..5233ddf 100644
--- a/media/renderers/renderer_impl.cc
+++ b/media/renderers/renderer_impl.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
@@ -31,8 +32,6 @@
 // See |video_underflow_threshold_|.
 static const int kDefaultVideoUnderflowThresholdMs = 3000;
 
-static const int kAudioRestartUnderflowThresholdMs = 2000;
-
 class RendererImpl::RendererClientInternal final : public RendererClient {
  public:
   RendererClientInternal(DemuxerStream::Type type, RendererImpl* renderer)
@@ -95,11 +94,15 @@
       video_buffering_state_(BUFFERING_HAVE_NOTHING),
       audio_ended_(false),
       video_ended_(false),
+      audio_playing_(false),
+      video_playing_(false),
       cdm_context_(nullptr),
       underflow_disabled_for_testing_(false),
       clockless_video_playback_enabled_for_testing_(false),
       video_underflow_threshold_(
           base::TimeDelta::FromMilliseconds(kDefaultVideoUnderflowThresholdMs)),
+      pending_audio_track_change_(false),
+      pending_video_track_change_(false),
       weak_factory_(this) {
   weak_this_ = weak_factory_.GetWeakPtr();
   DVLOG(1) << __func__;
@@ -187,6 +190,7 @@
   DVLOG(1) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(flush_cb_.is_null());
+  DCHECK(!(pending_audio_track_change_ || pending_video_track_change_));
 
   if (state_ == STATE_FLUSHED) {
     task_runner_->PostTask(FROM_HERE, flush_cb);
@@ -201,24 +205,8 @@
   flush_cb_ = flush_cb;
   state_ = STATE_FLUSHING;
 
-  // If we are currently handling a media stream status change, then postpone
-  // Flush until after that's done (because stream status changes also flush
-  // audio_renderer_/video_renderer_ and they need to be restarted before they
-  // can be flushed again). OnStreamRestartCompleted will resume Flush
-  // processing after audio/video restart has completed and there are no other
-  // pending stream status changes.
-  // TODO(dalecurtis, servolk) We should abort the StartPlaying call post Flush
-  // to avoid unnecessary work.
-  if ((restarting_audio_ || restarting_video_) &&
-      pending_flush_for_stream_change_) {
-    pending_actions_.push_back(
-        base::Bind(&RendererImpl::FlushInternal, weak_this_));
-    return;
-  }
-
   // If a stream restart is pending, this Flush() will complete it. Upon flush
   // completion any pending actions will be executed as well.
-
   FlushInternal();
 }
 
@@ -234,10 +222,14 @@
   time_source_->SetMediaTime(time);
 
   state_ = STATE_PLAYING;
-  if (audio_renderer_)
+  if (audio_renderer_) {
+    audio_playing_ = true;
     audio_renderer_->StartPlaying();
-  if (video_renderer_)
+  }
+  if (video_renderer_) {
+    video_playing_ = true;
     video_renderer_->StartPlayingFrom(time);
+  }
 }
 
 void RendererImpl::SetPlaybackRate(double playback_rate) {
@@ -274,7 +266,7 @@
   // threads.
   {
     base::AutoLock lock(restarting_audio_lock_);
-    if (restarting_audio_) {
+    if (pending_audio_track_change_) {
       DCHECK_NE(kNoTimestamp, restarting_audio_time_);
       return restarting_audio_time_;
     }
@@ -358,6 +350,7 @@
   // pick the first enabled stream to preserve the existing behavior.
   DemuxerStream* audio_stream =
       media_resource_->GetFirstStream(DemuxerStream::AUDIO);
+
   if (!audio_stream) {
     audio_renderer_.reset();
     task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
@@ -408,6 +401,7 @@
   // pick the first enabled stream to preserve the existing behavior.
   DemuxerStream* video_stream =
       media_resource_->GetFirstStream(DemuxerStream::VIDEO);
+
   if (!video_stream) {
     video_renderer_.reset();
     task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK));
@@ -444,9 +438,6 @@
     return;
   }
 
-  media_resource_->SetStreamStatusChangeCB(
-      base::Bind(&RendererImpl::OnStreamStatusChanged, weak_this_));
-
   if (audio_renderer_) {
     time_source_ = audio_renderer_->GetTimeSource();
   } else if (!time_source_) {
@@ -473,19 +464,19 @@
   FlushAudioRenderer();
 }
 
+// TODO(tmathmeyer) Combine this functionality with track switching flushing.
 void RendererImpl::FlushAudioRenderer() {
   DVLOG(1) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK_EQ(state_, STATE_FLUSHING);
   DCHECK(!flush_cb_.is_null());
 
-  if (!audio_renderer_) {
+  if (!audio_renderer_ || !audio_playing_) {
     OnAudioRendererFlushDone();
-    return;
+  } else {
+    audio_renderer_->Flush(base::BindRepeating(
+        &RendererImpl::OnAudioRendererFlushDone, weak_this_));
   }
-
-  audio_renderer_->Flush(
-      base::Bind(&RendererImpl::OnAudioRendererFlushDone, weak_this_));
 }
 
 void RendererImpl::OnAudioRendererFlushDone() {
@@ -503,9 +494,10 @@
   // If we had a deferred video renderer underflow prior to the flush, it should
   // have been cleared by the audio renderer changing to BUFFERING_HAVE_NOTHING.
   DCHECK(deferred_video_underflow_cb_.IsCancelled());
-
   DCHECK_EQ(audio_buffering_state_, BUFFERING_HAVE_NOTHING);
   audio_ended_ = false;
+  audio_playing_ = false;
+
   FlushVideoRenderer();
 }
 
@@ -515,13 +507,12 @@
   DCHECK_EQ(state_, STATE_FLUSHING);
   DCHECK(!flush_cb_.is_null());
 
-  if (!video_renderer_) {
+  if (!video_renderer_ || !video_playing_) {
     OnVideoRendererFlushDone();
-    return;
+  } else {
+    video_renderer_->Flush(base::BindRepeating(
+        &RendererImpl::OnVideoRendererFlushDone, weak_this_));
   }
-
-  video_renderer_->Flush(
-      base::Bind(&RendererImpl::OnVideoRendererFlushDone, weak_this_));
 }
 
 void RendererImpl::OnVideoRendererFlushDone() {
@@ -538,84 +529,15 @@
 
   DCHECK_EQ(video_buffering_state_, BUFFERING_HAVE_NOTHING);
   video_ended_ = false;
+  video_playing_ = false;
   state_ = STATE_FLUSHED;
   base::ResetAndReturn(&flush_cb_).Run();
-
-  if (!pending_actions_.empty()) {
-    base::Closure closure = pending_actions_.front();
-    pending_actions_.pop_front();
-    closure.Run();
-  }
 }
 
-void RendererImpl::OnStreamStatusChanged(DemuxerStream* stream,
-                                         bool enabled,
-                                         base::TimeDelta time) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(stream);
-  bool video = (stream->type() == DemuxerStream::VIDEO);
-  DVLOG(1) << __func__ << (video ? " video" : " audio") << " stream=" << stream
-           << " enabled=" << enabled << " time=" << time.InSecondsF();
-
-  if ((state_ != STATE_PLAYING && state_ != STATE_FLUSHING &&
-       state_ != STATE_FLUSHED) ||
-      (audio_ended_ && video_ended_))
-    return;
-
-  if (restarting_audio_ || restarting_video_ || state_ == STATE_FLUSHING) {
-    DVLOG(3) << __func__ << ": postponed stream " << stream
-             << " status change handling.";
-    pending_actions_.push_back(base::Bind(&RendererImpl::OnStreamStatusChanged,
-                                          weak_this_, stream, enabled, time));
-    return;
-  }
-
-  DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED);
-  if (stream->type() == DemuxerStream::VIDEO) {
-    DCHECK(video_renderer_);
-    restarting_video_ = true;
-    base::Closure handle_track_status_cb =
-        base::Bind(stream == current_video_stream_
-                       ? &RendererImpl::RestartVideoRenderer
-                       : &RendererImpl::ReinitializeVideoRenderer,
-                   weak_this_, stream, time);
-    if (state_ == STATE_FLUSHED) {
-      handle_track_status_cb.Run();
-    } else {
-      pending_flush_for_stream_change_ = true;
-      video_renderer_->Flush(handle_track_status_cb);
-    }
-  } else if (stream->type() == DemuxerStream::AUDIO) {
-    DCHECK(audio_renderer_);
-    DCHECK(time_source_);
-    {
-      base::AutoLock lock(restarting_audio_lock_);
-      restarting_audio_time_ = time;
-      restarting_audio_ = true;
-    }
-    base::Closure handle_track_status_cb =
-        base::Bind(stream == current_audio_stream_
-                       ? &RendererImpl::RestartAudioRenderer
-                       : &RendererImpl::ReinitializeAudioRenderer,
-                   weak_this_, stream, time);
-    if (state_ == STATE_FLUSHED) {
-      handle_track_status_cb.Run();
-      return;
-    }
-    // Stop ticking (transition into paused state) in audio renderer before
-    // calling Flush, since after Flush we are going to restart playback by
-    // calling audio renderer StartPlaying which would fail in playing state.
-    if (time_ticking_) {
-      time_ticking_ = false;
-      time_source_->StopTicking();
-    }
-    pending_flush_for_stream_change_ = true;
-    audio_renderer_->Flush(handle_track_status_cb);
-  }
-}
-
-void RendererImpl::ReinitializeAudioRenderer(DemuxerStream* stream,
-                                             base::TimeDelta time) {
+void RendererImpl::ReinitializeAudioRenderer(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure reinitialize_completed_cb) {
   DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK_NE(stream, current_audio_stream_);
@@ -623,25 +545,31 @@
   current_audio_stream_ = stream;
   audio_renderer_->Initialize(
       stream, cdm_context_, audio_renderer_client_.get(),
-      base::Bind(&RendererImpl::OnAudioRendererReinitialized, weak_this_,
-                 stream, time));
+      base::BindRepeating(&RendererImpl::OnAudioRendererReinitialized,
+                          weak_this_, stream, time,
+                          base::Passed(&reinitialize_completed_cb)));
 }
 
-void RendererImpl::OnAudioRendererReinitialized(DemuxerStream* stream,
-                                                base::TimeDelta time,
-                                                PipelineStatus status) {
+void RendererImpl::OnAudioRendererReinitialized(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure reinitialize_completed_cb,
+    PipelineStatus status) {
   DVLOG(2) << __func__ << ": status=" << status;
   DCHECK_EQ(stream, current_audio_stream_);
 
   if (status != PIPELINE_OK) {
+    std::move(reinitialize_completed_cb).Run();
     OnError(status);
     return;
   }
-  RestartAudioRenderer(stream, time);
+  RestartAudioRenderer(stream, time, std::move(reinitialize_completed_cb));
 }
 
-void RendererImpl::ReinitializeVideoRenderer(DemuxerStream* stream,
-                                             base::TimeDelta time) {
+void RendererImpl::ReinitializeVideoRenderer(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure reinitialize_completed_cb) {
   DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK_NE(stream, current_video_stream_);
@@ -650,67 +578,78 @@
   video_renderer_->OnTimeStopped();
   video_renderer_->Initialize(
       stream, cdm_context_, video_renderer_client_.get(),
-      base::Bind(&RendererImpl::GetWallClockTimes, base::Unretained(this)),
-      base::Bind(&RendererImpl::OnVideoRendererReinitialized, weak_this_,
-                 stream, time));
+      base::BindRepeating(&RendererImpl::GetWallClockTimes,
+                          base::Unretained(this)),
+      base::BindRepeating(&RendererImpl::OnVideoRendererReinitialized,
+                          weak_this_, stream, time,
+                          base::Passed(&reinitialize_completed_cb)));
 }
 
-void RendererImpl::OnVideoRendererReinitialized(DemuxerStream* stream,
-                                                base::TimeDelta time,
-                                                PipelineStatus status) {
+void RendererImpl::OnVideoRendererReinitialized(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure reinitialize_completed_cb,
+    PipelineStatus status) {
   DVLOG(2) << __func__ << ": status=" << status;
   DCHECK_EQ(stream, current_video_stream_);
 
   if (status != PIPELINE_OK) {
+    std::move(reinitialize_completed_cb).Run();
     OnError(status);
     return;
   }
-  RestartVideoRenderer(stream, time);
+  RestartVideoRenderer(stream, time, std::move(reinitialize_completed_cb));
 }
 
-void RendererImpl::RestartAudioRenderer(DemuxerStream* stream,
-                                        base::TimeDelta time) {
+void RendererImpl::RestartAudioRenderer(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure restart_completed_cb) {
   DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
   DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED ||
-         state_ == STATE_FLUSHING);
-  DCHECK(time_source_);
   DCHECK(audio_renderer_);
   DCHECK_EQ(stream, current_audio_stream_);
+  DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED ||
+         state_ == STATE_FLUSHING);
 
-  audio_ended_ = false;
   if (state_ == STATE_FLUSHED) {
     // If we are in the FLUSHED state, then we are done. The audio renderer will
     // be restarted by a subsequent RendererImpl::StartPlayingFrom call.
-    OnStreamRestartCompleted();
-  } else {
-    // Stream restart will be completed when the audio renderer decodes enough
-    // data and reports HAVE_ENOUGH to HandleRestartedStreamBufferingChanges.
-    pending_flush_for_stream_change_ = false;
-    audio_renderer_->StartPlaying();
+    std::move(restart_completed_cb).Run();
+    return;
   }
+
+  audio_renderer_->StartPlaying();
+  {
+    base::AutoLock lock(restarting_audio_lock_);
+    audio_playing_ = true;
+    pending_audio_track_change_ = false;
+  }
+  std::move(restart_completed_cb).Run();
 }
 
-void RendererImpl::RestartVideoRenderer(DemuxerStream* stream,
-                                        base::TimeDelta time) {
+void RendererImpl::RestartVideoRenderer(
+    DemuxerStream* stream,
+    base::TimeDelta time,
+    base::OnceClosure restart_completed_cb) {
   DVLOG(2) << __func__ << " stream=" << stream << " time=" << time.InSecondsF();
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(video_renderer_);
+  DCHECK_EQ(stream, current_video_stream_);
   DCHECK(state_ == STATE_PLAYING || state_ == STATE_FLUSHED ||
          state_ == STATE_FLUSHING);
-  DCHECK_EQ(stream, current_video_stream_);
 
-  video_ended_ = false;
   if (state_ == STATE_FLUSHED) {
     // If we are in the FLUSHED state, then we are done. The video renderer will
     // be restarted by a subsequent RendererImpl::StartPlayingFrom call.
-    OnStreamRestartCompleted();
-  } else {
-    // Stream restart will be completed when the video renderer decodes enough
-    // data and reports HAVE_ENOUGH to HandleRestartedStreamBufferingChanges.
-    pending_flush_for_stream_change_ = false;
-    video_renderer_->StartPlayingFrom(time);
+    std::move(restart_completed_cb).Run();
+    return;
   }
+
+  video_renderer_->StartPlayingFrom(time);
+  video_playing_ = true;
+  pending_video_track_change_ = false;
+  std::move(restart_completed_cb).Run();
 }
 
 void RendererImpl::OnStatisticsUpdate(const PipelineStatistics& stats) {
@@ -718,90 +657,6 @@
   client_->OnStatisticsUpdate(stats);
 }
 
-bool RendererImpl::HandleRestartedStreamBufferingChanges(
-    DemuxerStream::Type type,
-    BufferingState new_buffering_state) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  // When restarting playback we want to defer the BUFFERING_HAVE_NOTHING for
-  // the stream being restarted, to allow continuing uninterrupted playback on
-  // the other stream.
-  if (type == DemuxerStream::VIDEO && restarting_video_) {
-    if (new_buffering_state == BUFFERING_HAVE_ENOUGH) {
-      DVLOG(1) << __func__ << " Got BUFFERING_HAVE_ENOUGH for video stream,"
-                              " resuming playback.";
-      task_runner_->PostTask(
-          FROM_HERE,
-          base::Bind(&RendererImpl::OnStreamRestartCompleted, weak_this_));
-      if (state_ == STATE_PLAYING &&
-          !deferred_video_underflow_cb_.IsCancelled()) {
-        // If deferred_video_underflow_cb_ wasn't triggered, then audio should
-        // still be playing, we only need to unpause the video stream.
-        DVLOG(4) << "deferred_video_underflow_cb_.Cancel()";
-        deferred_video_underflow_cb_.Cancel();
-        video_buffering_state_ = new_buffering_state;
-        if (playback_rate_ > 0)
-          video_renderer_->OnTimeProgressing();
-        return true;
-      }
-    }
-    // We don't handle the BUFFERING_HAVE_NOTHING case explicitly here, since
-    // the existing logic for deferring video underflow reporting in
-    // OnBufferingStateChange is exactly what we need. So fall through to the
-    // regular video underflow handling path in OnBufferingStateChange.
-  }
-
-  if (type == DemuxerStream::AUDIO && restarting_audio_) {
-    if (new_buffering_state == BUFFERING_HAVE_NOTHING) {
-      if (deferred_video_underflow_cb_.IsCancelled() &&
-          deferred_audio_restart_underflow_cb_.IsCancelled()) {
-        DVLOG(1) << __func__ << " Deferring BUFFERING_HAVE_NOTHING for "
-                                "audio stream which is being restarted.";
-        audio_buffering_state_ = new_buffering_state;
-        deferred_audio_restart_underflow_cb_.Reset(
-            base::Bind(&RendererImpl::OnBufferingStateChange, weak_this_, type,
-                       new_buffering_state));
-        task_runner_->PostDelayedTask(
-            FROM_HERE, deferred_audio_restart_underflow_cb_.callback(),
-            base::TimeDelta::FromMilliseconds(
-                kAudioRestartUnderflowThresholdMs));
-        return true;
-      }
-      // Cancel the deferred callback and report the underflow immediately.
-      DVLOG(4) << "deferred_audio_restart_underflow_cb_.Cancel()";
-      deferred_audio_restart_underflow_cb_.Cancel();
-    } else if (new_buffering_state == BUFFERING_HAVE_ENOUGH) {
-      DVLOG(1) << __func__ << " Got BUFFERING_HAVE_ENOUGH for audio stream,"
-                              " resuming playback.";
-      deferred_audio_restart_underflow_cb_.Cancel();
-      // Now that we have decoded enough audio, pause playback momentarily to
-      // ensure video renderer is synchronised with audio.
-      PausePlayback();
-      task_runner_->PostTask(
-          FROM_HERE,
-          base::Bind(&RendererImpl::OnStreamRestartCompleted, weak_this_));
-    }
-  }
-  return false;
-}
-
-void RendererImpl::OnStreamRestartCompleted() {
-  DVLOG(3) << __func__ << " restarting_audio_=" << restarting_audio_
-           << " restarting_video_=" << restarting_video_;
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK(restarting_audio_ || restarting_video_);
-  {
-    base::AutoLock lock(restarting_audio_lock_);
-    restarting_audio_ = false;
-    restarting_audio_time_ = kNoTimestamp;
-  }
-  restarting_video_ = false;
-  if (!pending_actions_.empty()) {
-    base::Closure closure = pending_actions_.front();
-    pending_actions_.pop_front();
-    closure.Run();
-  }
-}
-
 void RendererImpl::OnBufferingStateChange(DemuxerStream::Type type,
                                           BufferingState new_buffering_state) {
   DCHECK((type == DemuxerStream::AUDIO) || (type == DemuxerStream::VIDEO));
@@ -816,9 +671,16 @@
 
   bool was_waiting_for_enough_data = WaitingForEnoughData();
 
-  if (restarting_audio_ || restarting_video_) {
-    if (HandleRestartedStreamBufferingChanges(type, new_buffering_state))
+  if (new_buffering_state == BUFFERING_HAVE_NOTHING) {
+    if ((pending_audio_track_change_ && type == DemuxerStream::AUDIO) ||
+        (pending_video_track_change_ && type == DemuxerStream::VIDEO)) {
+      // Don't pass up a nothing event if it was triggered by a track change.
+      // This would cause the renderer to effectively lie about underflow state.
+      // Even though this might cause an immediate video underflow due to
+      // changing an audio track, all playing is paused when audio is disabled.
+      *buffering_state = new_buffering_state;
       return;
+    }
   }
 
   // When audio is present and has enough data, defer video underflow callbacks
@@ -869,9 +731,14 @@
 
   // Renderer prerolled.
   if (was_waiting_for_enough_data && !WaitingForEnoughData()) {
-    StartPlayback();
-    client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
-    return;
+    // Prevent condition where audio or video is sputtering and flipping back
+    // and forth between NOTHING and ENOUGH mixing with a track change, causing
+    // a StartPlayback to be called while the audio renderer is being flushed.
+    if (!pending_audio_track_change_ && !pending_video_track_change_) {
+      StartPlayback();
+      client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
+      return;
+    }
   }
 }
 
@@ -891,7 +758,8 @@
   DCHECK(task_runner_->BelongsToCurrentThread());
   switch (state_) {
     case STATE_PLAYING:
-      DCHECK(PlaybackHasEnded() || WaitingForEnoughData() || restarting_audio_)
+      DCHECK(PlaybackHasEnded() || WaitingForEnoughData() ||
+             pending_audio_track_change_)
           << "Playback should only pause due to ending or underflowing or"
              " when restarting audio stream";
 
@@ -912,11 +780,11 @@
       // An error state may occur at any time.
       break;
   }
-
   if (time_ticking_) {
     time_ticking_ = false;
     time_source_->StopTicking();
   }
+
   if (playback_rate_ > 0 && video_renderer_)
     video_renderer_->OnTimeStopped();
 }
@@ -925,13 +793,17 @@
   DVLOG(1) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK_EQ(state_, STATE_PLAYING);
-  DCHECK(!time_ticking_);
   DCHECK(!WaitingForEnoughData());
 
-  time_ticking_ = true;
-  time_source_->StartTicking();
-  if (playback_rate_ > 0 && video_renderer_)
+  if (!time_ticking_) {
+    time_ticking_ = true;
+    audio_playing_ = true;
+    time_source_->StartTicking();
+  }
+  if (playback_rate_ > 0 && video_renderer_) {
+    video_playing_ = true;
     video_renderer_->OnTimeProgressing();
+  }
 }
 
 void RendererImpl::OnRendererEnded(DemuxerStream::Type type) {
@@ -947,8 +819,8 @@
     audio_ended_ = true;
   } else {
     DCHECK(!video_ended_);
-    video_ended_ = true;
     DCHECK(video_renderer_);
+    video_ended_ = true;
     video_renderer_->OnTimeStopped();
   }
 
@@ -1031,4 +903,92 @@
   client_->OnVideoOpacityChange(opaque);
 }
 
+void RendererImpl::CleanUpTrackChange(base::RepeatingClosure on_finished,
+                                      bool* pending_change,
+                                      bool* ended,
+                                      bool* playing) {
+  {
+    // This lock is required for setting pending_audio_track_change_, and has
+    // no effect when setting pending_video_track_change_.
+    base::AutoLock lock(restarting_audio_lock_);
+    *pending_change = *ended = *playing = false;
+  }
+  std::move(on_finished).Run();
+}
+
+void RendererImpl::OnSelectedVideoTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  DCHECK_LT(enabled_tracks.size(), 2u);
+  DemuxerStream* stream = enabled_tracks.empty() ? nullptr : enabled_tracks[0];
+
+  if (!stream && !video_playing_) {
+    std::move(change_completed_cb).Run();
+    return;
+  }
+
+  // 'fixing' the stream -> restarting if its the same stream,
+  //                        reinitializing if it is different.
+  base::RepeatingClosure fix_stream_cb;
+  if (stream && stream != current_video_stream_) {
+    fix_stream_cb = base::BindRepeating(
+        &RendererImpl::ReinitializeVideoRenderer, weak_this_, stream,
+        GetMediaTime(), base::Passed(&change_completed_cb));
+  } else {
+    fix_stream_cb = base::BindRepeating(
+        &RendererImpl::RestartVideoRenderer, weak_this_, current_video_stream_,
+        GetMediaTime(), base::Passed(&change_completed_cb));
+  }
+
+  pending_video_track_change_ = true;
+  video_renderer_->Flush(base::BindRepeating(
+      &RendererImpl::CleanUpTrackChange, weak_this_,
+      base::Passed(&fix_stream_cb), &pending_video_track_change_, &video_ended_,
+      &video_playing_));
+}
+
+void RendererImpl::OnEnabledAudioTracksChanged(
+    const std::vector<DemuxerStream*>& enabled_tracks,
+    base::OnceClosure change_completed_cb) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  DCHECK_LT(enabled_tracks.size(), 2u);
+  DemuxerStream* stream = enabled_tracks.empty() ? nullptr : enabled_tracks[0];
+
+  if (!stream && !audio_playing_) {
+    std::move(change_completed_cb).Run();
+    return;
+  }
+
+  // 'fixing' the stream -> restarting if its the same stream,
+  //                        reinitializing if it is different.
+  base::RepeatingClosure fix_stream_cb;
+
+  if (stream && stream != current_audio_stream_) {
+    fix_stream_cb = base::BindRepeating(
+        &RendererImpl::ReinitializeAudioRenderer, weak_this_, stream,
+        GetMediaTime(), base::Passed(&change_completed_cb));
+  } else {
+    fix_stream_cb = base::BindRepeating(
+        &RendererImpl::RestartAudioRenderer, weak_this_, current_audio_stream_,
+        GetMediaTime(), base::Passed(&change_completed_cb));
+  }
+
+  {
+    base::AutoLock lock(restarting_audio_lock_);
+    pending_audio_track_change_ = true;
+    restarting_audio_time_ = time_source_->CurrentMediaTime();
+  }
+
+  if (audio_playing_)
+    PausePlayback();
+
+  audio_renderer_->Flush(base::BindRepeating(
+      &RendererImpl::CleanUpTrackChange, weak_this_,
+      base::Passed(&fix_stream_cb), &pending_audio_track_change_, &audio_ended_,
+      &audio_playing_));
+}
+
 }  // namespace media
diff --git a/media/renderers/renderer_impl.h b/media/renderers/renderer_impl.h
index cd73bf26..d50c928 100644
--- a/media/renderers/renderer_impl.h
+++ b/media/renderers/renderer_impl.h
@@ -62,6 +62,12 @@
   void SetPlaybackRate(double playback_rate) final;
   void SetVolume(float volume) final;
   base::TimeDelta GetMediaTime() final;
+  void OnSelectedVideoTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb) override;
+  void OnEnabledAudioTracksChanged(
+      const std::vector<DemuxerStream*>& enabled_tracks,
+      base::OnceClosure change_completed_cb) override;
 
   // Helper functions for testing purposes. Must be called before Initialize().
   void DisableUnderflowForTesting();
@@ -106,25 +112,24 @@
   void FlushVideoRenderer();
   void OnVideoRendererFlushDone();
 
-  // This function notifies the renderer that the status of the demuxer |stream|
-  // has been changed, the new status is |enabled| and the change occured while
-  // playback position was |time|.
-  void OnStreamStatusChanged(DemuxerStream* stream,
-                             bool enabled,
-                             base::TimeDelta time);
-
   // Reinitialize audio/video renderer during a demuxer stream switching. The
   // renderer must be flushed first, and when the re-init is completed the
   // corresponding callback will be invoked to restart playback.
   // The |stream| parameter specifies the new demuxer stream, and the |time|
   // parameter specifies the time on media timeline where the switch occured.
-  void ReinitializeAudioRenderer(DemuxerStream* stream, base::TimeDelta time);
+  void ReinitializeAudioRenderer(DemuxerStream* stream,
+                                 base::TimeDelta time,
+                                 base::OnceClosure reinitialize_completed_cb);
   void OnAudioRendererReinitialized(DemuxerStream* stream,
                                     base::TimeDelta time,
+                                    base::OnceClosure reinitialize_completed_cb,
                                     PipelineStatus status);
-  void ReinitializeVideoRenderer(DemuxerStream* stream, base::TimeDelta time);
+  void ReinitializeVideoRenderer(DemuxerStream* stream,
+                                 base::TimeDelta time,
+                                 base::OnceClosure restart_completed_cb);
   void OnVideoRendererReinitialized(DemuxerStream* stream,
                                     base::TimeDelta time,
+                                    base::OnceClosure restart_completed_cb,
                                     PipelineStatus status);
 
   // Restart audio/video renderer playback after a demuxer stream switch or
@@ -134,8 +139,18 @@
   // needs to be restarted. It is necessary for demuxers with independent
   // streams (e.g. MSE / ChunkDemuxer) to synchronize data reading between those
   // streams.
-  void RestartAudioRenderer(DemuxerStream* stream, base::TimeDelta time);
-  void RestartVideoRenderer(DemuxerStream* stream, base::TimeDelta time);
+  void RestartAudioRenderer(DemuxerStream* stream,
+                            base::TimeDelta time,
+                            base::OnceClosure restart_completed_cb);
+  void RestartVideoRenderer(DemuxerStream* stream,
+                            base::TimeDelta time,
+                            base::OnceClosure restart_completed_cb);
+
+  // Fix state booleans after the stream switching is finished.
+  void CleanUpTrackChange(base::RepeatingClosure on_finished,
+                          bool* pending_change,
+                          bool* ended,
+                          bool* playing);
 
   // Callback executed by filters to update statistics.
   void OnStatisticsUpdate(const PipelineStatistics& stats);
@@ -217,6 +232,8 @@
   // Whether we've received the audio/video ended events.
   bool audio_ended_;
   bool video_ended_;
+  bool audio_playing_;
+  bool video_playing_;
 
   CdmContext* cdm_context_;
 
@@ -238,21 +255,10 @@
   // TODO(servolk): Get rid of the lock and replace restarting_audio_ with
   // std::atomic<bool> when atomics are unbanned in Chromium.
   base::Lock restarting_audio_lock_;
-  bool restarting_audio_ = false;
+  bool pending_audio_track_change_ = false;
   base::TimeDelta restarting_audio_time_ = kNoTimestamp;
 
-  bool restarting_video_ = false;
-
-  // Flush operations and media track status changes must be serialized to avoid
-  // interfering with each other. This list will hold a list of postponed
-  // actions that need to be completed after the current async operation is
-  // completed.
-  std::list<base::Closure> pending_actions_;
-
-  // Pending flush indicates that a track change is in the middle of a Flush and
-  // that another one can't be scheduled at this time. Instead it should be
-  // added to |pending_actions_|.
-  bool pending_flush_for_stream_change_ = false;
+  bool pending_video_track_change_ = false;
 
   base::WeakPtr<RendererImpl> weak_this_;
   base::WeakPtrFactory<RendererImpl> weak_factory_;
diff --git a/media/renderers/renderer_impl_unittest.cc b/media/renderers/renderer_impl_unittest.cc
index c10add5..cac4c02 100644
--- a/media/renderers/renderer_impl_unittest.cc
+++ b/media/renderers/renderer_impl_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/bind_helpers.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -28,6 +29,7 @@
 using ::testing::SaveArg;
 using ::testing::StrictMock;
 using ::testing::WithArg;
+using ::testing::WithArgs;
 
 namespace media {
 
@@ -68,6 +70,8 @@
     MOCK_METHOD0(OnFlushed, void());
     MOCK_METHOD1(OnCdmAttached, void(bool));
     MOCK_METHOD1(OnDurationChange, void(base::TimeDelta duration));
+    MOCK_METHOD0(OnVideoTrackChangeComplete, void());
+    MOCK_METHOD0(OnAudioTrackChangeComplete, void());
 
    private:
     DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
@@ -102,8 +106,6 @@
       DemuxerStream::Type type) {
     std::unique_ptr<StrictMock<MockDemuxerStream>> stream(
         new StrictMock<MockDemuxerStream>(type));
-    EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-        .Times(testing::AnyNumber());
     return stream;
   }
 
@@ -302,6 +304,37 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void SetAudioTrackSwitchExpectations() {
+    InSequence track_switch_seq;
+
+    // Called from withing OnEnabledAudioTracksChanged
+    EXPECT_CALL(time_source_, CurrentMediaTime());
+    EXPECT_CALL(time_source_, CurrentMediaTime());
+    EXPECT_CALL(time_source_, StopTicking());
+    EXPECT_CALL(*audio_renderer_, Flush(_));
+
+    // Callback into RestartAudioRenderer
+    EXPECT_CALL(*audio_renderer_, StartPlaying());
+
+    // Callback into OnBufferingStateChange
+    EXPECT_CALL(time_source_, StartTicking());
+    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
+  }
+
+  void SetVideoTrackSwitchExpectations() {
+    InSequence track_switch_seq;
+
+    // Called from withing OnSelectedVideoTrackChanged
+    EXPECT_CALL(time_source_, CurrentMediaTime());
+    EXPECT_CALL(*video_renderer_, Flush(_));
+
+    // Callback into RestartVideoRenderer
+    EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
+
+    // Callback into OnBufferingStateChange
+    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
+  }
+
   // Fixture members.
   base::MessageLoop message_loop_;
   StrictMock<CallbackHelper> callbacks_;
@@ -769,365 +802,206 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(RendererImplTest, StreamStatusNotificationHandling) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
+TEST_F(RendererImplTest, AudioTrackDisableThenEnable) {
+  InitializeWithAudioAndVideo();
   Play();
+  Mock::VerifyAndClearExpectations(&time_source_);
+
+  base::RunLoop disable_wait;
+  SetAudioTrackSwitchExpectations();
+  renderer_impl_->OnEnabledAudioTracksChanged({}, disable_wait.QuitClosure());
+  disable_wait.Run();
+
+  base::RunLoop enable_wait;
+  SetAudioTrackSwitchExpectations();
+  renderer_impl_->OnEnabledAudioTracksChanged({streams_[0]},
+                                              enable_wait.QuitClosure());
+  enable_wait.Run();
+}
+
+TEST_F(RendererImplTest, VideoTrackDisableThenEnable) {
+  InitializeWithAudioAndVideo();
+  Play();
+  Mock::VerifyAndClearExpectations(&time_source_);
+
+  base::RunLoop disable_wait;
+  SetVideoTrackSwitchExpectations();
+  renderer_impl_->OnSelectedVideoTracksChanged({}, disable_wait.QuitClosure());
+  disable_wait.Run();
+
+  base::RunLoop enable_wait;
+  SetVideoTrackSwitchExpectations();
+  renderer_impl_->OnSelectedVideoTracksChanged({streams_[1]},
+                                               enable_wait.QuitClosure());
+  enable_wait.Run();
+
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(RendererImplTest, AudioUnderflowDuringAudioTrackChange) {
+  InitializeWithAudioAndVideo();
+  Play();
+
+  base::RunLoop loop;
+
+  // Underflow should occur immediately with a single audio track.
+  EXPECT_CALL(time_source_, StopTicking());
+
+  // Capture the callback from the audio renderer flush.
+  base::Closure audio_renderer_flush_cb;
+  EXPECT_CALL(*audio_renderer_, Flush(_))
+      .WillOnce(SaveArg<0>(&audio_renderer_flush_cb));
+
+  EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2);
+  std::vector<DemuxerStream*> tracks;
+  renderer_impl_->OnEnabledAudioTracksChanged({}, loop.QuitClosure());
 
   EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
 
-  // Verify that DemuxerStream status changes cause the corresponding
-  // audio/video renderer to be flushed and restarted.
-  EXPECT_CALL(time_source_, StopTicking());
-  EXPECT_CALL(*audio_renderer_, Flush(_));
-  EXPECT_CALL(*audio_renderer_, StartPlaying());
   EXPECT_CALL(time_source_, StartTicking());
-  stream_status_change_cb.Run(audio_stream_.get(), false, base::TimeDelta());
-
-  EXPECT_CALL(*video_renderer_, Flush(_));
-  EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
-  stream_status_change_cb.Run(video_stream_.get(), false, base::TimeDelta());
-  base::RunLoop().RunUntilIdle();
-}
-
-// Stream status changes are handled asynchronously by the renderer and may take
-// some time to process. This test verifies that all status changes are
-// processed correctly by the renderer even if status changes of the stream
-// happen much faster than the renderer can process them. In that case the
-// renderer may postpone processing status changes, but still must process all
-// of them eventually.
-TEST_F(RendererImplTest, PostponedStreamStatusNotificationHandling) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH))
-      .Times(2);
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(2);
-  EXPECT_CALL(time_source_, StartTicking()).Times(2);
-  EXPECT_CALL(*audio_renderer_, Flush(_)).Times(2);
-  EXPECT_CALL(*audio_renderer_, StartPlaying()).Times(2);
-  // The first stream status change will be processed immediately. Each status
-  // change processing involves Flush + StartPlaying when the Flush is done. The
-  // Flush operation is async in this case, so the second status change will be
-  // postponed by renderer until after processing the first one is finished. But
-  // we must still get two pairs of Flush/StartPlaying calls eventually.
-  stream_status_change_cb.Run(audio_stream_.get(), false, base::TimeDelta());
-  stream_status_change_cb.Run(audio_stream_.get(), true, base::TimeDelta());
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_CALL(*video_renderer_, Flush(_)).Times(2);
-  EXPECT_CALL(*video_renderer_, StartPlayingFrom(base::TimeDelta())).Times(2);
-  // The first stream status change will be processed immediately. Each status
-  // change processing involves Flush + StartPlaying when the Flush is done. The
-  // Flush operation is async in this case, so the second status change will be
-  // postponed by renderer until after processing the first one is finished. But
-  // we must still get two pairs of Flush/StartPlaying calls eventually.
-  stream_status_change_cb.Run(video_stream_.get(), false, base::TimeDelta());
-  stream_status_change_cb.Run(video_stream_.get(), true, base::TimeDelta());
-  base::RunLoop().RunUntilIdle();
-}
-
-// Verify that a RendererImpl::Flush gets postponed until after stream status
-// change handling is completed.
-TEST_F(RendererImplTest, FlushDuringAudioReinit) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  base::Closure audio_renderer_flush_cb;
-  EXPECT_CALL(*audio_renderer_, Flush(_))
-      .WillOnce(SaveArg<0>(&audio_renderer_flush_cb));
   EXPECT_CALL(*audio_renderer_, StartPlaying());
-
-  // This should start flushing the audio renderer (due to audio stream status
-  // change) and should populate the |audio_renderer_flush_cb|.
-  stream_status_change_cb.Run(audio_stream_.get(), false, base::TimeDelta());
-  EXPECT_TRUE(audio_renderer_flush_cb);
-  base::RunLoop().RunUntilIdle();
-
-  bool flush_done = false;
-
-  // Now that audio stream change is being handled the RendererImpl::Flush
-  // should be postponed, instead of being executed immediately.
-  EXPECT_CALL(callbacks_, OnFlushed()).WillOnce(SetBool(&flush_done, true));
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(flush_done);
-
-  // The renderer_impl_->Flush invoked above should proceed after the first
-  // audio renderer flush (initiated by the stream status change) completes.
-  SetFlushExpectationsForAVRenderers();
-  audio_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(flush_done);
-}
-
-// Verify that RendererImpl::Flush is not postponed by waiting for data.
-TEST_F(RendererImplTest, FlushDuringAudioReinitWhileWaiting) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  base::Closure audio_renderer_flush_cb;
-
-  // Override the standard buffering state transitions for Flush() and
-  // StartPlaying() setup above.
-  EXPECT_CALL(*audio_renderer_, Flush(_))
-      .WillOnce(DoAll(
-          SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_NOTHING),
-          SaveArg<0>(&audio_renderer_flush_cb)));
-  EXPECT_CALL(*audio_renderer_, StartPlaying())
-      .WillOnce(RunClosure(base::DoNothing::Repeatedly()));
-
-  // This should start flushing the audio renderer (due to audio stream status
-  // change) and should populate the |audio_renderer_flush_cb|.
-  stream_status_change_cb.Run(audio_stream_.get(), false, base::TimeDelta());
-  EXPECT_TRUE(audio_renderer_flush_cb);
-  base::RunLoop().RunUntilIdle();
-
-  bool flush_done = false;
-
-  // Complete the first half of the track change, it should be stuck in the
-  // StartPlaying() state after this.
-  EXPECT_CALL(callbacks_, OnFlushed()).WillOnce(SetBool(&flush_done, true));
-  audio_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(flush_done);
-
-  // Ensure that even though we're stuck waiting for have_enough from the
-  // audio renderer, that our flush still executes immediately.
-  SetFlushExpectationsForAVRenderers();
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(flush_done);
-}
-
-TEST_F(RendererImplTest, FlushDuringVideoReinit) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  base::Closure video_renderer_flush_cb;
-  EXPECT_CALL(*video_renderer_, Flush(_))
-      .WillOnce(SaveArg<0>(&video_renderer_flush_cb));
-  EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
-
-  // This should start flushing the video renderer (due to video stream status
-  // change) and should populate the |video_renderer_flush_cb|.
-  stream_status_change_cb.Run(video_stream_.get(), false, base::TimeDelta());
-  EXPECT_TRUE(video_renderer_flush_cb);
-  base::RunLoop().RunUntilIdle();
-
-  bool flush_done = false;
-
-  // Now that video stream change is being handled the RendererImpl::Flush
-  // should be postponed, instead of being executed immediately.
-  EXPECT_CALL(callbacks_, OnFlushed()).WillOnce(SetBool(&flush_done, true));
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(flush_done);
-
-  // The renderer_impl_->Flush invoked above should proceed after the first
-  // video renderer flush (initiated by the stream status change) completes.
-  SetFlushExpectationsForAVRenderers();
-  video_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(flush_done);
-}
-
-// Verify that RendererImpl::Flush is not postponed by waiting for data.
-TEST_F(RendererImplTest, FlushDuringVideoReinitWhileWaiting) {
-  CreateAudioAndVideoStream();
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  base::Closure video_renderer_flush_cb;
-
-  // Override the standard buffering state transitions for Flush() and
-  // StartPlaying() setup above.
-  EXPECT_CALL(*video_renderer_, Flush(_))
-      .WillOnce(DoAll(
-          SetBufferingState(&video_renderer_client_, BUFFERING_HAVE_NOTHING),
-          SaveArg<0>(&video_renderer_flush_cb)));
-  EXPECT_CALL(*video_renderer_, StartPlayingFrom(_))
-      .WillOnce(RunClosure(base::DoNothing::Repeatedly()));
-
-  // This should start flushing the video renderer (due to video stream status
-  // change) and should populate the |video_renderer_flush_cb|.
-  stream_status_change_cb.Run(video_stream_.get(), false, base::TimeDelta());
-  EXPECT_TRUE(video_renderer_flush_cb);
-  base::RunLoop().RunUntilIdle();
-
-  bool flush_done = false;
-
-  // Complete the first half of the track change, it should be stuck in the
-  // StartPlaying() state after this.
-  EXPECT_CALL(callbacks_, OnFlushed()).WillOnce(SetBool(&flush_done, true));
-  video_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(flush_done);
-
-  // Ensure that even though we're stuck waiting for have_enough from the
-  // video renderer, that our flush still executes immediately.
-  SetFlushExpectationsForAVRenderers();
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(flush_done);
-}
-
-// Test audio track switching when the RendererImpl is in STATE_FLUSHING/FLUSHED
-TEST_F(RendererImplTest, AudioTrackSwitchDuringFlush) {
-  CreateAudioAndVideoStream();
-  std::unique_ptr<StrictMock<MockDemuxerStream>> primary_audio_stream =
-      std::move(audio_stream_);
-  CreateAudioStream();
-  std::unique_ptr<StrictMock<MockDemuxerStream>> secondary_audio_stream =
-      std::move(audio_stream_);
-  audio_stream_ = std::move(primary_audio_stream);
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
-  Play();
-
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  EXPECT_CALL(*video_renderer_, Flush(_));
-
-  // Initiate RendererImpl::Flush, but postpone its completion by not calling
-  // audio renderer flush callback right away, i.e. pretending audio renderer
-  // flush takes a while.
-  base::Closure audio_renderer_flush_cb;
-  EXPECT_CALL(*audio_renderer_, Flush(_))
-      .WillOnce(SaveArg<0>(&audio_renderer_flush_cb));
-  EXPECT_CALL(callbacks_, OnFlushed());
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(audio_renderer_flush_cb);
-
-  // Now, while the RendererImpl::Flush is pending, perform an audio track
-  // switch. The handling of the track switch will be postponed until after
-  // RendererImpl::Flush completes.
-  stream_status_change_cb.Run(audio_stream_.get(), false, base::TimeDelta());
-  stream_status_change_cb.Run(secondary_audio_stream.get(), true,
-                              base::TimeDelta());
-
-  // Ensure that audio track switch occurs after Flush by verifying that the
-  // audio renderer is reinitialized with the secondary audio stream.
-  EXPECT_CALL(*audio_renderer_,
-              Initialize(secondary_audio_stream.get(), _, _, _));
-
-  // Complete the audio renderer flush, thus completing the renderer_impl_ Flush
-  // initiated above. This will transition the RendererImpl into the FLUSHED
-  // state and will process pending track switch, which should result in the
-  // reinitialization of the audio renderer for the secondary audio stream.
   audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
   audio_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
+  loop.Run();
 }
 
-// Test video track switching when the RendererImpl is in STATE_FLUSHING/FLUSHED
-TEST_F(RendererImplTest, VideoTrackSwitchDuringFlush) {
-  CreateAudioAndVideoStream();
-  std::unique_ptr<StrictMock<MockDemuxerStream>> primary_video_stream =
-      std::move(video_stream_);
-  CreateVideoStream();
-  std::unique_ptr<StrictMock<MockDemuxerStream>> secondary_video_stream =
-      std::move(video_stream_);
-  video_stream_ = std::move(primary_video_stream);
-
-  StreamStatusChangeCB stream_status_change_cb;
-  EXPECT_CALL(*demuxer_, SetStreamStatusChangeCB(_))
-      .WillOnce(SaveArg<0>(&stream_status_change_cb));
-  SetAudioRendererInitializeExpectations(PIPELINE_OK);
-  SetVideoRendererInitializeExpectations(PIPELINE_OK);
-  InitializeAndExpect(PIPELINE_OK);
+TEST_F(RendererImplTest, VideoUnderflowDuringVideoTrackChange) {
+  InitializeWithAudioAndVideo();
   Play();
 
-  EXPECT_CALL(time_source_, StopTicking()).Times(testing::AnyNumber());
-  EXPECT_CALL(*video_renderer_, OnTimeStopped()).Times(testing::AnyNumber());
-  EXPECT_CALL(*audio_renderer_, Flush(_));
+  base::RunLoop loop;
 
-  // Initiate RendererImpl::Flush, but postpone its completion by not calling
-  // video renderer flush callback right away, i.e. pretending video renderer
-  // flush takes a while.
+  // Capture the callback from the video renderer flush.
+  base::Closure video_renderer_flush_cb;
+  {
+    InSequence track_switch_seq;
+    EXPECT_CALL(time_source_, CurrentMediaTime());
+    EXPECT_CALL(*video_renderer_, Flush(_))
+        .WillOnce(SaveArg<0>(&video_renderer_flush_cb));
+    EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
+    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));
+  }
+
+  renderer_impl_->OnSelectedVideoTracksChanged({}, loop.QuitClosure());
+
+  video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+  video_renderer_flush_cb.Run();
+  loop.Run();
+}
+
+TEST_F(RendererImplTest, VideoUnderflowDuringAudioTrackChange) {
+  InitializeWithAudioAndVideo();
+  Play();
+
+  base::RunLoop loop;
+
+  // Capture the callback from the audio renderer flush.
+  base::Closure audio_renderer_flush_cb;
+  EXPECT_CALL(*audio_renderer_, Flush(_))
+      .WillOnce(SaveArg<0>(&audio_renderer_flush_cb));
+
+  EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2);
+  EXPECT_CALL(time_source_, StopTicking());
+  renderer_impl_->OnEnabledAudioTracksChanged({}, loop.QuitClosure());
+
+  EXPECT_CALL(*audio_renderer_, StartPlaying());
+  video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+  audio_renderer_flush_cb.Run();
+  loop.Run();
+}
+
+TEST_F(RendererImplTest, AudioUnderflowDuringVideoTrackChange) {
+  InitializeWithAudioAndVideo();
+  Play();
+
+  base::RunLoop loop;
+  EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
+  EXPECT_CALL(time_source_, CurrentMediaTime());
+
+  // Capture the callback from the audio renderer flush.
   base::Closure video_renderer_flush_cb;
   EXPECT_CALL(*video_renderer_, Flush(_))
       .WillOnce(SaveArg<0>(&video_renderer_flush_cb));
-  EXPECT_CALL(callbacks_, OnFlushed());
-  renderer_impl_->Flush(
-      base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(video_renderer_flush_cb);
 
-  // Now, while the RendererImpl::Flush is pending, perform a video track
-  // switch. The handling of the track switch will be postponed until after
-  // RendererImpl::Flush completes.
-  stream_status_change_cb.Run(video_stream_.get(), false, base::TimeDelta());
-  stream_status_change_cb.Run(secondary_video_stream.get(), true,
-                              base::TimeDelta());
+  renderer_impl_->OnSelectedVideoTracksChanged({}, loop.QuitClosure());
 
-  // Ensure that video track switch occurs after Flush by verifying that the
-  // video renderer is reinitialized with the secondary video stream.
-  EXPECT_CALL(*video_renderer_,
-              Initialize(secondary_video_stream.get(), _, _, _, _));
+  EXPECT_CALL(time_source_, StopTicking());
+  EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
 
-  // Complete the video renderer flush, thus completing the renderer_impl_ Flush
-  // initiated above. This will transition the RendererImpl into the FLUSHED
-  // state and will process pending track switch, which should result in the
-  // reinitialization of the video renderer for the secondary video stream.
-  video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+  audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+
   video_renderer_flush_cb.Run();
-  base::RunLoop().RunUntilIdle();
+  loop.Run();
+}
+
+TEST_F(RendererImplTest, VideoResumedFromUnderflowDuringAudioTrackChange) {
+  InitializeWithAudioAndVideo();
+  Play();
+
+  // Underflow the renderer.
+  base::RunLoop underflow_wait;
+  EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING))
+      .WillOnce(RunClosure(underflow_wait.QuitClosure()));
+  EXPECT_CALL(time_source_, StopTicking());
+  video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+  underflow_wait.Run();
+
+  // Start a track change.
+  base::Closure audio_renderer_flush_cb;
+  base::RunLoop track_change;
+  {
+    InSequence track_switch_seq;
+    EXPECT_CALL(time_source_, CurrentMediaTime()).Times(2);
+    EXPECT_CALL(*audio_renderer_, Flush(_))
+        .WillOnce(SaveArg<0>(&audio_renderer_flush_cb));
+  }
+  renderer_impl_->OnEnabledAudioTracksChanged({}, track_change.QuitClosure());
+
+  // Signal that the renderer has enough data to resume from underflow.
+  // Nothing should bubble up, since we are pending audio track change.
+  EXPECT_CALL(callbacks_, OnBufferingStateChange(_)).Times(0);
+  EXPECT_CALL(time_source_, StartTicking()).Times(0);
+  video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
+
+  // Finish the track change.
+  EXPECT_CALL(*audio_renderer_, StartPlaying());
+  audio_renderer_flush_cb.Run();
+  track_change.Run();
+}
+
+TEST_F(RendererImplTest, AudioResumedFromUnderflowDuringVideoTrackChange) {
+  InitializeWithAudioAndVideo();
+  Play();
+
+  // Underflow the renderer.
+  base::RunLoop underflow_wait;
+  EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING))
+      .WillOnce(RunClosure(underflow_wait.QuitClosure()));
+  EXPECT_CALL(time_source_, StopTicking());
+  audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
+  underflow_wait.Run();
+
+  // Start a track change.
+  base::Closure video_renderer_flush_cb;
+  base::RunLoop track_change;
+  {
+    InSequence track_switch_seq;
+    EXPECT_CALL(time_source_, CurrentMediaTime());
+    EXPECT_CALL(*video_renderer_, Flush(_))
+        .WillOnce(SaveArg<0>(&video_renderer_flush_cb));
+  }
+  renderer_impl_->OnSelectedVideoTracksChanged({}, track_change.QuitClosure());
+
+  // Signal that the renderer has enough data to resume from underflow.
+  // Nothing should bubble up, since we are pending audio track change.
+  EXPECT_CALL(callbacks_, OnBufferingStateChange(_)).Times(0);
+  EXPECT_CALL(time_source_, StartTicking()).Times(0);
+  audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
+
+  // Finish the track change.
+  EXPECT_CALL(*video_renderer_, StartPlayingFrom(_));
+  video_renderer_flush_cb.Run();
+  track_change.Run();
 }
 
 }  // namespace media
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index a7b9e00..2ac6708 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/command_line.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -680,7 +681,7 @@
 
   // Disable audio.
   std::vector<MediaTrack::Id> empty;
-  pipeline_->OnEnabledAudioTracksChanged(empty);
+  pipeline_->OnEnabledAudioTracksChanged(empty, base::DoNothing());
   scoped_task_environment_.RunUntilIdle();
 
   // Seek to flush the pipeline and ensure there's no prerolled audio data.
@@ -697,7 +698,7 @@
   // Re-enable audio.
   std::vector<MediaTrack::Id> audio_track_id;
   audio_track_id.push_back("2");
-  pipeline_->OnEnabledAudioTracksChanged(audio_track_id);
+  pipeline_->OnEnabledAudioTracksChanged(audio_track_id, base::DoNothing());
   scoped_task_environment_.RunUntilIdle();
 
   // Restart playback from 500ms position.
@@ -713,7 +714,7 @@
   ASSERT_EQ(PIPELINE_OK, Start("bear-320x240.webm", kHashed | kNoClockless));
 
   // Disable video.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   scoped_task_environment_.RunUntilIdle();
 
   // Seek to flush the pipeline and ensure there's no prerolled video data.
@@ -732,7 +733,8 @@
   EXPECT_HASH_EQ(kNullVideoHash, GetVideoHash());
 
   // Re-enable video.
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"),
+                                         base::DoNothing());
   scoped_task_environment_.RunUntilIdle();
 
   // Seek to flush video pipeline and reset the video hash again to clear state
@@ -751,8 +753,8 @@
 
 TEST_F(PipelineIntegrationTest, TrackStatusChangesBeforePipelineStarted) {
   std::vector<MediaTrack::Id> empty_track_ids;
-  pipeline_->OnEnabledAudioTracksChanged(empty_track_ids);
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnEnabledAudioTracksChanged(empty_track_ids, base::DoNothing());
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
 }
 
 TEST_F(PipelineIntegrationTest, TrackStatusChangesAfterPipelineEnded) {
@@ -761,14 +763,15 @@
   ASSERT_TRUE(WaitUntilOnEnded());
   std::vector<MediaTrack::Id> track_ids;
   // Disable audio track.
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   // Re-enable audio track.
   track_ids.push_back("2");
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   // Disable video track.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   // Re-enable video track.
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"),
+                                         base::DoNothing());
 }
 
 TEST_F(PipelineIntegrationTest, TrackStatusChangesWhileSuspended) {
@@ -785,27 +788,27 @@
   std::vector<MediaTrack::Id> track_ids;
 
   // Disable audio track.
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   ASSERT_TRUE(Resume(TimestampMs(100)));
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
   ASSERT_TRUE(Suspend());
 
   // Re-enable audio track.
   track_ids.push_back("2");
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   ASSERT_TRUE(Resume(TimestampMs(200)));
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(300)));
   ASSERT_TRUE(Suspend());
 
   // Disable video track.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   ASSERT_TRUE(Resume(TimestampMs(300)));
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(400)));
   ASSERT_TRUE(Suspend());
 
   // Re-enable video track.
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"));
-  ASSERT_TRUE(Resume(TimestampMs(400)));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"),
+                                         base::DoNothing());
   ASSERT_TRUE(WaitUntilOnEnded());
 }
 
@@ -821,7 +824,7 @@
 
   // Disable the audio track.
   std::vector<MediaTrack::Id> track_ids;
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   // pipeline.Suspend() releases renderers and pipeline.Resume() recreates and
   // reinitializes renderers while the audio track is disabled.
   ASSERT_TRUE(Suspend());
@@ -829,7 +832,7 @@
   // Now re-enable the audio track, playback should continue successfully.
   EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)).Times(1);
   track_ids.push_back("2");
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
 
   Stop();
@@ -845,13 +848,14 @@
   EXPECT_CALL(*this, OnVideoOpacityChange(true)).Times(AnyNumber());
 
   // Disable the video track.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   // pipeline.Suspend() releases renderers and pipeline.Resume() recreates and
   // reinitializes renderers while the video track is disabled.
   ASSERT_TRUE(Suspend());
   ASSERT_TRUE(Resume(TimestampMs(100)));
   // Now re-enable the video track, playback should continue successfully.
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"),
+                                         base::DoNothing());
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
 
   Stop();
@@ -864,11 +868,12 @@
   // Disable audio track first, to re-enable it later and stop the pipeline
   // (which destroys the media renderer) while audio restart is pending.
   std::vector<MediaTrack::Id> track_ids;
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
-  ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
+
+  // Playback is paused while all audio tracks are disabled.
 
   track_ids.push_back("2");
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   Stop();
 }
 
@@ -878,10 +883,11 @@
 
   // Disable video track first, to re-enable it later and stop the pipeline
   // (which destroys the media renderer) while video restart is pending.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
 
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("1"),
+                                         base::DoNothing());
   Stop();
 }
 
@@ -894,7 +900,7 @@
   // disable TrackId=4 and enable TrackId=5.
   std::vector<MediaTrack::Id> track_ids;
   track_ids.push_back("5");
-  pipeline_->OnEnabledAudioTracksChanged(track_ids);
+  pipeline_->OnEnabledAudioTracksChanged(track_ids, base::DoNothing());
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
   Stop();
 }
@@ -906,7 +912,8 @@
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(100)));
   // The first video track (TrackId=1) is enabled by default. This should
   // disable TrackId=1 and enable TrackId=2.
-  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("2"));
+  pipeline_->OnSelectedVideoTrackChanged(MediaTrack::Id("2"),
+                                         base::DoNothing());
   ASSERT_TRUE(WaitUntilCurrentTimeIsAfter(TimestampMs(200)));
   Stop();
 }
@@ -1406,7 +1413,7 @@
 
   // Disable the video track and start playback. Renderer won't read from the
   // disabled video stream, so the video stream read position should be 0.
-  pipeline_->OnSelectedVideoTrackChanged(base::nullopt);
+  pipeline_->OnSelectedVideoTrackChanged(base::nullopt, base::DoNothing());
   Play();
 
   // Wait until audio playback advances past 2 seconds and call MSE GC algorithm