PiperOrigin-RevId: 574308136
This commit is contained in:
Googler 2023-10-17 17:13:34 -07:00 committed by Copybara-Service
parent 1a43aa3602
commit ff330bd8e9
7 changed files with 233 additions and 503 deletions

View File

@ -350,9 +350,15 @@ public interface Player {
return false;
}
PositionInfo that = (PositionInfo) o;
return equalsForBundling(that)
return mediaItemIndex == that.mediaItemIndex
&& periodIndex == that.periodIndex
&& positionMs == that.positionMs
&& contentPositionMs == that.contentPositionMs
&& adGroupIndex == that.adGroupIndex
&& adIndexInAdGroup == that.adIndexInAdGroup
&& Objects.equal(windowUid, that.windowUid)
&& Objects.equal(periodUid, that.periodUid);
&& Objects.equal(periodUid, that.periodUid)
&& Objects.equal(mediaItem, that.mediaItem);
}
@Override
@ -369,21 +375,6 @@ 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);
@ -394,36 +385,6 @@ public interface Player {
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.
*
* <p>The filtered fields are reset to their default values.
*
* <p>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}
*
@ -433,28 +394,31 @@ 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();
if (mediaItemIndex != 0) {
bundle.putInt(FIELD_MEDIA_ITEM_INDEX, mediaItemIndex);
}
if (mediaItem != null) {
bundle.putInt(FIELD_MEDIA_ITEM_INDEX, canAccessTimeline ? mediaItemIndex : 0);
if (mediaItem != null && canAccessCurrentMediaItem) {
bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle());
}
if (periodIndex != 0) {
bundle.putInt(FIELD_PERIOD_INDEX, periodIndex);
}
if (positionMs != 0) {
bundle.putLong(FIELD_POSITION_MS, positionMs);
}
if (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);
}
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;
}

View File

@ -1443,29 +1443,36 @@ public abstract class Timeline implements Bundleable {
}
/**
* Returns a copy of this timeline containing just the single specified {@link Window}.
* Returns a {@link Bundle} containing just the specified {@link Window}.
*
* <p>The method returns the same instance if there is only one window.
* <p>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()}.
*
* @param windowIndex The index of the {@link Window} to include in the copy.
* @return A {@link Timeline} with just the single specified {@link Window}.
* @param windowIndex The index of the {@link Window} to include in the {@link Bundle}.
*/
@UnstableApi
public final Timeline copyWithSingleWindow(int windowIndex) {
if (getWindowCount() == 1) {
return this;
}
public final Bundle toBundleWithOneWindowOnly(int windowIndex) {
Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0);
ImmutableList.Builder<Period> periods = ImmutableList.builder();
List<Bundle> periodBundles = new ArrayList<>();
Period period = new Period();
for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) {
Period period = getPeriod(i, new Period(), /* setIds= */ true);
getPeriod(i, period, /* setIds= */ false);
period.windowIndex = 0;
periods.add(period);
periodBundles.add(period.toBundle());
}
window.lastPeriodIndex = window.lastPeriodIndex - window.firstPeriodIndex;
window.firstPeriodIndex = 0;
return new RemotableTimeline(
ImmutableList.of(window), periods.build(), /* shuffledWindowIndices= */ new int[] {0});
Bundle windowBundle = window.toBundle();
Bundle bundle = new Bundle();
BundleUtil.putBinder(
bundle, FIELD_WINDOWS, new BundleListRetriever(ImmutableList.of(windowBundle)));
BundleUtil.putBinder(bundle, FIELD_PERIODS, new BundleListRetriever(periodBundles));
bundle.putIntArray(FIELD_SHUFFLED_WINDOW_INDICES, new int[] {0});
return bundle;
}
/**

View File

@ -114,10 +114,8 @@ import java.util.List;
MediaUtils.intersect(playerCommandsFromSession, playerCommandsFromPlayer);
bundle.putBundle(
FIELD_PLAYER_INFO,
playerInfo
.filterByAvailableCommands(
intersectedCommands, /* excludeTimeline= */ false, /* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
intersectedCommands, /* excludeTimeline= */ false, /* excludeTracks= */ false));
bundle.putInt(FIELD_SESSION_INTERFACE_VERSION, sessionInterfaceVersion);
return bundle;
}

View File

