From 6153b6d740633a0b56a33243137de45e98be8580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Sun, 9 Jun 2024 16:17:01 +0200 Subject: [PATCH 1/4] Add the `ExoPlayer.Builder.setMaxSeekToPreviousPosition(long)` method This method allows customizing the maximum position when using `Player.seekToPrevious()`. This commit also adds two new methods to `TestExoPlayerBuilder`: - `setMaxSeekToPreviousPosition(long)` - `getMaxSeekToPreviousPosition()` --- .../androidx/media3/exoplayer/ExoPlayer.java | 20 +++++++++++++++++++ .../media3/exoplayer/ExoPlayerImpl.java | 4 +++- .../test/utils/TestExoPlayerBuilder.java | 20 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 08428368ef..86cf79948c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -495,6 +495,7 @@ public interface ExoPlayer extends Player { /* package */ SeekParameters seekParameters; /* package */ long seekBackIncrementMs; /* package */ long seekForwardIncrementMs; + /* package */ long maxSeekToPreviousPositionMs; /* package */ LivePlaybackSpeedControl livePlaybackSpeedControl; /* package */ long releaseTimeoutMs; /* package */ long detachSurfaceTimeoutMs; @@ -702,6 +703,7 @@ public interface ExoPlayer extends Player { seekParameters = SeekParameters.DEFAULT; seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS; seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; + maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build(); clock = Clock.DEFAULT; releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS; @@ -1124,6 +1126,24 @@ public interface ExoPlayer extends Player { return this; } + /** + * Sets the maximum position for which {@link #seekToPrevious()} seeks to the previous {@link + * MediaItem}. + * + * @param maxSeekToPreviousPositionMs The maximum position, in milliseconds. + * @return This builder. + * @throws IllegalArgumentException If {@code maxSeekToPreviousPositionMs} is negative. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + @CanIgnoreReturnValue + @UnstableApi + public Builder setMaxSeekToPreviousPosition(long maxSeekToPreviousPositionMs) { + checkArgument(maxSeekToPreviousPositionMs >= 0L); + checkState(!buildCalled); + this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; + return this; + } + /** * Sets a timeout for calls to {@link #release} and {@link #setForegroundMode}. * diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index edcde1d4cc..fed65154a9 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -174,6 +174,7 @@ import java.util.concurrent.TimeoutException; private final BandwidthMeter bandwidthMeter; private final long seekBackIncrementMs; private final long seekForwardIncrementMs; + private final long maxSeekToPreviousPositionMs; private final Clock clock; private final ComponentListener componentListener; private final FrameMetadataListener frameMetadataListener; @@ -286,6 +287,7 @@ import java.util.concurrent.TimeoutException; this.seekParameters = builder.seekParameters; this.seekBackIncrementMs = builder.seekBackIncrementMs; this.seekForwardIncrementMs = builder.seekForwardIncrementMs; + this.maxSeekToPreviousPositionMs = builder.maxSeekToPreviousPositionMs; this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems; this.applicationLooper = builder.looper; this.clock = builder.clock; @@ -972,7 +974,7 @@ import java.util.concurrent.TimeoutException; @Override public long getMaxSeekToPreviousPosition() { verifyApplicationThread(); - return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; + return maxSeekToPreviousPositionMs; } @Override diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java index 73bdb26ea4..79d438bee0 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java @@ -54,6 +54,7 @@ public class TestExoPlayerBuilder { private @MonotonicNonNull Looper looper; private long seekBackIncrementMs; private long seekForwardIncrementMs; + private long maxSeekToPreviousPositionMs; private boolean deviceVolumeControlEnabled; private boolean suppressPlaybackWhenUnsuitableOutput; @Nullable private ExoPlayer.PreloadConfiguration preloadConfiguration; @@ -71,6 +72,7 @@ public class TestExoPlayerBuilder { } seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS; seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; + maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; deviceVolumeControlEnabled = false; } @@ -316,6 +318,23 @@ public class TestExoPlayerBuilder { return seekForwardIncrementMs; } + /** + * Sets the max seek to previous position, in milliseconds, to be used by the player. + * + * @param maxSeekToPreviousPositionMs The max seek to previous position to be used by the player. + * @return This builder. + */ + @CanIgnoreReturnValue + public TestExoPlayerBuilder setMaxSeekToPreviousPosition(long maxSeekToPreviousPositionMs) { + this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; + return this; + } + + /** Returns the max seek to previous position used by the player. */ + public long getMaxSeekToPreviousPosition() { + return maxSeekToPreviousPositionMs; + } + /** * See {@link ExoPlayer.Builder#setSuppressPlaybackOnUnsuitableOutput(boolean)} for details. * @@ -379,6 +398,7 @@ public class TestExoPlayerBuilder { .setLooper(looper) .setSeekBackIncrementMs(seekBackIncrementMs) .setSeekForwardIncrementMs(seekForwardIncrementMs) + .setMaxSeekToPreviousPosition(maxSeekToPreviousPositionMs) .setDeviceVolumeControlEnabled(deviceVolumeControlEnabled) .setSuppressPlaybackOnUnsuitableOutput(suppressPlaybackWhenUnsuitableOutput) .experimentalSetDynamicSchedulingEnabled(dynamicSchedulingEnabled); From 424d2a52fec1340fd1bc28bb5ddec1a4e7605140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Sun, 9 Jun 2024 16:23:56 +0200 Subject: [PATCH 2/4] Support setting the max seek to previous position on `CastPlayer` --- .../java/androidx/media3/cast/CastPlayer.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java index f18fde331c..56309d8de4 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java @@ -124,6 +124,7 @@ public final class CastPlayer extends BasePlayer { private final MediaItemConverter mediaItemConverter; private final long seekBackIncrementMs; private final long seekForwardIncrementMs; + private final long maxSeekToPreviousPositionMs; // TODO: Allow custom implementations of CastTimelineTracker. private final CastTimelineTracker timelineTracker; private final Timeline.Period period; @@ -200,11 +201,40 @@ public final class CastPlayer extends BasePlayer { MediaItemConverter mediaItemConverter, @IntRange(from = 1) long seekBackIncrementMs, @IntRange(from = 1) long seekForwardIncrementMs) { + this( + castContext, + mediaItemConverter, + seekBackIncrementMs, + seekForwardIncrementMs, + C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); + } + + /** + * Creates a new cast player. + * + * @param castContext The context from which the cast session is obtained. + * @param mediaItemConverter The {@link MediaItemConverter} to use. + * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds. + * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds. + * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} + * seeks to the previous {@link MediaItem}, in milliseconds. + * @throws IllegalArgumentException If {@code seekBackIncrementMs} or {@code + * seekForwardIncrementMs} is non-positive, or if {@code maxSeekToPreviousPositionMs} is + * negative. + */ + public CastPlayer( + CastContext castContext, + MediaItemConverter mediaItemConverter, + @IntRange(from = 1) long seekBackIncrementMs, + @IntRange(from = 1) long seekForwardIncrementMs, + @IntRange(from = 0) long maxSeekToPreviousPositionMs) { checkArgument(seekBackIncrementMs > 0 && seekForwardIncrementMs > 0); + checkArgument(maxSeekToPreviousPositionMs >= 0L); this.castContext = castContext; this.mediaItemConverter = mediaItemConverter; this.seekBackIncrementMs = seekBackIncrementMs; this.seekForwardIncrementMs = seekForwardIncrementMs; + this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; timelineTracker = new CastTimelineTracker(mediaItemConverter); period = new Timeline.Period(); statusListener = new StatusListener(); @@ -481,7 +511,7 @@ public final class CastPlayer extends BasePlayer { @Override public long getMaxSeekToPreviousPosition() { - return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; + return maxSeekToPreviousPositionMs; } @Override From 63103978beda87bfeb22d36385bf0a7c87937fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Muller?= Date: Sun, 9 Jun 2024 16:31:26 +0200 Subject: [PATCH 3/4] Make `MediaControllerImplLegacy.getMaxSeekToPreviousPosition()` return the correct value --- .../java/androidx/media3/session/MediaControllerImplLegacy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index 2f29b9465f..f3d3ee810e 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -938,7 +938,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; @Override public long getMaxSeekToPreviousPosition() { - return 0L; + return controllerInfo.playerInfo.maxSeekToPreviousPositionMs; } @Override From cd2250b5fa840c461f2a24b47850d1a70e41358f Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 14 Jun 2024 16:43:52 +0100 Subject: [PATCH 4/4] Formatting fixes and additional plumbin in legacy controller --- RELEASENOTES.md | 3 + .../java/androidx/media3/cast/CastPlayer.java | 2 +- .../androidx/media3/exoplayer/ExoPlayer.java | 4 +- .../media3/exoplayer/ExoPlayerTest.java | 14 +++-- .../media3/session/ConnectionState.java | 4 +- .../session/MediaControllerImplLegacy.java | 9 ++- .../media3/session/MediaControllerStub.java | 31 +++++++--- .../media3/session/MediaSessionStub.java | 2 +- .../androidx/media3/session/PlayerInfo.java | 51 ++++++++++++----- .../media3/session/PlayerInfoTest.java | 57 +++++++++++++++---- .../test/utils/TestExoPlayerBuilder.java | 4 +- 11 files changed, 138 insertions(+), 43 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 739fe7419a..edddbf001f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,9 @@ * ExoPlayer: * Add `ExoPlayer.isReleased()` to check whether `Exoplayer.release()` has been called. + * Add `ExoPlayer.Builder.setMaxSeekToPreviousPositionMs` to configure the + maximum position for which `seekToPrevious()` seeks to the previous item + ([#1425](https://github.com/androidx/media/issues/1425)). * Transformer: * Remove `ExportResult.processedInputs` field. If you use this field for codec details, then use `DefaultDecoderFactory.listener` instead. In diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java index 56309d8de4..ab318c999a 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java @@ -217,7 +217,7 @@ public final class CastPlayer extends BasePlayer { * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds. * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds. * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} - * seeks to the previous {@link MediaItem}, in milliseconds. + * seeks to the previous {@link MediaItem}, in milliseconds. * @throws IllegalArgumentException If {@code seekBackIncrementMs} or {@code * seekForwardIncrementMs} is non-positive, or if {@code maxSeekToPreviousPositionMs} is * negative. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 86cf79948c..9433a68002 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -543,6 +543,7 @@ public interface ExoPlayer extends Player { *
  • {@link SeekParameters}: {@link SeekParameters#DEFAULT} *
  • {@code seekBackIncrementMs}: {@link C#DEFAULT_SEEK_BACK_INCREMENT_MS} *
  • {@code seekForwardIncrementMs}: {@link C#DEFAULT_SEEK_FORWARD_INCREMENT_MS} + *
  • {@code maxSeekToPreviousPositionMs}: {@link C#DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS} *
  • {@code releaseTimeoutMs}: {@link #DEFAULT_RELEASE_TIMEOUT_MS} *
  • {@code detachSurfaceTimeoutMs}: {@link #DEFAULT_DETACH_SURFACE_TIMEOUT_MS} *
  • {@code pauseAtEndOfMediaItems}: {@code false} @@ -1137,7 +1138,8 @@ public interface ExoPlayer extends Player { */ @CanIgnoreReturnValue @UnstableApi - public Builder setMaxSeekToPreviousPosition(long maxSeekToPreviousPositionMs) { + public Builder setMaxSeekToPreviousPositionMs( + @IntRange(from = 0) long maxSeekToPreviousPositionMs) { checkArgument(maxSeekToPreviousPositionMs >= 0L); checkState(!buildCalled); this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index 8ce6082f24..3565f0b6a1 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -12990,9 +12990,12 @@ public class ExoPlayerTest { @Test public void seekToPrevious_withPreviousWindowAndCloseToStart_seeksToPreviousWindow() { - ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build(); + ExoPlayer player = + parameterizeTestExoPlayerBuilder( + new TestExoPlayerBuilder(context).setMaxSeekToPreviousPositionMs(10_000)) + .build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); + player.seekTo(/* mediaItemIndex= */ 1, 10_000); player.seekToPrevious(); @@ -13004,9 +13007,12 @@ public class ExoPlayerTest { @Test public void seekToPrevious_notCloseToStart_seeksToZero() { - ExoPlayer player = parameterizeTestExoPlayerBuilder(new TestExoPlayerBuilder(context)).build(); + ExoPlayer player = + parameterizeTestExoPlayerBuilder( + new TestExoPlayerBuilder(context).setMaxSeekToPreviousPositionMs(10_000)) + .build(); player.addMediaSources(ImmutableList.of(new FakeMediaSource(), new FakeMediaSource())); - player.seekTo(/* mediaItemIndex= */ 1, C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS + 1); + player.seekTo(/* mediaItemIndex= */ 1, 10_001); player.seekToPrevious(); diff --git a/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java b/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java index d34abaa0a2..f6dd0a9d39 100644 --- a/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java +++ b/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java @@ -173,7 +173,9 @@ import java.util.List; @Nullable Bundle sessionExtras = bundle.getBundle(FIELD_SESSION_EXTRAS); @Nullable Bundle playerInfoBundle = bundle.getBundle(FIELD_PLAYER_INFO); PlayerInfo playerInfo = - playerInfoBundle == null ? PlayerInfo.DEFAULT : PlayerInfo.fromBundle(playerInfoBundle); + playerInfoBundle == null + ? PlayerInfo.DEFAULT + : PlayerInfo.fromBundle(playerInfoBundle, sessionInterfaceVersion); return new ConnectionState( libraryVersion, sessionInterfaceVersion, diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index f3d3ee810e..775e811442 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -2132,6 +2132,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; LegacyConversions.convertToIsDeviceMuted(newLegacyPlayerInfo.playbackInfoCompat); long seekBackIncrementMs = oldControllerInfo.playerInfo.seekBackIncrementMs; long seekForwardIncrementMs = oldControllerInfo.playerInfo.seekForwardIncrementMs; + long maxSeekToPreviousPositionMs = oldControllerInfo.playerInfo.maxSeekToPreviousPositionMs; return createControllerInfo( currentTimeline, @@ -2161,7 +2162,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; deviceVolume, deviceMuted, seekBackIncrementMs, - seekForwardIncrementMs); + seekForwardIncrementMs, + maxSeekToPreviousPositionMs); } /** @@ -2330,7 +2332,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; int deviceVolume, boolean deviceMuted, long seekBackIncrementMs, - long seekForwardIncrementMs) { + long seekForwardIncrementMs, + long maxSeekToPreviousPositionMs) { @Nullable MediaItem currentMediaItem = currentTimeline.getMediaItemAt(currentMediaItemIndex); PositionInfo positionInfo = @@ -2379,7 +2382,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization; /* mediaMetadata= */ mediaMetadata, seekBackIncrementMs, seekForwardIncrementMs, - /* maxSeekToPreviousPositionMs= */ 0L, + maxSeekToPreviousPositionMs, /* currentTracks= */ Tracks.EMPTY, /* parameters= */ TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java index 320c4339b4..57c6d99d48 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.os.RemoteException; import android.text.TextUtils; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Player.Commands; import androidx.media3.common.util.BundleCollectionUtil; import androidx.media3.common.util.Log; @@ -38,7 +39,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; private static final String TAG = "MediaControllerStub"; /** The version of the IMediaController interface. */ - public static final int VERSION_INT = 5; + public static final int VERSION_INT = 6; private final WeakReference controller; @@ -112,14 +113,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; } List layout; try { - @Nullable MediaControllerImplBase controller = this.controller.get(); - @Nullable - SessionToken connectedToken = controller == null ? null : controller.getConnectedToken(); - if (connectedToken == null) { + int sessionInterfaceVersion = getSessionInterfaceVersion(); + if (sessionInterfaceVersion == C.INDEX_UNSET) { // Stale event. return; } - int sessionInterfaceVersion = connectedToken.getInterfaceVersion(); layout = BundleCollectionUtil.fromBundleList( bundle -> CommandButton.fromBundle(bundle, sessionInterfaceVersion), @@ -239,7 +237,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; } PlayerInfo playerInfo; try { - playerInfo = PlayerInfo.fromBundle(playerInfoBundle); + int sessionInterfaceVersion = getSessionInterfaceVersion(); + if (sessionInterfaceVersion == C.INDEX_UNSET) { + // Stale event. + return; + } + playerInfo = PlayerInfo.fromBundle(playerInfoBundle, sessionInterfaceVersion); } catch (RuntimeException e) { Log.w(TAG, "Ignoring malformed Bundle for PlayerInfo", e); return; @@ -375,6 +378,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; } } + /** Returns session interface version or {@link C#INDEX_UNSET} for stale events. */ + private int getSessionInterfaceVersion() { + @Nullable MediaControllerImplBase controller = this.controller.get(); + if (controller == null) { + return C.INDEX_UNSET; + } + @Nullable SessionToken connectedToken = controller.getConnectedToken(); + if (connectedToken == null) { + // Stale event. + return C.INDEX_UNSET; + } + return connectedToken.getInterfaceVersion(); + } + /* @FunctionalInterface */ private interface ControllerTask { diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java index 619413e36f..b032a38924 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java @@ -118,7 +118,7 @@ import java.util.concurrent.ExecutionException; private static final String TAG = "MediaSessionStub"; /** The version of the IMediaSession interface. */ - public static final int VERSION_INT = 3; + public static final int VERSION_INT = 4; /** * Sequence number used when a controller method is triggered on the sesison side that wasn't diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java b/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java index d1c15f7a3a..5dda00e9e8 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java @@ -30,6 +30,7 @@ import androidx.annotation.FloatRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.AudioAttributes; +import androidx.media3.common.C; import androidx.media3.common.DeviceInfo; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaMetadata; @@ -457,9 +458,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; /* isPlaying= */ false, /* isLoading= */ false, MediaMetadata.EMPTY, - /* seekBackIncrementMs= */ 0, - /* seekForwardIncrementMs= */ 0, - /* maxSeekToPreviousPositionMs= */ 0, + /* seekBackIncrementMs= */ C.DEFAULT_SEEK_BACK_INCREMENT_MS, + /* seekForwardIncrementMs= */ C.DEFAULT_SEEK_FORWARD_INCREMENT_MS, + /* maxSeekToPreviousPositionMs= */ C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS, /* currentTracks= */ Tracks.EMPTY, TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT); @@ -816,9 +817,16 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; private static final String FIELD_DISCONTINUITY_REASON = Util.intToStringMaxRadix(23); private static final String FIELD_CUE_GROUP = Util.intToStringMaxRadix(24); private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(25); - private static final String FIELD_SEEK_BACK_INCREMENT_MS = Util.intToStringMaxRadix(26); - private static final String FIELD_SEEK_FORWARD_INCREMENT_MS = Util.intToStringMaxRadix(27); - private static final String FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS = Util.intToStringMaxRadix(28); + + @VisibleForTesting + static final String FIELD_SEEK_BACK_INCREMENT_MS = Util.intToStringMaxRadix(26); + + @VisibleForTesting + static final String FIELD_SEEK_FORWARD_INCREMENT_MS = Util.intToStringMaxRadix(27); + + @VisibleForTesting + static final String FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS = Util.intToStringMaxRadix(28); + private static final String FIELD_TRACK_SELECTION_PARAMETERS = Util.intToStringMaxRadix(29); private static final String FIELD_CURRENT_TRACKS = Util.intToStringMaxRadix(30); private static final String FIELD_TIMELINE_CHANGE_REASON = Util.intToStringMaxRadix(31); @@ -977,13 +985,19 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; if (!mediaMetadata.equals(MediaMetadata.EMPTY)) { bundle.putBundle(FIELD_MEDIA_METADATA, mediaMetadata.toBundle()); } - if (seekBackIncrementMs != 0) { + long defaultSeekBackIncrementMs = + controllerInterfaceVersion < 6 ? 0 : C.DEFAULT_SEEK_BACK_INCREMENT_MS; + if (seekBackIncrementMs != defaultSeekBackIncrementMs) { bundle.putLong(FIELD_SEEK_BACK_INCREMENT_MS, seekBackIncrementMs); } - if (seekForwardIncrementMs != 0) { + long defaultSeekForwardIncrementMs = + controllerInterfaceVersion < 6 ? 0 : C.DEFAULT_SEEK_FORWARD_INCREMENT_MS; + if (seekForwardIncrementMs != defaultSeekForwardIncrementMs) { bundle.putLong(FIELD_SEEK_FORWARD_INCREMENT_MS, seekForwardIncrementMs); } - if (maxSeekToPreviousPositionMs != 0) { + long defaultMaxSeekToPreviousPositionMs = + controllerInterfaceVersion < 6 ? 0 : C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; + if (maxSeekToPreviousPositionMs != defaultMaxSeekToPreviousPositionMs) { bundle.putLong(FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS, maxSeekToPreviousPositionMs); } if (!currentTracks.equals(Tracks.EMPTY)) { @@ -996,7 +1010,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; } /** Restores a {@code PlayerInfo} from a {@link Bundle}. */ - public static PlayerInfo fromBundle(Bundle bundle) { + public static PlayerInfo fromBundle(Bundle bundle, int sessionInterfaceVersion) { @Nullable IBinder inProcessBinder = bundle.getBinder(FIELD_IN_PROCESS_BINDER); if (inProcessBinder instanceof InProcessBinder) { return ((InProcessBinder) inProcessBinder).getPlayerInfo(); @@ -1080,11 +1094,22 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; mediaMetadataBundle == null ? MediaMetadata.EMPTY : MediaMetadata.fromBundle(mediaMetadataBundle); - long seekBackIncrementMs = bundle.getLong(FIELD_SEEK_BACK_INCREMENT_MS, /* defaultValue= */ 0); + long seekBackIncrementMs = + bundle.getLong( + FIELD_SEEK_BACK_INCREMENT_MS, + /* defaultValue= */ sessionInterfaceVersion < 4 ? 0 : C.DEFAULT_SEEK_BACK_INCREMENT_MS); long seekForwardIncrementMs = - bundle.getLong(FIELD_SEEK_FORWARD_INCREMENT_MS, /* defaultValue= */ 0); + bundle.getLong( + FIELD_SEEK_FORWARD_INCREMENT_MS, + /* defaultValue= */ sessionInterfaceVersion < 4 + ? 0 + : C.DEFAULT_SEEK_FORWARD_INCREMENT_MS); long maxSeekToPreviousPosition = - bundle.getLong(FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS, /* defaultValue= */ 0); + bundle.getLong( + FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS, + /* defaultValue= */ sessionInterfaceVersion < 4 + ? 0 + : C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS); Bundle currentTracksBundle = bundle.getBundle(FIELD_CURRENT_TRACKS); Tracks currentTracks = currentTracksBundle == null ? Tracks.EMPTY : Tracks.fromBundle(currentTracksBundle); diff --git a/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java b/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java index fd0520e1c0..c95f7f46df 100644 --- a/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java @@ -163,7 +163,8 @@ public class PlayerInfoTest { .setVideoSize(new VideoSize(/* width= */ 1024, /* height= */ 768)) .build(); - PlayerInfo infoAfterBundling = PlayerInfo.fromBundle(playerInfo.toBundleInProcess()); + PlayerInfo infoAfterBundling = + PlayerInfo.fromBundle(playerInfo.toBundleInProcess(), MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5); assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4); @@ -292,7 +293,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5); assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4); @@ -413,7 +415,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ true, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(0); assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(0); @@ -482,7 +485,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.mediaMetadata).isEqualTo(MediaMetadata.EMPTY); assertThat(infoAfterBundling.playlistMetadata).isEqualTo(MediaMetadata.EMPTY); @@ -502,7 +506,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.volume).isEqualTo(1f); } @@ -522,7 +527,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.deviceVolume).isEqualTo(0); assertThat(infoAfterBundling.deviceMuted).isFalse(); @@ -546,7 +552,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.audioAttributes).isEqualTo(AudioAttributes.DEFAULT); } @@ -568,7 +575,8 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ false) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.cueGroup).isEqualTo(CueGroup.EMPTY_TIME_ZERO); } @@ -598,14 +606,16 @@ public class PlayerInfoTest { .build(), /* excludeTimeline= */ false, /* excludeTracks= */ true) - .toBundleInProcess()); + .toBundleInProcess(), + MediaSessionStub.VERSION_INT); assertThat(infoAfterBundling.currentTracks).isEqualTo(Tracks.EMPTY); } @Test public void toBundleFromBundle_withDefaultValues_restoresAllData() { - PlayerInfo roundTripValue = PlayerInfo.fromBundle(PlayerInfo.DEFAULT.toBundleInProcess()); + PlayerInfo roundTripValue = + PlayerInfo.fromBundle(PlayerInfo.DEFAULT.toBundleInProcess(), MediaSessionStub.VERSION_INT); assertThat(roundTripValue.oldPositionInfo).isEqualTo(PlayerInfo.DEFAULT.oldPositionInfo); assertThat(roundTripValue.newPositionInfo).isEqualTo(PlayerInfo.DEFAULT.newPositionInfo); @@ -659,6 +669,21 @@ public class PlayerInfoTest { assertThat(bundle.isEmpty()).isTrue(); } + @Test + public void + toBundleForRemoteProcess_withDefaultValuesForControllerInterfaceBefore6_includesSeekLimits() { + // Controller before version 6 uses 0 values for the three seek limit default values. The + // Bundle should include these to overwrite the presumed 0 on the controller side. + Bundle bundle = + PlayerInfo.DEFAULT.toBundleForRemoteProcess(/* controllerInterfaceVersion= */ 5); + + assertThat(bundle.keySet()) + .containsAtLeast( + PlayerInfo.FIELD_SEEK_BACK_INCREMENT_MS, + PlayerInfo.FIELD_SEEK_FORWARD_INCREMENT_MS, + PlayerInfo.FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS); + } + @Test public void toBundleForRemoteProcess_withDefaultValuesForControllerInterfaceBefore3_includesPositionInfos() { @@ -673,4 +698,16 @@ public class PlayerInfoTest { PlayerInfo.FIELD_NEW_POSITION_INFO, PlayerInfo.FIELD_OLD_POSITION_INFO); } + + @Test + public void fromBundle_withEmptyBundleForSessionInterfaceBefore4_restoresSeekLimitsAsZero() { + // Session before version 4 uses 0 values for the three seek limit default values. We need to + // restore those instead of current default. + + PlayerInfo playerInfo = PlayerInfo.fromBundle(Bundle.EMPTY, /* sessionInterfaceVersion= */ 3); + + assertThat(playerInfo.seekBackIncrementMs).isEqualTo(0); + assertThat(playerInfo.seekForwardIncrementMs).isEqualTo(0); + assertThat(playerInfo.maxSeekToPreviousPositionMs).isEqualTo(0); + } } diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java index 79d438bee0..61cf20e26a 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestExoPlayerBuilder.java @@ -325,7 +325,7 @@ public class TestExoPlayerBuilder { * @return This builder. */ @CanIgnoreReturnValue - public TestExoPlayerBuilder setMaxSeekToPreviousPosition(long maxSeekToPreviousPositionMs) { + public TestExoPlayerBuilder setMaxSeekToPreviousPositionMs(long maxSeekToPreviousPositionMs) { this.maxSeekToPreviousPositionMs = maxSeekToPreviousPositionMs; return this; } @@ -398,7 +398,7 @@ public class TestExoPlayerBuilder { .setLooper(looper) .setSeekBackIncrementMs(seekBackIncrementMs) .setSeekForwardIncrementMs(seekForwardIncrementMs) - .setMaxSeekToPreviousPosition(maxSeekToPreviousPositionMs) + .setMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs) .setDeviceVolumeControlEnabled(deviceVolumeControlEnabled) .setSuppressPlaybackOnUnsuitableOutput(suppressPlaybackWhenUnsuitableOutput) .experimentalSetDynamicSchedulingEnabled(dynamicSchedulingEnabled);