PiperOrigin-RevId: 574530273
This commit is contained in:
tonihei 2023-10-18 11:03:04 -07:00 committed by Copybara-Service
parent aec6db77fa
commit c0759a4e62
12 changed files with 739 additions and 257 deletions

View File

@ -31,6 +31,7 @@ import androidx.annotation.FloatRange;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.text.Cue; import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
@ -350,15 +351,9 @@ public interface Player {
return false; return false;
} }
PositionInfo that = (PositionInfo) o; PositionInfo that = (PositionInfo) o;
return mediaItemIndex == that.mediaItemIndex return equalsForBundling(that)
&& periodIndex == that.periodIndex
&& positionMs == that.positionMs
&& contentPositionMs == that.contentPositionMs
&& adGroupIndex == that.adGroupIndex
&& adIndexInAdGroup == that.adIndexInAdGroup
&& Objects.equal(windowUid, that.windowUid) && Objects.equal(windowUid, that.windowUid)
&& Objects.equal(periodUid, that.periodUid) && Objects.equal(periodUid, that.periodUid);
&& Objects.equal(mediaItem, that.mediaItem);
} }
@Override @Override
@ -375,16 +370,97 @@ public interface Player {
adIndexInAdGroup); 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. // 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_MEDIA_ITEM = Util.intToStringMaxRadix(1);
private static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(2); @VisibleForTesting static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(2);
private static final String FIELD_POSITION_MS = Util.intToStringMaxRadix(3); @VisibleForTesting 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_CONTENT_POSITION_MS = Util.intToStringMaxRadix(4);
private static final String FIELD_AD_GROUP_INDEX = Util.intToStringMaxRadix(5); private static final String FIELD_AD_GROUP_INDEX = Util.intToStringMaxRadix(5);
private static final String FIELD_AD_INDEX_IN_AD_GROUP = Util.intToStringMaxRadix(6); 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}
*
* <p>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} * {@inheritDoc}
* *
@ -394,32 +470,7 @@ public interface Player {
@UnstableApi @UnstableApi
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
return toBundle(/* canAccessCurrentMediaItem= */ true, /* canAccessTimeline= */ true); return toBundle(Integer.MAX_VALUE);
}
/**
* 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;
} }
/** Object that can restore {@link PositionInfo} from a {@link Bundle}. */ /** Object that can restore {@link PositionInfo} from a {@link Bundle}. */
@ -3320,7 +3371,7 @@ public interface Player {
* remote device is returned. * remote device is returned.
* *
* <p>Note that this method returns the volume of the device. To check the current stream volume, * <p>Note that this method returns the volume of the device. To check the current stream volume,
* use {@link getVolume()}. * use {@link #getVolume()}.
* *
* <p>This method must only be called if {@link #COMMAND_GET_DEVICE_VOLUME} is {@linkplain * <p>This method must only be called if {@link #COMMAND_GET_DEVICE_VOLUME} is {@linkplain
* #getAvailableCommands() available}. * #getAvailableCommands() available}.

View File

@ -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}.
* *
* <p>The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of * <p>The method returns the same instance if there is only one window.
* 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 {@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 @UnstableApi
public final Bundle toBundleWithOneWindowOnly(int windowIndex) { public final Timeline copyWithSingleWindow(int windowIndex) {
Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0); if (getWindowCount() == 1) {
return this;
List<Bundle> periodBundles = new ArrayList<>(); }
Period period = new Period(); Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0);
for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) { ImmutableList.Builder<Period> periods = ImmutableList.builder();
getPeriod(i, period, /* setIds= */ false); for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) {
period.windowIndex = 0; Period period = getPeriod(i, new Period(), /* setIds= */ true);
periodBundles.add(period.toBundle()); period.windowIndex = 0;
periods.add(period);
} }
window.lastPeriodIndex = window.lastPeriodIndex - window.firstPeriodIndex; window.lastPeriodIndex = window.lastPeriodIndex - window.firstPeriodIndex;
window.firstPeriodIndex = 0; window.firstPeriodIndex = 0;
Bundle windowBundle = window.toBundle(); return new RemotableTimeline(
ImmutableList.of(window), periods.build(), /* shuffledWindowIndices= */ new int[] {0});
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

@ -17,6 +17,7 @@ package androidx.media3.common;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.os.Bundle;
import androidx.media3.common.Player.PositionInfo; import androidx.media3.common.Player.PositionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
@ -31,7 +32,7 @@ public class PositionInfoTest {
PositionInfo positionInfo = PositionInfo positionInfo =
new PositionInfo( new PositionInfo(
/* windowUid= */ null, /* windowUid= */ null,
/* windowIndex= */ 23, /* mediaItemIndex= */ 23,
new MediaItem.Builder().setMediaId("1234").build(), new MediaItem.Builder().setMediaId("1234").build(),
/* periodUid= */ null, /* periodUid= */ null,
/* periodIndex= */ 11, /* periodIndex= */ 11,
@ -48,7 +49,7 @@ public class PositionInfoTest {
PositionInfo positionInfo = PositionInfo positionInfo =
new PositionInfo( new PositionInfo(
/* windowUid= */ new Object(), /* windowUid= */ new Object(),
/* windowIndex= */ 23, /* mediaItemIndex= */ 23,
MediaItem.fromUri("https://exoplayer.dev"), MediaItem.fromUri("https://exoplayer.dev"),
/* periodUid= */ null, /* periodUid= */ null,
/* periodIndex= */ 11, /* periodIndex= */ 11,
@ -66,7 +67,7 @@ public class PositionInfoTest {
PositionInfo positionInfo = PositionInfo positionInfo =
new PositionInfo( new PositionInfo(
/* windowUid= */ null, /* windowUid= */ null,
/* windowIndex= */ 23, /* mediaItemIndex= */ 23,
MediaItem.fromUri("https://exoplayer.dev"), MediaItem.fromUri("https://exoplayer.dev"),
/* periodUid= */ new Object(), /* periodUid= */ new Object(),
/* periodIndex= */ 11, /* periodIndex= */ 11,
@ -78,4 +79,69 @@ public class PositionInfoTest {
PositionInfo positionInfoFromBundle = PositionInfo.CREATOR.fromBundle(positionInfo.toBundle()); PositionInfo positionInfoFromBundle = PositionInfo.CREATOR.fromBundle(positionInfo.toBundle());
assertThat(positionInfoFromBundle.periodUid).isNull(); assertThat(positionInfoFromBundle.periodUid).isNull();
} }
@Test
public void roundTripViaBundle_withDefaultValues_yieldsEqualInstance() {
PositionInfo defaultPositionInfo =
new PositionInfo(
/* windowUid= */ null,
/* mediaItemIndex= */ 0,
/* mediaItem= */ null,
/* periodUid= */ null,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
PositionInfo roundTripValue = PositionInfo.CREATOR.fromBundle(defaultPositionInfo.toBundle());
assertThat(roundTripValue).isEqualTo(defaultPositionInfo);
}
@Test
public void toBundle_withDefaultValues_omitsAllData() {
PositionInfo defaultPositionInfo =
new PositionInfo(
/* windowUid= */ null,
/* mediaItemIndex= */ 0,
/* mediaItem= */ null,
/* periodUid= */ null,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Bundle bundle =
defaultPositionInfo.toBundle(/* controllerInterfaceVersion= */ Integer.MAX_VALUE);
assertThat(bundle.isEmpty()).isTrue();
}
@Test
public void toBundle_withDefaultValuesForControllerInterfaceBefore3_includesDefaultValues() {
// Controller before version 3 uses invalid default values for indices and the Bundle should
// always include them to avoid using the default values in the controller code.
PositionInfo defaultPositionInfo =
new PositionInfo(
/* windowUid= */ null,
/* mediaItemIndex= */ 0,
/* mediaItem= */ null,
/* periodUid= */ null,
/* periodIndex= */ 0,
/* positionMs= */ 0,
/* contentPositionMs= */ 0,
/* adGroupIndex= */ C.INDEX_UNSET,
/* adIndexInAdGroup= */ C.INDEX_UNSET);
Bundle bundle = defaultPositionInfo.toBundle(/* controllerInterfaceVersion= */ 2);
assertThat(bundle.keySet())
.containsAtLeast(
PositionInfo.FIELD_MEDIA_ITEM_INDEX,
PositionInfo.FIELD_CONTENT_POSITION_MS,
PositionInfo.FIELD_PERIOD_INDEX,
PositionInfo.FIELD_POSITION_MS);
}
} }

View File

@ -98,6 +98,10 @@ import java.util.List;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
return toBundle(Integer.MAX_VALUE);
}
public Bundle toBundle(int controllerInterfaceVersion) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt(FIELD_LIBRARY_VERSION, libraryVersion); bundle.putInt(FIELD_LIBRARY_VERSION, libraryVersion);
BundleCompat.putBinder(bundle, FIELD_SESSION_BINDER, sessionBinder.asBinder()); BundleCompat.putBinder(bundle, FIELD_SESSION_BINDER, sessionBinder.asBinder());
@ -114,8 +118,10 @@ import java.util.List;
MediaUtils.intersect(playerCommandsFromSession, playerCommandsFromPlayer); MediaUtils.intersect(playerCommandsFromSession, playerCommandsFromPlayer);
bundle.putBundle( bundle.putBundle(
FIELD_PLAYER_INFO, FIELD_PLAYER_INFO,
playerInfo.toBundle( playerInfo
intersectedCommands, /* excludeTimeline= */ false, /* excludeTracks= */ false)); .filterByAvailableCommands(
intersectedCommands, /* excludeTimeline= */ false, /* excludeTracks= */ false)
.toBundle(controllerInterfaceVersion));
bundle.putInt(FIELD_SESSION_INTERFACE_VERSION, sessionInterfaceVersion); bundle.putInt(FIELD_SESSION_INTERFACE_VERSION, sessionInterfaceVersion);
return bundle; return bundle;
} }

View File

@ -1668,7 +1668,8 @@ public class MediaSession {
int seq, int seq,
SessionPositionInfo sessionPositionInfo, SessionPositionInfo sessionPositionInfo,
boolean canAccessCurrentMediaItem, boolean canAccessCurrentMediaItem,
boolean canAccessTimeline) boolean canAccessTimeline,
int controllerInterfaceVersion)
throws RemoteException {} throws RemoteException {}
// Mostly matched with MediaController.ControllerCallback // Mostly matched with MediaController.ControllerCallback

View File

@ -932,7 +932,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
controller, controller,
(controllerCb, seq) -> (controllerCb, seq) ->
controllerCb.onPeriodicSessionPositionInfoChanged( controllerCb.onPeriodicSessionPositionInfoChanged(
seq, sessionPositionInfo, canAccessCurrentMediaItem, canAccessTimeline)); seq,
sessionPositionInfo,
canAccessCurrentMediaItem,
canAccessTimeline,
controller.getInterfaceVersion()));
} }
try { try {
sessionLegacyStub sessionLegacyStub
@ -941,7 +945,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* seq= */ 0, /* seq= */ 0,
sessionPositionInfo, sessionPositionInfo,
/* canAccessCurrentMediaItem= */ true, /* canAccessCurrentMediaItem= */ true,
/* canAccessTimeline= */ true); /* canAccessTimeline= */ true,
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Exception in using media1 API", e); Log.e(TAG, "Exception in using media1 API", e);
} }

