use of androidx.media3.common.IllegalSeekPositionException in project media by androidx.
the class ExoPlayerTest method illegalSeekPositionDoesThrow.
@Test
public void illegalSeekPositionDoesThrow() throws Exception {
final IllegalSeekPositionException[] exception = new IllegalSeekPositionException[1];
ActionSchedule actionSchedule = new ActionSchedule.Builder(TAG).waitForPlaybackState(Player.STATE_BUFFERING).executeRunnable(new PlayerRunnable() {
@Override
public void run(ExoPlayer player) {
try {
player.seekTo(/* mediaItemIndex= */
100, /* positionMs= */
0);
} catch (IllegalSeekPositionException e) {
exception[0] = e;
}
}
}).waitForPlaybackState(Player.STATE_ENDED).build();
new ExoPlayerTestRunner.Builder(context).setActionSchedule(actionSchedule).build().start().blockUntilActionScheduleFinished(TIMEOUT_MS).blockUntilEnded(TIMEOUT_MS);
assertThat(exception[0]).isNotNull();
}
use of androidx.media3.common.IllegalSeekPositionException in project media by androidx.
the class MediaControllerImplLegacy method seekToInternal.
private void seekToInternal(int mediaItemIndex, long positionMs) {
int currentMediaItemIndex = getCurrentMediaItemIndex();
Timeline currentTimeline = controllerInfo.playerInfo.timeline;
if (currentMediaItemIndex != mediaItemIndex && (mediaItemIndex < 0 || mediaItemIndex >= currentTimeline.getWindowCount())) {
throw new IllegalSeekPositionException(currentTimeline, mediaItemIndex, positionMs);
}
if (isPlayingAd()) {
return;
}
int newMediaItemIndex = currentMediaItemIndex;
@Nullable @MediaItemTransitionReason Integer mediaItemTransitionReason = null;
if (mediaItemIndex != currentMediaItemIndex) {
QueueTimeline queueTimeline = (QueueTimeline) controllerInfo.playerInfo.timeline;
long queueId = queueTimeline.getQueueId(mediaItemIndex);
if (queueId != QueueItem.UNKNOWN_ID) {
controllerCompat.getTransportControls().skipToQueueItem(queueId);
newMediaItemIndex = mediaItemIndex;
mediaItemTransitionReason = MEDIA_ITEM_TRANSITION_REASON_SEEK;
} else {
Log.w(TAG, "Cannot seek to new media item due to the missing queue Id at media item," + " mediaItemIndex=" + mediaItemIndex);
}
}
@Nullable @DiscontinuityReason Integer discontinuityReason;
long currentPositionMs = getCurrentPosition();
long newPositionMs;
if (positionMs == C.TIME_UNSET) {
newPositionMs = currentPositionMs;
discontinuityReason = null;
} else {
controllerCompat.getTransportControls().seekTo(positionMs);
newPositionMs = positionMs;
discontinuityReason = DISCONTINUITY_REASON_SEEK;
}
long newDurationMs;
long newBufferedPositionMs;
int newBufferedPercentage;
long newTotalBufferedDurationMs;
if (mediaItemTransitionReason == null) {
// Follows the ExoPlayerImpl's state masking for seek within the current item.
long oldBufferedPositionMs = getBufferedPosition();
newDurationMs = getDuration();
newBufferedPositionMs = (newPositionMs < currentPositionMs) ? newPositionMs : max(newPositionMs, oldBufferedPositionMs);
newBufferedPercentage = (newDurationMs == C.TIME_UNSET) ? 0 : (int) (newBufferedPositionMs * 100L / newDurationMs);
newTotalBufferedDurationMs = newBufferedPositionMs - newPositionMs;
} else {
newDurationMs = C.TIME_UNSET;
newBufferedPositionMs = 0L;
newBufferedPercentage = 0;
newTotalBufferedDurationMs = 0L;
}
PositionInfo positionInfo = createPositionInfo(newMediaItemIndex, !currentTimeline.isEmpty() ? currentTimeline.getWindow(newMediaItemIndex, new Window()).mediaItem : null, newPositionMs);
PlayerInfo maskedPlayerInfo = controllerInfo.playerInfo.copyWithSessionPositionInfo(createSessionPositionInfo(positionInfo, /* isPlayingAd= */
false, newDurationMs, newBufferedPositionMs, newBufferedPercentage, newTotalBufferedDurationMs));
if (maskedPlayerInfo.playbackState != Player.STATE_IDLE) {
maskedPlayerInfo = maskedPlayerInfo.copyWithPlaybackState(Player.STATE_BUFFERING, /* playerError= */
null);
}
ControllerInfo maskedControllerInfo = new ControllerInfo(maskedPlayerInfo, controllerInfo.availableSessionCommands, controllerInfo.availablePlayerCommands, controllerInfo.customLayout);
updateStateMaskedControllerInfo(maskedControllerInfo, discontinuityReason, mediaItemTransitionReason);
}
use of androidx.media3.common.IllegalSeekPositionException in project media by androidx.
the class MediaControllerImplBase method seekToInternal.
private void seekToInternal(int windowIndex, long positionMs) {
Timeline timeline = playerInfo.timeline;
if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) {
throw new IllegalSeekPositionException(timeline, windowIndex, positionMs);
}
if (isPlayingAd()) {
return;
}
@Player.State int newPlaybackState = getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : Player.STATE_BUFFERING;
PlayerInfo newPlayerInfo = playerInfo.copyWithPlaybackState(newPlaybackState, playerInfo.playerError);
@Nullable PeriodInfo periodInfo = getPeriodInfo(timeline, windowIndex, positionMs);
if (periodInfo == null) {
// Timeline is empty.
PositionInfo newPositionInfo = new PositionInfo(/* windowUid= */
null, windowIndex, /* mediaItem= */
null, /* periodUid= */
null, /* periodIndex= */
0, /* positionMs= */
positionMs == C.TIME_UNSET ? 0 : positionMs, /* contentPositionMs= */
positionMs == C.TIME_UNSET ? 0 : positionMs, /* adGroupIndex= */
C.INDEX_UNSET, /* adIndexInAdGroup= */
C.INDEX_UNSET);
newPlayerInfo = maskTimelineAndPositionInfo(playerInfo, playerInfo.timeline, newPositionInfo, new SessionPositionInfo(newPositionInfo, playerInfo.sessionPositionInfo.isPlayingAd, /* eventTimeMs= */
SystemClock.elapsedRealtime(), playerInfo.sessionPositionInfo.durationMs, /* bufferedPositionMs= */
positionMs == C.TIME_UNSET ? 0 : positionMs, /* bufferedPercentage= */
0, /* totalBufferedDurationMs= */
0, playerInfo.sessionPositionInfo.currentLiveOffsetMs, playerInfo.sessionPositionInfo.contentDurationMs, /* contentBufferedPositionMs= */
positionMs == C.TIME_UNSET ? 0 : positionMs), DISCONTINUITY_REASON_SEEK);
} else {
newPlayerInfo = maskPositionInfo(newPlayerInfo, timeline, periodInfo);
}
boolean mediaItemTransition = !playerInfo.timeline.isEmpty() && newPlayerInfo.sessionPositionInfo.positionInfo.mediaItemIndex != playerInfo.sessionPositionInfo.positionInfo.mediaItemIndex;
boolean positionDiscontinuity = mediaItemTransition || newPlayerInfo.sessionPositionInfo.positionInfo.positionMs != playerInfo.sessionPositionInfo.positionInfo.positionMs;
if (!positionDiscontinuity) {
return;
}
updatePlayerInfo(newPlayerInfo, /* ignored */
TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, positionDiscontinuity, /* positionDiscontinuityReason= */
DISCONTINUITY_REASON_SEEK, mediaItemTransition, MEDIA_ITEM_TRANSITION_REASON_SEEK);
}
use of androidx.media3.common.IllegalSeekPositionException in project media by androidx.
the class ExoPlayerImpl method setMediaSourcesInternal.
private void setMediaSourcesInternal(List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs, boolean resetToDefaultPosition) {
int currentWindowIndex = getCurrentWindowIndexInternal();
long currentPositionMs = getCurrentPosition();
pendingOperationAcks++;
if (!mediaSourceHolderSnapshots.isEmpty()) {
removeMediaSourceHolders(/* fromIndex= */
0, /* toIndexExclusive= */
mediaSourceHolderSnapshots.size());
}
List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(/* index= */
0, mediaSources);
Timeline timeline = createMaskingTimeline();
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs);
}
// Evaluate the actual start position.
if (resetToDefaultPosition) {
startWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled);
startPositionMs = C.TIME_UNSET;
} else if (startWindowIndex == C.INDEX_UNSET) {
startWindowIndex = currentWindowIndex;
startPositionMs = currentPositionMs;
}
PlaybackInfo newPlaybackInfo = maskTimelineAndPosition(playbackInfo, timeline, maskWindowPositionMsOrGetPeriodPositionUs(timeline, startWindowIndex, startPositionMs));
// Mask the playback state.
int maskingPlaybackState = newPlaybackInfo.playbackState;
if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) {
// Position reset to startWindowIndex (results in pending initial seek).
if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) {
// Setting an empty timeline or invalid seek transitions to ended.
maskingPlaybackState = STATE_ENDED;
} else {
maskingPlaybackState = STATE_BUFFERING;
}
}
newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState);
internalPlayer.setMediaSources(holders, startWindowIndex, Util.msToUs(startPositionMs), shuffleOrder);
boolean positionDiscontinuity = !playbackInfo.periodId.periodUid.equals(newPlaybackInfo.periodId.periodUid) && !playbackInfo.timeline.isEmpty();
updatePlaybackInfo(newPlaybackInfo, /* timelineChangeReason= */
TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* seekProcessed= */
false, /* positionDiscontinuity= */
positionDiscontinuity, DISCONTINUITY_REASON_REMOVE, /* discontinuityWindowStartPositionUs= */
getCurrentPositionUsInternal(newPlaybackInfo), /* ignored */
C.INDEX_UNSET);
}
use of androidx.media3.common.IllegalSeekPositionException in project media by androidx.
the class ExoPlayerImplInternal method resolveSeekPositionUs.
/**
* Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the
* internal timeline.
*
* @param seekPosition The position to resolve.
* @param trySubsequentPeriods Whether the position can be resolved to a subsequent matching
* period if the original period is no longer available.
* @return The resolved position, or null if resolution was not successful.
* @throws IllegalSeekPositionException If the window index of the seek position is outside the
* bounds of the timeline.
*/
@Nullable
private static Pair<Object, Long> resolveSeekPositionUs(Timeline timeline, SeekPosition seekPosition, boolean trySubsequentPeriods, @RepeatMode int repeatMode, boolean shuffleModeEnabled, Timeline.Window window, Timeline.Period period) {
Timeline seekTimeline = seekPosition.timeline;
if (timeline.isEmpty()) {
// We don't have a valid timeline yet, so we can't resolve the position.
return null;
}
if (seekTimeline.isEmpty()) {
// The application performed a blind seek with an empty timeline (most likely based on
// knowledge of what the future timeline will be). Use the internal timeline.
seekTimeline = timeline;
}
// Map the SeekPosition to a position in the corresponding timeline.
Pair<Object, Long> periodPositionUs;
try {
periodPositionUs = seekTimeline.getPeriodPositionUs(window, period, seekPosition.windowIndex, seekPosition.windowPositionUs);
} catch (IndexOutOfBoundsException e) {
// The window index of the seek position was outside the bounds of the timeline.
return null;
}
if (timeline.equals(seekTimeline)) {
// Our internal timeline is the seek timeline, so the mapped position is correct.
return periodPositionUs;
}
// Attempt to find the mapped period in the internal timeline.
int periodIndex = timeline.getIndexOfPeriod(periodPositionUs.first);
if (periodIndex != C.INDEX_UNSET) {
// We successfully located the period in the internal timeline.
if (seekTimeline.getPeriodByUid(periodPositionUs.first, period).isPlaceholder && seekTimeline.getWindow(period.windowIndex, window).firstPeriodIndex == seekTimeline.getIndexOfPeriod(periodPositionUs.first)) {
// The seek timeline was using a placeholder, so we need to re-resolve using the updated
// timeline in case the resolved position changed. Only resolve the first period in a window
// because subsequent periods must start at position 0 and don't need to be resolved.
int newWindowIndex = timeline.getPeriodByUid(periodPositionUs.first, period).windowIndex;
periodPositionUs = timeline.getPeriodPositionUs(window, period, newWindowIndex, seekPosition.windowPositionUs);
}
return periodPositionUs;
}
if (trySubsequentPeriods) {
// Try and find a subsequent period from the seek timeline in the internal timeline.
@Nullable Object periodUid = resolveSubsequentPeriod(window, period, repeatMode, shuffleModeEnabled, periodPositionUs.first, seekTimeline, timeline);
if (periodUid != null) {
// We found one. Use the default position of the corresponding window.
return timeline.getPeriodPositionUs(window, period, timeline.getPeriodByUid(periodUid, period).windowIndex, /* windowPositionUs= */
C.TIME_UNSET);
}
}
// We didn't find one. Give up.
return null;
}
Aggregations