@ -117,12 +117,6 @@ import java.util.concurrent.ExecutionException;
/** The version of the IMediaSession interface. */
public static final int VERSION_INT = 2;
/**
* Sequence number used when a controller method is triggered on the sesison side that wasn't
* initiated by the controller itself.
*/
public static final int UNKNOWN_SEQUENCE_NUMBER = Integer.MIN_VALUE;
private final WeakReference<MediaSessionImpl> sessionImpl;
private final MediaSessionManager sessionManager;
private final ConnectedControllersManager<IBinder> connectedControllersManager;
@ -291,18 +285,6 @@ import java.util.concurrent.ExecutionException;
int sequenceNumber,
@Player.Command int command,
SessionTask<ListenableFuture<Void>, K> task) {
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo, sequenceNumber, command, task);
}
}
private <K extends MediaSessionImpl> void queueSessionTaskWithPlayerCommandForControllerInfo(
ControllerInfo controller,
int sequenceNumber,
@Player.Command int command,
SessionTask<ListenableFuture<Void>, K> task) {
long token = Binder.clearCallingIdentity();
try {
@SuppressWarnings({"unchecked", "cast.unsafe"})
@ -311,6 +293,11 @@ import java.util.concurrent.ExecutionException;
if (sessionImpl == null || sessionImpl.isReleased()) {
return;
}
@Nullable
ControllerInfo controller = connectedControllersManager.getController(caller.asBinder());
if (controller == null) {
return;
}
postOrRun(
sessionImpl.getApplicationHandler(),
() -> {
@ -537,10 +524,7 @@ import java.util.concurrent.ExecutionException;
}
try {
caller.onConnected(
sequencedFutureManager.obtainNextSequenceNumber(),
caller instanceof MediaControllerStub
? state.toBundleInProcess()
: state.toBundle());
sequencedFutureManager.obtainNextSequenceNumber(), state.toBundle());
connected = true;
} catch (RemoteException e) {
// Controller may be died prematurely.
@ -634,19 +618,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
stopForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void stopForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo,
sequenceNumber,
COMMAND_STOP,
sendSessionResultSuccess(PlayerWrapper::stop));
queueSessionTaskWithPlayerCommand(
caller, sequenceNumber, COMMAND_STOP, sendSessionResultSuccess(player -> player.stop()));
}
@Override
@ -701,30 +674,27 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controller = connectedControllersManager.getController(caller.asBinder());
if (controller != null) {
playForControllerInfo(controller, sequenceNumber);
if (controller == null) {
return;
}
}
public void playForControllerInfo(ControllerInfo controller, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controller,
queueSessionTaskWithPlayerCommand(
caller,
sequenceNumber,
COMMAND_PLAY_PAUSE,
sendSessionResultSuccess(
player -> {
@Nullable MediaSessionImpl impl = sessionImpl.get();
if (impl == null || impl.isReleased()) {
@Nullable MediaSessionImpl sessionImpl = this.sessionImpl.get();
if (sessionImpl == null || sessionImpl.isReleased()) {
return;
}
if (impl.onPlayRequested()) {
if (sessionImpl.onPlayRequested()) {
if (player.getMediaItemCount() == 0) {
// The player is in IDLE or ENDED state and has no media items in the playlist
// yet. Handle the play command as a playback resumption command to try resume
// yet.
// Handle the play command as a playback resumption command to try resume
// playback.
impl.prepareAndPlayForPlaybackResumption(controller, player);
sessionImpl.prepareAndPlayForPlaybackResumption(controller, player);
} else {
Util.handlePlayButtonAction(player);
}
@ -737,16 +707,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
pauseForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void pauseForControllerInfo(ControllerInfo controller, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controller, sequenceNumber, COMMAND_PLAY_PAUSE, sendSessionResultSuccess(Player::pause));
queueSessionTaskWithPlayerCommand(
caller, sequenceNumber, COMMAND_PLAY_PAUSE, sendSessionResultSuccess(Player::pause));
}
@Override
@ -822,19 +784,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
seekBackForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void seekBackForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo,
sequenceNumber,
COMMAND_SEEK_BACK,
sendSessionResultSuccess(Player::seekBack));
queueSessionTaskWithPlayerCommand(
caller, sequenceNumber, COMMAND_SEEK_BACK, sendSessionResultSuccess(Player::seekBack));
}
@Override
@ -842,16 +793,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
seekForwardForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void seekForwardForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo,
queueSessionTaskWithPlayerCommand(
caller,
sequenceNumber,
COMMAND_SEEK_FORWARD,
sendSessionResultSuccess(Player::seekForward));
@ -1419,16 +1362,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
seekToPreviousForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void seekToPreviousForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo,
queueSessionTaskWithPlayerCommand(
caller,
sequenceNumber,
COMMAND_SEEK_TO_PREVIOUS,
sendSessionResultSuccess(Player::seekToPrevious));
@ -1439,19 +1374,8 @@ import java.util.concurrent.ExecutionException;
if (caller == null) {
return;
}
@Nullable
ControllerInfo controllerInfo = connectedControllersManager.getController(caller.asBinder());
if (controllerInfo != null) {
seekToNextForControllerInfo(controllerInfo, sequenceNumber);
}
}
public void seekToNextForControllerInfo(ControllerInfo controllerInfo, int sequenceNumber) {
queueSessionTaskWithPlayerCommandForControllerInfo(
controllerInfo,
sequenceNumber,
COMMAND_SEEK_TO_NEXT,
sendSessionResultSuccess(Player::seekToNext));
queueSessionTaskWithPlayerCommand(
caller, sequenceNumber, COMMAND_SEEK_TO_NEXT, sendSessionResultSuccess(Player::seekToNext));
}
@Override
@ -1989,24 +1913,17 @@ import java.util.concurrent.ExecutionException;
boolean bundlingExclusionsTracks =
excludeTracks || !availableCommands.contains(Player.COMMAND_GET_TRACKS);
if (controllerInterfaceVersion >= 2) {
PlayerInfo filteredPlayerInfo =
playerInfo.filterByAvailableCommands(availableCommands, excludeTimeline, excludeTracks);
Bundle playerInfoBundle =
iController instanceof MediaControllerStub
? filteredPlayerInfo.toBundleInProcess()
: filteredPlayerInfo.toBundle();
iController.onPlayerInfoChangedWithExclusions(
sequenceNumber,
playerInfoBundle,
playerInfo.toBundle(availableCommands, excludeTimeline, excludeTracks),
new PlayerInfo.BundlingExclusions(bundlingExclusionsTimeline, bundlingExclusionsTracks)
.toBundle());
} else {
PlayerInfo filteredPlayerInfo =
playerInfo.filterByAvailableCommands(
availableCommands, excludeTimeline, /* excludeTracks= */ true);
//noinspection deprecation
iController.onPlayerInfoChanged(
sequenceNumber, filteredPlayerInfo.toBundle(), bundlingExclusionsTimeline);
sequenceNumber,
playerInfo.toBundle(availableCommands, excludeTimeline, /* excludeTracks= */ true),
bundlingExclusionsTimeline);
}
}
@ -2075,9 +1992,7 @@ import java.util.concurrent.ExecutionException;
throws RemoteException {
iController.onPeriodicSessionPositionInfoChanged(
sequenceNumber,
sessionPositionInfo
.filterByAvailableCommands(canAccessCurrentMediaItem, canAccessTimeline)
.toBundle());
sessionPositionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline));
}
@Override

