mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Forward seek command details to seekTo method in BasePlayer
BasePlayer simplifies implementations by handling all the various seek methods and forwarding to a single method that can then be implemented by subclasses. However, this loses the information about the concrete entry point used for seeking, which is relevant when the subclass wants to verify or filter by Player.Command. This can be improved by adding the command as a new parameter. Since we have to change the method anyway, we can also incorporate the boolean flag about whether the current item is repeated to avoid the separate method. PiperOrigin-RevId: 494948094 (cherry picked from commit ab6fc6a08d0908afe59e7cd17fcaefa96acf1816)
This commit is contained in:
parent
80be30f511
commit
cdc07e2175
@ -13,6 +13,8 @@
|
|||||||
playback thread for a new ExoPlayer instance.
|
playback thread for a new ExoPlayer instance.
|
||||||
* Allow download manager helpers to be cleared
|
* Allow download manager helpers to be cleared
|
||||||
([#10776](https://github.com/google/ExoPlayer/issues/10776)).
|
([#10776](https://github.com/google/ExoPlayer/issues/10776)).
|
||||||
|
* Add parameter to `BasePlayer.seekTo` to also indicate the command used
|
||||||
|
for seeking.
|
||||||
* Audio:
|
* Audio:
|
||||||
* Use the compressed audio format bitrate to calculate the min buffer size
|
* Use the compressed audio format bitrate to calculate the min buffer size
|
||||||
for `AudioTrack` in direct playbacks (passthrough).
|
for `AudioTrack` in direct playbacks (passthrough).
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.cast;
|
package androidx.media3.cast;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
@ -399,7 +400,12 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
// don't implement onPositionDiscontinuity().
|
// don't implement onPositionDiscontinuity().
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
MediaStatus mediaStatus = getMediaStatus();
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
// We assume the default position is 0. There is no support for seeking to the default position
|
// We assume the default position is 0. There is no support for seeking to the default position
|
||||||
// in RemoteMediaClient.
|
// in RemoteMediaClient.
|
||||||
|
@ -15,14 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.ForOverride;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Abstract base {@link Player} which implements common implementation independent methods. */
|
/** Abstract base {@link Player} which implements common implementation independent methods. */
|
||||||
@ -121,27 +122,23 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToDefaultPosition() {
|
public final void seekToDefaultPosition() {
|
||||||
seekToDefaultPosition(getCurrentMediaItemIndex());
|
seekToDefaultPositionInternal(
|
||||||
|
getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_DEFAULT_POSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToDefaultPosition(int mediaItemIndex) {
|
public final void seekToDefaultPosition(int mediaItemIndex) {
|
||||||
seekTo(mediaItemIndex, /* positionMs= */ C.TIME_UNSET);
|
seekToDefaultPositionInternal(mediaItemIndex, Player.COMMAND_SEEK_TO_MEDIA_ITEM);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void seekTo(long positionMs) {
|
|
||||||
seekTo(getCurrentMediaItemIndex(), positionMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekBack() {
|
public final void seekBack() {
|
||||||
seekToOffset(-getSeekBackIncrement());
|
seekToOffset(-getSeekBackIncrement(), Player.COMMAND_SEEK_BACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekForward() {
|
public final void seekForward() {
|
||||||
seekToOffset(getSeekForwardIncrement());
|
seekToOffset(getSeekForwardIncrement(), Player.COMMAND_SEEK_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,15 +184,7 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToPreviousMediaItem() {
|
public final void seekToPreviousMediaItem() {
|
||||||
int previousMediaItemIndex = getPreviousMediaItemIndex();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
|
||||||
if (previousMediaItemIndex == C.INDEX_UNSET) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (previousMediaItemIndex == getCurrentMediaItemIndex()) {
|
|
||||||
repeatCurrentMediaItem();
|
|
||||||
} else {
|
|
||||||
seekToDefaultPosition(previousMediaItemIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -207,12 +196,12 @@ public abstract class BasePlayer implements Player {
|
|||||||
boolean hasPreviousMediaItem = hasPreviousMediaItem();
|
boolean hasPreviousMediaItem = hasPreviousMediaItem();
|
||||||
if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) {
|
if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) {
|
||||||
if (hasPreviousMediaItem) {
|
if (hasPreviousMediaItem) {
|
||||||
seekToPreviousMediaItem();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
}
|
}
|
||||||
} else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) {
|
} else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) {
|
||||||
seekToPreviousMediaItem();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
} else {
|
} else {
|
||||||
seekTo(/* positionMs= */ 0);
|
seekToCurrentItem(/* positionMs= */ 0, Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,15 +248,7 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToNextMediaItem() {
|
public final void seekToNextMediaItem() {
|
||||||
int nextMediaItemIndex = getNextMediaItemIndex();
|
seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
|
||||||
if (nextMediaItemIndex == C.INDEX_UNSET) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (nextMediaItemIndex == getCurrentMediaItemIndex()) {
|
|
||||||
repeatCurrentMediaItem();
|
|
||||||
} else {
|
|
||||||
seekToDefaultPosition(nextMediaItemIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -277,12 +258,42 @@ public abstract class BasePlayer implements Player {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasNextMediaItem()) {
|
if (hasNextMediaItem()) {
|
||||||
seekToNextMediaItem();
|
seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT);
|
||||||
} else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) {
|
} else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) {
|
||||||
seekToDefaultPosition();
|
seekToDefaultPositionInternal(getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_NEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void seekTo(long positionMs) {
|
||||||
|
seekToCurrentItem(positionMs, Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void seekTo(int mediaItemIndex, long positionMs) {
|
||||||
|
seekTo(
|
||||||
|
mediaItemIndex,
|
||||||
|
positionMs,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks to a position in the specified {@link MediaItem}.
|
||||||
|
*
|
||||||
|
* @param mediaItemIndex The index of the {@link MediaItem}.
|
||||||
|
* @param positionMs The seek position in the specified {@link MediaItem} in milliseconds, or
|
||||||
|
* {@link C#TIME_UNSET} to seek to the media item's default position.
|
||||||
|
* @param seekCommand The {@link Player.Command} used to trigger the seek.
|
||||||
|
* @param isRepeatingCurrentItem Whether this seeks repeats the current item.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public abstract void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setPlaybackSpeed(float speed) {
|
public final void setPlaybackSpeed(float speed) {
|
||||||
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
||||||
@ -437,29 +448,63 @@ public abstract class BasePlayer implements Player {
|
|||||||
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
|
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Repeat the current media item.
|
|
||||||
*
|
|
||||||
* <p>The default implementation seeks to the default position in the current item, which can be
|
|
||||||
* overridden for additional handling.
|
|
||||||
*/
|
|
||||||
@ForOverride
|
|
||||||
protected void repeatCurrentMediaItem() {
|
|
||||||
seekToDefaultPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private @RepeatMode int getRepeatModeForNavigation() {
|
private @RepeatMode int getRepeatModeForNavigation() {
|
||||||
@RepeatMode int repeatMode = getRepeatMode();
|
@RepeatMode int repeatMode = getRepeatMode();
|
||||||
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToOffset(long offsetMs) {
|
private void seekToCurrentItem(long positionMs, @Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
getCurrentMediaItemIndex(), positionMs, seekCommand, /* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToOffset(long offsetMs, @Player.Command int seekCommand) {
|
||||||
long positionMs = getCurrentPosition() + offsetMs;
|
long positionMs = getCurrentPosition() + offsetMs;
|
||||||
long durationMs = getDuration();
|
long durationMs = getDuration();
|
||||||
if (durationMs != C.TIME_UNSET) {
|
if (durationMs != C.TIME_UNSET) {
|
||||||
positionMs = min(positionMs, durationMs);
|
positionMs = min(positionMs, durationMs);
|
||||||
}
|
}
|
||||||
positionMs = max(positionMs, 0);
|
positionMs = max(positionMs, 0);
|
||||||
seekTo(positionMs);
|
seekToCurrentItem(positionMs, seekCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToDefaultPositionInternal(int mediaItemIndex, @Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
mediaItemIndex,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
seekCommand,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToNextMediaItemInternal(@Player.Command int seekCommand) {
|
||||||
|
int nextMediaItemIndex = getNextMediaItemIndex();
|
||||||
|
if (nextMediaItemIndex == C.INDEX_UNSET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nextMediaItemIndex == getCurrentMediaItemIndex()) {
|
||||||
|
repeatCurrentMediaItem(seekCommand);
|
||||||
|
} else {
|
||||||
|
seekToDefaultPositionInternal(nextMediaItemIndex, seekCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToPreviousMediaItemInternal(@Player.Command int seekCommand) {
|
||||||
|
int previousMediaItemIndex = getPreviousMediaItemIndex();
|
||||||
|
if (previousMediaItemIndex == C.INDEX_UNSET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (previousMediaItemIndex == getCurrentMediaItemIndex()) {
|
||||||
|
repeatCurrentMediaItem(seekCommand);
|
||||||
|
} else {
|
||||||
|
seekToDefaultPositionInternal(previousMediaItemIndex, seekCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void repeatCurrentMediaItem(@Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
getCurrentMediaItemIndex(),
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
seekCommand,
|
||||||
|
/* isRepeatingCurrentItem= */ true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
@ -32,6 +33,7 @@ import android.view.TextureView;
|
|||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.common.text.CueGroup;
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
@ -2133,13 +2135,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekTo(int mediaItemIndex, long positionMs) {
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
// TODO: implement.
|
public final void seekTo(
|
||||||
throw new IllegalStateException();
|
int mediaItemIndex,
|
||||||
}
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
@Override
|
boolean isRepeatingCurrentItem) {
|
||||||
protected final void repeatCurrentMediaItem() {
|
|
||||||
// TODO: implement.
|
// TODO: implement.
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,318 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
|
import androidx.media3.test.utils.StubPlayer;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Tests for {@link BasePlayer}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class BasePlayerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekTo_withIndexAndPosition_usesCommandSeekToMediaItem() {
|
||||||
|
BasePlayer player = spy(new TestBasePlayer());
|
||||||
|
|
||||||
|
player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 4000);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ 4000,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekTo_withPosition_usesCommandSeekInCurrentMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekTo(/* positionMs= */ 4000);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 4000,
|
||||||
|
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToDefaultPosition_withIndex_usesCommandSeekToMediaItem() {
|
||||||
|
BasePlayer player = spy(new TestBasePlayer());
|
||||||
|
|
||||||
|
player.seekToDefaultPosition(/* mediaItemIndex= */ 2);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToDefaultPosition_withoutIndex_usesCommandSeekToDefaultPosition() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToDefaultPosition();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_DEFAULT_POSITION,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNext_usesCommandSeekToNext() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToNext();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_NEXT,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNextMediaItem_usesCommandSeekToNextMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToNextMediaItem();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekForward_usesCommandSeekForward() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public long getSeekForwardIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekForward();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 7000,
|
||||||
|
Player.COMMAND_SEEK_FORWARD,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToPrevious_usesCommandSeekToPrevious() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxSeekToPreviousPosition() {
|
||||||
|
return 4000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToPrevious();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_PREVIOUS,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToPreviousMediaItem_usesCommandSeekToPreviousMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToPreviousMediaItem();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekBack_usesCommandSeekBack() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public long getSeekBackIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekBack();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 3000,
|
||||||
|
Player.COMMAND_SEEK_BACK,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestBasePlayer extends StubPlayer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSeekBackIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSeekForwardIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxSeekToPreviousPosition() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getCurrentTimeline() {
|
||||||
|
return new FakeTimeline(/* windowCount= */ 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDuration() {
|
||||||
|
return 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlayingAd() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRepeatMode() {
|
||||||
|
return Player.REPEAT_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getShuffleModeEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -823,16 +823,51 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void repeatCurrentMediaItem() {
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
seekToInternal(
|
analyticsCollector.notifySeekStarted();
|
||||||
getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET, /* repeatMediaItem= */ true);
|
Timeline timeline = playbackInfo.timeline;
|
||||||
}
|
if (mediaItemIndex < 0
|
||||||
|
|| (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) {
|
||||||
@Override
|
throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs);
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
}
|
||||||
verifyApplicationThread();
|
pendingOperationAcks++;
|
||||||
seekToInternal(mediaItemIndex, positionMs, /* repeatMediaItem= */ false);
|
if (isPlayingAd()) {
|
||||||
|
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
|
||||||
|
// general because the midroll ad preceding the seek destination must be played before the
|
||||||
|
// content position can be played, if a different ad is playing at the moment.
|
||||||
|
Log.w(TAG, "seekTo ignored because an ad is playing");
|
||||||
|
ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate =
|
||||||
|
new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo);
|
||||||
|
playbackInfoUpdate.incrementPendingOperationAcks(1);
|
||||||
|
playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@Player.State
|
||||||
|
int newPlaybackState =
|
||||||
|
getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING;
|
||||||
|
int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
|
||||||
|
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
|
||||||
|
newPlaybackInfo =
|
||||||
|
maskTimelineAndPosition(
|
||||||
|
newPlaybackInfo,
|
||||||
|
timeline,
|
||||||
|
maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs));
|
||||||
|
internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs));
|
||||||
|
updatePlaybackInfo(
|
||||||
|
newPlaybackInfo,
|
||||||
|
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
|
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
||||||
|
/* seekProcessed= */ true,
|
||||||
|
/* positionDiscontinuity= */ true,
|
||||||
|
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
|
||||||
|
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
|
||||||
|
oldMaskingMediaItemIndex,
|
||||||
|
isRepeatingCurrentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -2696,48 +2731,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToInternal(int mediaItemIndex, long positionMs, boolean repeatMediaItem) {
|
|
||||||
analyticsCollector.notifySeekStarted();
|
|
||||||
Timeline timeline = playbackInfo.timeline;
|
|
||||||
if (mediaItemIndex < 0
|
|
||||||
|| (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) {
|
|
||||||
throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs);
|
|
||||||
}
|
|
||||||
pendingOperationAcks++;
|
|
||||||
if (isPlayingAd()) {
|
|
||||||
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
|
|
||||||
// general because the midroll ad preceding the seek destination must be played before the
|
|
||||||
// content position can be played, if a different ad is playing at the moment.
|
|
||||||
Log.w(TAG, "seekTo ignored because an ad is playing");
|
|
||||||
ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate =
|
|
||||||
new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo);
|
|
||||||
playbackInfoUpdate.incrementPendingOperationAcks(1);
|
|
||||||
playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@Player.State
|
|
||||||
int newPlaybackState =
|
|
||||||
getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING;
|
|
||||||
int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
|
|
||||||
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
|
|
||||||
newPlaybackInfo =
|
|
||||||
maskTimelineAndPosition(
|
|
||||||
newPlaybackInfo,
|
|
||||||
timeline,
|
|
||||||
maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs));
|
|
||||||
internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs));
|
|
||||||
updatePlaybackInfo(
|
|
||||||
newPlaybackInfo,
|
|
||||||
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
|
||||||
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
|
||||||
/* seekProcessed= */ true,
|
|
||||||
/* positionDiscontinuity= */ true,
|
|
||||||
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
|
|
||||||
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
|
|
||||||
oldMaskingMediaItemIndex,
|
|
||||||
repeatMediaItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
|
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
|
||||||
return new DeviceInfo(
|
return new DeviceInfo(
|
||||||
DeviceInfo.PLAYBACK_TYPE_LOCAL,
|
DeviceInfo.PLAYBACK_TYPE_LOCAL,
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
package androidx.media3.exoplayer;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioDeviceInfo;
|
import android.media.AudioDeviceInfo;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -1004,10 +1006,16 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
return player.isLoading();
|
return player.isLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ForOverride") // Forwarding to ForOverride method in ExoPlayerImpl.
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
blockUntilConstructorFinished();
|
blockUntilConstructorFinished();
|
||||||
player.seekTo(mediaItemIndex, positionMs);
|
player.seekTo(mediaItemIndex, positionMs, seekCommand, isRepeatingCurrentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -147,7 +147,11 @@ public class StubPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user