mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Allow externally provided Timeline in SimpleBasePlayer.State
The Timeline, Tracks and MediaMetadata are currently provided with a list of MediaItemData objects, that are a declarative version of these classes. This works well for cases where SimpleBasePlayer is used for external systems or custom players that don't have a Timeline object available already. However, this makes it really hard to provide the data if the app already has a Timeline, currently requiring to convert it back and forth to a list of MediaItemData. This change adds an override for `State.Builder.setPlaylist` that allows to set these 3 objects directly without going through MediaItemData. The conversion only happens when needed (e.g. when modifying the playlist). PiperOrigin-RevId: 649667983
This commit is contained in:
parent
fafd927702
commit
b2585aad0f
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
* Common Library:
|
* Common Library:
|
||||||
* Replace `SimpleBasePlayer.State.playlist` by `getPlaylist()` method.
|
* Replace `SimpleBasePlayer.State.playlist` by `getPlaylist()` method.
|
||||||
|
* Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to
|
||||||
|
directly specify a `Timeline` and current `Tracks` and `Metadata`
|
||||||
|
instead of building a playlist structure.
|
||||||
* ExoPlayer:
|
* ExoPlayer:
|
||||||
* `MediaCodecRenderer.onProcessedStreamChange()` can now be called for
|
* `MediaCodecRenderer.onProcessedStreamChange()` can now be called for
|
||||||
every media item. Previously it was not called for the first one. Use
|
every media item. Previously it was not called for the first one. Use
|
||||||
|
@ -127,8 +127,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
private Size surfaceSize;
|
private Size surfaceSize;
|
||||||
private boolean newlyRenderedFirstFrame;
|
private boolean newlyRenderedFirstFrame;
|
||||||
private Metadata timedMetadata;
|
private Metadata timedMetadata;
|
||||||
private ImmutableList<MediaItemData> playlist;
|
@Nullable private ImmutableList<MediaItemData> playlist;
|
||||||
private Timeline timeline;
|
private Timeline timeline;
|
||||||
|
@Nullable private Tracks currentTracks;
|
||||||
|
@Nullable private MediaMetadata currentMetadata;
|
||||||
private MediaMetadata playlistMetadata;
|
private MediaMetadata playlistMetadata;
|
||||||
private int currentMediaItemIndex;
|
private int currentMediaItemIndex;
|
||||||
private int currentAdGroupIndex;
|
private int currentAdGroupIndex;
|
||||||
@ -171,7 +173,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
newlyRenderedFirstFrame = false;
|
newlyRenderedFirstFrame = false;
|
||||||
timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET);
|
timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET);
|
||||||
playlist = ImmutableList.of();
|
playlist = ImmutableList.of();
|
||||||
timeline = new PlaylistTimeline(ImmutableList.of());
|
timeline = Timeline.EMPTY;
|
||||||
|
currentTracks = null;
|
||||||
|
currentMetadata = null;
|
||||||
playlistMetadata = MediaMetadata.EMPTY;
|
playlistMetadata = MediaMetadata.EMPTY;
|
||||||
currentMediaItemIndex = C.INDEX_UNSET;
|
currentMediaItemIndex = C.INDEX_UNSET;
|
||||||
currentAdGroupIndex = C.INDEX_UNSET;
|
currentAdGroupIndex = C.INDEX_UNSET;
|
||||||
@ -214,7 +218,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame;
|
this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame;
|
||||||
this.timedMetadata = state.timedMetadata;
|
this.timedMetadata = state.timedMetadata;
|
||||||
this.timeline = state.timeline;
|
this.timeline = state.timeline;
|
||||||
|
if (state.timeline instanceof PlaylistTimeline) {
|
||||||
this.playlist = ((PlaylistTimeline) state.timeline).playlist;
|
this.playlist = ((PlaylistTimeline) state.timeline).playlist;
|
||||||
|
} else {
|
||||||
|
this.currentTracks = state.currentTracks;
|
||||||
|
this.currentMetadata = state.currentMetadata;
|
||||||
|
}
|
||||||
this.playlistMetadata = state.playlistMetadata;
|
this.playlistMetadata = state.playlistMetadata;
|
||||||
this.currentMediaItemIndex = state.currentMediaItemIndex;
|
this.currentMediaItemIndex = state.currentMediaItemIndex;
|
||||||
this.currentAdGroupIndex = state.currentAdGroupIndex;
|
this.currentAdGroupIndex = state.currentAdGroupIndex;
|
||||||
@ -539,10 +548,13 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the list of {@link MediaItemData media items} in the playlist.
|
* Sets the playlist as a list of {@link MediaItemData media items}.
|
||||||
*
|
*
|
||||||
* <p>All items must have unique {@linkplain MediaItemData.Builder#setUid UIDs}.
|
* <p>All items must have unique {@linkplain MediaItemData.Builder#setUid UIDs}.
|
||||||
*
|
*
|
||||||
|
* <p>This call replaces any previous playlist set via {@link #setPlaylist(Timeline, Tracks,
|
||||||
|
* MediaMetadata)}.
|
||||||
|
*
|
||||||
* @param playlist The list of {@link MediaItemData media items} in the playlist.
|
* @param playlist The list of {@link MediaItemData media items} in the playlist.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
@ -554,6 +566,33 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
this.playlist = ImmutableList.copyOf(playlist);
|
this.playlist = ImmutableList.copyOf(playlist);
|
||||||
this.timeline = new PlaylistTimeline(this.playlist);
|
this.timeline = new PlaylistTimeline(this.playlist);
|
||||||
|
this.currentTracks = null;
|
||||||
|
this.currentMetadata = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playlist as a {@link Timeline} with information about the current {@link Tracks}
|
||||||
|
* and {@link MediaMetadata}.
|
||||||
|
*
|
||||||
|
* <p>This call replaces any previous playlist set via {@link #setPlaylist(List)}.
|
||||||
|
*
|
||||||
|
* @param timeline The {@link Timeline} containing the playlist data.
|
||||||
|
* @param currentTracks The {@link Tracks} of the {@linkplain #setCurrentMediaItemIndex
|
||||||
|
* current media item}.
|
||||||
|
* @param currentMetadata The combined {@link MediaMetadata} of the {@linkplain
|
||||||
|
* #setCurrentMediaItemIndex current media item}. If null, the current metadata is assumed
|
||||||
|
* to be the combination of the {@link MediaItem#mediaMetadata MediaItem} metadata and the
|
||||||
|
* metadata of the selected {@link Format#metadata Formats}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setPlaylist(
|
||||||
|
Timeline timeline, Tracks currentTracks, @Nullable MediaMetadata currentMetadata) {
|
||||||
|
this.playlist = null;
|
||||||
|
this.timeline = timeline;
|
||||||
|
this.currentTracks = currentTracks;
|
||||||
|
this.currentMetadata = currentMetadata;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,6 +893,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
/** The {@link Timeline}. */
|
/** The {@link Timeline}. */
|
||||||
public final Timeline timeline;
|
public final Timeline timeline;
|
||||||
|
|
||||||
|
/** The current {@link Tracks}. */
|
||||||
|
public final Tracks currentTracks;
|
||||||
|
|
||||||
|
/** The current combined {@link MediaMetadata}. */
|
||||||
|
public final MediaMetadata currentMetadata;
|
||||||
|
|
||||||
/** The playlist {@link MediaMetadata}. */
|
/** The playlist {@link MediaMetadata}. */
|
||||||
public final MediaMetadata playlistMetadata;
|
public final MediaMetadata playlistMetadata;
|
||||||
|
|
||||||
@ -914,6 +959,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
public final long discontinuityPositionMs;
|
public final long discontinuityPositionMs;
|
||||||
|
|
||||||
private State(Builder builder) {
|
private State(Builder builder) {
|
||||||
|
Tracks currentTracks = builder.currentTracks;
|
||||||
|
MediaMetadata currentMetadata = builder.currentMetadata;
|
||||||
if (builder.timeline.isEmpty()) {
|
if (builder.timeline.isEmpty()) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
builder.playbackState == Player.STATE_IDLE
|
builder.playbackState == Player.STATE_IDLE
|
||||||
@ -923,6 +970,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
builder.currentAdGroupIndex == C.INDEX_UNSET
|
builder.currentAdGroupIndex == C.INDEX_UNSET
|
||||||
&& builder.currentAdIndexInAdGroup == C.INDEX_UNSET,
|
&& builder.currentAdIndexInAdGroup == C.INDEX_UNSET,
|
||||||
"Ads not allowed if playlist is empty");
|
"Ads not allowed if playlist is empty");
|
||||||
|
if (currentTracks == null) {
|
||||||
|
currentTracks = Tracks.EMPTY;
|
||||||
|
}
|
||||||
|
if (currentMetadata == null) {
|
||||||
|
currentMetadata = MediaMetadata.EMPTY;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int mediaItemIndex = builder.currentMediaItemIndex;
|
int mediaItemIndex = builder.currentMediaItemIndex;
|
||||||
if (mediaItemIndex == C.INDEX_UNSET) {
|
if (mediaItemIndex == C.INDEX_UNSET) {
|
||||||
@ -953,6 +1006,17 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
"Ad group has less ads than adIndexInGroupIndex");
|
"Ad group has less ads than adIndexInGroupIndex");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (builder.playlist != null) {
|
||||||
|
MediaItemData mediaItemData = builder.playlist.get(mediaItemIndex);
|
||||||
|
currentTracks = mediaItemData.tracks;
|
||||||
|
currentMetadata = mediaItemData.mediaMetadata;
|
||||||
|
}
|
||||||
|
if (currentMetadata == null) {
|
||||||
|
currentMetadata =
|
||||||
|
getCombinedMediaMetadata(
|
||||||
|
builder.timeline.getWindow(mediaItemIndex, new Timeline.Window()).mediaItem,
|
||||||
|
checkNotNull(currentTracks));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (builder.playerError != null) {
|
if (builder.playerError != null) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
@ -1014,6 +1078,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame;
|
this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame;
|
||||||
this.timedMetadata = builder.timedMetadata;
|
this.timedMetadata = builder.timedMetadata;
|
||||||
this.timeline = builder.timeline;
|
this.timeline = builder.timeline;
|
||||||
|
this.currentTracks = checkNotNull(currentTracks);
|
||||||
|
this.currentMetadata = currentMetadata;
|
||||||
this.playlistMetadata = builder.playlistMetadata;
|
this.playlistMetadata = builder.playlistMetadata;
|
||||||
this.currentMediaItemIndex = builder.currentMediaItemIndex;
|
this.currentMediaItemIndex = builder.currentMediaItemIndex;
|
||||||
this.currentAdGroupIndex = builder.currentAdGroupIndex;
|
this.currentAdGroupIndex = builder.currentAdGroupIndex;
|
||||||
@ -1039,8 +1105,20 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
* @see Builder#setPlaylist(List)
|
* @see Builder#setPlaylist(List)
|
||||||
*/
|
*/
|
||||||
public ImmutableList<MediaItemData> getPlaylist() {
|
public ImmutableList<MediaItemData> getPlaylist() {
|
||||||
|
if (timeline instanceof PlaylistTimeline) {
|
||||||
return ((PlaylistTimeline) timeline).playlist;
|
return ((PlaylistTimeline) timeline).playlist;
|
||||||
}
|
}
|
||||||
|
Timeline.Window window = new Timeline.Window();
|
||||||
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
ImmutableList.Builder<MediaItemData> items =
|
||||||
|
ImmutableList.builderWithExpectedSize(timeline.getWindowCount());
|
||||||
|
for (int i = 0; i < timeline.getWindowCount(); i++) {
|
||||||
|
items.add(
|
||||||
|
MediaItemData.buildFromState(
|
||||||
|
/* state= */ this, /* mediaItemIndex= */ i, period, window));
|
||||||
|
}
|
||||||
|
return items.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object o) {
|
public boolean equals(@Nullable Object o) {
|
||||||
@ -1076,6 +1154,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
&& newlyRenderedFirstFrame == state.newlyRenderedFirstFrame
|
&& newlyRenderedFirstFrame == state.newlyRenderedFirstFrame
|
||||||
&& timedMetadata.equals(state.timedMetadata)
|
&& timedMetadata.equals(state.timedMetadata)
|
||||||
&& timeline.equals(state.timeline)
|
&& timeline.equals(state.timeline)
|
||||||
|
&& currentTracks.equals(state.currentTracks)
|
||||||
|
&& currentMetadata.equals(state.currentMetadata)
|
||||||
&& playlistMetadata.equals(state.playlistMetadata)
|
&& playlistMetadata.equals(state.playlistMetadata)
|
||||||
&& currentMediaItemIndex == state.currentMediaItemIndex
|
&& currentMediaItemIndex == state.currentMediaItemIndex
|
||||||
&& currentAdGroupIndex == state.currentAdGroupIndex
|
&& currentAdGroupIndex == state.currentAdGroupIndex
|
||||||
@ -1119,6 +1199,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0);
|
result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0);
|
||||||
result = 31 * result + timedMetadata.hashCode();
|
result = 31 * result + timedMetadata.hashCode();
|
||||||
result = 31 * result + timeline.hashCode();
|
result = 31 * result + timeline.hashCode();
|
||||||
|
result = 31 * result + currentTracks.hashCode();
|
||||||
|
result = 31 * result + currentMetadata.hashCode();
|
||||||
result = 31 * result + playlistMetadata.hashCode();
|
result = 31 * result + playlistMetadata.hashCode();
|
||||||
result = 31 * result + currentMediaItemIndex;
|
result = 31 * result + currentMediaItemIndex;
|
||||||
result = 31 * result + currentAdGroupIndex;
|
result = 31 * result + currentAdGroupIndex;
|
||||||
@ -1142,9 +1224,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
private final int[] windowIndexByPeriodIndex;
|
private final int[] windowIndexByPeriodIndex;
|
||||||
private final HashMap<Object, Integer> periodIndexByUid;
|
private final HashMap<Object, Integer> periodIndexByUid;
|
||||||
|
|
||||||
public PlaylistTimeline(ImmutableList<MediaItemData> playlist) {
|
public PlaylistTimeline(List<MediaItemData> playlist) {
|
||||||
int mediaItemCount = playlist.size();
|
int mediaItemCount = playlist.size();
|
||||||
this.playlist = playlist;
|
this.playlist = ImmutableList.copyOf(playlist);
|
||||||
this.firstPeriodIndexByWindowIndex = new int[mediaItemCount];
|
this.firstPeriodIndexByWindowIndex = new int[mediaItemCount];
|
||||||
int periodCount = 0;
|
int periodCount = 0;
|
||||||
for (int i = 0; i < mediaItemCount; i++) {
|
for (int i = 0; i < mediaItemCount; i++) {
|
||||||
@ -1642,7 +1724,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
public final ImmutableList<PeriodData> periods;
|
public final ImmutableList<PeriodData> periods;
|
||||||
|
|
||||||
private final long[] periodPositionInWindowUs;
|
private final long[] periodPositionInWindowUs;
|
||||||
private final MediaMetadata combinedMediaMetadata;
|
|
||||||
|
|
||||||
private MediaItemData(Builder builder) {
|
private MediaItemData(Builder builder) {
|
||||||
if (builder.liveConfiguration == null) {
|
if (builder.liveConfiguration == null) {
|
||||||
@ -1690,8 +1771,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
periodPositionInWindowUs[i + 1] = periodPositionInWindowUs[i] + periods.get(i).durationUs;
|
periodPositionInWindowUs[i + 1] = periodPositionInWindowUs[i] + periods.get(i).durationUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
combinedMediaMetadata =
|
|
||||||
mediaMetadata != null ? mediaMetadata : getCombinedMediaMetadata(mediaItem, tracks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a {@link Builder} pre-populated with the current values. */
|
/** Returns a {@link Builder} pre-populated with the current values. */
|
||||||
@ -1750,6 +1829,39 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MediaItemData buildFromState(
|
||||||
|
State state, int mediaItemIndex, Timeline.Period period, Timeline.Window window) {
|
||||||
|
boolean isCurrentItem = getCurrentMediaItemIndexInternal(state) == mediaItemIndex;
|
||||||
|
state.timeline.getWindow(mediaItemIndex, window);
|
||||||
|
ImmutableList.Builder<PeriodData> periods = ImmutableList.builder();
|
||||||
|
for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) {
|
||||||
|
state.timeline.getPeriod(/* periodIndex= */ i, period, /* setIds= */ true);
|
||||||
|
periods.add(
|
||||||
|
new PeriodData.Builder(checkNotNull(period.uid))
|
||||||
|
.setAdPlaybackState(period.adPlaybackState)
|
||||||
|
.setDurationUs(period.durationUs)
|
||||||
|
.setIsPlaceholder(period.isPlaceholder)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
return new MediaItemData.Builder(window.uid)
|
||||||
|
.setDefaultPositionUs(window.defaultPositionUs)
|
||||||
|
.setDurationUs(window.durationUs)
|
||||||
|
.setElapsedRealtimeEpochOffsetMs(window.elapsedRealtimeEpochOffsetMs)
|
||||||
|
.setIsDynamic(window.isDynamic)
|
||||||
|
.setIsPlaceholder(window.isPlaceholder)
|
||||||
|
.setIsSeekable(window.isSeekable)
|
||||||
|
.setLiveConfiguration(window.liveConfiguration)
|
||||||
|
.setManifest(window.manifest)
|
||||||
|
.setMediaItem(window.mediaItem)
|
||||||
|
.setMediaMetadata(isCurrentItem ? state.currentMetadata : null)
|
||||||
|
.setPeriods(periods.build())
|
||||||
|
.setPositionInFirstPeriodUs(window.positionInFirstPeriodUs)
|
||||||
|
.setPresentationStartTimeMs(window.presentationStartTimeMs)
|
||||||
|
.setTracks(isCurrentItem ? state.currentTracks : Tracks.EMPTY)
|
||||||
|
.setWindowStartTimeMs(window.windowStartTimeMs)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private Timeline.Window getWindow(int firstPeriodIndex, Timeline.Window window) {
|
private Timeline.Window getWindow(int firstPeriodIndex, Timeline.Window window) {
|
||||||
int periodCount = periods.isEmpty() ? 1 : periods.size();
|
int periodCount = periods.isEmpty() ? 1 : periods.size();
|
||||||
window.set(
|
window.set(
|
||||||
@ -1805,25 +1917,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
Object periodId = periods.get(periodIndexInMediaItem).uid;
|
Object periodId = periods.get(periodIndexInMediaItem).uid;
|
||||||
return Pair.create(uid, periodId);
|
return Pair.create(uid, periodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) {
|
|
||||||
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
|
|
||||||
int trackGroupCount = tracks.getGroups().size();
|
|
||||||
for (int i = 0; i < trackGroupCount; i++) {
|
|
||||||
Tracks.Group group = tracks.getGroups().get(i);
|
|
||||||
for (int j = 0; j < group.length; j++) {
|
|
||||||
if (group.isTrackSelected(j)) {
|
|
||||||
Format format = group.getTrackFormat(j);
|
|
||||||
if (format.metadata != null) {
|
|
||||||
for (int k = 0; k < format.metadata.length(); k++) {
|
|
||||||
format.metadata.get(k).populateMediaMetadata(metadataBuilder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return metadataBuilder.populate(mediaItem.mediaMetadata).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Data describing the properties of a period inside a {@link MediaItemData}. */
|
/** Data describing the properties of a period inside a {@link MediaItemData}. */
|
||||||
@ -2157,7 +2250,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems),
|
/* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems),
|
||||||
/* placeholderStateSupplier= */ () -> {
|
/* placeholderStateSupplier= */ () -> {
|
||||||
List<MediaItemData> placeholderPlaylist = buildMutablePlaylistFromState(state);
|
List<MediaItemData> placeholderPlaylist =
|
||||||
|
buildMutablePlaylistFromState(state, period, window);
|
||||||
for (int i = 0; i < mediaItems.size(); i++) {
|
for (int i = 0; i < mediaItems.size(); i++) {
|
||||||
placeholderPlaylist.add(
|
placeholderPlaylist.add(
|
||||||
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
||||||
@ -2197,7 +2291,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
/* pendingOperation= */ handleMoveMediaItems(
|
/* pendingOperation= */ handleMoveMediaItems(
|
||||||
fromIndex, correctedToIndex, correctedNewIndex),
|
fromIndex, correctedToIndex, correctedNewIndex),
|
||||||
/* placeholderStateSupplier= */ () -> {
|
/* placeholderStateSupplier= */ () -> {
|
||||||
List<MediaItemData> placeholderPlaylist = buildMutablePlaylistFromState(state);
|
List<MediaItemData> placeholderPlaylist =
|
||||||
|
buildMutablePlaylistFromState(state, period, window);
|
||||||
Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex);
|
Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex);
|
||||||
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
|
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
|
||||||
});
|
});
|
||||||
@ -2216,7 +2311,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
|
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
|
||||||
/* placeholderStateSupplier= */ () -> {
|
/* placeholderStateSupplier= */ () -> {
|
||||||
List<MediaItemData> placeholderPlaylist = buildMutablePlaylistFromState(state);
|
List<MediaItemData> placeholderPlaylist =
|
||||||
|
buildMutablePlaylistFromState(state, period, window);
|
||||||
for (int i = 0; i < mediaItems.size(); i++) {
|
for (int i = 0; i < mediaItems.size(); i++) {
|
||||||
placeholderPlaylist.add(
|
placeholderPlaylist.add(
|
||||||
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
||||||
@ -2262,7 +2358,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex),
|
/* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex),
|
||||||
/* placeholderStateSupplier= */ () -> {
|
/* placeholderStateSupplier= */ () -> {
|
||||||
List<MediaItemData> placeholderPlaylist = buildMutablePlaylistFromState(state);
|
List<MediaItemData> placeholderPlaylist =
|
||||||
|
buildMutablePlaylistFromState(state, period, window);
|
||||||
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
|
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
|
||||||
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
|
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
|
||||||
});
|
});
|
||||||
@ -2469,7 +2566,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
@Override
|
@Override
|
||||||
public final Tracks getCurrentTracks() {
|
public final Tracks getCurrentTracks() {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
return getCurrentTracksInternal(state);
|
return state.currentTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -2495,7 +2592,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
@Override
|
@Override
|
||||||
public final MediaMetadata getMediaMetadata() {
|
public final MediaMetadata getMediaMetadata() {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
return getMediaMetadataInternal(state);
|
return state.currentMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -3420,10 +3517,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
|
|
||||||
boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady;
|
boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady;
|
||||||
boolean playbackStateChanged = previousState.playbackState != newState.playbackState;
|
boolean playbackStateChanged = previousState.playbackState != newState.playbackState;
|
||||||
Tracks previousTracks = getCurrentTracksInternal(previousState);
|
|
||||||
Tracks newTracks = getCurrentTracksInternal(newState);
|
|
||||||
MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState);
|
|
||||||
MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState);
|
|
||||||
int positionDiscontinuityReason =
|
int positionDiscontinuityReason =
|
||||||
getPositionDiscontinuityReason(
|
getPositionDiscontinuityReason(
|
||||||
previousState, newState, forceSeekDiscontinuity, window, period);
|
previousState, newState, forceSeekDiscontinuity, window, period);
|
||||||
@ -3484,14 +3577,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
listener ->
|
listener ->
|
||||||
listener.onTrackSelectionParametersChanged(newState.trackSelectionParameters));
|
listener.onTrackSelectionParametersChanged(newState.trackSelectionParameters));
|
||||||
}
|
}
|
||||||
if (!previousTracks.equals(newTracks)) {
|
if (!previousState.currentTracks.equals(newState.currentTracks)) {
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newTracks));
|
Player.EVENT_TRACKS_CHANGED,
|
||||||
|
listener -> listener.onTracksChanged(newState.currentTracks));
|
||||||
}
|
}
|
||||||
if (!previousMediaMetadata.equals(newMediaMetadata)) {
|
if (!previousState.currentMetadata.equals(newState.currentMetadata)) {
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
EVENT_MEDIA_METADATA_CHANGED,
|
EVENT_MEDIA_METADATA_CHANGED,
|
||||||
listener -> listener.onMediaMetadataChanged(newMediaMetadata));
|
listener -> listener.onMediaMetadataChanged(newState.currentMetadata));
|
||||||
}
|
}
|
||||||
if (previousState.isLoading != newState.isLoading) {
|
if (previousState.isLoading != newState.isLoading) {
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
@ -3687,20 +3781,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
&& state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
|
&& state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Tracks getCurrentTracksInternal(State state) {
|
|
||||||
return state.timeline.isEmpty()
|
|
||||||
? Tracks.EMPTY
|
|
||||||
: ((PlaylistTimeline) state.timeline)
|
|
||||||
.playlist.get(getCurrentMediaItemIndexInternal(state)).tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaMetadata getMediaMetadataInternal(State state) {
|
|
||||||
return state.timeline.isEmpty()
|
|
||||||
? MediaMetadata.EMPTY
|
|
||||||
: ((PlaylistTimeline) state.timeline)
|
|
||||||
.playlist.get(getCurrentMediaItemIndexInternal(state)).combinedMediaMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getCurrentMediaItemIndexInternal(State state) {
|
private static int getCurrentMediaItemIndexInternal(State state) {
|
||||||
if (state.currentMediaItemIndex != C.INDEX_UNSET) {
|
if (state.currentMediaItemIndex != C.INDEX_UNSET) {
|
||||||
return state.currentMediaItemIndex;
|
return state.currentMediaItemIndex;
|
||||||
@ -3971,8 +4051,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
Timeline.Period period,
|
Timeline.Period period,
|
||||||
Timeline.Window window) {
|
Timeline.Window window) {
|
||||||
State.Builder stateBuilder = oldState.buildUpon();
|
State.Builder stateBuilder = oldState.buildUpon();
|
||||||
stateBuilder.setPlaylist(newPlaylist);
|
Timeline newTimeline = new PlaylistTimeline(newPlaylist);
|
||||||
Timeline newTimeline = stateBuilder.timeline;
|
|
||||||
Timeline oldTimeline = oldState.timeline;
|
Timeline oldTimeline = oldState.timeline;
|
||||||
long oldPositionMs = oldState.contentPositionMsSupplier.get();
|
long oldPositionMs = oldState.contentPositionMsSupplier.get();
|
||||||
int oldIndex = getCurrentMediaItemIndexInternal(oldState);
|
int oldIndex = getCurrentMediaItemIndexInternal(oldState);
|
||||||
@ -4008,11 +4087,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
long newPositionMs,
|
long newPositionMs,
|
||||||
Timeline.Window window) {
|
Timeline.Window window) {
|
||||||
State.Builder stateBuilder = oldState.buildUpon();
|
State.Builder stateBuilder = oldState.buildUpon();
|
||||||
Timeline newTimeline = oldState.timeline;
|
Timeline newTimeline =
|
||||||
if (newPlaylist != null) {
|
newPlaylist == null ? oldState.timeline : new PlaylistTimeline(newPlaylist);
|
||||||
stateBuilder.setPlaylist(newPlaylist);
|
|
||||||
newTimeline = stateBuilder.timeline;
|
|
||||||
}
|
|
||||||
if (oldState.playbackState != Player.STATE_IDLE) {
|
if (oldState.playbackState != Player.STATE_IDLE) {
|
||||||
if (newTimeline.isEmpty()
|
if (newTimeline.isEmpty()
|
||||||
|| (newIndex != C.INDEX_UNSET && newIndex >= newTimeline.getWindowCount())) {
|
|| (newIndex != C.INDEX_UNSET && newIndex >= newTimeline.getWindowCount())) {
|
||||||
@ -4060,6 +4136,19 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
.getWindow(getCurrentMediaItemIndexInternal(oldState), window)
|
.getWindow(getCurrentMediaItemIndexInternal(oldState), window)
|
||||||
.uid
|
.uid
|
||||||
.equals(newTimeline.getWindow(newIndex, window).uid);
|
.equals(newTimeline.getWindow(newIndex, window).uid);
|
||||||
|
// Set timeline, resolving tracks and metadata to the new index.
|
||||||
|
if (newTimeline.isEmpty()) {
|
||||||
|
stateBuilder.setPlaylist(newTimeline, Tracks.EMPTY, /* currentMetadata= */ null);
|
||||||
|
} else if (newTimeline instanceof PlaylistTimeline) {
|
||||||
|
MediaItemData mediaItemData = ((PlaylistTimeline) newTimeline).playlist.get(newIndex);
|
||||||
|
stateBuilder.setPlaylist(newTimeline, mediaItemData.tracks, mediaItemData.mediaMetadata);
|
||||||
|
} else {
|
||||||
|
boolean keepTracksAndMetadata = !oldOrNewPlaylistEmpty && !mediaItemChanged;
|
||||||
|
stateBuilder.setPlaylist(
|
||||||
|
newTimeline,
|
||||||
|
keepTracksAndMetadata ? oldState.currentTracks : Tracks.EMPTY,
|
||||||
|
keepTracksAndMetadata ? oldState.currentMetadata : null);
|
||||||
|
}
|
||||||
if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) {
|
if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) {
|
||||||
// New item or seeking back. Assume no buffer and no ad playback persists.
|
// New item or seeking back. Assume no buffer and no ad playback persists.
|
||||||
stateBuilder
|
stateBuilder
|
||||||
@ -4098,9 +4187,36 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return stateBuilder.build();
|
return stateBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<MediaItemData> buildMutablePlaylistFromState(State state) {
|
private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) {
|
||||||
|
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
|
||||||
|
int trackGroupCount = tracks.getGroups().size();
|
||||||
|
for (int i = 0; i < trackGroupCount; i++) {
|
||||||
|
Tracks.Group group = tracks.getGroups().get(i);
|
||||||
|
for (int j = 0; j < group.length; j++) {
|
||||||
|
if (group.isTrackSelected(j)) {
|
||||||
|
Format format = group.getTrackFormat(j);
|
||||||
|
if (format.metadata != null) {
|
||||||
|
for (int k = 0; k < format.metadata.length(); k++) {
|
||||||
|
format.metadata.get(k).populateMediaMetadata(metadataBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadataBuilder.populate(mediaItem.mediaMetadata).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MediaItemData> buildMutablePlaylistFromState(
|
||||||
|
State state, Timeline.Period period, Timeline.Window window) {
|
||||||
|
if (state.timeline instanceof PlaylistTimeline) {
|
||||||
return new ArrayList<>(((PlaylistTimeline) state.timeline).playlist);
|
return new ArrayList<>(((PlaylistTimeline) state.timeline).playlist);
|
||||||
}
|
}
|
||||||
|
ArrayList<MediaItemData> items = new ArrayList<>(state.timeline.getWindowCount());
|
||||||
|
for (int i = 0; i < state.timeline.getWindowCount(); i++) {
|
||||||
|
items.add(MediaItemData.buildFromState(state, /* mediaItemIndex= */ i, period, window));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class PlaceholderUid {}
|
private static final class PlaceholderUid {}
|
||||||
}
|
}
|
||||||
|
@ -575,7 +575,8 @@ public abstract class Timeline {
|
|||||||
*/
|
*/
|
||||||
public boolean isPlaceholder;
|
public boolean isPlaceholder;
|
||||||
|
|
||||||
private AdPlaybackState adPlaybackState;
|
/** The {@link AdPlaybackState} for all ads in this period. */
|
||||||
|
@UnstableApi public AdPlaybackState adPlaybackState;
|
||||||
|
|
||||||
/** Creates a new instance with no ad playback state. */
|
/** Creates a new instance with no ad playback state. */
|
||||||
public Period() {
|
public Period() {
|
||||||
|
@ -40,7 +40,9 @@ import androidx.media3.common.SimpleBasePlayer.State;
|
|||||||
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;
|
||||||
|
import androidx.media3.extractor.metadata.icy.IcyInfo;
|
||||||
import androidx.media3.test.utils.FakeMetadataEntry;
|
import androidx.media3.test.utils.FakeMetadataEntry;
|
||||||
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
import androidx.media3.test.utils.TestUtil;
|
import androidx.media3.test.utils.TestUtil;
|
||||||
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;
|
||||||
@ -225,6 +227,15 @@ public class SimpleBasePlayerTest {
|
|||||||
Size surfaceSize = new Size(480, 360);
|
Size surfaceSize = new Size(480, 360);
|
||||||
DeviceInfo deviceInfo =
|
DeviceInfo deviceInfo =
|
||||||
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL).setMaxVolume(7).build();
|
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL).setMaxVolume(7).build();
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
ImmutableList<SimpleBasePlayer.MediaItemData> playlist =
|
ImmutableList<SimpleBasePlayer.MediaItemData> playlist =
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(),
|
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(),
|
||||||
@ -236,6 +247,8 @@ public class SimpleBasePlayerTest {
|
|||||||
new AdPlaybackState(
|
new AdPlaybackState(
|
||||||
/* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666))
|
/* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666))
|
||||||
.build()))
|
.build()))
|
||||||
|
.setMediaMetadata(mediaMetadata)
|
||||||
|
.setTracks(tracks)
|
||||||
.build());
|
.build());
|
||||||
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build();
|
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build();
|
||||||
SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456;
|
SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456;
|
||||||
@ -314,6 +327,8 @@ public class SimpleBasePlayerTest {
|
|||||||
assertThat(state.timedMetadata).isEqualTo(timedMetadata);
|
assertThat(state.timedMetadata).isEqualTo(timedMetadata);
|
||||||
assertThat(state.getPlaylist()).isEqualTo(playlist);
|
assertThat(state.getPlaylist()).isEqualTo(playlist);
|
||||||
assertThat(state.timeline.getWindowCount()).isEqualTo(2);
|
assertThat(state.timeline.getWindowCount()).isEqualTo(2);
|
||||||
|
assertThat(state.currentTracks).isEqualTo(tracks);
|
||||||
|
assertThat(state.currentMetadata).isEqualTo(mediaMetadata);
|
||||||
assertThat(state.playlistMetadata).isEqualTo(playlistMetadata);
|
assertThat(state.playlistMetadata).isEqualTo(playlistMetadata);
|
||||||
assertThat(state.currentMediaItemIndex).isEqualTo(1);
|
assertThat(state.currentMediaItemIndex).isEqualTo(1);
|
||||||
assertThat(state.currentAdGroupIndex).isEqualTo(1);
|
assertThat(state.currentAdGroupIndex).isEqualTo(1);
|
||||||
@ -328,6 +343,69 @@ public class SimpleBasePlayerTest {
|
|||||||
assertThat(state.discontinuityPositionMs).isEqualTo(400);
|
assertThat(state.discontinuityPositionMs).isEqualTo(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stateBuilderBuild_withExplicitTimeline_setsCorrectValues() {
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
|
||||||
|
|
||||||
|
State state = new State.Builder().setPlaylist(timeline, tracks, mediaMetadata).build();
|
||||||
|
|
||||||
|
assertThat(state.timeline).isEqualTo(timeline);
|
||||||
|
assertThat(state.currentTracks).isEqualTo(tracks);
|
||||||
|
assertThat(state.currentMetadata).isEqualTo(mediaMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
stateBuilderBuild_withUndefinedMediaMetadataAndExplicitTimeline_derivesMediaMetadataFromTracksAndMediaItem()
|
||||||
|
throws Exception {
|
||||||
|
Timeline timeline =
|
||||||
|
new FakeTimeline(
|
||||||
|
new FakeTimeline.TimelineWindowDefinition(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* id= */ 0,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ true,
|
||||||
|
/* isLive= */ true,
|
||||||
|
/* isPlaceholder= */ false,
|
||||||
|
/* durationUs= */ 1000,
|
||||||
|
/* defaultPositionUs= */ 0,
|
||||||
|
/* windowOffsetInFirstPeriodUs= */ 0,
|
||||||
|
ImmutableList.of(AdPlaybackState.NONE),
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setMediaId("1")
|
||||||
|
.setMediaMetadata(new MediaMetadata.Builder().setArtist("artist").build())
|
||||||
|
.build()));
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(
|
||||||
|
new Format.Builder()
|
||||||
|
.setMetadata(
|
||||||
|
new Metadata(
|
||||||
|
new IcyInfo(
|
||||||
|
/* rawMetadata= */ new byte[0], "title", /* url= */ null)))
|
||||||
|
.build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
|
||||||
|
State state =
|
||||||
|
new State.Builder().setPlaylist(timeline, tracks, /* currentMetadata= */ null).build();
|
||||||
|
|
||||||
|
assertThat(state.currentMetadata)
|
||||||
|
.isEqualTo(new MediaMetadata.Builder().setArtist("artist").setTitle("title").build());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stateBuilderBuild_emptyTimelineWithReadyState_throwsException() {
|
public void stateBuilderBuild_emptyTimelineWithReadyState_throwsException() {
|
||||||
assertThrows(
|
assertThrows(
|
||||||
@ -8070,6 +8148,211 @@ public class SimpleBasePlayerTest {
|
|||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||||
|
@Test
|
||||||
|
public void seekTo_asyncHandlingToNewItem_usesPlaceholderStateWithUpdatedTracksAndMetadata() {
|
||||||
|
MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build();
|
||||||
|
Tracks newTracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
MediaMetadata newMediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
State state =
|
||||||
|
new State.Builder()
|
||||||
|
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||||
|
.setPlaylist(
|
||||||
|
ImmutableList.of(
|
||||||
|
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
|
||||||
|
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2)
|
||||||
|
.setMediaItem(newMediaItem)
|
||||||
|
.setTracks(newTracks)
|
||||||
|
.setMediaMetadata(newMediaMetadata)
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
SettableFuture<?> future = SettableFuture.create();
|
||||||
|
SimpleBasePlayer player =
|
||||||
|
new SimpleBasePlayer(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
protected State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ListenableFuture<?> handleSeek(
|
||||||
|
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Listener listener = mock(Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000);
|
||||||
|
|
||||||
|
// Verify placeholder state and listener calls.
|
||||||
|
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||||
|
assertThat(player.getCurrentTracks()).isEqualTo(newTracks);
|
||||||
|
assertThat(player.getMediaMetadata()).isEqualTo(newMediaMetadata);
|
||||||
|
verify(listener).onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
|
||||||
|
verify(listener).onTracksChanged(newTracks);
|
||||||
|
verify(listener).onMediaMetadataChanged(newMediaMetadata);
|
||||||
|
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||||
|
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
seekTo_asyncHandlingToNewItemWithExplicitTimeline_usesPlaceholderStateWithEmptyTracksAndMetadata() {
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
|
||||||
|
State state =
|
||||||
|
new State.Builder()
|
||||||
|
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||||
|
.setPlaylist(timeline, tracks, mediaMetadata)
|
||||||
|
.build();
|
||||||
|
SettableFuture<?> future = SettableFuture.create();
|
||||||
|
SimpleBasePlayer player =
|
||||||
|
new SimpleBasePlayer(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
protected State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ListenableFuture<?> handleSeek(
|
||||||
|
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Listener listener = mock(Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000);
|
||||||
|
|
||||||
|
// Verify placeholder state and listener calls.
|
||||||
|
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
|
||||||
|
assertThat(player.getCurrentTracks()).isEqualTo(Tracks.EMPTY);
|
||||||
|
assertThat(player.getMediaMetadata()).isEqualTo(MediaMetadata.EMPTY);
|
||||||
|
verify(listener)
|
||||||
|
.onMediaItemTransition(
|
||||||
|
timeline.getWindow(/* windowIndex= */ 1, new Timeline.Window()).mediaItem,
|
||||||
|
Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
|
||||||
|
verify(listener).onTracksChanged(Tracks.EMPTY);
|
||||||
|
verify(listener).onMediaMetadataChanged(MediaMetadata.EMPTY);
|
||||||
|
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||||
|
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
seekTo_asyncHandlingToSameItem_usesPlaceholderStateWithoutChangingTracksAndMetadata() {
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
State state =
|
||||||
|
new State.Builder()
|
||||||
|
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||||
|
.setPlaylist(
|
||||||
|
ImmutableList.of(
|
||||||
|
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1)
|
||||||
|
.setTracks(tracks)
|
||||||
|
.setMediaMetadata(mediaMetadata)
|
||||||
|
.build()))
|
||||||
|
.build();
|
||||||
|
SettableFuture<?> future = SettableFuture.create();
|
||||||
|
SimpleBasePlayer player =
|
||||||
|
new SimpleBasePlayer(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
protected State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ListenableFuture<?> handleSeek(
|
||||||
|
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Listener listener = mock(Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
player.seekTo(/* positionMs= */ 3000);
|
||||||
|
|
||||||
|
// Verify placeholder state and listener calls.
|
||||||
|
assertThat(player.getCurrentTracks()).isEqualTo(tracks);
|
||||||
|
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
|
||||||
|
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||||
|
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
seekTo_asyncHandlingToSameItemWithExplicitTimeline_usesPlaceholderStateWithoutChangingTracksAndMetadata() {
|
||||||
|
Tracks tracks =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {true})));
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
|
||||||
|
State state =
|
||||||
|
new State.Builder()
|
||||||
|
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
|
||||||
|
.setPlaylist(timeline, tracks, mediaMetadata)
|
||||||
|
.build();
|
||||||
|
SettableFuture<?> future = SettableFuture.create();
|
||||||
|
SimpleBasePlayer player =
|
||||||
|
new SimpleBasePlayer(Looper.myLooper()) {
|
||||||
|
@Override
|
||||||
|
protected State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ListenableFuture<?> handleSeek(
|
||||||
|
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Listener listener = mock(Listener.class);
|
||||||
|
player.addListener(listener);
|
||||||
|
|
||||||
|
player.seekTo(/* positionMs= */ 3000);
|
||||||
|
|
||||||
|
// Verify placeholder state and listener calls.
|
||||||
|
assertThat(player.getCurrentTracks()).isEqualTo(tracks);
|
||||||
|
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
|
||||||
|
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
|
||||||
|
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void seekTo_withoutAvailableCommandForSeekToMediaItem_isNotForwarded() {
|
public void seekTo_withoutAvailableCommandForSeekToMediaItem_isNotForwarded() {
|
||||||
State state =
|
State state =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user