View File

@ -1331,7 +1331,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
int unusedSeq, int unusedSeq,
SessionPositionInfo unusedSessionPositionInfo, SessionPositionInfo unusedSessionPositionInfo,
boolean unusedCanAccessCurrentMediaItem, boolean unusedCanAccessCurrentMediaItem,
boolean unusedCanAccessTimeline) boolean unusedCanAccessTimeline,
int controllerInterfaceVersion)
throws RemoteException { throws RemoteException {
updateLegacySessionPlaybackState(sessionImpl.getPlayerWrapper()); updateLegacySessionPlaybackState(sessionImpl.getPlayerWrapper());
} }

View File

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

View File

@ -22,10 +22,13 @@ 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.STATE_IDLE;
import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.CheckResult; import androidx.annotation.CheckResult;
import androidx.annotation.FloatRange; import androidx.annotation.FloatRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Bundleable; import androidx.media3.common.Bundleable;
import androidx.media3.common.DeviceInfo; import androidx.media3.common.DeviceInfo;
@ -44,6 +47,7 @@ import androidx.media3.common.Tracks;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.text.CueGroup; import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
@ -811,10 +815,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
private static final String FIELD_IS_PLAYING = Util.intToStringMaxRadix(16); private static final String FIELD_IS_PLAYING = Util.intToStringMaxRadix(16);
private static final String FIELD_IS_LOADING = Util.intToStringMaxRadix(17); private static final String FIELD_IS_LOADING = Util.intToStringMaxRadix(17);
private static final String FIELD_PLAYBACK_ERROR = Util.intToStringMaxRadix(18); private static final String FIELD_PLAYBACK_ERROR = Util.intToStringMaxRadix(18);
private static final String FIELD_SESSION_POSITION_INFO = Util.intToStringMaxRadix(19); @VisibleForTesting static final String FIELD_SESSION_POSITION_INFO = Util.intToStringMaxRadix(19);
private static final String FIELD_MEDIA_ITEM_TRANSITION_REASON = Util.intToStringMaxRadix(20); private static final String FIELD_MEDIA_ITEM_TRANSITION_REASON = Util.intToStringMaxRadix(20);
private static final String FIELD_OLD_POSITION_INFO = Util.intToStringMaxRadix(21); @VisibleForTesting static final String FIELD_OLD_POSITION_INFO = Util.intToStringMaxRadix(21);
private static final String FIELD_NEW_POSITION_INFO = Util.intToStringMaxRadix(22); @VisibleForTesting static final String FIELD_NEW_POSITION_INFO = Util.intToStringMaxRadix(22);
private static final String FIELD_DISCONTINUITY_REASON = Util.intToStringMaxRadix(23); 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_CUE_GROUP = Util.intToStringMaxRadix(24);
private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(25); private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(25);
@ -824,94 +828,198 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
private static final String FIELD_TRACK_SELECTION_PARAMETERS = Util.intToStringMaxRadix(29); 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_CURRENT_TRACKS = Util.intToStringMaxRadix(30);
private static final String FIELD_TIMELINE_CHANGE_REASON = Util.intToStringMaxRadix(31); 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 = 32 // Next field key = 33
public Bundle toBundle( /**
* 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(
Player.Commands availableCommands, boolean excludeTimeline, boolean excludeTracks) { Player.Commands availableCommands, boolean excludeTimeline, boolean excludeTracks) {
Bundle bundle = new Bundle(); PlayerInfo.Builder builder = new Builder(this);
boolean canAccessCurrentMediaItem = boolean canAccessCurrentMediaItem =
availableCommands.contains(Player.COMMAND_GET_CURRENT_MEDIA_ITEM); availableCommands.contains(Player.COMMAND_GET_CURRENT_MEDIA_ITEM);
boolean canAccessTimeline = availableCommands.contains(Player.COMMAND_GET_TIMELINE); boolean canAccessTimeline = availableCommands.contains(Player.COMMAND_GET_TIMELINE);
if (playerError != null) { builder.setSessionPositionInfo(
bundle.putBundle(FIELD_PLAYBACK_ERROR, playerError.toBundle()); 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); if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
bundle.putBundle( builder.setPlaylistMetadata(MediaMetadata.EMPTY);
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));
} }
bundle.putInt(FIELD_TIMELINE_CHANGE_REASON, timelineChangeReason); if (!availableCommands.contains(Player.COMMAND_GET_VOLUME)) {
bundle.putBundle(FIELD_VIDEO_SIZE, videoSize.toBundle()); builder.setVolume(1);
if (availableCommands.contains(Player.COMMAND_GET_METADATA)) {
bundle.putBundle(FIELD_PLAYLIST_METADATA, playlistMetadata.toBundle());
} }
if (availableCommands.contains(Player.COMMAND_GET_VOLUME)) { if (!availableCommands.contains(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
bundle.putFloat(FIELD_VOLUME, volume); builder.setAudioAttributes(AudioAttributes.DEFAULT);
} }
if (availableCommands.contains(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) { if (!availableCommands.contains(Player.COMMAND_GET_TEXT)) {
bundle.putBundle(FIELD_AUDIO_ATTRIBUTES, audioAttributes.toBundle()); builder.setCues(CueGroup.EMPTY_TIME_ZERO);
} }
if (availableCommands.contains(Player.COMMAND_GET_TEXT)) { if (!availableCommands.contains(Player.COMMAND_GET_DEVICE_VOLUME)) {
bundle.putBundle(FIELD_CUE_GROUP, cueGroup.toBundle()); builder.setDeviceVolume(0).setDeviceMuted(false);
} }
bundle.putBundle(FIELD_DEVICE_INFO, deviceInfo.toBundle()); if (!availableCommands.contains(Player.COMMAND_GET_METADATA)) {
if (availableCommands.contains(Player.COMMAND_GET_DEVICE_VOLUME)) { builder.setMediaMetadata(MediaMetadata.EMPTY);
bundle.putInt(FIELD_DEVICE_VOLUME, deviceVolume);
bundle.putBoolean(FIELD_DEVICE_MUTED, deviceMuted);
} }
bundle.putBoolean(FIELD_PLAY_WHEN_READY, playWhenReady); if (excludeTracks || !availableCommands.contains(Player.COMMAND_GET_TRACKS)) {
bundle.putInt(FIELD_PLAYBACK_SUPPRESSION_REASON, playbackSuppressionReason); builder.setCurrentTracks(Tracks.EMPTY);
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());
} }
bundle.putLong(FIELD_SEEK_BACK_INCREMENT_MS, seekBackIncrementMs); return builder.build();
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());
/**
* 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; return bundle;
} }
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
return toBundle( return toBundle(Integer.MAX_VALUE);
/* availableCommands= */ new Player.Commands.Builder().addAllCommands().build(), }
/* excludeTimeline= */ false,
/* excludeTracks= */ false); 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}. */ /** Object that can restore {@link PlayerInfo} from a {@link Bundle}. */
public static final Creator<PlayerInfo> CREATOR = PlayerInfo::fromBundle; public static final Creator<PlayerInfo> CREATOR = PlayerInfo::fromBundle;
private static PlayerInfo fromBundle(Bundle bundle) { 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 Bundle playerErrorBundle = bundle.getBundle(FIELD_PLAYBACK_ERROR);
@Nullable @Nullable
PlaybackException playerError = PlaybackException playerError =
playerErrorBundle == null ? null : PlaybackException.CREATOR.fromBundle(playerErrorBundle); playerErrorBundle == null ? null : PlaybackException.CREATOR.fromBundle(playerErrorBundle);
int mediaItemTransitionReason = int mediaItemTransitionReason =
bundle.getInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, MEDIA_ITEM_TRANSITION_REASON_REPEAT); bundle.getInt(FIELD_MEDIA_ITEM_TRANSITION_REASON, MEDIA_ITEM_TRANSITION_REASON_DEFAULT);
@Nullable Bundle sessionPositionInfoBundle = bundle.getBundle(FIELD_SESSION_POSITION_INFO); @Nullable Bundle sessionPositionInfoBundle = bundle.getBundle(FIELD_SESSION_POSITION_INFO);
SessionPositionInfo sessionPositionInfo = SessionPositionInfo sessionPositionInfo =
sessionPositionInfoBundle == null sessionPositionInfoBundle == null
@ -928,7 +1036,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
? SessionPositionInfo.DEFAULT_POSITION_INFO ? SessionPositionInfo.DEFAULT_POSITION_INFO
: PositionInfo.CREATOR.fromBundle(newPositionInfoBundle); : PositionInfo.CREATOR.fromBundle(newPositionInfoBundle);
int discontinuityReason = int discontinuityReason =
bundle.getInt(FIELD_DISCONTINUITY_REASON, DISCONTINUITY_REASON_AUTO_TRANSITION); bundle.getInt(FIELD_DISCONTINUITY_REASON, DISCONTINUITY_REASON_DEFAULT);
@Nullable Bundle playbackParametersBundle = bundle.getBundle(FIELD_PLAYBACK_PARAMETERS); @Nullable Bundle playbackParametersBundle = bundle.getBundle(FIELD_PLAYBACK_PARAMETERS);
PlaybackParameters playbackParameters = PlaybackParameters playbackParameters =
playbackParametersBundle == null playbackParametersBundle == null
@ -974,7 +1082,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
int playWhenReadyChangeReason = int playWhenReadyChangeReason =
bundle.getInt( bundle.getInt(
FIELD_PLAY_WHEN_READY_CHANGE_REASON, FIELD_PLAY_WHEN_READY_CHANGE_REASON,
/* defaultValue= */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); /* defaultValue= */ PLAY_WHEN_READY_CHANGE_REASON_DEFAULT);
@Player.PlaybackSuppressionReason @Player.PlaybackSuppressionReason
int playbackSuppressionReason = int playbackSuppressionReason =
bundle.getInt( bundle.getInt(
@ -1036,4 +1144,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
currentTracks, currentTracks,
trackSelectionParameters); trackSelectionParameters);
} }
private final class InProcessBinder extends Binder {
public PlayerInfo getPlayerInfo() {
return PlayerInfo.this;
}
}
} }

