diff --git a/libraries/common/src/main/java/androidx/media3/common/Player.java b/libraries/common/src/main/java/androidx/media3/common/Player.java index 3a631280ca..9c444e5d97 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -31,6 +31,7 @@ import androidx.annotation.FloatRange; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Size; @@ -350,15 +351,9 @@ public interface Player { return false; } PositionInfo that = (PositionInfo) o; - return mediaItemIndex == that.mediaItemIndex - && periodIndex == that.periodIndex - && positionMs == that.positionMs - && contentPositionMs == that.contentPositionMs - && adGroupIndex == that.adGroupIndex - && adIndexInAdGroup == that.adIndexInAdGroup + return equalsForBundling(that) && Objects.equal(windowUid, that.windowUid) - && Objects.equal(periodUid, that.periodUid) - && Objects.equal(mediaItem, that.mediaItem); + && Objects.equal(periodUid, that.periodUid); } @Override @@ -375,16 +370,97 @@ public interface Player { adIndexInAdGroup); } + /** + * Returns whether this position info and the other position info would result in the same + * {@link #toBundle() Bundle}. + */ + @UnstableApi + public boolean equalsForBundling(PositionInfo other) { + return mediaItemIndex == other.mediaItemIndex + && periodIndex == other.periodIndex + && positionMs == other.positionMs + && contentPositionMs == other.contentPositionMs + && adGroupIndex == other.adGroupIndex + && adIndexInAdGroup == other.adIndexInAdGroup + && Objects.equal(mediaItem, other.mediaItem); + } + // Bundleable implementation. - private static final String FIELD_MEDIA_ITEM_INDEX = Util.intToStringMaxRadix(0); + @VisibleForTesting static final String FIELD_MEDIA_ITEM_INDEX = Util.intToStringMaxRadix(0); private static final String FIELD_MEDIA_ITEM = Util.intToStringMaxRadix(1); - private static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(2); - private static final String FIELD_POSITION_MS = Util.intToStringMaxRadix(3); - private static final String FIELD_CONTENT_POSITION_MS = Util.intToStringMaxRadix(4); + @VisibleForTesting static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(2); + @VisibleForTesting static final String FIELD_POSITION_MS = Util.intToStringMaxRadix(3); + @VisibleForTesting static final String FIELD_CONTENT_POSITION_MS = Util.intToStringMaxRadix(4); private static final String FIELD_AD_GROUP_INDEX = Util.intToStringMaxRadix(5); private static final String FIELD_AD_INDEX_IN_AD_GROUP = Util.intToStringMaxRadix(6); + /** + * Returns a copy of this position info, filtered by the specified available commands. + * + *
The filtered fields are reset to their default values. + * + *
The return value may be the same object if nothing is filtered. + * + * @param canAccessCurrentMediaItem Whether {@link Player#COMMAND_GET_CURRENT_MEDIA_ITEM} is + * available. + * @param canAccessTimeline Whether {@link Player#COMMAND_GET_TIMELINE} is available. + * @return The filtered position info. + */ + @UnstableApi + public PositionInfo filterByAvailableCommands( + boolean canAccessCurrentMediaItem, boolean canAccessTimeline) { + if (canAccessCurrentMediaItem && canAccessTimeline) { + return this; + } + return new PositionInfo( + windowUid, + canAccessTimeline ? mediaItemIndex : 0, + canAccessCurrentMediaItem ? mediaItem : null, + periodUid, + canAccessTimeline ? periodIndex : 0, + canAccessCurrentMediaItem ? positionMs : 0, + canAccessCurrentMediaItem ? contentPositionMs : 0, + canAccessCurrentMediaItem ? adGroupIndex : C.INDEX_UNSET, + canAccessCurrentMediaItem ? adIndexInAdGroup : C.INDEX_UNSET); + } + + /** + * {@inheritDoc} + * + *
It omits the {@link #windowUid} and {@link #periodUid} fields. The {@link #windowUid} and + * {@link #periodUid} of an instance restored by {@link #CREATOR} will always be {@code null}. + * + * @param controllerInterfaceVersion The interface version of the media controller this Bundle + * will be sent to. + */ + @UnstableApi + public Bundle toBundle(int controllerInterfaceVersion) { + Bundle bundle = new Bundle(); + if (controllerInterfaceVersion < 3 || mediaItemIndex != 0) { + bundle.putInt(FIELD_MEDIA_ITEM_INDEX, mediaItemIndex); + } + if (mediaItem != null) { + bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); + } + if (controllerInterfaceVersion < 3 || periodIndex != 0) { + bundle.putInt(FIELD_PERIOD_INDEX, periodIndex); + } + if (controllerInterfaceVersion < 3 || positionMs != 0) { + bundle.putLong(FIELD_POSITION_MS, positionMs); + } + if (controllerInterfaceVersion < 3 || contentPositionMs != 0) { + bundle.putLong(FIELD_CONTENT_POSITION_MS, contentPositionMs); + } + if (adGroupIndex != C.INDEX_UNSET) { + bundle.putInt(FIELD_AD_GROUP_INDEX, adGroupIndex); + } + if (adIndexInAdGroup != C.INDEX_UNSET) { + bundle.putInt(FIELD_AD_INDEX_IN_AD_GROUP, adIndexInAdGroup); + } + return bundle; + } + /** * {@inheritDoc} * @@ -394,32 +470,7 @@ public interface Player { @UnstableApi @Override public Bundle toBundle() { - return toBundle(/* canAccessCurrentMediaItem= */ true, /* canAccessTimeline= */ true); - } - - /** - * Returns a {@link Bundle} representing the information stored in this object, filtered by - * available commands. - * - * @param canAccessCurrentMediaItem Whether the {@link Bundle} should contain information - * accessbile with {@link #COMMAND_GET_CURRENT_MEDIA_ITEM}. - * @param canAccessTimeline Whether the {@link Bundle} should contain information accessbile - * with {@link #COMMAND_GET_TIMELINE}. - */ - @UnstableApi - public Bundle toBundle(boolean canAccessCurrentMediaItem, boolean canAccessTimeline) { - Bundle bundle = new Bundle(); - bundle.putInt(FIELD_MEDIA_ITEM_INDEX, canAccessTimeline ? mediaItemIndex : 0); - if (mediaItem != null && canAccessCurrentMediaItem) { - bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle()); - } - bundle.putInt(FIELD_PERIOD_INDEX, canAccessTimeline ? periodIndex : 0); - bundle.putLong(FIELD_POSITION_MS, canAccessCurrentMediaItem ? positionMs : 0); - bundle.putLong(FIELD_CONTENT_POSITION_MS, canAccessCurrentMediaItem ? contentPositionMs : 0); - bundle.putInt(FIELD_AD_GROUP_INDEX, canAccessCurrentMediaItem ? adGroupIndex : C.INDEX_UNSET); - bundle.putInt( - FIELD_AD_INDEX_IN_AD_GROUP, canAccessCurrentMediaItem ? adIndexInAdGroup : C.INDEX_UNSET); - return bundle; + return toBundle(Integer.MAX_VALUE); } /** Object that can restore {@link PositionInfo} from a {@link Bundle}. */ @@ -3320,7 +3371,7 @@ public interface Player { * remote device is returned. * *
Note that this method returns the volume of the device. To check the current stream volume, - * use {@link getVolume()}. + * use {@link #getVolume()}. * *
This method must only be called if {@link #COMMAND_GET_DEVICE_VOLUME} is {@linkplain * #getAvailableCommands() available}. diff --git a/libraries/common/src/main/java/androidx/media3/common/Timeline.java b/libraries/common/src/main/java/androidx/media3/common/Timeline.java index d92f586ccc..4b338ee44b 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Timeline.java +++ b/libraries/common/src/main/java/androidx/media3/common/Timeline.java @@ -1443,36 +1443,29 @@ public abstract class Timeline implements Bundleable { } /** - * Returns a {@link Bundle} containing just the specified {@link Window}. + * Returns a copy of this timeline containing just the single specified {@link Window}. * - *
The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of - * an instance restored by {@link #CREATOR} may have missing fields as described in {@link - * Window#toBundle()} and {@link Period#toBundle()}. + *
The method returns the same instance if there is only one window.
*
- * @param windowIndex The index of the {@link Window} to include in the {@link Bundle}.
+ * @param windowIndex The index of the {@link Window} to include in the copy.
+ * @return A {@link Timeline} with just the single specified {@link Window}.
*/
@UnstableApi
- public final Bundle toBundleWithOneWindowOnly(int windowIndex) {
- Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0);
-
- List The filtered fields are reset to their default values.
+ *
+ * @param availableCommands The available {@link Player.Commands} used to filter values.
+ * @param excludeTimeline Whether to filter the {@link #timeline} even if {@link
+ * Player#COMMAND_GET_TIMELINE} is available.
+ * @param excludeTracks Whether to filter the {@link #currentTracks} even if {@link
+ * Player#COMMAND_GET_TRACKS} is available.
+ * @return The filtered player info.
+ */
+ public PlayerInfo filterByAvailableCommands(
Player.Commands availableCommands, boolean excludeTimeline, boolean excludeTracks) {
- Bundle bundle = new Bundle();
+ PlayerInfo.Builder builder = new Builder(this);
boolean canAccessCurrentMediaItem =
availableCommands.contains(Player.COMMAND_GET_CURRENT_MEDIA_ITEM);
boolean canAccessTimeline = availableCommands.contains(Player.COMMAND_GET_TIMELINE);
- if (playerError != null) {
- bundle.putBundle(FIELD_PLAYBACK_ERROR, playerError.toBundle());
+ builder.setSessionPositionInfo(
+ sessionPositionInfo.filterByAvailableCommands(
+ canAccessCurrentMediaItem, canAccessTimeline));
+ builder.setOldPositionInfo(
+ oldPositionInfo.filterByAvailableCommands(canAccessCurrentMediaItem, canAccessTimeline));
+ builder.setNewPositionInfo(
+ newPositionInfo.filterByAvailableCommands(canAccessCurrentMediaItem, canAccessTimeline));
+ if (!canAccessTimeline && canAccessCurrentMediaItem && !timeline.isEmpty()) {
+ builder.setTimeline(
+ timeline.copyWithSingleWindow(sessionPositionInfo.positionInfo.mediaItemIndex));
+ } else if (excludeTimeline || !canAccessTimeline) {
+ builder.setTimeline(Timeline.EMPTY);
}
- bundle.putInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, mediaItemTransitionReason);
- bundle.putBundle(
- FIELD_SESSION_POSITION_INFO,
- sessionPositionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline));
- bundle.putBundle(
- FIELD_OLD_POSITION_INFO,
- oldPositionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline));
- bundle.putBundle(
- FIELD_NEW_POSITION_INFO,
- newPositionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline));
- bundle.putInt(FIELD_DISCONTINUITY_REASON, discontinuityReason);
- bundle.putBundle(FIELD_PLAYBACK_PARAMETERS, playbackParameters.toBundle());
- bundle.putInt(FIELD_REPEAT_MODE, repeatMode);
- bundle.putBoolean(FIELD_SHUFFLE_MODE_ENABLED, shuffleModeEnabled);
- if (!excludeTimeline && canAccessTimeline) {
- bundle.putBundle(FIELD_TIMELINE, timeline.toBundle());
- } else if (!canAccessTimeline && canAccessCurrentMediaItem && !timeline.isEmpty()) {
- bundle.putBundle(
- FIELD_TIMELINE,
- timeline.toBundleWithOneWindowOnly(sessionPositionInfo.positionInfo.mediaItemIndex));
+ if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
+ builder.setPlaylistMetadata(MediaMetadata.EMPTY);
}
- bundle.putInt(FIELD_TIMELINE_CHANGE_REASON, timelineChangeReason);
- bundle.putBundle(FIELD_VIDEO_SIZE, videoSize.toBundle());
- if (availableCommands.contains(Player.COMMAND_GET_METADATA)) {
- bundle.putBundle(FIELD_PLAYLIST_METADATA, playlistMetadata.toBundle());
+ if (!availableCommands.contains(Player.COMMAND_GET_VOLUME)) {
+ builder.setVolume(1);
}
- if (availableCommands.contains(Player.COMMAND_GET_VOLUME)) {
- bundle.putFloat(FIELD_VOLUME, volume);
+ if (!availableCommands.contains(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
+ builder.setAudioAttributes(AudioAttributes.DEFAULT);
}
- if (availableCommands.contains(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
- bundle.putBundle(FIELD_AUDIO_ATTRIBUTES, audioAttributes.toBundle());
+ if (!availableCommands.contains(Player.COMMAND_GET_TEXT)) {
+ builder.setCues(CueGroup.EMPTY_TIME_ZERO);
}
- if (availableCommands.contains(Player.COMMAND_GET_TEXT)) {
- bundle.putBundle(FIELD_CUE_GROUP, cueGroup.toBundle());
+ if (!availableCommands.contains(Player.COMMAND_GET_DEVICE_VOLUME)) {
+ builder.setDeviceVolume(0).setDeviceMuted(false);
}
- bundle.putBundle(FIELD_DEVICE_INFO, deviceInfo.toBundle());
- if (availableCommands.contains(Player.COMMAND_GET_DEVICE_VOLUME)) {
- bundle.putInt(FIELD_DEVICE_VOLUME, deviceVolume);
- bundle.putBoolean(FIELD_DEVICE_MUTED, deviceMuted);
+ if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
+ builder.setMediaMetadata(MediaMetadata.EMPTY);
}
- bundle.putBoolean(FIELD_PLAY_WHEN_READY, playWhenReady);
- bundle.putInt(FIELD_PLAYBACK_SUPPRESSION_REASON, playbackSuppressionReason);
- bundle.putInt(FIELD_PLAYBACK_STATE, playbackState);
- bundle.putBoolean(FIELD_IS_PLAYING, isPlaying);
- bundle.putBoolean(FIELD_IS_LOADING, isLoading);
- if (availableCommands.contains(Player.COMMAND_GET_METADATA)) {
- bundle.putBundle(FIELD_MEDIA_METADATA, mediaMetadata.toBundle());
+ if (excludeTracks || !availableCommands.contains(Player.COMMAND_GET_TRACKS)) {
+ builder.setCurrentTracks(Tracks.EMPTY);
}
- bundle.putLong(FIELD_SEEK_BACK_INCREMENT_MS, seekBackIncrementMs);
- bundle.putLong(FIELD_SEEK_FORWARD_INCREMENT_MS, seekForwardIncrementMs);
- bundle.putLong(FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS, maxSeekToPreviousPositionMs);
- if (!excludeTracks && availableCommands.contains(Player.COMMAND_GET_TRACKS)) {
- bundle.putBundle(FIELD_CURRENT_TRACKS, currentTracks.toBundle());
- }
- bundle.putBundle(FIELD_TRACK_SELECTION_PARAMETERS, trackSelectionParameters.toBundle());
+ return builder.build();
+ }
+
+ /**
+ * Returns a {@link Bundle} that stores a direct object reference to this class for in-process
+ * sharing.
+ */
+ public Bundle toBundleInProcess() {
+ Bundle bundle = new Bundle();
+ BundleUtil.putBinder(bundle, FIELD_IN_PROCESS_BINDER, new InProcessBinder());
return bundle;
}
@Override
public Bundle toBundle() {
- return toBundle(
- /* availableCommands= */ new Player.Commands.Builder().addAllCommands().build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false);
+ return toBundle(Integer.MAX_VALUE);
+ }
+
+ public Bundle toBundle(int controllerInterfaceVersion) {
+ Bundle bundle = new Bundle();
+ if (playerError != null) {
+ bundle.putBundle(FIELD_PLAYBACK_ERROR, playerError.toBundle());
+ }
+ if (mediaItemTransitionReason != MEDIA_ITEM_TRANSITION_REASON_DEFAULT) {
+ bundle.putInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, mediaItemTransitionReason);
+ }
+ if (controllerInterfaceVersion < 3
+ || !sessionPositionInfo.equals(SessionPositionInfo.DEFAULT)) {
+ bundle.putBundle(
+ FIELD_SESSION_POSITION_INFO, sessionPositionInfo.toBundle(controllerInterfaceVersion));
+ }
+ if (controllerInterfaceVersion < 3
+ || !SessionPositionInfo.DEFAULT_POSITION_INFO.equalsForBundling(oldPositionInfo)) {
+ bundle.putBundle(
+ FIELD_OLD_POSITION_INFO, oldPositionInfo.toBundle(controllerInterfaceVersion));
+ }
+ if (controllerInterfaceVersion < 3
+ || !SessionPositionInfo.DEFAULT_POSITION_INFO.equalsForBundling(newPositionInfo)) {
+ bundle.putBundle(
+ FIELD_NEW_POSITION_INFO, newPositionInfo.toBundle(controllerInterfaceVersion));
+ }
+ if (discontinuityReason != DISCONTINUITY_REASON_DEFAULT) {
+ bundle.putInt(FIELD_DISCONTINUITY_REASON, discontinuityReason);
+ }
+ if (!playbackParameters.equals(PlaybackParameters.DEFAULT)) {
+ bundle.putBundle(FIELD_PLAYBACK_PARAMETERS, playbackParameters.toBundle());
+ }
+ if (repeatMode != Player.REPEAT_MODE_OFF) {
+ bundle.putInt(FIELD_REPEAT_MODE, repeatMode);
+ }
+ if (shuffleModeEnabled) {
+ bundle.putBoolean(FIELD_SHUFFLE_MODE_ENABLED, shuffleModeEnabled);
+ }
+ if (!timeline.equals(Timeline.EMPTY)) {
+ bundle.putBundle(FIELD_TIMELINE, timeline.toBundle());
+ }
+ if (timelineChangeReason != TIMELINE_CHANGE_REASON_DEFAULT) {
+ bundle.putInt(FIELD_TIMELINE_CHANGE_REASON, timelineChangeReason);
+ }
+ if (!videoSize.equals(VideoSize.UNKNOWN)) {
+ bundle.putBundle(FIELD_VIDEO_SIZE, videoSize.toBundle());
+ }
+ if (!playlistMetadata.equals(MediaMetadata.EMPTY)) {
+ bundle.putBundle(FIELD_PLAYLIST_METADATA, playlistMetadata.toBundle());
+ }
+ if (volume != 1) {
+ bundle.putFloat(FIELD_VOLUME, volume);
+ }
+ if (!audioAttributes.equals(AudioAttributes.DEFAULT)) {
+ bundle.putBundle(FIELD_AUDIO_ATTRIBUTES, audioAttributes.toBundle());
+ }
+ if (!cueGroup.equals(CueGroup.EMPTY_TIME_ZERO)) {
+ bundle.putBundle(FIELD_CUE_GROUP, cueGroup.toBundle());
+ }
+ if (!deviceInfo.equals(DeviceInfo.UNKNOWN)) {
+ bundle.putBundle(FIELD_DEVICE_INFO, deviceInfo.toBundle());
+ }
+ if (deviceVolume != 0) {
+ bundle.putInt(FIELD_DEVICE_VOLUME, deviceVolume);
+ }
+ if (deviceMuted) {
+ bundle.putBoolean(FIELD_DEVICE_MUTED, deviceMuted);
+ }
+ if (playWhenReady) {
+ bundle.putBoolean(FIELD_PLAY_WHEN_READY, playWhenReady);
+ }
+ if (playWhenReadyChangeReason != PLAY_WHEN_READY_CHANGE_REASON_DEFAULT) {
+ bundle.putInt(FIELD_PLAY_WHEN_READY_CHANGE_REASON, playWhenReadyChangeReason);
+ }
+ if (playbackSuppressionReason != PLAYBACK_SUPPRESSION_REASON_NONE) {
+ bundle.putInt(FIELD_PLAYBACK_SUPPRESSION_REASON, playbackSuppressionReason);
+ }
+ if (playbackState != STATE_IDLE) {
+ bundle.putInt(FIELD_PLAYBACK_STATE, playbackState);
+ }
+ if (isPlaying) {
+ bundle.putBoolean(FIELD_IS_PLAYING, isPlaying);
+ }
+ if (isLoading) {
+ bundle.putBoolean(FIELD_IS_LOADING, isLoading);
+ }
+ if (!mediaMetadata.equals(MediaMetadata.EMPTY)) {
+ bundle.putBundle(FIELD_MEDIA_METADATA, mediaMetadata.toBundle());
+ }
+ if (seekBackIncrementMs != 0) {
+ bundle.putLong(FIELD_SEEK_BACK_INCREMENT_MS, seekBackIncrementMs);
+ }
+ if (seekForwardIncrementMs != 0) {
+ bundle.putLong(FIELD_SEEK_FORWARD_INCREMENT_MS, seekForwardIncrementMs);
+ }
+ if (maxSeekToPreviousPositionMs != 0) {
+ bundle.putLong(FIELD_MAX_SEEK_TO_PREVIOUS_POSITION_MS, maxSeekToPreviousPositionMs);
+ }
+ if (!currentTracks.equals(Tracks.EMPTY)) {
+ bundle.putBundle(FIELD_CURRENT_TRACKS, currentTracks.toBundle());
+ }
+ if (!trackSelectionParameters.equals(TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT)) {
+ bundle.putBundle(FIELD_TRACK_SELECTION_PARAMETERS, trackSelectionParameters.toBundle());
+ }
+ return bundle;
}
/** Object that can restore {@link PlayerInfo} from a {@link Bundle}. */
public static final Creator The filtered fields are reset to their default values.
+ *
+ * The return value may be the same object if nothing is filtered.
+ *
+ * @param canAccessCurrentMediaItem Whether {@link Player#COMMAND_GET_CURRENT_MEDIA_ITEM} is
+ * available.
+ * @param canAccessTimeline Whether {@link Player#COMMAND_GET_TIMELINE} is available.
+ * @return The filtered session position info.
+ */
+ public SessionPositionInfo filterByAvailableCommands(
+ boolean canAccessCurrentMediaItem, boolean canAccessTimeline) {
+ if (canAccessCurrentMediaItem && canAccessTimeline) {
+ return this;
+ }
+ return new SessionPositionInfo(
+ positionInfo.filterByAvailableCommands(canAccessCurrentMediaItem, canAccessTimeline),
+ canAccessCurrentMediaItem && isPlayingAd,
+ eventTimeMs,
+ canAccessCurrentMediaItem ? durationMs : C.TIME_UNSET,
+ canAccessCurrentMediaItem ? bufferedPositionMs : 0,
+ canAccessCurrentMediaItem ? bufferedPercentage : 0,
+ canAccessCurrentMediaItem ? totalBufferedDurationMs : 0,
+ canAccessCurrentMediaItem ? currentLiveOffsetMs : C.TIME_UNSET,
+ canAccessCurrentMediaItem ? contentDurationMs : C.TIME_UNSET,
+ canAccessCurrentMediaItem ? contentBufferedPositionMs : 0);
+ }
@Override
public Bundle toBundle() {
- return toBundle(/* canAccessCurrentMediaItem= */ true, /* canAccessTimeline= */ true);
+ return toBundle(Integer.MAX_VALUE);
}
- public Bundle toBundle(boolean canAccessCurrentMediaItem, boolean canAccessTimeline) {
+ public Bundle toBundle(int controllerInterfaceVersion) {
Bundle bundle = new Bundle();
- bundle.putBundle(
- FIELD_POSITION_INFO, positionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline));
- bundle.putBoolean(FIELD_IS_PLAYING_AD, canAccessCurrentMediaItem && isPlayingAd);
- bundle.putLong(FIELD_EVENT_TIME_MS, eventTimeMs);
- bundle.putLong(FIELD_DURATION_MS, canAccessCurrentMediaItem ? durationMs : C.TIME_UNSET);
- bundle.putLong(FIELD_BUFFERED_POSITION_MS, canAccessCurrentMediaItem ? bufferedPositionMs : 0);
- bundle.putInt(FIELD_BUFFERED_PERCENTAGE, canAccessCurrentMediaItem ? bufferedPercentage : 0);
- bundle.putLong(
- FIELD_TOTAL_BUFFERED_DURATION_MS, canAccessCurrentMediaItem ? totalBufferedDurationMs : 0);
- bundle.putLong(
- FIELD_CURRENT_LIVE_OFFSET_MS,
- canAccessCurrentMediaItem ? currentLiveOffsetMs : C.TIME_UNSET);
- bundle.putLong(
- FIELD_CONTENT_DURATION_MS, canAccessCurrentMediaItem ? contentDurationMs : C.TIME_UNSET);
- bundle.putLong(
- FIELD_CONTENT_BUFFERED_POSITION_MS,
- canAccessCurrentMediaItem ? contentBufferedPositionMs : 0);
+ if (controllerInterfaceVersion < 3 || !DEFAULT_POSITION_INFO.equalsForBundling(positionInfo)) {
+ bundle.putBundle(FIELD_POSITION_INFO, positionInfo.toBundle(controllerInterfaceVersion));
+ }
+ if (isPlayingAd) {
+ bundle.putBoolean(FIELD_IS_PLAYING_AD, isPlayingAd);
+ }
+ if (eventTimeMs != C.TIME_UNSET) {
+ bundle.putLong(FIELD_EVENT_TIME_MS, eventTimeMs);
+ }
+ if (durationMs != C.TIME_UNSET) {
+ bundle.putLong(FIELD_DURATION_MS, durationMs);
+ }
+ if (controllerInterfaceVersion < 3 || bufferedPositionMs != 0) {
+ bundle.putLong(FIELD_BUFFERED_POSITION_MS, bufferedPositionMs);
+ }
+ if (bufferedPercentage != 0) {
+ bundle.putInt(FIELD_BUFFERED_PERCENTAGE, bufferedPercentage);
+ }
+ if (totalBufferedDurationMs != 0) {
+ bundle.putLong(FIELD_TOTAL_BUFFERED_DURATION_MS, totalBufferedDurationMs);
+ }
+ if (currentLiveOffsetMs != C.TIME_UNSET) {
+ bundle.putLong(FIELD_CURRENT_LIVE_OFFSET_MS, currentLiveOffsetMs);
+ }
+ if (contentDurationMs != C.TIME_UNSET) {
+ bundle.putLong(FIELD_CONTENT_DURATION_MS, contentDurationMs);
+ }
+ if (controllerInterfaceVersion < 3 || contentBufferedPositionMs != 0) {
+ bundle.putLong(FIELD_CONTENT_BUFFERED_POSITION_MS, contentBufferedPositionMs);
+ }
return bundle;
}
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 c3e6736ed3..3ed7f51c61 100644
--- a/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/PlayerInfoTest.java
@@ -71,7 +71,7 @@ public class PlayerInfoTest {
}
@Test
- public void toBundleFromBundle_withAllCommands_restoresAllData() {
+ public void toBundleFromBundle_restoresAllData() {
PlayerInfo playerInfo =
new PlayerInfo.Builder(PlayerInfo.DEFAULT)
.setOldPositionInfo(
@@ -151,7 +151,7 @@ public class PlayerInfoTest {
new PlaybackException(
/* message= */ null, /* cause= */ null, PlaybackException.ERROR_CODE_TIMEOUT))
.setPlayWhenReady(true)
- .setPlayWhenReadyChangeReason(Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
+ .setPlayWhenReadyChangeReason(Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS)
.setRepeatMode(Player.REPEAT_MODE_ONE)
.setSeekBackIncrement(7000)
.setSeekForwardIncrement(6000)
@@ -163,12 +163,7 @@ public class PlayerInfoTest {
.setVideoSize(new VideoSize(/* width= */ 1024, /* height= */ 768))
.build();
- PlayerInfo infoAfterBundling =
- PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder().addAllCommands().build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ PlayerInfo infoAfterBundling = PlayerInfo.CREATOR.fromBundle(playerInfo.toBundle());
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4);
@@ -205,8 +200,8 @@ public class PlayerInfoTest {
assertThat(infoAfterBundling.timeline.getWindowCount()).isEqualTo(10);
assertThat(infoAfterBundling.timelineChangeReason)
.isEqualTo(Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE);
- assertThat(infoAfterBundling.mediaMetadata.title).isEqualTo("title");
- assertThat(infoAfterBundling.playlistMetadata.artist).isEqualTo("artist");
+ assertThat(infoAfterBundling.mediaMetadata.title.toString()).isEqualTo("title");
+ assertThat(infoAfterBundling.playlistMetadata.artist.toString()).isEqualTo("artist");
assertThat(infoAfterBundling.volume).isEqualTo(0.5f);
assertThat(infoAfterBundling.deviceVolume).isEqualTo(10);
assertThat(infoAfterBundling.deviceMuted).isTrue();
@@ -229,7 +224,7 @@ public class PlayerInfoTest {
.isEqualTo(PlaybackException.ERROR_CODE_TIMEOUT);
assertThat(infoAfterBundling.playWhenReady).isTrue();
assertThat(infoAfterBundling.playWhenReadyChangeReason)
- .isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
+ .isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
assertThat(infoAfterBundling.repeatMode).isEqualTo(Player.REPEAT_MODE_ONE);
assertThat(infoAfterBundling.seekBackIncrementMs).isEqualTo(7000);
assertThat(infoAfterBundling.seekForwardIncrementMs).isEqualTo(6000);
@@ -289,13 +284,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4);
@@ -408,13 +405,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_TIMELINE)
- .build(),
- /* excludeTimeline= */ true,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_TIMELINE)
+ .build(),
+ /* excludeTimeline= */ true,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(0);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(0);
@@ -475,13 +474,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_METADATA)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_METADATA)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.mediaMetadata).isEqualTo(MediaMetadata.EMPTY);
assertThat(infoAfterBundling.playlistMetadata).isEqualTo(MediaMetadata.EMPTY);
@@ -493,13 +494,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_VOLUME)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_VOLUME)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.volume).isEqualTo(1f);
}
@@ -511,13 +514,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_DEVICE_VOLUME)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_DEVICE_VOLUME)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.deviceVolume).isEqualTo(0);
assertThat(infoAfterBundling.deviceMuted).isFalse();
@@ -533,13 +538,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_AUDIO_ATTRIBUTES)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_AUDIO_ATTRIBUTES)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.audioAttributes).isEqualTo(AudioAttributes.DEFAULT);
}
@@ -553,13 +560,15 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_TEXT)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ false));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_TEXT)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ false)
+ .toBundle());
assertThat(infoAfterBundling.cueGroup).isEqualTo(CueGroup.EMPTY_TIME_ZERO);
}
@@ -581,14 +590,84 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
- playerInfo.toBundle(
- new Player.Commands.Builder()
- .addAllCommands()
- .remove(Player.COMMAND_GET_TRACKS)
- .build(),
- /* excludeTimeline= */ false,
- /* excludeTracks= */ true));
+ playerInfo
+ .filterByAvailableCommands(
+ new Player.Commands.Builder()
+ .addAllCommands()
+ .remove(Player.COMMAND_GET_TRACKS)
+ .build(),
+ /* excludeTimeline= */ false,
+ /* excludeTracks= */ true)
+ .toBundle());
assertThat(infoAfterBundling.currentTracks).isEqualTo(Tracks.EMPTY);
}
+
+ @Test
+ public void toBundleFromBundle_withDefaultValues_restoresAllData() {
+ PlayerInfo roundTripValue = PlayerInfo.CREATOR.fromBundle(PlayerInfo.DEFAULT.toBundle());
+
+ assertThat(roundTripValue.oldPositionInfo).isEqualTo(PlayerInfo.DEFAULT.oldPositionInfo);
+ assertThat(roundTripValue.newPositionInfo).isEqualTo(PlayerInfo.DEFAULT.newPositionInfo);
+ assertThat(roundTripValue.sessionPositionInfo)
+ .isEqualTo(PlayerInfo.DEFAULT.sessionPositionInfo);
+ assertThat(roundTripValue.timeline).isEqualTo(PlayerInfo.DEFAULT.timeline);
+ assertThat(roundTripValue.timelineChangeReason)
+ .isEqualTo(PlayerInfo.DEFAULT.timelineChangeReason);
+ assertThat(roundTripValue.mediaMetadata).isEqualTo(PlayerInfo.DEFAULT.mediaMetadata);
+ assertThat(roundTripValue.playlistMetadata).isEqualTo(PlayerInfo.DEFAULT.playlistMetadata);
+ assertThat(roundTripValue.volume).isEqualTo(PlayerInfo.DEFAULT.volume);
+ assertThat(roundTripValue.deviceVolume).isEqualTo(PlayerInfo.DEFAULT.deviceVolume);
+ assertThat(roundTripValue.deviceMuted).isEqualTo(PlayerInfo.DEFAULT.deviceMuted);
+ assertThat(roundTripValue.audioAttributes).isEqualTo(PlayerInfo.DEFAULT.audioAttributes);
+ assertThat(roundTripValue.cueGroup).isEqualTo(PlayerInfo.DEFAULT.cueGroup);
+ assertThat(roundTripValue.currentTracks).isEqualTo(PlayerInfo.DEFAULT.currentTracks);
+ assertThat(roundTripValue.deviceInfo).isEqualTo(PlayerInfo.DEFAULT.deviceInfo);
+ assertThat(roundTripValue.discontinuityReason)
+ .isEqualTo(PlayerInfo.DEFAULT.discontinuityReason);
+ assertThat(roundTripValue.isLoading).isEqualTo(PlayerInfo.DEFAULT.isLoading);
+ assertThat(roundTripValue.isPlaying).isEqualTo(PlayerInfo.DEFAULT.isPlaying);
+ assertThat(roundTripValue.maxSeekToPreviousPositionMs)
+ .isEqualTo(PlayerInfo.DEFAULT.maxSeekToPreviousPositionMs);
+ assertThat(roundTripValue.mediaItemTransitionReason)
+ .isEqualTo(PlayerInfo.DEFAULT.mediaItemTransitionReason);
+ assertThat(roundTripValue.playbackParameters).isEqualTo(PlayerInfo.DEFAULT.playbackParameters);
+ assertThat(roundTripValue.playbackState).isEqualTo(PlayerInfo.DEFAULT.playbackState);
+ assertThat(roundTripValue.playbackSuppressionReason)
+ .isEqualTo(PlayerInfo.DEFAULT.playbackSuppressionReason);
+ assertThat(roundTripValue.playerError).isEqualTo(PlayerInfo.DEFAULT.playerError);
+ assertThat(roundTripValue.playWhenReady).isEqualTo(PlayerInfo.DEFAULT.playWhenReady);
+ assertThat(roundTripValue.playWhenReadyChangeReason)
+ .isEqualTo(PlayerInfo.DEFAULT.playWhenReadyChangeReason);
+ assertThat(roundTripValue.repeatMode).isEqualTo(PlayerInfo.DEFAULT.repeatMode);
+ assertThat(roundTripValue.seekBackIncrementMs)
+ .isEqualTo(PlayerInfo.DEFAULT.seekBackIncrementMs);
+ assertThat(roundTripValue.seekForwardIncrementMs)
+ .isEqualTo(PlayerInfo.DEFAULT.seekForwardIncrementMs);
+ assertThat(roundTripValue.shuffleModeEnabled).isEqualTo(PlayerInfo.DEFAULT.shuffleModeEnabled);
+ assertThat(roundTripValue.trackSelectionParameters)
+ .isEqualTo(PlayerInfo.DEFAULT.trackSelectionParameters);
+ assertThat(roundTripValue.videoSize).isEqualTo(PlayerInfo.DEFAULT.videoSize);
+ }
+
+ @Test
+ public void toBundle_withDefaultValues_omitsAllData() {
+ Bundle bundle =
+ PlayerInfo.DEFAULT.toBundle(/* controllerInterfaceVersion= */ Integer.MAX_VALUE);
+
+ assertThat(bundle.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void toBundle_withDefaultValuesForControllerInterfaceBefore3_includesPositionInfos() {
+ // Controller before version 3 uses invalid default values for indices in (Session)PositionInfo.
+ // The Bundle should always include these fields to avoid using the invalid defaults.
+ Bundle bundle = PlayerInfo.DEFAULT.toBundle(/* controllerInterfaceVersion= */ 2);
+
+ assertThat(bundle.keySet())
+ .containsAtLeast(
+ PlayerInfo.FIELD_SESSION_POSITION_INFO,
+ PlayerInfo.FIELD_NEW_POSITION_INFO,
+ PlayerInfo.FIELD_OLD_POSITION_INFO);
+ }
}
diff --git a/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java b/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java
index 9ea9b236b3..96dc7e76ed 100644
--- a/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/SessionPositionInfoTest.java
@@ -85,4 +85,35 @@ public class SessionPositionInfoTest {
/* contentDurationMs= */ 400L,
/* contentBufferedPositionMs= */ 223L));
}
+
+ @Test
+ public void roundTripViaBundle_withDefaultValues_yieldsEqualInstance() {
+ SessionPositionInfo roundTripValue =
+ SessionPositionInfo.CREATOR.fromBundle(SessionPositionInfo.DEFAULT.toBundle());
+
+ assertThat(roundTripValue).isEqualTo(SessionPositionInfo.DEFAULT);
+ }
+
+ @Test
+ public void toBundle_withDefaultValues_omitsAllData() {
+ Bundle bundle =
+ SessionPositionInfo.DEFAULT.toBundle(/* controllerInterfaceVersion= */ Integer.MAX_VALUE);
+
+ assertThat(bundle.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void
+ toBundle_withDefaultValuesForControllerInterfaceBefore3_includesPositionInfoAndBufferedValues() {
+ // Controller before version 3 uses invalid default values for indices in PositionInfo and for
+ // the buffered positions. The Bundle should always include these fields to avoid using the
+ // invalid defaults.
+ Bundle bundle = SessionPositionInfo.DEFAULT.toBundle(/* controllerInterfaceVersion= */ 2);
+
+ assertThat(bundle.keySet())
+ .containsAtLeast(
+ SessionPositionInfo.FIELD_BUFFERED_POSITION_MS,
+ SessionPositionInfo.FIELD_CONTENT_BUFFERED_POSITION_MS,
+ SessionPositionInfo.FIELD_POSITION_INFO);
+ }
}