Exclude tracks from PlayerInfo if not changed

This change includes a change in the `IMediaController.aidl` file and needs
to provide backwards compatibility for when a client connects that is of an older or
newer version of the current service implementation.

This CL proposes to create a new AIDL method `onPlayerInfoChangedWithExtensions`
that is easier to extend in the future because it does use an `Bundle` rather than
primitives. A `Bundle` can be changed in a backward/forwards compatible way
in case we need further changes.

The compatibility handling is provided in `MediaSessionStub` and `MediaControllerStub`. The approach is not based on specific AIDL/Binder features but implemented fully in application code.

Issue: androidx/media#102
#minor-release
PiperOrigin-RevId: 490483068
This commit is contained in:
bachinger 2022-11-23 14:10:08 +00:00 committed by Ian Baker
parent 10fac6847a
commit 3d8c52f28d
11 changed files with 396 additions and 131 deletions

View File

@ -677,7 +677,8 @@ public interface Player {
* to the current {@link #getRepeatMode() repeat mode}. * to the current {@link #getRepeatMode() repeat mode}.
* *
* <p>Note that this callback is also called when the playlist becomes non-empty or empty as a * <p>Note that this callback is also called when the playlist becomes non-empty or empty as a
* consequence of a playlist change. * consequence of a playlist change or {@linkplain #onAvailableCommandsChanged(Commands) a
* change in available commands}.
* *
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration. * other events that happen in the same {@link Looper} message queue iteration.

View File

@ -35,14 +35,19 @@ oneway interface IMediaController {
void onSetCustomLayout(int seq, in List<Bundle> commandButtonList) = 3003; void onSetCustomLayout(int seq, in List<Bundle> commandButtonList) = 3003;
void onCustomCommand(int seq, in Bundle command, in Bundle args) = 3004; void onCustomCommand(int seq, in Bundle command, in Bundle args) = 3004;
void onDisconnected(int seq) = 3005; void onDisconnected(int seq) = 3005;
void onPlayerInfoChanged(int seq, in Bundle playerInfoBundle, boolean isTimelineExcluded) = 3006; /** Deprecated: Use onPlayerInfoChangedWithExclusions from MediaControllerStub#VERSION_INT=2. */
void onPlayerInfoChanged(
int seq, in Bundle playerInfoBundle, boolean isTimelineExcluded) = 3006;
/** Introduced to deprecate onPlayerInfoChanged (from MediaControllerStub#VERSION_INT=2). */
void onPlayerInfoChangedWithExclusions(
int seq, in Bundle playerInfoBundle, in Bundle playerInfoExclusions) = 3012;
void onPeriodicSessionPositionInfoChanged(int seq, in Bundle sessionPositionInfo) = 3007; void onPeriodicSessionPositionInfoChanged(int seq, in Bundle sessionPositionInfo) = 3007;
void onAvailableCommandsChangedFromPlayer(int seq, in Bundle commandsBundle) = 3008; void onAvailableCommandsChangedFromPlayer(int seq, in Bundle commandsBundle) = 3008;
void onAvailableCommandsChangedFromSession( void onAvailableCommandsChangedFromSession(
int seq, in Bundle sessionCommandsBundle, in Bundle playerCommandsBundle) = 3009; int seq, in Bundle sessionCommandsBundle, in Bundle playerCommandsBundle) = 3009;
void onRenderedFirstFrame(int seq) = 3010; void onRenderedFirstFrame(int seq) = 3010;
void onExtrasChanged(int seq, in Bundle extras) = 3011; void onExtrasChanged(int seq, in Bundle extras) = 3011;
// Next Id for MediaController: 3012 // Next Id for MediaController: 3013
void onChildrenChanged( void onChildrenChanged(
int seq, String parentId, int itemCount, in @nullable Bundle libraryParams) = 4000; int seq, String parentId, int itemCount, in @nullable Bundle libraryParams) = 4000;

View File

@ -23,6 +23,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.usToMs; import static androidx.media3.common.util.Util.usToMs;
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage; import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
import static androidx.media3.session.MediaUtils.intersect; import static androidx.media3.session.MediaUtils.intersect;
import static androidx.media3.session.MediaUtils.mergePlayerInfo;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -42,6 +43,7 @@ import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaBrowserCompat;
import android.util.Pair;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
@ -79,6 +81,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.session.MediaController.MediaControllerImpl; import androidx.media3.session.MediaController.MediaControllerImpl;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -129,7 +132,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
@Nullable private IMediaSession iSession; @Nullable private IMediaSession iSession;
private long lastReturnedCurrentPositionMs; private long lastReturnedCurrentPositionMs;
private long lastSetPlayWhenReadyCalledTimeMs; private long lastSetPlayWhenReadyCalledTimeMs;
@Nullable private Timeline pendingPlayerInfoUpdateTimeline; @Nullable private PlayerInfo pendingPlayerInfo;
@Nullable private BundlingExclusions pendingBundlingExclusions;
public MediaControllerImplBase( public MediaControllerImplBase(
Context context, Context context,
@ -2329,30 +2333,41 @@ import org.checkerframework.checker.nullness.qual.NonNull;
} }
@SuppressWarnings("deprecation") // Implementing and calling deprecated listener method. @SuppressWarnings("deprecation") // Implementing and calling deprecated listener method.
void onPlayerInfoChanged(PlayerInfo newPlayerInfo, boolean isTimelineExcluded) { void onPlayerInfoChanged(PlayerInfo newPlayerInfo, BundlingExclusions bundlingExclusions) {
if (!isConnected()) { if (!isConnected()) {
return; return;
} }
if (pendingPlayerInfo != null && pendingBundlingExclusions != null) {
Pair<PlayerInfo, BundlingExclusions> mergedPlayerInfoUpdate =
mergePlayerInfo(
pendingPlayerInfo,
pendingBundlingExclusions,
newPlayerInfo,
bundlingExclusions,
intersectedPlayerCommands);
newPlayerInfo = mergedPlayerInfoUpdate.first;
bundlingExclusions = mergedPlayerInfoUpdate.second;
}
pendingPlayerInfo = null;
pendingBundlingExclusions = null;
if (!pendingMaskingSequencedFutureNumbers.isEmpty()) { if (!pendingMaskingSequencedFutureNumbers.isEmpty()) {
// We are still waiting for all pending masking operations to be handled. // We are still waiting for all pending masking operations to be handled.
if (!isTimelineExcluded) { pendingPlayerInfo = newPlayerInfo;
pendingPlayerInfoUpdateTimeline = newPlayerInfo.timeline; pendingBundlingExclusions = bundlingExclusions;
}
return; return;
} }
PlayerInfo oldPlayerInfo = playerInfo; PlayerInfo oldPlayerInfo = playerInfo;
if (isTimelineExcluded) {
newPlayerInfo =
newPlayerInfo.copyWithTimeline(
pendingPlayerInfoUpdateTimeline != null
? pendingPlayerInfoUpdateTimeline
: oldPlayerInfo.timeline);
}
// Assigning class variable now so that all getters called from listeners see the updated value. // Assigning class variable now so that all getters called from listeners see the updated value.
// But we need to use a local final variable to ensure listeners get consistent parameters. // But we need to use a local final variable to ensure listeners get consistent parameters.
playerInfo = newPlayerInfo; playerInfo =
PlayerInfo finalPlayerInfo = newPlayerInfo; mergePlayerInfo(
pendingPlayerInfoUpdateTimeline = null; oldPlayerInfo,
/* oldBundlingExclusions= */ BundlingExclusions.NONE,
newPlayerInfo,
/* newBundlingExclusions= */ bundlingExclusions,
intersectedPlayerCommands)
.first;
PlayerInfo finalPlayerInfo = playerInfo;
PlaybackException oldPlayerError = oldPlayerInfo.playerError; PlaybackException oldPlayerError = oldPlayerInfo.playerError;
PlaybackException playerError = finalPlayerInfo.playerError; PlaybackException playerError = finalPlayerInfo.playerError;
boolean errorsMatch = boolean errorsMatch =
@ -2397,7 +2412,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/* eventFlag= */ Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, /* eventFlag= */ Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
listener -> listener.onShuffleModeEnabledChanged(finalPlayerInfo.shuffleModeEnabled)); listener -> listener.onShuffleModeEnabledChanged(finalPlayerInfo.shuffleModeEnabled));
} }
if (!isTimelineExcluded && !Util.areEqual(oldPlayerInfo.timeline, finalPlayerInfo.timeline)) { if (!Util.areEqual(oldPlayerInfo.timeline, finalPlayerInfo.timeline)) {
listeners.queueEvent( listeners.queueEvent(
/* eventFlag= */ Player.EVENT_TIMELINE_CHANGED, /* eventFlag= */ Player.EVENT_TIMELINE_CHANGED,
listener -> listener ->

View File

@ -26,6 +26,7 @@ import androidx.media3.common.Player.Commands;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
@ -35,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
private static final String TAG = "MediaControllerStub"; private static final String TAG = "MediaControllerStub";
/** The version of the IMediaController interface. */ /** The version of the IMediaController interface. */
public static final int VERSION_INT = 1; public static final int VERSION_INT = 2;
private final WeakReference<MediaControllerImplBase> controller; private final WeakReference<MediaControllerImplBase> controller;
@ -169,8 +170,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
controller -> controller.notifyPeriodicSessionPositionInfoChanged(sessionPositionInfo)); controller -> controller.notifyPeriodicSessionPositionInfoChanged(sessionPositionInfo));
} }
/**
* @deprecated Use {@link #onPlayerInfoChangedWithExclusions} from {@link #VERSION_INT} 2.
*/
@Override @Override
@Deprecated
public void onPlayerInfoChanged(int seq, Bundle playerInfoBundle, boolean isTimelineExcluded) { public void onPlayerInfoChanged(int seq, Bundle playerInfoBundle, boolean isTimelineExcluded) {
onPlayerInfoChangedWithExclusions(
seq,
playerInfoBundle,
new BundlingExclusions(isTimelineExcluded, /* areCurrentTracksExcluded= */ true)
.toBundle());
}
/** Added in {@link #VERSION_INT} 2. */
@Override
public void onPlayerInfoChangedWithExclusions(
int seq, Bundle playerInfoBundle, Bundle playerInfoExclusions) {
PlayerInfo playerInfo; PlayerInfo playerInfo;
try { try {
playerInfo = PlayerInfo.CREATOR.fromBundle(playerInfoBundle); playerInfo = PlayerInfo.CREATOR.fromBundle(playerInfoBundle);
@ -178,8 +194,15 @@ import org.checkerframework.checker.nullness.qual.NonNull;
Log.w(TAG, "Ignoring malformed Bundle for PlayerInfo", e); Log.w(TAG, "Ignoring malformed Bundle for PlayerInfo", e);
return; return;
} }
BundlingExclusions bundlingExclusions;
try {
bundlingExclusions = BundlingExclusions.CREATOR.fromBundle(playerInfoExclusions);
} catch (RuntimeException e) {
Log.w(TAG, "Ignoring malformed Bundle for BundlingExclusions", e);
return;
}
dispatchControllerTaskOnHandler( dispatchControllerTaskOnHandler(
controller -> controller.onPlayerInfoChanged(playerInfo, isTimelineExcluded)); controller -> controller.onPlayerInfoChanged(playerInfo, bundlingExclusions));
} }
@Override @Override

View File

@ -1136,7 +1136,8 @@ public class MediaSession {
boolean excludeMediaItemsMetadata, boolean excludeMediaItemsMetadata,
boolean excludeCues, boolean excludeCues,
boolean excludeTimeline, boolean excludeTimeline,
boolean excludeTracks) boolean excludeTracks,
int controllerInterfaceVersion)
throws RemoteException {} throws RemoteException {}
default void onPeriodicSessionPositionInfoChanged( default void onPeriodicSessionPositionInfoChanged(

View File

@ -15,6 +15,10 @@
*/ */
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA;
import static androidx.media3.common.Player.COMMAND_GET_TEXT;
import static androidx.media3.common.Player.COMMAND_GET_TIMELINE;
import static androidx.media3.common.Player.COMMAND_GET_TRACKS;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
@ -274,7 +278,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
playerInfo = newPlayerWrapper.createPlayerInfoForBundling(); playerInfo = newPlayerWrapper.createPlayerInfoForBundling();
onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false); onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ false, /* excludeTracks= */ false);
} }
public void release() { public void release() {
@ -374,7 +379,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
controller, controller,
(callback, seq) -> (callback, seq) ->
callback.onAvailableCommandsChangedFromSession(seq, sessionCommands, playerCommands)); callback.onAvailableCommandsChangedFromSession(seq, sessionCommands, playerCommands));
onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false); onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ false, /* excludeTracks= */ false);
} else { } else {
sessionLegacyStub sessionLegacyStub
.getConnectedControllersManager() .getConnectedControllersManager()
@ -387,7 +393,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
(controller, seq) -> controller.sendCustomCommand(seq, command, args)); (controller, seq) -> controller.sendCustomCommand(seq, command, args));
} }
private void dispatchOnPlayerInfoChanged(PlayerInfo playerInfo, boolean excludeTimeline) { private void dispatchOnPlayerInfoChanged(
PlayerInfo playerInfo, boolean excludeTimeline, boolean excludeTracks) {
List<ControllerInfo> controllers = List<ControllerInfo> controllers =
sessionStub.getConnectedControllersManager().getConnectedControllers(); sessionStub.getConnectedControllersManager().getConnectedControllers();
@ -395,8 +402,9 @@ import org.checkerframework.checker.initialization.qual.Initialized;
ControllerInfo controller = controllers.get(i); ControllerInfo controller = controllers.get(i);
try { try {
int seq; int seq;
SequencedFutureManager manager = ConnectedControllersManager<IBinder> controllersManager =
sessionStub.getConnectedControllersManager().getSequencedFutureManager(controller); sessionStub.getConnectedControllersManager();
SequencedFutureManager manager = controllersManager.getSequencedFutureManager(controller);
if (manager != null) { if (manager != null) {
seq = manager.obtainNextSequenceNumber(); seq = manager.obtainNextSequenceNumber();
} else { } else {
@ -410,19 +418,18 @@ import org.checkerframework.checker.initialization.qual.Initialized;
.onPlayerInfoChanged( .onPlayerInfoChanged(
seq, seq,
playerInfo, playerInfo,
/* excludeMediaItems= */ !sessionStub /* excludeMediaItems= */ !controllersManager.isPlayerCommandAvailable(
.getConnectedControllersManager() controller, COMMAND_GET_TIMELINE),
.isPlayerCommandAvailable(controller, Player.COMMAND_GET_TIMELINE), /* excludeMediaItemsMetadata= */ !controllersManager.isPlayerCommandAvailable(
/* excludeMediaItemsMetadata= */ !sessionStub controller, COMMAND_GET_MEDIA_ITEMS_METADATA),
.getConnectedControllersManager() /* excludeCues= */ !controllersManager.isPlayerCommandAvailable(
.isPlayerCommandAvailable(controller, Player.COMMAND_GET_MEDIA_ITEMS_METADATA), controller, COMMAND_GET_TEXT),
/* excludeCues= */ !sessionStub excludeTimeline
.getConnectedControllersManager() || !controllersManager.isPlayerCommandAvailable(
.isPlayerCommandAvailable(controller, Player.COMMAND_GET_TEXT), controller, COMMAND_GET_TIMELINE),
excludeTimeline, excludeTracks
/* excludeTracks= */ !sessionStub || !controllersManager.isPlayerCommandAvailable(controller, COMMAND_GET_TRACKS),
.getConnectedControllersManager() controller.getInterfaceVersion());
.isPlayerCommandAvailable(controller, Player.COMMAND_GET_TRACKS));
} catch (DeadObjectException e) { } catch (DeadObjectException e) {
onDeadObjectException(controller); onDeadObjectException(controller);
} catch (RemoteException e) { } catch (RemoteException e) {
@ -745,7 +752,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithPlayerError(error); session.playerInfo = session.playerInfo.copyWithPlayerError(error);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onPlayerError(seq, error)); (callback, seq) -> callback.onPlayerError(seq, error));
} }
@ -765,7 +773,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
// Note: OK to omit mediaItem here, because PlayerInfo changed message will copy playerInfo // Note: OK to omit mediaItem here, because PlayerInfo changed message will copy playerInfo
// with sessionPositionInfo, which includes current window index. // with sessionPositionInfo, which includes current window index.
session.playerInfo = session.playerInfo.copyWithMediaItemTransitionReason(reason); session.playerInfo = session.playerInfo.copyWithMediaItemTransitionReason(reason);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onMediaItemTransition(seq, mediaItem, reason)); (callback, seq) -> callback.onMediaItemTransition(seq, mediaItem, reason));
} }
@ -785,7 +794,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
session.playerInfo = session.playerInfo =
session.playerInfo.copyWithPlayWhenReady( session.playerInfo.copyWithPlayWhenReady(
playWhenReady, reason, session.playerInfo.playbackSuppressionReason); playWhenReady, reason, session.playerInfo.playbackSuppressionReason);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onPlayWhenReadyChanged(seq, playWhenReady, reason)); (callback, seq) -> callback.onPlayWhenReadyChanged(seq, playWhenReady, reason));
} }
@ -806,7 +816,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
session.playerInfo.playWhenReady, session.playerInfo.playWhenReady,
session.playerInfo.playWhenReadyChangedReason, session.playerInfo.playWhenReadyChangedReason,
reason); reason);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onPlaybackSuppressionReasonChanged(seq, reason)); (callback, seq) -> callback.onPlaybackSuppressionReasonChanged(seq, reason));
} }
@ -824,7 +835,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
session.playerInfo = session.playerInfo =
session.playerInfo.copyWithPlaybackState(playbackState, player.getPlayerError()); session.playerInfo.copyWithPlaybackState(playbackState, player.getPlayerError());
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> { (callback, seq) -> {
callback.onPlaybackStateChanged(seq, playbackState, player.getPlayerError()); callback.onPlaybackStateChanged(seq, playbackState, player.getPlayerError());
@ -843,7 +855,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithIsPlaying(isPlaying); session.playerInfo = session.playerInfo.copyWithIsPlaying(isPlaying);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onIsPlayingChanged(seq, isPlaying)); (callback, seq) -> callback.onIsPlayingChanged(seq, isPlaying));
} }
@ -860,7 +873,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithIsLoading(isLoading); session.playerInfo = session.playerInfo.copyWithIsLoading(isLoading);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onIsLoadingChanged(seq, isLoading)); (callback, seq) -> callback.onIsLoadingChanged(seq, isLoading));
} }
@ -880,7 +894,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
session.playerInfo = session.playerInfo =
session.playerInfo.copyWithPositionInfos(oldPosition, newPosition, reason); session.playerInfo.copyWithPositionInfos(oldPosition, newPosition, reason);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> (callback, seq) ->
callback.onPositionDiscontinuity(seq, oldPosition, newPosition, reason)); callback.onPositionDiscontinuity(seq, oldPosition, newPosition, reason));
@ -898,7 +913,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithPlaybackParameters(playbackParameters); session.playerInfo = session.playerInfo.copyWithPlaybackParameters(playbackParameters);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onPlaybackParametersChanged(seq, playbackParameters)); (callback, seq) -> callback.onPlaybackParametersChanged(seq, playbackParameters));
} }
@ -915,7 +931,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithSeekBackIncrement(seekBackIncrementMs); session.playerInfo = session.playerInfo.copyWithSeekBackIncrement(seekBackIncrementMs);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onSeekBackIncrementChanged(seq, seekBackIncrementMs)); (callback, seq) -> callback.onSeekBackIncrementChanged(seq, seekBackIncrementMs));
} }
@ -932,7 +949,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithSeekForwardIncrement(seekForwardIncrementMs); session.playerInfo = session.playerInfo.copyWithSeekForwardIncrement(seekForwardIncrementMs);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onSeekForwardIncrementChanged(seq, seekForwardIncrementMs)); (callback, seq) -> callback.onSeekForwardIncrementChanged(seq, seekForwardIncrementMs));
} }
@ -951,7 +969,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
session.playerInfo = session.playerInfo =
session.playerInfo.copyWithTimelineAndSessionPositionInfo( session.playerInfo.copyWithTimelineAndSessionPositionInfo(
timeline, player.createSessionPositionInfoForBundling()); timeline, player.createSessionPositionInfoForBundling());
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ false, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onTimelineChanged(seq, timeline, reason)); (callback, seq) -> callback.onTimelineChanged(seq, timeline, reason));
} }
@ -964,7 +983,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
session.verifyApplicationThread(); session.verifyApplicationThread();
session.playerInfo = session.playerInfo.copyWithPlaylistMetadata(playlistMetadata); session.playerInfo = session.playerInfo.copyWithPlaylistMetadata(playlistMetadata);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onPlaylistMetadataChanged(seq, playlistMetadata)); (callback, seq) -> callback.onPlaylistMetadataChanged(seq, playlistMetadata));
} }
@ -981,7 +1001,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithRepeatMode(repeatMode); session.playerInfo = session.playerInfo.copyWithRepeatMode(repeatMode);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onRepeatModeChanged(seq, repeatMode)); (callback, seq) -> callback.onRepeatModeChanged(seq, repeatMode));
} }
@ -998,7 +1019,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithShuffleModeEnabled(shuffleModeEnabled); session.playerInfo = session.playerInfo.copyWithShuffleModeEnabled(shuffleModeEnabled);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onShuffleModeEnabledChanged(seq, shuffleModeEnabled)); (callback, seq) -> callback.onShuffleModeEnabledChanged(seq, shuffleModeEnabled));
} }
@ -1015,7 +1037,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithAudioAttributes(attributes); session.playerInfo = session.playerInfo.copyWithAudioAttributes(attributes);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(controller, seq) -> controller.onAudioAttributesChanged(seq, attributes)); (controller, seq) -> controller.onAudioAttributesChanged(seq, attributes));
} }
@ -1028,7 +1051,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
session.verifyApplicationThread(); session.verifyApplicationThread();
session.playerInfo = session.playerInfo.copyWithVideoSize(size); session.playerInfo = session.playerInfo.copyWithVideoSize(size);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onVideoSizeChanged(seq, size)); (callback, seq) -> callback.onVideoSizeChanged(seq, size));
} }
@ -1041,7 +1065,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
session.verifyApplicationThread(); session.verifyApplicationThread();
session.playerInfo = session.playerInfo.copyWithVolume(volume); session.playerInfo = session.playerInfo.copyWithVolume(volume);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onVolumeChanged(seq, volume)); (callback, seq) -> callback.onVolumeChanged(seq, volume));
} }
@ -1058,7 +1083,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = new PlayerInfo.Builder(session.playerInfo).setCues(cueGroup).build(); session.playerInfo = new PlayerInfo.Builder(session.playerInfo).setCues(cueGroup).build();
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
} }
@Override @Override
@ -1073,7 +1099,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithDeviceInfo(deviceInfo); session.playerInfo = session.playerInfo.copyWithDeviceInfo(deviceInfo);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onDeviceInfoChanged(seq, deviceInfo)); (callback, seq) -> callback.onDeviceInfoChanged(seq, deviceInfo));
} }
@ -1090,7 +1117,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithDeviceVolume(volume, muted); session.playerInfo = session.playerInfo.copyWithDeviceVolume(volume, muted);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onDeviceVolumeChanged(seq, volume, muted)); (callback, seq) -> callback.onDeviceVolumeChanged(seq, volume, muted));
} }
@ -1106,7 +1134,9 @@ import org.checkerframework.checker.initialization.qual.Initialized;
if (player == null) { if (player == null) {
return; return;
} }
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ false); boolean excludeTracks = !availableCommands.contains(COMMAND_GET_TRACKS);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ false, excludeTracks);
session.dispatchRemoteControllerTaskWithoutReturn( session.dispatchRemoteControllerTaskWithoutReturn(
(callback, seq) -> callback.onAvailableCommandsChangedFromPlayer(seq, availableCommands)); (callback, seq) -> callback.onAvailableCommandsChangedFromPlayer(seq, availableCommands));
@ -1128,7 +1158,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithCurrentTracks(tracks); session.playerInfo = session.playerInfo.copyWithCurrentTracks(tracks);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ false);
session.dispatchRemoteControllerTaskWithoutReturn( session.dispatchRemoteControllerTaskWithoutReturn(
(callback, seq) -> callback.onTracksChanged(seq, tracks)); (callback, seq) -> callback.onTracksChanged(seq, tracks));
} }
@ -1145,7 +1176,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithTrackSelectionParameters(parameters); session.playerInfo = session.playerInfo.copyWithTrackSelectionParameters(parameters);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskWithoutReturn( session.dispatchRemoteControllerTaskWithoutReturn(
(callback, seq) -> callback.onTrackSelectionParametersChanged(seq, parameters)); (callback, seq) -> callback.onTrackSelectionParametersChanged(seq, parameters));
} }
@ -1162,7 +1194,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
return; return;
} }
session.playerInfo = session.playerInfo.copyWithMediaMetadata(mediaMetadata); session.playerInfo = session.playerInfo.copyWithMediaMetadata(mediaMetadata);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
session.dispatchRemoteControllerTaskToLegacyStub( session.dispatchRemoteControllerTaskToLegacyStub(
(callback, seq) -> callback.onMediaMetadataChanged(seq, mediaMetadata)); (callback, seq) -> callback.onMediaMetadataChanged(seq, mediaMetadata));
} }
@ -1190,7 +1223,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
} }
session.playerInfo = session.playerInfo =
session.playerInfo.copyWithMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs); session.playerInfo.copyWithMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs);
session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(/* excludeTimeline= */ true); session.onPlayerInfoChangedHandler.sendPlayerInfoChangedMessage(
/* excludeTimeline= */ true, /* excludeTracks= */ true);
} }
@Nullable @Nullable
@ -1224,10 +1258,12 @@ import org.checkerframework.checker.initialization.qual.Initialized;
private static final int MSG_PLAYER_INFO_CHANGED = 1; private static final int MSG_PLAYER_INFO_CHANGED = 1;
private boolean excludeTimeline; private boolean excludeTimeline;
private boolean excludeTracks;
public PlayerInfoChangedHandler(Looper looper) { public PlayerInfoChangedHandler(Looper looper) {
super(looper); super(looper);
excludeTimeline = true; excludeTimeline = true;
excludeTracks = true;
} }
@Override @Override
@ -1237,15 +1273,17 @@ import org.checkerframework.checker.initialization.qual.Initialized;
playerInfo.copyWithTimelineAndSessionPositionInfo( playerInfo.copyWithTimelineAndSessionPositionInfo(
getPlayerWrapper().getCurrentTimeline(), getPlayerWrapper().getCurrentTimeline(),
getPlayerWrapper().createSessionPositionInfoForBundling()); getPlayerWrapper().createSessionPositionInfoForBundling());
dispatchOnPlayerInfoChanged(playerInfo, excludeTimeline); dispatchOnPlayerInfoChanged(playerInfo, excludeTimeline, excludeTracks);
excludeTimeline = true; excludeTimeline = true;
excludeTracks = true;
} else { } else {
throw new IllegalStateException("Invalid message what=" + msg.what); throw new IllegalStateException("Invalid message what=" + msg.what);
} }
} }
public void sendPlayerInfoChangedMessage(boolean excludeTimeline) { public void sendPlayerInfoChangedMessage(boolean excludeTimeline, boolean excludeTracks) {
this.excludeTimeline = this.excludeTimeline && excludeTimeline; this.excludeTimeline = this.excludeTimeline && excludeTimeline;
this.excludeTracks = this.excludeTracks && excludeTracks;
if (!onPlayerInfoChangedHandler.hasMessages(MSG_PLAYER_INFO_CHANGED)) { if (!onPlayerInfoChangedHandler.hasMessages(MSG_PLAYER_INFO_CHANGED)) {
onPlayerInfoChangedHandler.sendEmptyMessage(MSG_PLAYER_INFO_CHANGED); onPlayerInfoChangedHandler.sendEmptyMessage(MSG_PLAYER_INFO_CHANGED);
} }

View File

@ -70,6 +70,7 @@ import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Rating; import androidx.media3.common.Rating;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Consumer; import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
@ -1596,17 +1597,32 @@ import java.util.concurrent.ExecutionException;
boolean excludeMediaItemsMetadata, boolean excludeMediaItemsMetadata,
boolean excludeCues, boolean excludeCues,
boolean excludeTimeline, boolean excludeTimeline,
boolean excludeTracks) boolean excludeTracks,
int controllerInterfaceVersion)
throws RemoteException { throws RemoteException {
iController.onPlayerInfoChanged( Assertions.checkState(controllerInterfaceVersion != 0);
sequenceNumber, if (controllerInterfaceVersion >= 2) {
playerInfo.toBundle( iController.onPlayerInfoChangedWithExclusions(
excludeMediaItems, sequenceNumber,
excludeMediaItemsMetadata, playerInfo.toBundle(
excludeCues, excludeMediaItems,
excludeTimeline, excludeMediaItemsMetadata,
excludeTracks), excludeCues,
/* isTimelineExcluded= */ excludeTimeline); excludeTimeline,
excludeTracks),
new PlayerInfo.BundlingExclusions(excludeTimeline, excludeTracks).toBundle());
} else {
//noinspection deprecation
iController.onPlayerInfoChanged(
sequenceNumber,
playerInfo.toBundle(
excludeMediaItems,
excludeMediaItemsMetadata,
excludeCues,
excludeTimeline,
/* excludeTracks= */ true),
excludeTimeline);
}
} }
@Override @Override