View File

@ -19,8 +19,10 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.Bundleable; import androidx.media3.common.Bundleable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.Player.PositionInfo; import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
@ -101,9 +103,9 @@ import com.google.common.base.Objects;
return false; return false;
} }
SessionPositionInfo other = (SessionPositionInfo) obj; SessionPositionInfo other = (SessionPositionInfo) obj;
return positionInfo.equals(other.positionInfo) return eventTimeMs == other.eventTimeMs
&& positionInfo.equals(other.positionInfo)
&& isPlayingAd == other.isPlayingAd && isPlayingAd == other.isPlayingAd
&& eventTimeMs == other.eventTimeMs
&& durationMs == other.durationMs && durationMs == other.durationMs
&& bufferedPositionMs == other.bufferedPositionMs && bufferedPositionMs == other.bufferedPositionMs
&& bufferedPercentage == other.bufferedPercentage && bufferedPercentage == other.bufferedPercentage
@ -157,41 +159,86 @@ import com.google.common.base.Objects;
// Bundleable implementation. // Bundleable implementation.
private static final String FIELD_POSITION_INFO = Util.intToStringMaxRadix(0); @VisibleForTesting static final String FIELD_POSITION_INFO = Util.intToStringMaxRadix(0);
private static final String FIELD_IS_PLAYING_AD = Util.intToStringMaxRadix(1); private static final String FIELD_IS_PLAYING_AD = Util.intToStringMaxRadix(1);
private static final String FIELD_EVENT_TIME_MS = Util.intToStringMaxRadix(2); private static final String FIELD_EVENT_TIME_MS = Util.intToStringMaxRadix(2);
private static final String FIELD_DURATION_MS = Util.intToStringMaxRadix(3); private static final String FIELD_DURATION_MS = Util.intToStringMaxRadix(3);
private static final String FIELD_BUFFERED_POSITION_MS = Util.intToStringMaxRadix(4); @VisibleForTesting static final String FIELD_BUFFERED_POSITION_MS = Util.intToStringMaxRadix(4);
private static final String FIELD_BUFFERED_PERCENTAGE = Util.intToStringMaxRadix(5); private static final String FIELD_BUFFERED_PERCENTAGE = Util.intToStringMaxRadix(5);
private static final String FIELD_TOTAL_BUFFERED_DURATION_MS = Util.intToStringMaxRadix(6); private static final String FIELD_TOTAL_BUFFERED_DURATION_MS = Util.intToStringMaxRadix(6);
private static final String FIELD_CURRENT_LIVE_OFFSET_MS = Util.intToStringMaxRadix(7); private static final String FIELD_CURRENT_LIVE_OFFSET_MS = Util.intToStringMaxRadix(7);
private static final String FIELD_CONTENT_DURATION_MS = Util.intToStringMaxRadix(8); private static final String FIELD_CONTENT_DURATION_MS = Util.intToStringMaxRadix(8);
private static final String FIELD_CONTENT_BUFFERED_POSITION_MS = Util.intToStringMaxRadix(9);
@VisibleForTesting
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 @Override
public Bundle toBundle() { 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 bundle = new Bundle();
bundle.putBundle( if (controllerInterfaceVersion < 3 || !DEFAULT_POSITION_INFO.equalsForBundling(positionInfo)) {
FIELD_POSITION_INFO, positionInfo.toBundle(canAccessCurrentMediaItem, canAccessTimeline)); bundle.putBundle(FIELD_POSITION_INFO, positionInfo.toBundle(controllerInterfaceVersion));
bundle.putBoolean(FIELD_IS_PLAYING_AD, canAccessCurrentMediaItem && isPlayingAd); }
if (isPlayingAd) {
bundle.putBoolean(FIELD_IS_PLAYING_AD, isPlayingAd);
}
if (eventTimeMs != C.TIME_UNSET) {
bundle.putLong(FIELD_EVENT_TIME_MS, eventTimeMs); 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); if (durationMs != C.TIME_UNSET) {
bundle.putInt(FIELD_BUFFERED_PERCENTAGE, canAccessCurrentMediaItem ? bufferedPercentage : 0); bundle.putLong(FIELD_DURATION_MS, durationMs);
bundle.putLong( }
FIELD_TOTAL_BUFFERED_DURATION_MS, canAccessCurrentMediaItem ? totalBufferedDurationMs : 0); if (controllerInterfaceVersion < 3 || bufferedPositionMs != 0) {
bundle.putLong( bundle.putLong(FIELD_BUFFERED_POSITION_MS, bufferedPositionMs);
FIELD_CURRENT_LIVE_OFFSET_MS, }
canAccessCurrentMediaItem ? currentLiveOffsetMs : C.TIME_UNSET); if (bufferedPercentage != 0) {
bundle.putLong( bundle.putInt(FIELD_BUFFERED_PERCENTAGE, bufferedPercentage);
FIELD_CONTENT_DURATION_MS, canAccessCurrentMediaItem ? contentDurationMs : C.TIME_UNSET); }
bundle.putLong( if (totalBufferedDurationMs != 0) {
FIELD_CONTENT_BUFFERED_POSITION_MS, bundle.putLong(FIELD_TOTAL_BUFFERED_DURATION_MS, totalBufferedDurationMs);
canAccessCurrentMediaItem ? contentBufferedPositionMs : 0); }
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; return bundle;
} }

View File

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

View File

@ -85,4 +85,35 @@ public class SessionPositionInfoTest {
/* contentDurationMs= */ 400L, /* contentDurationMs= */ 400L,
/* contentBufferedPositionMs= */ 223L)); /* 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);
}
} }