From 49b57b8da390e5fd42ea97df6447cad1c4df1657 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Apr 2025 06:03:03 -0700 Subject: [PATCH] Integrate `PlayerControlView` with new scrubbing mode This also tweaks the logic in `Util.shouldShowPlayButton` to special-case the 'scrubbing' suppression reason. In most cases of playback suppression (e.g. transient loss of audio focus due to a phone call), the recommended UX is to show a 'play' button (i.e. the UI looks like the player is paused). This doesn't look right when scrubbing since although 'ongoing' playback is suppressed, the image on screen is constantly changing, so a 'pause' button is kept (i.e. the UI looks like the player is playing). PiperOrigin-RevId: 751385521 --- RELEASENOTES.md | 7 ++ .../androidx/media3/common/util/Util.java | 4 +- libraries/ui/proguard-rules.txt | 2 + .../androidx/media3/ui/PlayerControlView.java | 93 ++++++++++++++++++- .../java/androidx/media3/ui/PlayerView.java | 13 +++ libraries/ui/src/main/res/values/attrs.xml | 3 + 6 files changed, 119 insertions(+), 3 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b8905d66dc..cedebcb51b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -64,6 +64,13 @@ being enabled). Any changes made to the Player outside of the observation period are now picked up ([#2313](https://github.com/androidx/media/issues/2313)). + * Add support for ExoPlayer's scrubbing mode to `PlayerControlView`. When + enabled, this puts the player into scrubbing mode when the user starts + dragging the scrubber bar, issues a `player.seekTo` call for every + movement, and then exits scrubbing mode when the touch is lifted from + the screen. This integration can be enabled with either + `time_bar_scrubbing_enabled = true` in XML or the + `setTimeBarScrubbingEnabled(boolean)` method from Java/Kotlin. * Downloads: * Add partial download support for progressive streams. Apps can prepare a progressive stream with `DownloadHelper`, and request a diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index 4de9b0ca95..6734f41d0f 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -3713,7 +3713,9 @@ public final class Util { || player.getPlaybackState() == Player.STATE_IDLE || player.getPlaybackState() == Player.STATE_ENDED || (shouldShowPlayIfSuppressed - && player.getPlaybackSuppressionReason() != Player.PLAYBACK_SUPPRESSION_REASON_NONE); + && player.getPlaybackSuppressionReason() != Player.PLAYBACK_SUPPRESSION_REASON_NONE + && player.getPlaybackSuppressionReason() + != Player.PLAYBACK_SUPPRESSION_REASON_SCRUBBING); } /** diff --git a/libraries/ui/proguard-rules.txt b/libraries/ui/proguard-rules.txt index c6f7553c44..288c5d050a 100644 --- a/libraries/ui/proguard-rules.txt +++ b/libraries/ui/proguard-rules.txt @@ -12,6 +12,8 @@ -keepnames class androidx.media3.exoplayer.ExoPlayer {} -keepclassmembers class androidx.media3.exoplayer.ExoPlayer { void setImageOutput(androidx.media3.exoplayer.image.ImageOutput); + void setScrubbingModeEnabled(boolean); + boolean isScrubbingModeEnabled(); } -keepclasseswithmembers class androidx.media3.exoplayer.image.ImageOutput { void onImageAvailable(long, android.graphics.Bitmap); diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java index 5456b4847f..f901b26825 100644 --- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java +++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java @@ -79,12 +79,15 @@ import androidx.media3.common.TrackSelectionOverride; import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.Tracks; import androidx.media3.common.util.Assertions; +import androidx.media3.common.util.Log; import androidx.media3.common.util.RepeatModeUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.common.collect.ImmutableList; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -92,6 +95,7 @@ import java.util.Formatter; import java.util.List; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; /** * A view for controlling {@link Player} instances. @@ -156,6 +160,14 @@ import java.util.concurrent.CopyOnWriteArrayList; *
  • Corresponding method: {@link #setAnimationEnabled(boolean)} *
  • Default: true * + *
  • {@code time_bar_scrubbing_enabled} - Whether the time bar should {@linkplain + * Player#seekTo seek} immediately as the user drags the scrubber around (true), or only seek + * when the user releases the scrubber (false). This can only be used if the {@linkplain + * #setPlayer connected player} is an instance of {@code androidx.media3.exoplayer.ExoPlayer}. + * *
  • {@code time_bar_min_update_interval} - Specifies the minimum interval between time * bar position updates. *