From 81e7b31be187c1ca7de2129d942b62fdff0079d6 Mon Sep 17 00:00:00 2001
From: tonihei <tonihei@google.com>
Date: Thu, 4 Jul 2019 12:54:56 +0100
Subject: [PATCH] Apply playback parameters in a consistent way.

Currently, we sometimes apply new playback parameters directly and sometimes
through the list of playbackParameterCheckpoints. Only when using the checkpoints,
we also reset the offset and corresponding position for speedup position
calculation. However, these offsets need to be changed in all cases to prevent
calculation errors during speedup calculation[1].

This change channels all playback parameters changes through the checkpoints to
ensure the offsets get updated accordingly. This fixes an issue introduced in
https://github.com/google/ExoPlayer/commit/31911ca54a13b0003d6cf902b95c2ed445afa930.

[1] - The speed up is calculated using the ratio of input and output bytes in
SonicAudioProcessor.scaleDurationForSpeedUp. Whenever we set new playback
parameters to the audio processor these two counts are reset. If we don't reset
the offsets too, the scaled timestamp can be a large value compared to the input
and output bytes causing massive inaccuracies (like the +20 seconds in the
linked issue).

Issue:#6117
PiperOrigin-RevId: 256533780
---
 RELEASENOTES.md                               |  7 ++-
 .../exoplayer2/audio/DefaultAudioSink.java    | 47 ++++++++++---------
 2 files changed, 29 insertions(+), 25 deletions(-)

diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index d76ca54b7b3..855b0e94f23 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -12,8 +12,11 @@
   checks ([#5568](https://github.com/google/ExoPlayer/issues/5568)).
 * Decoders: Prefer decoders that advertise format support over ones that do not,
   even if they are listed lower in the `MediaCodecList`.
-* Audio: Fix an issue where not all audio was played out when the configuration
-  for the underlying track was changing (e.g., at some period transitions).
+* Audio:
+  * Fix an issue where not all audio was played out when the configuration
+    for the underlying track was changing (e.g., at some period transitions).
+  * Fix an issue where playback speed was applied inaccurately in playlists
+    ([#6117](https://github.com/google/ExoPlayer/issues/6117)).
 * Add a workaround for broken raw audio decoding on Oppo R9
   ([#5782](https://github.com/google/ExoPlayer/issues/5782)).
 * Add VR player demo.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
index f982efa9a7d..e3f753958e7 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java
@@ -501,7 +501,7 @@ private void flushAudioProcessors() {
     }
   }
 
-  private void initialize() throws InitializationException {
+  private void initialize(long presentationTimeUs) throws InitializationException {
     // If we're asynchronously releasing a previous audio track then we block until it has been
     // released. This guarantees that we cannot end up in a state where we have multiple audio
     // track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
@@ -533,11 +533,7 @@ private void initialize() throws InitializationException {
       }
     }
 
-    playbackParameters =
-        configuration.canApplyPlaybackParameters
-            ? audioProcessorChain.applyPlaybackParameters(playbackParameters)
-            : PlaybackParameters.DEFAULT;
-    setupAudioProcessors();
+    applyPlaybackParameters(playbackParameters, presentationTimeUs);
 
     audioTrackPositionTracker.setAudioTrack(
         audioTrack,
@@ -591,15 +587,12 @@ public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
         configuration = pendingConfiguration;
         pendingConfiguration = null;
       }
-      playbackParameters =
-          configuration.canApplyPlaybackParameters
-              ? audioProcessorChain.applyPlaybackParameters(playbackParameters)
-              : PlaybackParameters.DEFAULT;
-      setupAudioProcessors();
+      // Re-apply playback parameters.
+      applyPlaybackParameters(playbackParameters, presentationTimeUs);
     }
 
     if (!isInitialized()) {
-      initialize();
+      initialize(presentationTimeUs);
       if (playing) {
         play();
       }
@@ -635,15 +628,7 @@ public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
         }
         PlaybackParameters newPlaybackParameters = afterDrainPlaybackParameters;
         afterDrainPlaybackParameters = null;
-        newPlaybackParameters = audioProcessorChain.applyPlaybackParameters(newPlaybackParameters);
-        // Store the position and corresponding media time from which the parameters will apply.
-        playbackParametersCheckpoints.add(
-            new PlaybackParametersCheckpoint(
-                newPlaybackParameters,
-                Math.max(0, presentationTimeUs),
-                configuration.framesToDurationUs(getWrittenFrames())));
-        // Update the set of active audio processors to take into account the new parameters.
-        setupAudioProcessors();
+        applyPlaybackParameters(newPlaybackParameters, presentationTimeUs);
       }
 
       if (startMediaTimeState == START_NOT_SET) {
@@ -857,8 +842,9 @@ public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParam
         // parameters apply.
         afterDrainPlaybackParameters = playbackParameters;
       } else {
-        // Update the playback parameters now.
-        this.playbackParameters = audioProcessorChain.applyPlaybackParameters(playbackParameters);
+        // Update the playback parameters now. They will be applied to the audio processors during
+        // initialization.
+        this.playbackParameters = playbackParameters;
       }
     }
     return this.playbackParameters;
@@ -1040,6 +1026,21 @@ public void run() {
     }.start();
   }
 
+  private void applyPlaybackParameters(
+      PlaybackParameters playbackParameters, long presentationTimeUs) {
+    PlaybackParameters newPlaybackParameters =
+        configuration.canApplyPlaybackParameters
+            ? audioProcessorChain.applyPlaybackParameters(playbackParameters)
+            : PlaybackParameters.DEFAULT;
+    // Store the position and corresponding media time from which the parameters will apply.
+    playbackParametersCheckpoints.add(
+        new PlaybackParametersCheckpoint(
+            newPlaybackParameters,
+            /* mediaTimeUs= */ Math.max(0, presentationTimeUs),
+            /* positionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
+    setupAudioProcessors();
+  }
+
   private long applySpeedup(long positionUs) {
     @Nullable PlaybackParametersCheckpoint checkpoint = null;
     while (!playbackParametersCheckpoints.isEmpty()