View File

@ -22,9 +22,7 @@ import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_R
import static androidx.media3.common.Player.STATE_IDLE;
import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.CheckResult;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
@ -46,7 +44,6 @@ import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
@ -827,188 +824,94 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
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);
private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(32);
// Next field key = 33
// Next field key = 32
/**
* Returns a copy of this player info, filtered by the specified available commands.
*
* <p>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(
public Bundle toBundle(
Player.Commands availableCommands, boolean excludeTimeline, boolean excludeTracks) {
PlayerInfo.Builder builder = new Builder(this);
Bundle bundle = new Bundle();
boolean canAccessCurrentMediaItem =
availableCommands.contains(Player.COMMAND_GET_CURRENT_MEDIA_ITEM);
boolean canAccessTimeline = availableCommands.contains(Player.COMMAND_GET_TIMELINE);
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);
if (playerError != null) {
bundle.putBundle(FIELD_PLAYBACK_ERROR, playerError.toBundle());
}
if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
builder.setPlaylistMetadata(MediaMetadata.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_VOLUME)) {
builder.setVolume(1);
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_AUDIO_ATTRIBUTES)) {
builder.setAudioAttributes(AudioAttributes.DEFAULT);
if (availableCommands.contains(Player.COMMAND_GET_VOLUME)) {
bundle.putFloat(FIELD_VOLUME, volume);
}
if (!availableCommands.contains(Player.COMMAND_GET_TEXT)) {
builder.setCues(CueGroup.EMPTY_TIME_ZERO);
if (availableCommands.contains(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
bundle.putBundle(FIELD_AUDIO_ATTRIBUTES, audioAttributes.toBundle());
}
if (!availableCommands.contains(Player.COMMAND_GET_DEVICE_VOLUME)) {
builder.setDeviceVolume(0).setDeviceMuted(false);
if (availableCommands.contains(Player.COMMAND_GET_TEXT)) {
bundle.putBundle(FIELD_CUE_GROUP, cueGroup.toBundle());
}
if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
builder.setMediaMetadata(MediaMetadata.EMPTY);
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 (excludeTracks || !availableCommands.contains(Player.COMMAND_GET_TRACKS)) {
builder.setCurrentTracks(Tracks.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());
}
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());
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 bundle;
}
@Override
public Bundle toBundle() {
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 (!sessionPositionInfo.equals(SessionPositionInfo.DEFAULT)) {
bundle.putBundle(FIELD_SESSION_POSITION_INFO, sessionPositionInfo.toBundle());
}
if (!SessionPositionInfo.DEFAULT_POSITION_INFO.equalsForBundling(oldPositionInfo)) {
bundle.putBundle(FIELD_OLD_POSITION_INFO, oldPositionInfo.toBundle());
}
if (!SessionPositionInfo.DEFAULT_POSITION_INFO.equalsForBundling(newPositionInfo)) {
bundle.putBundle(FIELD_NEW_POSITION_INFO, newPositionInfo.toBundle());
}
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;
return toBundle(
/* availableCommands= */ new Player.Commands.Builder().addAllCommands().build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false);
}
/** Object that can restore {@link PlayerInfo} from a {@link Bundle}. */
public static final Creator<PlayerInfo> CREATOR = PlayerInfo::fromBundle;
private static PlayerInfo fromBundle(Bundle bundle) {
@Nullable IBinder inProcessBinder = BundleUtil.getBinder(bundle, FIELD_IN_PROCESS_BINDER);
if (inProcessBinder instanceof InProcessBinder) {
return ((InProcessBinder) inProcessBinder).getPlayerInfo();
}
@Nullable Bundle playerErrorBundle = bundle.getBundle(FIELD_PLAYBACK_ERROR);
@Nullable
PlaybackException playerError =
playerErrorBundle == null ? null : PlaybackException.CREATOR.fromBundle(playerErrorBundle);
int mediaItemTransitionReason =
bundle.getInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, MEDIA_ITEM_TRANSITION_REASON_DEFAULT);
bundle.getInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, MEDIA_ITEM_TRANSITION_REASON_REPEAT);
@Nullable Bundle sessionPositionInfoBundle = bundle.getBundle(FIELD_SESSION_POSITION_INFO);
SessionPositionInfo sessionPositionInfo =
sessionPositionInfoBundle == null
@ -1025,7 +928,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
? SessionPositionInfo.DEFAULT_POSITION_INFO
: PositionInfo.CREATOR.fromBundle(newPositionInfoBundle);
int discontinuityReason =
bundle.getInt(FIELD_DISCONTINUITY_REASON, DISCONTINUITY_REASON_DEFAULT);
bundle.getInt(FIELD_DISCONTINUITY_REASON, DISCONTINUITY_REASON_AUTO_TRANSITION);
@Nullable Bundle playbackParametersBundle = bundle.getBundle(FIELD_PLAYBACK_PARAMETERS);
PlaybackParameters playbackParameters =
playbackParametersBundle == null
@ -1071,7 +974,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
int playWhenReadyChangeReason =
bundle.getInt(
FIELD_PLAY_WHEN_READY_CHANGE_REASON,
/* defaultValue= */ PLAY_WHEN_READY_CHANGE_REASON_DEFAULT);
/* defaultValue= */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
@Player.PlaybackSuppressionReason
int playbackSuppressionReason =
bundle.getInt(
@ -1133,10 +1036,4 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
currentTracks,
trackSelectionParameters);
}
private final class InProcessBinder extends Binder {
public PlayerInfo getPlayerInfo() {
return PlayerInfo.this;
}
}
}