View File

@ -62,6 +62,7 @@ import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.media.session.PlaybackStateCompat.CustomAction; import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat; import androidx.media.AudioAttributesCompat;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot; import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
@ -87,6 +88,7 @@ import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams; import androidx.media3.session.MediaLibraryService.LibraryParams;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -1288,6 +1290,46 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return intersectCommandsBuilder.build(); return intersectCommandsBuilder.build();
} }
/**
* Merges the excluded fields into the {@code newPlayerInfo} by taking the values of the {@code
* previousPlayerInfo} and taking into account the passed available commands.
*
* @param oldPlayerInfo The old {@link PlayerInfo}.
* @param oldBundlingExclusions The bundling exlusions in the old {@link PlayerInfo}.
* @param newPlayerInfo The new {@link PlayerInfo}.
* @param newBundlingExclusions The bundling exlusions in the new {@link PlayerInfo}.
* @param availablePlayerCommands The available commands to take into account when merging.
* @return A pair with the resulting {@link PlayerInfo} and {@link BundlingExclusions}.
*/
public static Pair<PlayerInfo, BundlingExclusions> mergePlayerInfo(
PlayerInfo oldPlayerInfo,
BundlingExclusions oldBundlingExclusions,
PlayerInfo newPlayerInfo,
BundlingExclusions newBundlingExclusions,
Commands availablePlayerCommands) {
PlayerInfo mergedPlayerInfo = newPlayerInfo;
BundlingExclusions mergedBundlingExclusions = newBundlingExclusions;
if (newBundlingExclusions.isTimelineExcluded
&& availablePlayerCommands.contains(Player.COMMAND_GET_TIMELINE)
&& !oldBundlingExclusions.isTimelineExcluded) {
// Use the previous timeline if it is excluded in the most recent update.
mergedPlayerInfo = mergedPlayerInfo.copyWithTimeline(oldPlayerInfo.timeline);
mergedBundlingExclusions =
new BundlingExclusions(
/* isTimelineExcluded= */ false, mergedBundlingExclusions.areCurrentTracksExcluded);
}
if (newBundlingExclusions.areCurrentTracksExcluded
&& availablePlayerCommands.contains(Player.COMMAND_GET_TRACKS)
&& !oldBundlingExclusions.areCurrentTracksExcluded) {
// Use the previous tracks if it is excluded in the most recent update.
mergedPlayerInfo = mergedPlayerInfo.copyWithCurrentTracks(oldPlayerInfo.currentTracks);
mergedBundlingExclusions =
new BundlingExclusions(
mergedBundlingExclusions.isTimelineExcluded, /* areCurrentTracksExcluded= */ false);
}
return new Pair<>(mergedPlayerInfo, mergedBundlingExclusions);
}
private static byte[] convertToByteArray(Bitmap bitmap) throws IOException { private static byte[] convertToByteArray(Bitmap bitmap) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream); bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream);

