diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 2cc423be34..5b0f7c3b0a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -22,7 +22,6 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.annotation.SuppressLint; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.util.Pair; import androidx.annotation.Nullable; import com.google.android.exoplayer2.PlayerMessage.Target; @@ -67,7 +66,8 @@ import java.util.concurrent.TimeoutException; private final Renderer[] renderers; private final TrackSelector trackSelector; - private final PlaybackUpdateListenerImpl playbackUpdateListener; + private final Handler playbackInfoUpdateHandler; + private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; private final Handler internalPlayerHandler; private final CopyOnWriteArrayList listeners; @@ -87,8 +87,6 @@ import java.util.concurrent.TimeoutException; @DiscontinuityReason private int pendingDiscontinuityReason; @PlayWhenReadyChangeReason private int pendingPlayWhenReadyChangeReason; private boolean foregroundMode; - private int pendingSetPlaybackSpeedAcks; - private float playbackSpeed; private SeekParameters seekParameters; private ShuffleOrder shuffleOrder; private boolean pauseAtEndOfMediaItems; @@ -155,9 +153,11 @@ import java.util.concurrent.TimeoutException; new TrackSelection[renderers.length], null); period = new Timeline.Period(); - playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED; maskingWindowIndex = C.INDEX_UNSET; - playbackUpdateListener = new PlaybackUpdateListenerImpl(applicationLooper); + playbackInfoUpdateHandler = new Handler(applicationLooper); + playbackInfoUpdateListener = + playbackInfoUpdate -> + playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); pendingListenerNotifications = new ArrayDeque<>(); if (analyticsCollector != null) { @@ -179,7 +179,7 @@ import java.util.concurrent.TimeoutException; pauseAtEndOfMediaItems, applicationLooper, clock, - playbackUpdateListener); + playbackInfoUpdateListener); internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); } @@ -595,7 +595,7 @@ import java.util.concurrent.TimeoutException; // general because the midroll ad preceding the seek destination must be played before the // content position can be played, if a different ad is playing at the moment. Log.w(TAG, "seekTo ignored because an ad is playing"); - playbackUpdateListener.onPlaybackInfoUpdate( + playbackInfoUpdateListener.onPlaybackInfoUpdate( new ExoPlayerImplInternal.PlaybackInfoUpdate(playbackInfo)); return; } @@ -632,30 +632,30 @@ import java.util.concurrent.TimeoutException; @Deprecated @Override public PlaybackParameters getPlaybackParameters() { - return new PlaybackParameters(playbackSpeed); + return new PlaybackParameters(playbackInfo.playbackSpeed); } - @SuppressWarnings("deprecation") @Override public void setPlaybackSpeed(float playbackSpeed) { checkState(playbackSpeed > 0); - if (this.playbackSpeed == playbackSpeed) { + if (playbackInfo.playbackSpeed == playbackSpeed) { return; } - pendingSetPlaybackSpeedAcks++; - this.playbackSpeed = playbackSpeed; - PlaybackParameters playbackParameters = new PlaybackParameters(playbackSpeed); + PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed); + pendingOperationAcks++; internalPlayer.setPlaybackSpeed(playbackSpeed); - notifyListeners( - listener -> { - listener.onPlaybackParametersChanged(playbackParameters); - listener.onPlaybackSpeedChanged(playbackSpeed); - }); + updatePlaybackInfo( + newPlaybackInfo, + /* positionDiscontinuity= */ false, + /* ignored */ DISCONTINUITY_REASON_INTERNAL, + /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, + /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, + /* seekProcessed= */ false); } @Override public float getPlaybackSpeed() { - return playbackSpeed; + return playbackInfo.playbackSpeed; } @Override @@ -726,7 +726,7 @@ import java.util.concurrent.TimeoutException; ExoPlaybackException.createForUnexpected( new RuntimeException(new TimeoutException("Player release timed out."))))); } - playbackUpdateListener.handler.removeCallbacksAndMessages(null); + playbackInfoUpdateHandler.removeCallbacksAndMessages(null); if (analyticsCollector != null) { bandwidthMeter.removeEventListener(analyticsCollector); } @@ -896,23 +896,6 @@ import java.util.concurrent.TimeoutException; return mediaSources; } - @SuppressWarnings("deprecation") - private void handlePlaybackSpeed(float playbackSpeed, boolean operationAck) { - if (operationAck) { - pendingSetPlaybackSpeedAcks--; - } - if (pendingSetPlaybackSpeedAcks == 0) { - if (this.playbackSpeed != playbackSpeed) { - this.playbackSpeed = playbackSpeed; - notifyListeners( - listener -> { - listener.onPlaybackParametersChanged(new PlaybackParameters(playbackSpeed)); - listener.onPlaybackSpeedChanged(playbackSpeed); - }); - } - } - } - private void handlePlaybackInfo(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate) { pendingOperationAcks -= playbackInfoUpdate.operationAcks; if (playbackInfoUpdate.positionDiscontinuity) { @@ -996,7 +979,7 @@ import java.util.concurrent.TimeoutException; PlaybackInfo playbackInfo, PlaybackInfo oldPlaybackInfo, boolean positionDiscontinuity, - int positionDiscontinuityReason, + @DiscontinuityReason int positionDiscontinuityReason, boolean timelineChanged) { Timeline oldTimeline = oldPlaybackInfo.timeline; @@ -1360,49 +1343,6 @@ import java.util.concurrent.TimeoutException; return positionMs; } - private final class PlaybackUpdateListenerImpl - implements ExoPlayerImplInternal.PlaybackUpdateListener, Handler.Callback { - private static final int MSG_PLAYBACK_INFO_CHANGED = 0; - private static final int MSG_PLAYBACK_SPEED_CHANGED = 1; - - private final Handler handler; - - private PlaybackUpdateListenerImpl(Looper applicationLooper) { - handler = Util.createHandler(applicationLooper, /* callback= */ this); - } - - @Override - public void onPlaybackInfoUpdate(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfo) { - handler.obtainMessage(MSG_PLAYBACK_INFO_CHANGED, playbackInfo).sendToTarget(); - } - - @Override - public void onPlaybackSpeedChange(float playbackSpeed, boolean acknowledgeCommand) { - handler - .obtainMessage( - MSG_PLAYBACK_SPEED_CHANGED, - /* arg1= */ acknowledgeCommand ? 1 : 0, - /* arg2= */ 0, - /* obj= */ playbackSpeed) - .sendToTarget(); - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_PLAYBACK_INFO_CHANGED: - handlePlaybackInfo((ExoPlayerImplInternal.PlaybackInfoUpdate) msg.obj); - break; - case MSG_PLAYBACK_SPEED_CHANGED: - handlePlaybackSpeed((Float) msg.obj, /* operationAck= */ msg.arg1 != 0); - break; - default: - throw new IllegalStateException(); - } - return true; - } - } - private static final class PlaybackInfoUpdate implements Runnable { private final PlaybackInfo playbackInfo; @@ -1424,6 +1364,7 @@ import java.util.concurrent.TimeoutException; private final boolean playWhenReadyChanged; private final boolean playbackSuppressionReasonChanged; private final boolean isPlayingChanged; + private final boolean playbackSpeedChanged; public PlaybackInfoUpdate( PlaybackInfo playbackInfo, @@ -1461,6 +1402,7 @@ import java.util.concurrent.TimeoutException; playbackSuppressionReasonChanged = previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason; isPlayingChanged = isPlaying(previousPlaybackInfo) != isPlaying(playbackInfo); + playbackSpeedChanged = previousPlaybackInfo.playbackSpeed != playbackInfo.playbackSpeed; } @SuppressWarnings("deprecation") @@ -1526,6 +1468,15 @@ import java.util.concurrent.TimeoutException; invokeAll( listenerSnapshot, listener -> listener.onIsPlayingChanged(isPlaying(playbackInfo))); } + if (playbackSpeedChanged) { + PlaybackParameters playbackParameters = new PlaybackParameters(playbackInfo.playbackSpeed); + invokeAll( + listenerSnapshot, + listener -> { + listener.onPlaybackSpeedChanged(playbackInfo.playbackSpeed); + listener.onPlaybackParametersChanged(playbackParameters); + }); + } if (seekProcessed) { invokeAll(listenerSnapshot, EventListener::onSeekProcessed); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index e785727aa7..f2570aa552 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -109,10 +109,8 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - public interface PlaybackUpdateListener { + public interface PlaybackInfoUpdateListener { void onPlaybackInfoUpdate(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfo); - - void onPlaybackSpeedChange(float playbackSpeed, boolean acknowledgeCommand); } // Internal messages @@ -168,7 +166,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private final DefaultMediaClock mediaClock; private final ArrayList pendingMessages; private final Clock clock; - private final PlaybackUpdateListener playbackUpdateListener; + private final PlaybackInfoUpdateListener playbackInfoUpdateListener; private final MediaPeriodQueue queue; private final MediaSourceList mediaSourceList; @@ -210,8 +208,8 @@ import java.util.concurrent.atomic.AtomicBoolean; boolean pauseAtEndOfWindow, Looper applicationLooper, Clock clock, - PlaybackUpdateListener playbackUpdateListener) { - this.playbackUpdateListener = playbackUpdateListener; + PlaybackInfoUpdateListener playbackInfoUpdateListener) { + this.playbackInfoUpdateListener = playbackInfoUpdateListener; this.renderers = renderers; this.trackSelector = trackSelector; this.emptyTrackSelectorResult = emptyTrackSelectorResult; @@ -624,7 +622,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private void maybeNotifyPlaybackInfoChanged() { playbackInfoUpdate.setPlaybackInfo(playbackInfo); if (playbackInfoUpdate.hasPendingChange) { - playbackUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); + playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate); playbackInfoUpdate = new PlaybackInfoUpdate(playbackInfo); } } @@ -1279,6 +1277,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mediaPeriodId, playbackInfo.playWhenReady, playbackInfo.playbackSuppressionReason, + playbackInfo.playbackSpeed, startPositionUs, /* totalBufferedDurationUs= */ 0, startPositionUs); @@ -1964,7 +1963,8 @@ import java.util.concurrent.atomic.AtomicBoolean; private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand) throws ExoPlaybackException { - playbackUpdateListener.onPlaybackSpeedChange(playbackSpeed, acknowledgeCommand); + playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeCommand ? 1 : 0); + playbackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed); updateTrackSelectionPlaybackSpeed(playbackSpeed); for (Renderer renderer : renderers) { if (renderer != null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java index 6ad8adf7f4..4893aa1230 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlaybackInfo.java @@ -63,6 +63,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; public final boolean playWhenReady; /** Reason why playback is suppressed even though {@link #playWhenReady} is {@code true}. */ @PlaybackSuppressionReason public final int playbackSuppressionReason; + /** The playback speed. */ + public final float playbackSpeed; /** * Position up to which media is buffered in {@link #loadingMediaPeriodId) relative to the start @@ -101,6 +103,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; PLACEHOLDER_MEDIA_PERIOD_ID, /* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE, + Player.DEFAULT_PLAYBACK_SPEED, /* bufferedPositionUs= */ 0, /* totalBufferedDurationUs= */ 0, /* positionUs= */ 0); @@ -133,6 +136,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; MediaPeriodId loadingMediaPeriodId, boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason, + float playbackSpeed, long bufferedPositionUs, long totalBufferedDurationUs, long positionUs) { @@ -147,6 +151,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; this.loadingMediaPeriodId = loadingMediaPeriodId; this.playWhenReady = playWhenReady; this.playbackSuppressionReason = playbackSuppressionReason; + this.playbackSpeed = playbackSpeed; this.bufferedPositionUs = bufferedPositionUs; this.totalBufferedDurationUs = totalBufferedDurationUs; this.positionUs = positionUs; @@ -190,6 +195,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -215,6 +221,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -240,6 +247,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -265,6 +273,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -290,6 +299,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -315,6 +325,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); @@ -344,6 +355,33 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; loadingMediaPeriodId, playWhenReady, playbackSuppressionReason, + playbackSpeed, + bufferedPositionUs, + totalBufferedDurationUs, + positionUs); + } + + /** + * Copies playback info with new playback speed. + * + * @param playbackSpeed New playback speed. See {@link #playbackSpeed}. + * @return Copied playback info with new playback speed. + */ + @CheckResult + public PlaybackInfo copyWithPlaybackSpeed(float playbackSpeed) { + return new PlaybackInfo( + timeline, + periodId, + requestedContentPositionUs, + playbackState, + playbackError, + isLoading, + trackGroups, + trackSelectorResult, + loadingMediaPeriodId, + playWhenReady, + playbackSuppressionReason, + playbackSpeed, bufferedPositionUs, totalBufferedDurationUs, positionUs); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 339351fa8f..a648ef7fb1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -62,6 +62,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.AdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.testutil.Action; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; @@ -82,6 +83,7 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit import com.google.android.exoplayer2.testutil.FakeTrackSelection; import com.google.android.exoplayer2.testutil.FakeTrackSelector; import com.google.android.exoplayer2.testutil.TestExoPlayer; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.Allocation; @@ -3333,15 +3335,29 @@ public final class ExoPlayerTest { } @Test - public void setPlaybackParametersConsecutivelyNotifiesListenerForEveryChangeOnce() + public void setPlaybackParametersConsecutivelyNotifiesListenerForEveryChangeOnceAndIsMasked() throws Exception { + List maskedPlaybackSpeeds = new ArrayList<>(); + Action getPlaybackSpeedAction = + new Action("getPlaybackSpeed", /* description= */ null) { + @Override + protected void doActionImpl( + SimpleExoPlayer player, + DefaultTrackSelector trackSelector, + @Nullable Surface surface) { + maskedPlaybackSpeeds.add(player.getPlaybackSpeed()); + } + }; ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG) .pause() .waitForPlaybackState(Player.STATE_READY) .setPlaybackSpeed(1.1f) + .apply(getPlaybackSpeedAction) .setPlaybackSpeed(1.2f) + .apply(getPlaybackSpeedAction) .setPlaybackSpeed(1.3f) + .apply(getPlaybackSpeedAction) .play() .build(); List reportedPlaybackSpeeds = new ArrayList<>(); @@ -3360,6 +3376,7 @@ public final class ExoPlayerTest { .blockUntilEnded(TIMEOUT_MS); assertThat(reportedPlaybackSpeeds).containsExactly(1.1f, 1.2f, 1.3f).inOrder(); + assertThat(maskedPlaybackSpeeds).isEqualTo(reportedPlaybackSpeeds); } @Test diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 1df27bc07b..7ec39f9d72 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -437,6 +437,7 @@ public final class MediaPeriodQueueTest { /* loadingMediaPeriodId= */ null, /* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE, + /* playbackSpeed= */ Player.DEFAULT_PLAYBACK_SPEED, /* bufferedPositionUs= */ 0, /* totalBufferedDurationUs= */ 0, /* positionUs= */ 0);