View File

@ -21,7 +21,6 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.media3.common.Bundleable;
import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
@ -102,9 +101,9 @@ import com.google.common.base.Objects;
return false;
}
SessionPositionInfo other = (SessionPositionInfo) obj;
return eventTimeMs == other.eventTimeMs
&& positionInfo.equals(other.positionInfo)
return positionInfo.equals(other.positionInfo)
&& isPlayingAd == other.isPlayingAd
&& eventTimeMs == other.eventTimeMs
&& durationMs == other.durationMs
&& bufferedPositionMs == other.bufferedPositionMs
&& bufferedPercentage == other.bufferedPercentage
@ -169,69 +168,30 @@ import com.google.common.base.Objects;
private static final String FIELD_CONTENT_DURATION_MS = Util.intToStringMaxRadix(8);
private static final String FIELD_CONTENT_BUFFERED_POSITION_MS = Util.intToStringMaxRadix(9);
/**
* Returns a copy of this session position info, filtered by the specified available commands.
*
* <p>The filtered fields are reset to their default values.
*
* <p>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);
}
public Bundle toBundle(boolean canAccessCurrentMediaItem, boolean canAccessTimeline) {
Bundle bundle = new Bundle();
if (!DEFAULT_POSITION_INFO.equalsForBundling(positionInfo)) {
bundle.putBundle(FIELD_POSITION_INFO, positionInfo.toBundle());
}
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 (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 (contentBufferedPositionMs != 0) {
bundle.putLong(FIELD_CONTENT_BUFFERED_POSITION_MS, contentBufferedPositionMs);
}
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);
return bundle;
}

View File

@ -71,7 +71,7 @@ public class PlayerInfoTest {
}
@Test
public void toBundleFromBundle_restoresAllData() {
public void toBundleFromBundle_withAllCommands_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_AUDIO_FOCUS_LOSS)
.setPlayWhenReadyChangeReason(Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
.setRepeatMode(Player.REPEAT_MODE_ONE)
.setSeekBackIncrement(7000)
.setSeekForwardIncrement(6000)
@ -163,7 +163,12 @@ public class PlayerInfoTest {
.setVideoSize(new VideoSize(/* width= */ 1024, /* height= */ 768))
.build();
PlayerInfo infoAfterBundling = PlayerInfo.CREATOR.fromBundle(playerInfo.toBundle());
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo.toBundle(
new Player.Commands.Builder().addAllCommands().build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4);
@ -224,7 +229,7 @@ public class PlayerInfoTest {
.isEqualTo(PlaybackException.ERROR_CODE_TIMEOUT);
assertThat(infoAfterBundling.playWhenReady).isTrue();
assertThat(infoAfterBundling.playWhenReadyChangeReason)
.isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
.isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
assertThat(infoAfterBundling.repeatMode).isEqualTo(Player.REPEAT_MODE_ONE);
assertThat(infoAfterBundling.seekBackIncrementMs).isEqualTo(7000);
assertThat(infoAfterBundling.seekForwardIncrementMs).isEqualTo(6000);
@ -284,15 +289,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(5);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(4);
@ -405,15 +408,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TIMELINE)
.build(),
/* excludeTimeline= */ true,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TIMELINE)
.build(),
/* excludeTimeline= */ true,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.oldPositionInfo.mediaItemIndex).isEqualTo(0);
assertThat(infoAfterBundling.oldPositionInfo.periodIndex).isEqualTo(0);
@ -474,15 +475,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_METADATA)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_METADATA)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.mediaMetadata).isEqualTo(MediaMetadata.EMPTY);
assertThat(infoAfterBundling.playlistMetadata).isEqualTo(MediaMetadata.EMPTY);
@ -494,15 +493,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_VOLUME)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_VOLUME)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.volume).isEqualTo(1f);
}
@ -514,15 +511,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_DEVICE_VOLUME)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_DEVICE_VOLUME)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.deviceVolume).isEqualTo(0);
assertThat(infoAfterBundling.deviceMuted).isFalse();
@ -538,15 +533,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_AUDIO_ATTRIBUTES)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_AUDIO_ATTRIBUTES)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.audioAttributes).isEqualTo(AudioAttributes.DEFAULT);
}
@ -560,15 +553,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TEXT)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TEXT)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ false));
assertThat(infoAfterBundling.cueGroup).isEqualTo(CueGroup.EMPTY_TIME_ZERO);
}
@ -590,15 +581,13 @@ public class PlayerInfoTest {
PlayerInfo infoAfterBundling =
PlayerInfo.CREATOR.fromBundle(
playerInfo
.filterByAvailableCommands(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TRACKS)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ true)
.toBundle());
playerInfo.toBundle(
new Player.Commands.Builder()
.addAllCommands()
.remove(Player.COMMAND_GET_TRACKS)
.build(),
/* excludeTimeline= */ false,
/* excludeTracks= */ true));
assertThat(infoAfterBundling.currentTracks).isEqualTo(Tracks.EMPTY);
}