View File

@ -66,6 +66,10 @@ import java.lang.annotation.Target;
*/ */
public static class BundlingExclusions implements Bundleable { public static class BundlingExclusions implements Bundleable {
/** Bundling exclusions with no exclusions. */
public static final BundlingExclusions NONE =
new BundlingExclusions(
/* isTimelineExcluded= */ false, /* areCurrentTracksExcluded= */ false);
/** Whether the {@linkplain PlayerInfo#timeline timeline} is excluded. */ /** Whether the {@linkplain PlayerInfo#timeline timeline} is excluded. */
public final boolean isTimelineExcluded; public final boolean isTimelineExcluded;
/** Whether the {@linkplain PlayerInfo#currentTracks current tracks} are excluded. */ /** Whether the {@linkplain PlayerInfo#currentTracks current tracks} are excluded. */

View File

@ -1052,8 +1052,8 @@ public class MediaControllerListenerTest {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = controllerTestRule.createController(remoteSession.getToken());
AtomicReference<Tracks> changedCurrentTracksFromParamRef = new AtomicReference<>(); AtomicReference<Tracks> changedCurrentTracksFromParamRef = new AtomicReference<>();
AtomicReference<Tracks> changedCurrentTracksFromGetterRef = new AtomicReference<>(); AtomicReference<Tracks> changedCurrentTracksFromGetterRef = new AtomicReference<>();
AtomicReference<Tracks> changedCurrentTracksFromOnEventsRef = new AtomicReference<>(); List<Tracks> changedCurrentTracksFromOnEvents = new ArrayList<>();
AtomicReference<Player.Events> eventsRef = new AtomicReference<>(); List<Player.Events> capturedEvents = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(2); CountDownLatch latch = new CountDownLatch(2);
Player.Listener listener = Player.Listener listener =
new Player.Listener() { new Player.Listener() {
@ -1061,13 +1061,12 @@ public class MediaControllerListenerTest {
public void onTracksChanged(Tracks currentTracks) { public void onTracksChanged(Tracks currentTracks) {
changedCurrentTracksFromParamRef.set(currentTracks); changedCurrentTracksFromParamRef.set(currentTracks);
changedCurrentTracksFromGetterRef.set(controller.getCurrentTracks()); changedCurrentTracksFromGetterRef.set(controller.getCurrentTracks());
latch.countDown();
} }
@Override @Override
public void onEvents(Player player, Player.Events events) { public void onEvents(Player player, Player.Events events) {
eventsRef.set(events); capturedEvents.add(events);
changedCurrentTracksFromOnEventsRef.set(player.getCurrentTracks()); changedCurrentTracksFromOnEvents.add(player.getCurrentTracks());
latch.countDown(); latch.countDown();
} }
}; };
@ -1081,13 +1080,22 @@ public class MediaControllerListenerTest {
}); });
player.notifyTracksChanged(currentTracks); player.notifyTracksChanged(currentTracks);
player.notifyIsLoadingChanged(true);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(initialCurrentTracksRef.get()).isEqualTo(Tracks.EMPTY); assertThat(initialCurrentTracksRef.get()).isEqualTo(Tracks.EMPTY);
assertThat(changedCurrentTracksFromParamRef.get()).isEqualTo(currentTracks); assertThat(changedCurrentTracksFromParamRef.get()).isEqualTo(currentTracks);
assertThat(changedCurrentTracksFromGetterRef.get()).isEqualTo(currentTracks); assertThat(changedCurrentTracksFromGetterRef.get()).isEqualTo(currentTracks);
assertThat(changedCurrentTracksFromOnEventsRef.get()).isEqualTo(currentTracks); assertThat(capturedEvents).hasSize(2);
assertThat(getEventsAsList(eventsRef.get())).containsExactly(Player.EVENT_TRACKS_CHANGED); assertThat(getEventsAsList(capturedEvents.get(0))).containsExactly(Player.EVENT_TRACKS_CHANGED);
assertThat(getEventsAsList(capturedEvents.get(1)))
.containsExactly(Player.EVENT_IS_LOADING_CHANGED);
assertThat(changedCurrentTracksFromOnEvents).hasSize(2);
assertThat(changedCurrentTracksFromOnEvents.get(0)).isEqualTo(currentTracks);
assertThat(changedCurrentTracksFromOnEvents.get(1)).isEqualTo(currentTracks);
// Assert that an equal instance is not re-sent over the binder.
assertThat(changedCurrentTracksFromOnEvents.get(0))
.isSameInstanceAs(changedCurrentTracksFromOnEvents.get(1));
} }
@Test @Test
@ -1142,6 +1150,9 @@ public class MediaControllerListenerTest {
assertThat(capturedCurrentTracks).containsExactly(Tracks.EMPTY); assertThat(capturedCurrentTracks).containsExactly(Tracks.EMPTY);
assertThat(initialCurrentTracksWithCommandAvailable.get().getGroups()).hasSize(1); assertThat(initialCurrentTracksWithCommandAvailable.get().getGroups()).hasSize(1);
assertThat(capturedCurrentTracksWithCommandAvailable.get().getGroups()).hasSize(1); assertThat(capturedCurrentTracksWithCommandAvailable.get().getGroups()).hasSize(1);
// Assert that an equal instance is not re-sent over the binder.
assertThat(initialCurrentTracksWithCommandAvailable.get())
.isSameInstanceAs(capturedCurrentTracksWithCommandAvailable.get());
} }
@Test @Test
@ -1181,6 +1192,7 @@ public class MediaControllerListenerTest {
availableCommands.get().buildUpon().remove(Player.COMMAND_GET_TRACKS).build()); availableCommands.get().buildUpon().remove(Player.COMMAND_GET_TRACKS).build());
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(capturedCurrentTracks).hasSize(2);
assertThat(capturedCurrentTracks.get(0).getGroups()).hasSize(1); assertThat(capturedCurrentTracks.get(0).getGroups()).hasSize(1);
assertThat(capturedCurrentTracks.get(1)).isEqualTo(Tracks.EMPTY); assertThat(capturedCurrentTracks.get(1)).isEqualTo(Tracks.EMPTY);
} }
@ -2203,7 +2215,7 @@ public class MediaControllerListenerTest {
} }
@Test @Test
public void onTimelineChanged_emptyMediaItemAndMediaMetadata_whenCommandUnavailableFromPlayer() public void onTimelineChanged_playerCommandUnavailable_emptyTimelineMediaItemAndMetadata()
throws Exception { throws Exception {
int testMediaItemsSize = 2; int testMediaItemsSize = 2;
List<MediaItem> testMediaItemList = MediaTestUtils.createMediaItems(testMediaItemsSize); List<MediaItem> testMediaItemList = MediaTestUtils.createMediaItems(testMediaItemsSize);
@ -2217,7 +2229,7 @@ public class MediaControllerListenerTest {
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>(); AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
List<Timeline> onEventsTimelines = new ArrayList<>(); List<Timeline> onEventsTimelines = new ArrayList<>();
AtomicReference<MediaMetadata> metadataFromGetterRef = new AtomicReference<>(); AtomicReference<MediaMetadata> metadataFromGetterRef = new AtomicReference<>();
AtomicReference<MediaItem> currentMediaItemGetterRef = new AtomicReference<>(); AtomicReference<Boolean> isCurrentMediaItemNullRef = new AtomicReference<>();
List<Player.Events> eventsList = new ArrayList<>(); List<Player.Events> eventsList = new ArrayList<>();
Player.Listener listener = Player.Listener listener =
new Player.Listener() { new Player.Listener() {
@ -2226,7 +2238,7 @@ public class MediaControllerListenerTest {
timelineFromParamRef.set(timeline); timelineFromParamRef.set(timeline);
timelineFromGetterRef.set(controller.getCurrentTimeline()); timelineFromGetterRef.set(controller.getCurrentTimeline());
metadataFromGetterRef.set(controller.getMediaMetadata()); metadataFromGetterRef.set(controller.getMediaMetadata());
currentMediaItemGetterRef.set(controller.getCurrentMediaItem()); isCurrentMediaItemNullRef.set(controller.getCurrentMediaItem() == null);
latch.countDown(); latch.countDown();
} }
@ -2244,24 +2256,7 @@ public class MediaControllerListenerTest {
remoteSession.getMockPlayer().notifyAvailableCommandsChanged(commandsWithoutGetTimeline); remoteSession.getMockPlayer().notifyAvailableCommandsChanged(commandsWithoutGetTimeline);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(timelineFromParamRef.get().getWindowCount()).isEqualTo(testMediaItemsSize); assertThat(timelineFromParamRef.get()).isEqualTo(Timeline.EMPTY);
for (int i = 0; i < timelineFromParamRef.get().getWindowCount(); i++) {
assertThat(
timelineFromParamRef
.get()
.getWindow(/* windowIndex= */ i, new Timeline.Window())
.mediaItem)
.isEqualTo(MediaItem.EMPTY);
}
assertThat(timelineFromGetterRef.get().getWindowCount()).isEqualTo(testMediaItemsSize);
for (int i = 0; i < timelineFromGetterRef.get().getWindowCount(); i++) {
assertThat(
timelineFromGetterRef
.get()
.getWindow(/* windowIndex= */ i, new Timeline.Window())
.mediaItem)
.isEqualTo(MediaItem.EMPTY);
}
assertThat(onEventsTimelines).hasSize(2); assertThat(onEventsTimelines).hasSize(2);
for (int i = 0; i < onEventsTimelines.get(1).getWindowCount(); i++) { for (int i = 0; i < onEventsTimelines.get(1).getWindowCount(); i++) {
assertThat( assertThat(
@ -2272,15 +2267,16 @@ public class MediaControllerListenerTest {
.isEqualTo(MediaItem.EMPTY); .isEqualTo(MediaItem.EMPTY);
} }
assertThat(metadataFromGetterRef.get()).isEqualTo(MediaMetadata.EMPTY); assertThat(metadataFromGetterRef.get()).isEqualTo(MediaMetadata.EMPTY);
assertThat(currentMediaItemGetterRef.get()).isEqualTo(MediaItem.EMPTY); assertThat(isCurrentMediaItemNullRef.get()).isTrue();
assertThat(eventsList).hasSize(2); assertThat(eventsList).hasSize(2);
assertThat(getEventsAsList(eventsList.get(0))) assertThat(getEventsAsList(eventsList.get(0)))
.containsExactly(Player.EVENT_AVAILABLE_COMMANDS_CHANGED); .containsExactly(Player.EVENT_AVAILABLE_COMMANDS_CHANGED);
assertThat(getEventsAsList(eventsList.get(1))).contains(Player.EVENT_TIMELINE_CHANGED); assertThat(getEventsAsList(eventsList.get(1)))
.containsExactly(Player.EVENT_TIMELINE_CHANGED, Player.EVENT_MEDIA_ITEM_TRANSITION);
} }
@Test @Test
public void onTimelineChanged_emptyMediaItemAndMediaMetadata_whenCommandUnavailableFromSession() public void onTimelineChanged_sessionCommandUnavailable_emptyTimelineMediaItemAndMetadata()
throws Exception { throws Exception {
int testMediaItemsSize = 2; int testMediaItemsSize = 2;
List<MediaItem> testMediaItemList = MediaTestUtils.createMediaItems(testMediaItemsSize); List<MediaItem> testMediaItemList = MediaTestUtils.createMediaItems(testMediaItemsSize);
@ -2293,7 +2289,7 @@ public class MediaControllerListenerTest {
AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>(); AtomicReference<Timeline> timelineFromParamRef = new AtomicReference<>();
AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>(); AtomicReference<Timeline> timelineFromGetterRef = new AtomicReference<>();
AtomicReference<MediaMetadata> metadataFromGetterRef = new AtomicReference<>(); AtomicReference<MediaMetadata> metadataFromGetterRef = new AtomicReference<>();
AtomicReference<MediaItem> currentMediaItemGetterRef = new AtomicReference<>(); AtomicReference<Boolean> isCurrentMediaItemNullRef = new AtomicReference<>();
List<Player.Events> eventsList = new ArrayList<>(); List<Player.Events> eventsList = new ArrayList<>();
Player.Listener listener = Player.Listener listener =
new Player.Listener() { new Player.Listener() {
@ -2302,7 +2298,7 @@ public class MediaControllerListenerTest {
timelineFromParamRef.set(timeline); timelineFromParamRef.set(timeline);
timelineFromGetterRef.set(controller.getCurrentTimeline()); timelineFromGetterRef.set(controller.getCurrentTimeline());
metadataFromGetterRef.set(controller.getMediaMetadata()); metadataFromGetterRef.set(controller.getMediaMetadata());
currentMediaItemGetterRef.set(controller.getCurrentMediaItem()); isCurrentMediaItemNullRef.set(controller.getCurrentMediaItem() == null);
latch.countDown(); latch.countDown();
} }
@ -2319,30 +2315,14 @@ public class MediaControllerListenerTest {
remoteSession.setAvailableCommands(SessionCommands.EMPTY, commandsWithoutGetTimeline); remoteSession.setAvailableCommands(SessionCommands.EMPTY, commandsWithoutGetTimeline);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(timelineFromParamRef.get().getWindowCount()).isEqualTo(testMediaItemsSize); assertThat(timelineFromParamRef.get()).isEqualTo(Timeline.EMPTY);
for (int i = 0; i < timelineFromParamRef.get().getWindowCount(); i++) {
assertThat(
timelineFromParamRef
.get()
.getWindow(/* windowIndex= */ i, new Timeline.Window())
.mediaItem)
.isEqualTo(MediaItem.EMPTY);
}
assertThat(timelineFromGetterRef.get().getWindowCount()).isEqualTo(testMediaItemsSize);
for (int i = 0; i < timelineFromGetterRef.get().getWindowCount(); i++) {
assertThat(
timelineFromGetterRef
.get()
.getWindow(/* windowIndex= */ i, new Timeline.Window())
.mediaItem)
.isEqualTo(MediaItem.EMPTY);
}
assertThat(metadataFromGetterRef.get()).isEqualTo(MediaMetadata.EMPTY); assertThat(metadataFromGetterRef.get()).isEqualTo(MediaMetadata.EMPTY);
assertThat(currentMediaItemGetterRef.get()).isEqualTo(MediaItem.EMPTY); assertThat(isCurrentMediaItemNullRef.get()).isTrue();
assertThat(eventsList).hasSize(2); assertThat(eventsList).hasSize(2);
assertThat(getEventsAsList(eventsList.get(0))) assertThat(getEventsAsList(eventsList.get(0)))
.containsExactly(Player.EVENT_AVAILABLE_COMMANDS_CHANGED); .containsExactly(Player.EVENT_AVAILABLE_COMMANDS_CHANGED);
assertThat(getEventsAsList(eventsList.get(1))).contains(Player.EVENT_TIMELINE_CHANGED); assertThat(getEventsAsList(eventsList.get(1)))
.containsExactly(Player.EVENT_TIMELINE_CHANGED, Player.EVENT_MEDIA_ITEM_TRANSITION);
} }
/** This also tests {@link MediaController#getAvailableCommands()}. */ /** This also tests {@link MediaController#getAvailableCommands()}. */

View File

@ -20,6 +20,9 @@ import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABL
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION; import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS; import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
import static androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS; import static androidx.media.utils.MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS;
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
import static androidx.media3.common.MimeTypes.VIDEO_H264;
import static androidx.media3.common.MimeTypes.VIDEO_H265;
import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY; import static androidx.media3.session.MediaConstants.EXTRA_KEY_ROOT_CHILDREN_BROWSABLE_ONLY;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@ -36,11 +39,13 @@ import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat; import androidx.media.AudioAttributesCompat;
import androidx.media.utils.MediaConstants; import androidx.media.utils.MediaConstants;
import androidx.media3.common.AudioAttributes; import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.HeartRating; import androidx.media3.common.HeartRating;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
@ -49,10 +54,15 @@ import androidx.media3.common.Player;
import androidx.media3.common.Rating; import androidx.media3.common.Rating;
import androidx.media3.common.StarRating; import androidx.media3.common.StarRating;
import androidx.media3.common.ThumbRating; import androidx.media3.common.ThumbRating;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.Tracks;
import androidx.media3.session.PlayerInfo.BundlingExclusions;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress; import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -623,4 +633,134 @@ public final class MediaUtilsTest {
state, /* metadataCompat= */ null, /* timeDiffMs= */ C.INDEX_UNSET); state, /* metadataCompat= */ null, /* timeDiffMs= */ C.INDEX_UNSET);
assertThat(totalBufferedDurationMs).isEqualTo(testTotalBufferedDurationMs); assertThat(totalBufferedDurationMs).isEqualTo(testTotalBufferedDurationMs);
} }
@Test
public void mergePlayerInfo_timelineAndTracksExcluded_correctMerge() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY
.buildUpon()
.add(Player.COMMAND_GET_TIMELINE)
.add(Player.COMMAND_GET_TRACKS)
.build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(oldPlayerInfo.timeline);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(oldPlayerInfo.currentTracks);
assertThat(mergeResult.second.isTimelineExcluded).isFalse();
assertThat(mergeResult.second.areCurrentTracksExcluded).isFalse();
}
@Test
public void mergePlayerInfo_getTimelineCommandNotAvailable_emptyTimeline() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_GET_TRACKS).build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(Timeline.EMPTY);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(oldPlayerInfo.currentTracks);
assertThat(mergeResult.second.isTimelineExcluded).isTrue();
assertThat(mergeResult.second.areCurrentTracksExcluded).isFalse();
}
@Test
public void mergePlayerInfo_getTracksCommandNotAvailable_emptyTracks() {
Timeline timeline =
new Timeline.RemotableTimeline(
ImmutableList.of(new Timeline.Window()),
ImmutableList.of(new Timeline.Period()),
/* shuffledWindowIndices= */ new int[] {0});
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
/* adaptiveSupported= */ false,
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
/* trackSelected= */ new boolean[] {true}),
new Tracks.Group(
new TrackGroup(
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
new Format.Builder().setSampleMimeType(VIDEO_H265).build()),
/* adaptiveSupported= */ true,
new int[] {C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_TYPE},
/* trackSelected= */ new boolean[] {false, true})));
PlayerInfo oldPlayerInfo =
PlayerInfo.DEFAULT.copyWithCurrentTracks(tracks).copyWithTimeline(timeline);
PlayerInfo newPlayerInfo = PlayerInfo.DEFAULT;
Player.Commands availableCommands =
Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_GET_TIMELINE).build();
Pair<PlayerInfo, BundlingExclusions> mergeResult =
MediaUtils.mergePlayerInfo(
oldPlayerInfo,
BundlingExclusions.NONE,
newPlayerInfo,
new BundlingExclusions(/* isTimelineExcluded= */ true, /* areTracksExcluded= */ true),
availableCommands);
assertThat(mergeResult.first.timeline).isSameInstanceAs(oldPlayerInfo.timeline);
assertThat(mergeResult.first.currentTracks).isSameInstanceAs(Tracks.EMPTY);
assertThat(mergeResult.second.isTimelineExcluded).isFalse();
assertThat(mergeResult.second.areCurrentTracksExcluded).isTrue();
}
} }