diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index c17f8e4b17..cdfc659cff 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -281,7 +281,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi player.setVideoListener(this); player.setVideoSurfaceHolder(surfaceView.getHolder()); if (shouldRestorePosition) { - player.seekTo(playerPeriodIndex, playerPosition); + player.seekInPeriod(playerPeriodIndex, playerPosition); } player.setPlayWhenReady(true); mediaController.setMediaPlayer(new PlayerControl(player)); @@ -371,7 +371,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi debugViewHelper.stop(); debugViewHelper = null; playerPeriodIndex = player.getCurrentPeriodIndex(); - playerPosition = player.getCurrentPosition(); + playerPosition = player.getCurrentPositionInPeriod(); shouldRestorePosition = false; Timeline playerTimeline = player.getCurrentTimeline(); if (playerTimeline != null) { diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 310e1c0dcd..e02456c656 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -245,7 +245,7 @@ public interface ExoPlayer { * @param mediaSource The {@link MediaSource} to play. * @param resetPosition Whether the playback position should be reset to the source's default * position. If false, playback will start from the position defined by - * {@link #getCurrentPeriodIndex()} and {@link #getCurrentPosition()}. + * {@link #getCurrentPeriodIndex()} and {@link #getCurrentPositionInPeriod()}. */ void setMediaSource(MediaSource mediaSource, boolean resetPosition); @@ -278,26 +278,52 @@ public interface ExoPlayer { * * @param positionMs The seek position. */ - void seekTo(long positionMs); - - /** - * Seeks to a position specified in milliseconds in the specified period. - * - * @param periodIndex The index of the period to seek to. - * @param positionMs The seek position relative to the start of the specified period. - */ - void seekTo(int periodIndex, long positionMs); + void seekInCurrentPeriod(long positionMs); /** * Seeks to the default position associated with the specified period. The position can depend on * the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will - * typically be the live edge. For other types of streams it will typically be the start of the - * stream. + * typically be the live edge of the window to which the period belongs. For other streams it will + * typically be the start of the period. * * @param periodIndex The index of the period whose associated default position should be seeked * to. */ - void seekToDefaultPosition(int periodIndex); + void seekToDefaultPositionForPeriod(int periodIndex); + + /** + * Seeks to a position specified in milliseconds in the specified period. + * + * @param periodIndex The index of the period. + * @param positionMs The seek position relative to the start of the period. + */ + void seekInPeriod(int periodIndex, long positionMs); + + /** + * Seeks to a position specified in milliseconds in the current window. + * + * @param positionMs The seek position. + */ + void seekInCurrentWindow(long positionMs); + + /** + * Seeks to the default position associated with the specified window. The position can depend on + * the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will + * typically be the live edge of the window. For other streams it will typically be the start of + * the window. + * + * @param windowIndex The index of the window whose associated default position should be seeked + * to. + */ + void seekToDefaultPositionForWindow(int windowIndex); + + /** + * Seeks to a position specified in milliseconds in the specified seek window. + * + * @param seekWindowIndex The index of the seek window. + * @param positionMs The seek position relative to the start of the window. + */ + void seekInWindow(int seekWindowIndex, long positionMs); /** * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention @@ -334,22 +360,6 @@ public interface ExoPlayer { */ void blockingSendMessages(ExoPlayerMessage... messages); - /** - * Returns the duration of the current period in milliseconds, or {@link #UNKNOWN_TIME} if the - * duration is not known. - */ - long getDuration(); - - /** - * Returns the playback position in the current period, in milliseconds. - */ - long getCurrentPosition(); - - /** - * Returns the index of the current period. - */ - int getCurrentPeriodIndex(); - /** * Returns the current {@link Timeline}, or {@code null} if there is no timeline. */ @@ -361,16 +371,71 @@ public interface ExoPlayer { */ Object getCurrentManifest(); - /** - * Returns an estimate of the absolute position in milliseconds up to which data is buffered, - * or {@link #UNKNOWN_TIME} if no estimate is available. - */ - long getBufferedPosition(); + // Period based. /** - * Returns an estimate of the percentage into the media up to which data is buffered, or 0 if no - * estimate is available. + * Returns the index of the current period. */ - int getBufferedPercentage(); + int getCurrentPeriodIndex(); + + /** + * Returns the duration of the current period in milliseconds, or {@link #UNKNOWN_TIME} if the + * duration is not known. + */ + long getCurrentPeriodDuration(); + + /** + * Returns the playback position in the current period, in milliseconds. + */ + long getCurrentPositionInPeriod(); + + /** + * Returns an estimate of the position in the current period up to which data is buffered, or + * {@link #UNKNOWN_TIME} if no estimate is available. + */ + long getBufferedPositionInPeriod(); + + /** + * Returns an estimate of the percentage in the current period up to which data is buffered, or 0 + * if no estimate is available. + */ + int getBufferedPercentageInPeriod(); + + // Window based. + + /** + * Returns the index of the seek window associated with the current period, or -1 if the timeline + * is not set. + */ + int getCurrentWindowIndex(); + + /** + * Returns the duration of the current window in milliseconds, or {@link #UNKNOWN_TIME} if the + * duration is not known. + */ + long getCurrentWindowDuration(); + + /** + * Returns the playback position in the current seek window, in milliseconds, or + * {@link #UNKNOWN_TIME} if the timeline is not set. + */ + long getCurrentPositionInWindow(); + + /** + * Returns an estimate of the position in the current window up to which data is buffered, or + * {@link #UNKNOWN_TIME} if no estimate is available. + */ + long getBufferedPositionInWindow(); + + /** + * Returns an estimate of the percentage in the current window up to which data is buffered, or 0 + * if no estimate is available. + */ + int getBufferedPercentageInWindow(); + + // Misc methods + + // TODO - Add a method/methods to expose this. + // getBufferedPosition -> periodIndex,position } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 7db6947a76..aad4d6d34e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -127,12 +127,17 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public void seekTo(long positionMs) { - seekTo(getCurrentPeriodIndex(), positionMs); + public void seekInCurrentPeriod(long positionMs) { + seekInPeriod(getCurrentPeriodIndex(), positionMs); } @Override - public void seekTo(int periodIndex, long positionMs) { + public void seekToDefaultPositionForPeriod(int periodIndex) { + seekInPeriod(periodIndex, UNKNOWN_TIME); + } + + @Override + public void seekInPeriod(int periodIndex, long positionMs) { boolean seekToDefaultPosition = positionMs == UNKNOWN_TIME; maskingPeriodIndex = periodIndex; maskingPositionMs = seekToDefaultPosition ? 0 : positionMs; @@ -146,8 +151,41 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public void seekToDefaultPosition(int periodIndex) { - seekTo(periodIndex, UNKNOWN_TIME); + public void seekInCurrentWindow(long positionMs) { + Timeline timeline = getCurrentTimeline(); + if (timeline == null) { + throw new IllegalArgumentException("Windows are not yet known"); + } + int windowIndex = timeline.getPeriodWindowIndex(getCurrentPeriodIndex()); + seekInWindow(windowIndex, positionMs); + } + + @Override + public void seekToDefaultPositionForWindow(int windowIndex) { + if (timeline == null) { + throw new IllegalArgumentException("Windows are not yet known"); + } + Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); + Window window = timeline.getWindow(windowIndex); + seekToDefaultPositionForPeriod(window.startPeriodIndex); + } + + @Override + public void seekInWindow(int windowIndex, long positionMs) { + if (timeline == null) { + throw new IllegalArgumentException("Windows are not yet known"); + } + Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); + Window window = timeline.getWindow(windowIndex); + int periodIndex = window.startPeriodIndex; + long periodPositionMs = window.startTimeMs + positionMs; + long periodDurationMs = timeline.getPeriodDurationMs(periodIndex); + while (periodDurationMs != UNKNOWN_TIME && periodPositionMs >= periodDurationMs + && periodIndex < window.endPeriodIndex) { + periodPositionMs -= periodDurationMs; + periodDurationMs = timeline.getPeriodDurationMs(++periodIndex); + } + seekInPeriod(periodIndex, periodPositionMs); } @Override @@ -172,7 +210,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public long getDuration() { + public int getCurrentPeriodIndex() { + return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex; + } + + @Override + public long getCurrentPeriodDuration() { if (timeline == null) { return UNKNOWN_TIME; } @@ -180,14 +223,89 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public long getCurrentPosition() { + public long getCurrentPositionInPeriod() { return pendingSeekAcks > 0 ? maskingPositionMs : playbackInfo.positionUs == C.UNSET_TIME_US ? 0 : (playbackInfo.positionUs / 1000); } @Override - public int getCurrentPeriodIndex() { - return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex; + public long getBufferedPositionInPeriod() { + if (pendingSeekAcks == 0) { + long bufferedPositionUs = playbackInfo.bufferedPositionUs; + return bufferedPositionUs == C.UNSET_TIME_US ? UNKNOWN_TIME : (bufferedPositionUs / 1000); + } else { + return maskingPositionMs; + } + } + + @Override + public int getBufferedPercentageInPeriod() { + if (timeline == null) { + return 0; + } + long bufferedPosition = getBufferedPositionInPeriod(); + long duration = getCurrentPeriodDuration(); + return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0 + : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); + } + + @Override + public int getCurrentWindowIndex() { + if (timeline == null) { + return -1; + } + return timeline.getPeriodWindowIndex(getCurrentPeriodIndex()); + } + + @Override + public long getCurrentWindowDuration() { + if (timeline == null) { + return UNKNOWN_TIME; + } + return timeline.getWindow(getCurrentWindowIndex()).durationMs; + } + + @Override + public long getCurrentPositionInWindow() { + if (timeline == null) { + return UNKNOWN_TIME; + } + int periodIndex = getCurrentPeriodIndex(); + int windowIndex = timeline.getPeriodWindowIndex(periodIndex); + Window window = timeline.getWindow(windowIndex); + long position = getCurrentPositionInPeriod(); + for (int i = window.startPeriodIndex; i < periodIndex; i++) { + position += timeline.getPeriodDurationMs(i); + } + position -= window.startTimeMs; + return position; + } + + @Override + public long getBufferedPositionInWindow() { + // TODO - Implement this properly. + if (timeline == null) { + return UNKNOWN_TIME; + } + int periodIndex = getCurrentPeriodIndex(); + int windowIndex = timeline.getPeriodWindowIndex(periodIndex); + Window window = timeline.getWindow(windowIndex); + if (window.startPeriodIndex == periodIndex && window.endPeriodIndex == periodIndex + && window.startTimeMs == 0 && window.durationMs == getCurrentPeriodDuration()) { + return getBufferedPositionInPeriod(); + } + return getCurrentPositionInWindow(); + } + + @Override + public int getBufferedPercentageInWindow() { + if (timeline == null) { + return 0; + } + long bufferedPosition = getBufferedPositionInWindow(); + long duration = getCurrentWindowDuration(); + return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0 + : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); } @Override @@ -200,24 +318,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return manifest; } - @Override - public long getBufferedPosition() { - if (pendingSeekAcks == 0) { - long bufferedPositionUs = playbackInfo.bufferedPositionUs; - return bufferedPositionUs == C.UNSET_TIME_US ? UNKNOWN_TIME : (bufferedPositionUs / 1000); - } else { - return maskingPositionMs; - } - } - - @Override - public int getBufferedPercentage() { - long bufferedPosition = getBufferedPosition(); - long duration = getDuration(); - return bufferedPosition == UNKNOWN_TIME || duration == UNKNOWN_TIME ? 0 - : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); - } - // Not private so it can be called from an inner class without going through a thunk method. /* package */ void handleEvent(Message msg) { switch (msg.what) { diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 28b521d628..50bb8811b0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -344,18 +344,33 @@ public final class SimpleExoPlayer implements ExoPlayer { } @Override - public void seekTo(long positionMs) { - player.seekTo(positionMs); + public void seekInCurrentPeriod(long positionMs) { + player.seekInCurrentPeriod(positionMs); } @Override - public void seekTo(int periodIndex, long positionMs) { - player.seekTo(periodIndex, positionMs); + public void seekToDefaultPositionForPeriod(int periodIndex) { + player.seekToDefaultPositionForPeriod(periodIndex); } @Override - public void seekToDefaultPosition(int periodIndex) { - player.seekToDefaultPosition(periodIndex); + public void seekInPeriod(int periodIndex, long positionMs) { + player.seekInPeriod(periodIndex, positionMs); + } + + @Override + public void seekInCurrentWindow(long positionMs) { + player.seekInCurrentWindow(positionMs); + } + + @Override + public void seekToDefaultPositionForWindow(int windowIndex) { + player.seekToDefaultPositionForWindow(windowIndex); + } + + @Override + public void seekInWindow(int windowIndex, long positionMs) { + player.seekInWindow(windowIndex, positionMs); } @Override @@ -378,21 +393,56 @@ public final class SimpleExoPlayer implements ExoPlayer { player.blockingSendMessages(messages); } - @Override - public long getDuration() { - return player.getDuration(); - } - - @Override - public long getCurrentPosition() { - return player.getCurrentPosition(); - } - @Override public int getCurrentPeriodIndex() { return player.getCurrentPeriodIndex(); } + @Override + public long getCurrentPeriodDuration() { + return player.getCurrentPeriodDuration(); + } + + @Override + public long getCurrentPositionInPeriod() { + return player.getCurrentPositionInPeriod(); + } + + @Override + public long getBufferedPositionInPeriod() { + return player.getBufferedPositionInPeriod(); + } + + @Override + public int getBufferedPercentageInPeriod() { + return player.getBufferedPercentageInPeriod(); + } + + @Override + public int getCurrentWindowIndex() { + return player.getCurrentWindowIndex(); + } + + @Override + public long getCurrentWindowDuration() { + return player.getCurrentWindowDuration(); + } + + @Override + public long getCurrentPositionInWindow() { + return player.getCurrentPositionInWindow(); + } + + @Override + public long getBufferedPositionInWindow() { + return player.getBufferedPositionInWindow(); + } + + @Override + public int getBufferedPercentageInWindow() { + return player.getBufferedPercentageInWindow(); + } + @Override public Timeline getCurrentTimeline() { return player.getCurrentTimeline(); @@ -403,16 +453,6 @@ public final class SimpleExoPlayer implements ExoPlayer { return player.getCurrentManifest(); } - @Override - public long getBufferedPosition() { - return player.getBufferedPosition(); - } - - @Override - public int getBufferedPercentage() { - return player.getBufferedPercentage(); - } - // Internal methods. private void buildRenderers(Context context, DrmSessionManager drmSessionManager, diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java b/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java index 765f2a9efb..128645b09a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/MediaControllerPrevNextClickListener.java @@ -48,17 +48,20 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { @Override public void onClick(View v) { - int currentPeriodIndex = player.getCurrentPeriodIndex(); + int currentWindowIndex = player.getCurrentWindowIndex(); + if (currentWindowIndex == -1) { + return; + } if (isNext) { - if (currentPeriodIndex < player.getCurrentTimeline().getPeriodCount() - 1) { - player.seekTo(currentPeriodIndex + 1, 0); + if (currentWindowIndex < player.getCurrentTimeline().getWindowCount() - 1) { + player.seekToDefaultPositionForWindow(currentWindowIndex + 1); } } else { - if (currentPeriodIndex > 0 - && player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) { - player.seekTo(currentPeriodIndex - 1, 0); + if (currentWindowIndex > 0 + && player.getCurrentPositionInWindow() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) { + player.seekToDefaultPositionForWindow(currentWindowIndex - 1); } else { - player.seekTo(currentPeriodIndex, 0); + player.seekInWindow(currentWindowIndex, 0); } } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/PlayerControl.java b/library/src/main/java/com/google/android/exoplayer2/ui/PlayerControl.java index 32cdd32b08..cd93f9df4e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/PlayerControl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/PlayerControl.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ui; import android.widget.MediaController.MediaPlayerControl; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; -import com.google.android.exoplayer2.util.Util; /** * An implementation of {@link MediaPlayerControl} for controlling an {@link ExoPlayer} instance. @@ -65,19 +64,19 @@ public class PlayerControl implements MediaPlayerControl { @Override public int getBufferPercentage() { - return exoPlayer.getBufferedPercentage(); + return exoPlayer.getBufferedPercentageInWindow(); } @Override public int getCurrentPosition() { - return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : (int) exoPlayer.getCurrentPosition(); + long position = exoPlayer.getCurrentPositionInWindow(); + return position == ExoPlayer.UNKNOWN_TIME ? 0 : (int) position; } @Override public int getDuration() { - return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : (int) exoPlayer.getDuration(); + long duration = exoPlayer.getCurrentWindowDuration(); + return duration == ExoPlayer.UNKNOWN_TIME ? 0 : (int) duration; } @Override @@ -97,9 +96,11 @@ public class PlayerControl implements MediaPlayerControl { @Override public void seekTo(int timeMillis) { - long seekPosition = exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : Util.constrainValue(timeMillis, 0, getDuration()); - exoPlayer.seekTo(seekPosition); + int windowIndex = exoPlayer.getCurrentWindowIndex(); + if (windowIndex == -1) { + return; + } + exoPlayer.seekInWindow(windowIndex, timeMillis); } } diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/Action.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/Action.java index 64484f7c5d..91b7a39d48 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/Action.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/Action.java @@ -56,7 +56,7 @@ public abstract class Action { protected abstract void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector); /** - * Calls {@link ExoPlayer#seekTo(long)}. + * Calls {@link ExoPlayer#seekInCurrentPeriod(long)}. */ public static final class Seek extends Action { @@ -73,7 +73,7 @@ public abstract class Action { @Override protected void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector) { - player.seekTo(positionMs); + player.seekInCurrentPeriod(positionMs); } } diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index 81a221336b..012fd6b04c 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -150,7 +150,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen @Override public final void onStop() { actionHandler.removeCallbacksAndMessages(null); - sourceDurationMs = player.getDuration(); + sourceDurationMs = player.getCurrentPeriodDuration(); player.release(); player = null; }