From 5f1a2c71f0de78f2cd1ce61e3e105a0901bcca93 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 24 Aug 2016 07:54:34 -0700 Subject: [PATCH] Finalize V2 ExoPlayer API There's still some internal to clean up to do, and in particular it remains a TODO to be able to handle seek calls before the timeline is set (for this CL, such calls are dropped). This change does however finalize the API. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=131171318 --- .../android/exoplayer2/demo/EventLogger.java | 31 +- .../exoplayer2/demo/PlayerActivity.java | 21 +- .../exoplayer2/ext/flac/FlacPlaybackTest.java | 4 +- .../exoplayer2/ext/opus/OpusPlaybackTest.java | 4 +- .../exoplayer2/ext/vp9/VpxPlaybackTest.java | 4 +- .../google/android/exoplayer2/ExoPlayer.java | 90 +--- .../android/exoplayer2/ExoPlayerImpl.java | 176 ++++---- .../exoplayer2/ExoPlayerImplInternal.java | 396 ++++++++++-------- .../android/exoplayer2/MediaTimeline.java | 122 ------ .../android/exoplayer2/MediaWindow.java | 101 ----- .../android/exoplayer2/SimpleExoPlayer.java | 50 +-- .../google/android/exoplayer2/Timeline.java | 272 ++++++++++++ .../source/ConcatenatingMediaSource.java | 124 ++---- .../source/ExtractorMediaPeriod.java | 4 +- .../source/ExtractorMediaSource.java | 19 +- .../exoplayer2/source/MediaSource.java | 4 +- .../exoplayer2/source/MergingMediaSource.java | 17 +- .../source/SinglePeriodMediaTimeline.java | 130 ------ .../source/SinglePeriodTimeline.java | 101 +++++ .../source/SingleSampleMediaSource.java | 6 +- .../source/dash/DashMediaSource.java | 131 +++--- .../exoplayer2/source/hls/HlsMediaPeriod.java | 6 +- .../exoplayer2/source/hls/HlsMediaSource.java | 4 +- .../source/smoothstreaming/SsMediaSource.java | 18 +- .../exoplayer2/ui/DebugTextViewHelper.java | 4 +- .../MediaControllerPrevNextClickListener.java | 15 +- .../playbacktests/util/ExoHostedTest.java | 4 +- 27 files changed, 848 insertions(+), 1010 deletions(-) delete mode 100644 library/src/main/java/com/google/android/exoplayer2/MediaTimeline.java delete mode 100644 library/src/main/java/com/google/android/exoplayer2/MediaWindow.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/Timeline.java delete mode 100644 library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodMediaTimeline.java create mode 100644 library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index cc74c3b7c4..0538eb5230 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -22,8 +22,8 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; @@ -57,16 +57,22 @@ import java.util.Locale; MappingTrackSelector.EventListener, MetadataRenderer.Output> { private static final String TAG = "EventLogger"; + private static final int MAX_TIMELINE_ITEM_LINES = 3; private static final NumberFormat TIME_FORMAT; static { TIME_FORMAT = NumberFormat.getInstance(Locale.US); TIME_FORMAT.setMinimumFractionDigits(2); TIME_FORMAT.setMaximumFractionDigits(2); + TIME_FORMAT.setGroupingUsed(false); } + private final Timeline.Window window; + private final Timeline.Period period; private final long startTimeMs; public EventLogger() { + window = new Timeline.Window(); + period = new Timeline.Period(); startTimeMs = SystemClock.elapsedRealtime(); } @@ -89,13 +95,24 @@ import java.util.Locale; } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { int periodCount = timeline.getPeriodCount(); int windowCount = timeline.getWindowCount(); - Log.d(TAG, "sourceInfo[startTime=" + timeline.getAbsoluteStartTime() + ", periodCount=" - + periodCount + ", windows: " + windowCount); - for (int windowIndex = 0; windowIndex < windowCount; windowIndex++) { - Log.d(TAG, " " + timeline.getWindow(windowIndex)); + Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount); + for (int i = 0; i < Math.min(periodCount, MAX_TIMELINE_ITEM_LINES); i++) { + timeline.getPeriod(i, period); + Log.d(TAG, " " + "period [" + getTimeString(period.getDurationMs()) + "]"); + } + if (periodCount > MAX_TIMELINE_ITEM_LINES) { + Log.d(TAG, " ..."); + } + for (int i = 0; i < Math.min(windowCount, MAX_TIMELINE_ITEM_LINES); i++) { + timeline.getWindow(i, window); + Log.d(TAG, " " + "window [" + getTimeString(window.getDurationMs()) + ", " + + window.isSeekable + ", " + window.isDynamic + "]"); + } + if (windowCount > MAX_TIMELINE_ITEM_LINES) { + Log.d(TAG, " ..."); } Log.d(TAG, "]"); } @@ -333,7 +350,7 @@ import java.util.Locale; } private static String getTimeString(long timeMs) { - return TIME_FORMAT.format((timeMs) / 1000f); + return timeMs == C.TIME_UNSET ? "?" : TIME_FORMAT.format((timeMs) / 1000f); } private static String getStateString(int state) { 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 7b94d7b98c..b5abda8047 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 @@ -40,9 +40,8 @@ import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.MediaTimeline; -import com.google.android.exoplayer2.MediaWindow; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; @@ -127,7 +126,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi private boolean playerNeedsSource; private boolean shouldRestorePosition; - private int playerPeriod; + private int playerWindow; private long playerPosition; // Activity lifecycle @@ -283,9 +282,9 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi player.setVideoSurfaceHolder(surfaceView.getHolder()); if (shouldRestorePosition) { if (playerPosition == C.TIME_UNSET) { - player.seekToDefaultPositionForPeriod(playerPeriod); + player.seekToDefaultPosition(playerWindow); } else { - player.seekInPeriod(playerPeriod, playerPosition); + player.seekTo(playerWindow, playerPosition); } } player.setPlayWhenReady(true); @@ -376,13 +375,13 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi debugViewHelper.stop(); debugViewHelper = null; shouldRestorePosition = false; - MediaTimeline playerTimeline = player.getCurrentTimeline(); - if (playerTimeline != null) { - MediaWindow window = playerTimeline.getWindow(player.getCurrentWindowIndex()); + Timeline timeline = player.getCurrentTimeline(); + if (timeline != null) { + playerWindow = player.getCurrentWindowIndex(); + Timeline.Window window = timeline.getWindow(playerWindow, new Timeline.Window()); if (!window.isDynamic) { shouldRestorePosition = true; - playerPeriod = player.getCurrentPeriodIndex(); - playerPosition = window.isSeekable ? player.getCurrentPositionInPeriod() : C.TIME_UNSET; + playerPosition = window.isSeekable ? player.getCurrentPosition() : C.TIME_UNSET; } } player.release(); @@ -417,7 +416,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. } diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index 1ff9bb457d..5b01d77a07 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -96,7 +96,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase { } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. } diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java index 3d422b51b7..dd3097b612 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer2/ext/opus/OpusPlaybackTest.java @@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -96,7 +96,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase { } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. } diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 8f7b37f1b3..339d73815f 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -115,7 +115,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. } 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 ee8de57495..762df4056f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -117,10 +117,10 @@ public interface ExoPlayer { */ void onPlayerStateChanged(boolean playWhenReady, int playbackState); - // TODO[playlists]: Should source-initiated resets also cause this to be called? + // TODO: Should be windowIndex and position in the window. /** - * Called when the player's position changes due to a discontinuity (seeking or playback - * transitioning to the next period). + * Called when the player's position changes due to a discontinuity (i.e. due to seeking, + * playback transitioning to the next window, or a source induced discontinuity). * * @param periodIndex The index of the period being played. * @param positionMs The playback position in that period, in milliseconds. @@ -133,7 +133,7 @@ public interface ExoPlayer { * @param timeline The source's timeline. * @param manifest The loaded manifest. */ - void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest); + void onSourceInfoRefreshed(Timeline timeline, Object manifest); /** * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} @@ -249,7 +249,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 #getCurrentPositionInPeriod()}. + * {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. */ void setMediaSource(MediaSource mediaSource, boolean resetPosition); @@ -277,35 +277,6 @@ public interface ExoPlayer { */ boolean isLoading(); - /** - * Seeks to a position specified in milliseconds in the current period. - * - * @param positionMs The seek position. - */ - @Deprecated - 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 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. - */ - @Deprecated - 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. - */ - @Deprecated - void seekInPeriod(int periodIndex, long positionMs); - /** * Seeks to the default position associated with the current window. The position can depend on * the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will @@ -333,7 +304,7 @@ public interface ExoPlayer { void seekTo(long positionMs); /** - * Seeks to a position specified in milliseconds in the specified seek window. + * Seeks to a position specified in milliseconds in the specified window. * * @param windowIndex The index of the window. * @param positionMs The seek position relative to the start of the window. @@ -376,9 +347,9 @@ public interface ExoPlayer { void blockingSendMessages(ExoPlayerMessage... messages); /** - * Returns the current {@link MediaTimeline}, or {@code null} if there is no timeline. + * Returns the current {@link Timeline}, or {@code null} if there is no timeline. */ - MediaTimeline getCurrentTimeline(); + Timeline getCurrentTimeline(); /** * Returns the current manifest. The type depends on the {@link MediaSource} passed to @@ -386,46 +357,9 @@ public interface ExoPlayer { */ Object getCurrentManifest(); - // Period based. - /** - * Returns the index of the current period. - */ - @Deprecated - int getCurrentPeriodIndex(); - - /** - * Returns the duration of the current period in milliseconds, or {@link C#TIME_UNSET} if the - * duration is not known. - */ - @Deprecated - long getCurrentPeriodDuration(); - - /** - * Returns the playback position in the current period, in milliseconds. - */ - @Deprecated - long getCurrentPositionInPeriod(); - - /** - * Returns an estimate of the position in the current period up to which data is buffered, or - * {@link C#TIME_UNSET} if no estimate is available. - */ - @Deprecated - 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. - */ - @Deprecated - int getBufferedPercentageInPeriod(); - - // MediaWindow based. - - /** - * Returns the index of the seek window associated with the current period, or - * {@link C#INDEX_UNSET} if the timeline is not set. + * Returns the index of the window associated with the current period, or {@link C#INDEX_UNSET} if + * the timeline is not set. */ int getCurrentWindowIndex(); @@ -436,8 +370,8 @@ public interface ExoPlayer { long getDuration(); /** - * Returns the playback position in the current seek window, in milliseconds, or - * {@link C#TIME_UNSET} if the timeline is not set. + * Returns the playback position in the current window, in milliseconds, or {@link C#TIME_UNSET} + * if the timeline is not set. */ long getCurrentPosition(); 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 b551a7b044..b62e196691 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -37,12 +37,14 @@ import java.util.concurrent.CopyOnWriteArraySet; private final Handler eventHandler; private final ExoPlayerImplInternal internalPlayer; private final CopyOnWriteArraySet listeners; + private final Timeline.Window window; + private final Timeline.Period period; private boolean playWhenReady; private int playbackState; private int pendingSeekAcks; private boolean isLoading; - private MediaTimeline timeline; + private Timeline timeline; private Object manifest; // Playback information when there is no pending seek/set source operation. @@ -68,6 +70,8 @@ import java.util.concurrent.CopyOnWriteArraySet; this.playWhenReady = false; this.playbackState = STATE_IDLE; this.listeners = new CopyOnWriteArraySet<>(); + window = new Timeline.Window(); + period = new Timeline.Period(); eventHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -126,30 +130,6 @@ import java.util.concurrent.CopyOnWriteArraySet; return isLoading; } - @Override - public void seekInCurrentPeriod(long positionMs) { - seekInPeriod(getCurrentPeriodIndex(), positionMs); - } - - @Override - public void seekToDefaultPositionForPeriod(int periodIndex) { - seekInPeriod(periodIndex, C.TIME_UNSET); - } - - @Override - public void seekInPeriod(int periodIndex, long positionMs) { - boolean seekToDefaultPosition = positionMs == C.TIME_UNSET; - maskingPeriodIndex = periodIndex; - maskingPositionMs = seekToDefaultPosition ? 0 : positionMs; - pendingSeekAcks++; - internalPlayer.seekTo(periodIndex, seekToDefaultPosition ? C.TIME_UNSET : positionMs * 1000); - if (!seekToDefaultPosition) { - for (EventListener listener : listeners) { - listener.onPositionDiscontinuity(periodIndex, positionMs); - } - } - } - @Override public void seekToDefaultPosition() { seekToDefaultPosition(getCurrentWindowIndex()); @@ -158,38 +138,38 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public void seekToDefaultPosition(int windowIndex) { if (timeline == null) { - throw new IllegalArgumentException("Windows are not yet known"); + // TODO: Handle seeks before the timeline is set. + return; } Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); - seekToDefaultPositionForPeriod(timeline.getWindowFirstPeriodIndex(windowIndex)); + int periodIndex = timeline.getWindow(windowIndex, window).firstPeriodIndex; + seekToDefaultPositionForPeriod(periodIndex); } @Override public void seekTo(long positionMs) { - MediaTimeline timeline = getCurrentTimeline(); if (timeline == null) { - throw new IllegalArgumentException("Windows are not yet known"); + // TODO: Handle seeks before the timeline is set. + return; } - int windowIndex = timeline.getPeriodWindowIndex(getCurrentPeriodIndex()); - seekTo(windowIndex, positionMs); + seekTo(getCurrentWindowIndex(), positionMs); } @Override public void seekTo(int windowIndex, long positionMs) { if (timeline == null) { - throw new IllegalArgumentException("Windows are not yet known"); + // TODO: Handle seeks before the timeline is set. + return; } Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount()); - int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); - int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); - int periodIndex = firstPeriodIndex; - long periodPositionMs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000 - + positionMs; - long periodDurationMs = timeline.getPeriodDurationMs(periodIndex); + timeline.getWindow(windowIndex, window); + int periodIndex = window.firstPeriodIndex; + long periodPositionMs = window.getPositionInFirstPeriodMs() + positionMs; + long periodDurationMs = timeline.getPeriod(periodIndex, period).getDurationMs(); while (periodDurationMs != C.TIME_UNSET && periodPositionMs >= periodDurationMs - && periodIndex < lastPeriodIndex) { + && periodIndex < window.lastPeriodIndex) { periodPositionMs -= periodDurationMs; - periodDurationMs = timeline.getPeriodDurationMs(++periodIndex); + periodDurationMs = timeline.getPeriod(++periodIndex, period).getDurationMs(); } seekInPeriod(periodIndex, periodPositionMs); } @@ -215,55 +195,12 @@ import java.util.concurrent.CopyOnWriteArraySet; internalPlayer.blockingSendMessages(messages); } - @Override - @Deprecated - public int getCurrentPeriodIndex() { - return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex; - } - - @Override - @Deprecated - public long getCurrentPeriodDuration() { - if (timeline == null) { - return C.TIME_UNSET; - } - return timeline.getPeriodDurationMs(getCurrentPeriodIndex()); - } - - @Override - @Deprecated - public long getCurrentPositionInPeriod() { - return pendingSeekAcks > 0 ? maskingPositionMs : C.usToMs(playbackInfo.positionUs); - } - - @Override - @Deprecated - public long getBufferedPositionInPeriod() { - if (pendingSeekAcks == 0) { - return C.usToMs(playbackInfo.bufferedPositionUs); - } else { - return maskingPositionMs; - } - } - - @Override - @Deprecated - public int getBufferedPercentageInPeriod() { - if (timeline == null) { - return 0; - } - long bufferedPosition = getBufferedPositionInPeriod(); - long duration = getCurrentPeriodDuration(); - return (bufferedPosition == C.TIME_UNSET || duration == C.TIME_UNSET) ? 0 - : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); - } - @Override public int getCurrentWindowIndex() { if (timeline == null) { return C.INDEX_UNSET; } - return timeline.getPeriodWindowIndex(getCurrentPeriodIndex()); + return timeline.getPeriod(getCurrentPeriodIndex(), period).windowIndex; } @Override @@ -271,7 +208,7 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline == null) { return C.TIME_UNSET; } - return C.usToMs(timeline.getWindow(getCurrentWindowIndex()).durationUs); + return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); } @Override @@ -279,14 +216,8 @@ import java.util.concurrent.CopyOnWriteArraySet; if (timeline == null) { return C.TIME_UNSET; } - int periodIndex = getCurrentPeriodIndex(); - int windowIndex = timeline.getPeriodWindowIndex(periodIndex); - long positionMs = getCurrentPositionInPeriod(); - for (int i = timeline.getWindowFirstPeriodIndex(windowIndex); i < periodIndex; i++) { - positionMs += timeline.getPeriodDurationMs(i); - } - positionMs -= timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000; - return positionMs; + timeline.getPeriod(getCurrentPeriodIndex(), period); + return period.getPositionInWindowMs() + getCurrentPositionInPeriod(); } @Override @@ -296,13 +227,12 @@ import java.util.concurrent.CopyOnWriteArraySet; return C.TIME_UNSET; } int periodIndex = getCurrentPeriodIndex(); - int windowIndex = timeline.getPeriodWindowIndex(periodIndex); - MediaWindow window = timeline.getWindow(windowIndex); - int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); - int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); - if (firstPeriodIndex == periodIndex && lastPeriodIndex == periodIndex - && timeline.getWindowOffsetInFirstPeriodUs(windowIndex) == 0 - && window.durationUs == timeline.getPeriodDurationUs(periodIndex)) { + timeline.getPeriod(periodIndex, period); + int windowIndex = period.windowIndex; + timeline.getWindow(windowIndex, window); + if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex + && window.getPositionInFirstPeriodUs() == 0 + && window.getDurationUs() == period.getDurationUs()) { return getBufferedPositionInPeriod(); } return getCurrentPosition(); @@ -320,7 +250,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } @Override - public MediaTimeline getCurrentTimeline() { + public Timeline getCurrentTimeline() { return timeline; } @@ -329,6 +259,44 @@ import java.util.concurrent.CopyOnWriteArraySet; return manifest; } + // TODO: Remove + private void seekToDefaultPositionForPeriod(int periodIndex) { + seekInPeriod(periodIndex, C.TIME_UNSET); + } + + // TODO: Remove + private void seekInPeriod(int periodIndex, long positionMs) { + boolean seekToDefaultPosition = positionMs == C.TIME_UNSET; + maskingPeriodIndex = periodIndex; + maskingPositionMs = seekToDefaultPosition ? 0 : positionMs; + pendingSeekAcks++; + internalPlayer.seekTo(periodIndex, seekToDefaultPosition ? C.TIME_UNSET : positionMs * 1000); + if (!seekToDefaultPosition) { + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(periodIndex, positionMs); + } + } + } + + // TODO: Remove + private int getCurrentPeriodIndex() { + return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex; + } + + // TODO: Remove + private long getCurrentPositionInPeriod() { + return pendingSeekAcks > 0 ? maskingPositionMs : C.usToMs(playbackInfo.positionUs); + } + + // TODO: Remove + private long getBufferedPositionInPeriod() { + if (pendingSeekAcks == 0) { + return C.usToMs(playbackInfo.bufferedPositionUs); + } else { + return maskingPositionMs; + } + } + // 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) { @@ -361,16 +329,16 @@ import java.util.concurrent.CopyOnWriteArraySet; case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { if (pendingSeekAcks == 0) { playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + long positionMs = C.usToMs(playbackInfo.startPositionUs); for (EventListener listener : listeners) { - listener.onPositionDiscontinuity(playbackInfo.periodIndex, - C.usToMs(playbackInfo.startPositionUs)); + listener.onPositionDiscontinuity(playbackInfo.periodIndex, positionMs); } } break; } case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { @SuppressWarnings("unchecked") - Pair timelineAndManifest = (Pair) msg.obj; + Pair timelineAndManifest = (Pair) msg.obj; timeline = timelineAndManifest.first; manifest = timelineAndManifest.second; for (EventListener listener : listeners) { diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index d69304c31c..373f17c3c8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -106,6 +106,8 @@ import java.io.IOException; private final Handler handler; private final HandlerThread internalPlaybackThread; private final Handler eventHandler; + private final Timeline.Window window; + private final Timeline.Period period; private PlaybackInfo playbackInfo; private Renderer rendererMediaClockSource; @@ -126,11 +128,11 @@ import java.io.IOException; private boolean isTimelineReady; private boolean isTimelineEnded; private int bufferAheadPeriodCount; - private Period playingPeriod; - private Period readingPeriod; - private Period loadingPeriod; + private MediaPeriodHolder playingPeriodHolder; + private MediaPeriodHolder readingPeriodHolder; + private MediaPeriodHolder loadingPeriodHolder; - private MediaTimeline timeline; + private Timeline timeline; public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, boolean playWhenReady, Handler eventHandler, @@ -150,6 +152,8 @@ import java.io.IOException; } standaloneMediaClock = new StandaloneMediaClock(); enabledRenderers = new Renderer[0]; + window = new Timeline.Window(); + period = new Timeline.Period(); trackSelector.init(this); // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can @@ -220,7 +224,7 @@ import java.io.IOException; // MediaSource.Listener implementation. @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget(); } @@ -278,7 +282,7 @@ import java.io.IOException; return true; } case MSG_REFRESH_SOURCE_INFO: { - handleSourceInfoRefreshed((Pair) msg.obj); + handleSourceInfoRefreshed((Pair) msg.obj); return true; } case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: { @@ -375,13 +379,12 @@ import java.io.IOException; } private void updatePlaybackPositions() throws ExoPlaybackException { - if (playingPeriod == null) { + if (playingPeriodHolder == null) { return; } - MediaPeriod mediaPeriod = playingPeriod.mediaPeriod; // Update the playback position. - long periodPositionUs = mediaPeriod.readDiscontinuity(); + long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity(); if (periodPositionUs != C.TIME_UNSET) { resetRendererPosition(periodPositionUs); } else { @@ -391,23 +394,24 @@ import java.io.IOException; } else { rendererPositionUs = standaloneMediaClock.getPositionUs(); } - periodPositionUs = rendererPositionUs - playingPeriod.rendererPositionOffsetUs; + periodPositionUs = rendererPositionUs - playingPeriodHolder.rendererPositionOffsetUs; } playbackInfo.positionUs = periodPositionUs; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; // Update the buffered position. long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE - : mediaPeriod.getBufferedPositionUs(); + : playingPeriodHolder.mediaPeriod.getBufferedPositionUs(); playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE - ? timeline.getPeriodDurationUs(playingPeriod.index) : bufferedPositionUs; + ? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs() + : bufferedPositionUs; } private void doSomeWork() throws ExoPlaybackException, IOException { long operationStartTimeMs = SystemClock.elapsedRealtime(); updatePeriods(); - if (playingPeriod == null) { + if (playingPeriodHolder == null) { // We're still waiting for the first period to be prepared. maybeThrowPeriodPrepareError(); scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); @@ -438,10 +442,11 @@ import java.io.IOException; maybeThrowPeriodPrepareError(); } - long playingPeriodDuration = timeline.getPeriodDurationUs(playingPeriod.index); + long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) + .getDurationUs(); if (allRenderersEnded - && (playingPeriodDuration == C.TIME_UNSET - || playingPeriodDuration <= playbackInfo.positionUs) + && (playingPeriodDurationUs == C.TIME_UNSET + || playingPeriodDurationUs <= playbackInfo.positionUs) && isTimelineEnded) { setState(ExoPlayer.STATE_ENDED); stopRenderers(); @@ -526,27 +531,27 @@ import java.io.IOException; setState(ExoPlayer.STATE_BUFFERING); if (periodPositionUs == C.TIME_UNSET - || (readingPeriod != playingPeriod && (periodIndex == playingPeriod.index - || (readingPeriod != null && periodIndex == readingPeriod.index)))) { + || (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index + || (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) { // Clear the timeline because either the seek position is not known, or a renderer is reading // ahead to the next period and the seek is to either the playing or reading period. periodIndex = C.INDEX_UNSET; } // Clear the timeline, but keep the requested period if it is already prepared. - Period period = playingPeriod; - Period newPlayingPeriod = null; - while (period != null) { - if (period.index == periodIndex && period.prepared) { - newPlayingPeriod = period; + MediaPeriodHolder periodHolder = playingPeriodHolder; + MediaPeriodHolder newPlayingPeriodHolder = null; + while (periodHolder != null) { + if (periodHolder.index == periodIndex && periodHolder.prepared) { + newPlayingPeriodHolder = periodHolder; } else { - period.release(); + periodHolder.release(); } - period = period.nextPeriod; + periodHolder = periodHolder.next; } // Disable all the renderers if the period is changing. - if (newPlayingPeriod != playingPeriod) { + if (newPlayingPeriodHolder != playingPeriodHolder) { for (Renderer renderer : enabledRenderers) { renderer.disable(); } @@ -557,21 +562,21 @@ import java.io.IOException; // Update loaded periods. bufferAheadPeriodCount = 0; - if (newPlayingPeriod != null) { - newPlayingPeriod.nextPeriod = null; - setPlayingPeriod(newPlayingPeriod); + if (newPlayingPeriodHolder != null) { + newPlayingPeriodHolder.next = null; + setPlayingPeriodHolder(newPlayingPeriodHolder); updateTimelineState(); - readingPeriod = playingPeriod; - loadingPeriod = playingPeriod; - if (playingPeriod.hasEnabledTracks) { - periodPositionUs = playingPeriod.mediaPeriod.seekToUs(periodPositionUs); + readingPeriodHolder = playingPeriodHolder; + loadingPeriodHolder = playingPeriodHolder; + if (playingPeriodHolder.hasEnabledTracks) { + periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs); } resetRendererPosition(periodPositionUs); maybeContinueLoading(); } else { - playingPeriod = null; - readingPeriod = null; - loadingPeriod = null; + playingPeriodHolder = null; + readingPeriodHolder = null; + loadingPeriodHolder = null; if (periodPositionUs != C.TIME_UNSET) { resetRendererPosition(periodPositionUs); } @@ -582,7 +587,8 @@ import java.io.IOException; } private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { - long periodOffsetUs = playingPeriod == null ? 0 : playingPeriod.rendererPositionOffsetUs; + long periodOffsetUs = playingPeriodHolder == null ? 0 + : playingPeriodHolder.rendererPositionOffsetUs; rendererPositionUs = periodOffsetUs + periodPositionUs; standaloneMediaClock.setPositionUs(rendererPositionUs); for (Renderer renderer : enabledRenderers) { @@ -624,12 +630,13 @@ import java.io.IOException; mediaSource.releaseSource(); mediaSource = null; } - releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod); + releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder); isTimelineReady = false; isTimelineEnded = false; - playingPeriod = null; - readingPeriod = null; - loadingPeriod = null; + playingPeriodHolder = null; + readingPeriodHolder = null; + loadingPeriodHolder = null; timeline = null; bufferAheadPeriodCount = 0; loadControl.onTracksDisabled(); @@ -660,43 +667,43 @@ import java.io.IOException; } private void reselectTracksInternal() throws ExoPlaybackException { - if (playingPeriod == null) { + if (playingPeriodHolder == null) { // We don't have tracks yet, so we don't care. return; } // Reselect tracks on each period in turn, until the selection changes. - Period period = playingPeriod; + MediaPeriodHolder periodHolder = playingPeriodHolder; boolean selectionsChangedForReadPeriod = true; while (true) { - if (period == null || !period.prepared) { + if (periodHolder == null || !periodHolder.prepared) { // The reselection did not change any prepared periods. return; } - if (period.selectTracks()) { + if (periodHolder.selectTracks()) { // Selected tracks have changed for this period. break; } - if (period == readingPeriod) { + if (periodHolder == readingPeriodHolder) { // The track reselection didn't affect any period that has been read. selectionsChangedForReadPeriod = false; } - period = period.nextPeriod; + periodHolder = periodHolder.next; } if (selectionsChangedForReadPeriod) { // Release everything after the playing period because a renderer may have read data from a // track whose selection has now changed. - releasePeriodsFrom(playingPeriod.nextPeriod); - playingPeriod.nextPeriod = null; - readingPeriod = playingPeriod; - loadingPeriod = playingPeriod; + releasePeriodHoldersFrom(playingPeriodHolder.next); + playingPeriodHolder.next = null; + readingPeriodHolder = playingPeriodHolder; + loadingPeriodHolder = playingPeriodHolder; bufferAheadPeriodCount = 0; // Update streams for the new selection, recreating all streams if reading ahead. - boolean recreateStreams = readingPeriod != playingPeriod; + boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; boolean[] streamResetFlags = new boolean[renderers.length]; - long periodPositionUs = playingPeriod.updatePeriodTrackSelection(playbackInfo.positionUs, - loadControl, recreateStreams, streamResetFlags); + long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( + playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags); if (periodPositionUs != playbackInfo.positionUs) { playbackInfo.positionUs = periodPositionUs; resetRendererPosition(periodPositionUs); @@ -707,7 +714,7 @@ import java.io.IOException; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; - SampleStream sampleStream = playingPeriod.sampleStreams[i]; + SampleStream sampleStream = playingPeriodHolder.sampleStreams[i]; if (sampleStream != null) { enabledRendererCount++; } @@ -732,21 +739,21 @@ import java.io.IOException; } } } - trackSelector.onSelectionActivated(playingPeriod.trackSelectionData); + trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData); enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } else { // Release and re-prepare/buffer periods after the one whose selection changed. - loadingPeriod = period; - period = loadingPeriod.nextPeriod; - while (period != null) { - period.release(); - period = period.nextPeriod; + loadingPeriodHolder = periodHolder; + periodHolder = loadingPeriodHolder.next; + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; bufferAheadPeriodCount--; } - loadingPeriod.nextPeriod = null; + loadingPeriodHolder.next = null; long loadingPeriodPositionUs = Math.max(0, - rendererPositionUs - loadingPeriod.rendererPositionOffsetUs); - loadingPeriod.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false); + rendererPositionUs - loadingPeriodHolder.rendererPositionOffsetUs); + loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false); } maybeContinueLoading(); updatePlaybackPositions(); @@ -754,67 +761,72 @@ import java.io.IOException; } private boolean haveSufficientBuffer(boolean rebuffering) { - if (loadingPeriod == null) { + if (loadingPeriodHolder == null) { return false; } - long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs; + long loadingPeriodPositionUs = rendererPositionUs + - loadingPeriodHolder.rendererPositionOffsetUs; long loadingPeriodBufferedPositionUs = - !loadingPeriod.prepared ? 0 : loadingPeriod.mediaPeriod.getBufferedPositionUs(); + !loadingPeriodHolder.prepared ? 0 : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs(); if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) { - if (loadingPeriod.isLast) { + if (loadingPeriodHolder.isLast) { return true; } - loadingPeriodBufferedPositionUs = timeline.getPeriodDurationUs(loadingPeriod.index); + loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period) + .getDurationUs(); } return loadControl.shouldStartPlayback( loadingPeriodBufferedPositionUs - loadingPeriodPositionUs, rebuffering); } private void maybeThrowPeriodPrepareError() throws IOException { - if (loadingPeriod != null && !loadingPeriod.prepared - && (readingPeriod == null || readingPeriod.nextPeriod == loadingPeriod)) { + if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared + && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) { for (Renderer renderer : enabledRenderers) { if (!renderer.hasReadStreamToEnd()) { return; } } - loadingPeriod.mediaPeriod.maybeThrowPrepareError(); + loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError(); } } - private void handleSourceInfoRefreshed(Pair timelineAndManifest) + private void handleSourceInfoRefreshed(Pair timelineAndManifest) throws ExoPlaybackException, IOException { eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget(); - MediaTimeline oldTimeline = this.timeline; + Timeline oldTimeline = this.timeline; this.timeline = timelineAndManifest.first; // Update the loaded periods to take into account the new timeline. - if (playingPeriod != null) { - int index = timeline.getIndexOfPeriod(playingPeriod.id); + if (playingPeriodHolder != null) { + int index = timeline.getIndexOfPeriod(playingPeriodHolder.uid); if (index == C.INDEX_UNSET) { - attemptRestart(timeline, oldTimeline, playingPeriod.index); + attemptRestart(timeline, oldTimeline, playingPeriodHolder.index); return; } // The playing period is also in the new timeline. Update the index for each loaded period // until a period is found that does not match the old timeline. - playingPeriod.setIndex(timeline, index); + timeline.getPeriod(index, period, true); + playingPeriodHolder.setIndex(timeline, timeline.getWindow(period.windowIndex, window), + index); - Period previousPeriod = playingPeriod; + MediaPeriodHolder previousPeriod = playingPeriodHolder; boolean seenReadingPeriod = false; bufferAheadPeriodCount = 0; - while (previousPeriod.nextPeriod != null) { - Period period = previousPeriod.nextPeriod; + while (previousPeriod.next != null) { + MediaPeriodHolder periodHolder = previousPeriod.next; index++; - if (!period.id.equals(timeline.getPeriodId(index))) { + timeline.getPeriod(index, period, true); + if (!periodHolder.uid.equals(period.uid)) { if (!seenReadingPeriod) { // Renderers may have read a period that has been removed, so release all loaded periods // and seek to the current position of the playing period index. - index = playingPeriod.index; - releasePeriodsFrom(playingPeriod); - playingPeriod = null; - readingPeriod = null; - loadingPeriod = null; + index = playingPeriodHolder.index; + releasePeriodHoldersFrom(playingPeriodHolder); + playingPeriodHolder = null; + readingPeriodHolder = null; + loadingPeriodHolder = null; long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs); if (newPositionUs != playbackInfo.positionUs) { playbackInfo = new PlaybackInfo(index, newPositionUs); @@ -824,36 +836,39 @@ import java.io.IOException; } // Update the loading period to be the latest period that is still valid. - loadingPeriod = previousPeriod; - loadingPeriod.nextPeriod = null; + loadingPeriodHolder = previousPeriod; + loadingPeriodHolder.next = null; // Release the rest of the timeline. - releasePeriodsFrom(period); + releasePeriodHoldersFrom(periodHolder); break; } bufferAheadPeriodCount++; - period.setIndex(timeline, index); - if (period == readingPeriod) { + int windowIndex = timeline.getPeriod(index, period).windowIndex; + periodHolder.setIndex(timeline, timeline.getWindow(windowIndex, window), index); + if (periodHolder == readingPeriodHolder) { seenReadingPeriod = true; } - previousPeriod = period; + previousPeriod = periodHolder; } - } else if (loadingPeriod != null) { - Object id = loadingPeriod.id; - int index = timeline.getIndexOfPeriod(id); + } else if (loadingPeriodHolder != null) { + Object uid = loadingPeriodHolder.uid; + int index = timeline.getIndexOfPeriod(uid); if (index == C.INDEX_UNSET) { - attemptRestart(timeline, oldTimeline, loadingPeriod.index); + attemptRestart(timeline, oldTimeline, loadingPeriodHolder.index); return; } else { - loadingPeriod.setIndex(timeline, index); + int windowIndex = timeline.getPeriod(index, this.period).windowIndex; + loadingPeriodHolder.setIndex(timeline, timeline.getWindow(windowIndex, window), + index); } } // TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed. if (oldTimeline != null) { - int newPlayingIndex = playingPeriod != null ? playingPeriod.index - : loadingPeriod != null ? loadingPeriod.index : C.INDEX_UNSET; + int newPlayingIndex = playingPeriodHolder != null ? playingPeriodHolder.index + : loadingPeriodHolder != null ? loadingPeriodHolder.index : C.INDEX_UNSET; if (newPlayingIndex != C.INDEX_UNSET && newPlayingIndex != playbackInfo.periodIndex) { playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs); @@ -863,12 +878,13 @@ import java.io.IOException; } } - private void attemptRestart(MediaTimeline newTimeline, MediaTimeline oldTimeline, + private void attemptRestart(Timeline newTimeline, Timeline oldTimeline, int oldPeriodIndex) throws ExoPlaybackException { int newPeriodIndex = C.INDEX_UNSET; while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { - newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getPeriodId(++oldPeriodIndex)); + newPeriodIndex = + newTimeline.getIndexOfPeriod(oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid); } if (newPeriodIndex == C.INDEX_UNSET) { // We failed to find a replacement period. Stop the player. @@ -877,11 +893,11 @@ import java.io.IOException; } // Release all loaded periods. - releasePeriodsFrom(playingPeriod); + releasePeriodHoldersFrom(playingPeriodHolder); bufferAheadPeriodCount = 0; - playingPeriod = null; - readingPeriod = null; - loadingPeriod = null; + playingPeriodHolder = null; + readingPeriodHolder = null; + loadingPeriodHolder = null; // Find the default initial position in the window and seek to it. Pair defaultPosition = getDefaultPosition(newPeriodIndex); @@ -893,14 +909,16 @@ import java.io.IOException; } private Pair getDefaultPosition(int periodIndex) { - int windowIndex = timeline.getPeriodWindowIndex(periodIndex); - periodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); - int maxPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); - long periodPositionUs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) - + timeline.getWindow(windowIndex).defaultStartPositionUs; - while (periodIndex < maxPeriodIndex - && periodPositionUs > timeline.getPeriodDurationUs(periodIndex)) { - periodPositionUs -= timeline.getPeriodDurationUs(periodIndex++); + timeline.getPeriod(periodIndex, period); + timeline.getWindow(period.windowIndex, window); + periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + + window.getDefaultStartPositionUs(); + timeline.getPeriod(periodIndex, period); + while (periodIndex < window.lastPeriodIndex + && periodPositionUs > period.getDurationMs()) { + periodPositionUs -= period.getDurationUs(); + timeline.getPeriod(periodIndex++, period); } return Pair.create(periodIndex, periodPositionUs); } @@ -912,18 +930,21 @@ import java.io.IOException; return; } - if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast + if (loadingPeriodHolder == null + || (loadingPeriodHolder.isFullyBuffered() && !loadingPeriodHolder.isLast && bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) { // We don't have a loading period or it's fully loaded, so try and create the next one. - int newLoadingPeriodIndex = loadingPeriod == null ? playbackInfo.periodIndex - : loadingPeriod.index + 1; + int newLoadingPeriodIndex = loadingPeriodHolder == null ? playbackInfo.periodIndex + : loadingPeriodHolder.index + 1; if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { // The period is not available yet. mediaSource.maybeThrowSourceInfoRefreshError(); } else { - long periodStartPositionUs = loadingPeriod == null ? playbackInfo.positionUs - : (newLoadingPeriodIndex == timeline.getWindowFirstPeriodIndex(newLoadingPeriodIndex) - ? C.TIME_UNSET : 0); + int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex; + boolean isFirstPeriodInWindow = newLoadingPeriodIndex + == timeline.getWindow(windowIndex, window).firstPeriodIndex; + long periodStartPositionUs = loadingPeriodHolder == null ? playbackInfo.positionUs + : (isFirstPeriodInWindow ? C.TIME_UNSET : 0); if (periodStartPositionUs == C.TIME_UNSET) { // This is the first period of a new window or we don't have a start position, so seek to // the default position for the window. @@ -931,47 +952,50 @@ import java.io.IOException; newLoadingPeriodIndex = defaultPosition.first; periodStartPositionUs = defaultPosition.second; } - MediaPeriod mediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, + Object newPeriodUid = timeline.getPeriod(newLoadingPeriodIndex, period, true).uid; + MediaPeriod newMediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, loadControl.getAllocator(), periodStartPositionUs); - Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaSource, - mediaPeriod, timeline.getPeriodId(newLoadingPeriodIndex), periodStartPositionUs); - newPeriod.setIndex(timeline, newLoadingPeriodIndex); - if (loadingPeriod != null) { - loadingPeriod.setNextPeriod(newPeriod); - newPeriod.rendererPositionOffsetUs = loadingPeriod.rendererPositionOffsetUs - + timeline.getPeriodDurationUs(loadingPeriod.index); + MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, + trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs); + timeline.getWindow(windowIndex, window); + newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex); + if (loadingPeriodHolder != null) { + loadingPeriodHolder.setNext(newPeriodHolder); + newPeriodHolder.rendererPositionOffsetUs = loadingPeriodHolder.rendererPositionOffsetUs + + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs(); } bufferAheadPeriodCount++; - loadingPeriod = newPeriod; + loadingPeriodHolder = newPeriodHolder; setIsLoading(true); } } - if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) { + if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { setIsLoading(false); - } else if (loadingPeriod != null && loadingPeriod.needsContinueLoading) { + } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) { maybeContinueLoading(); } - if (playingPeriod == null) { + if (playingPeriodHolder == null) { // We're waiting for the first period to be prepared. return; } // Update the playing and reading periods. - while (playingPeriod != readingPeriod && playingPeriod.nextPeriod != null - && rendererPositionUs >= playingPeriod.nextPeriod.rendererPositionOffsetUs) { + while (playingPeriodHolder != readingPeriodHolder && playingPeriodHolder.next != null + && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) { // All enabled renderers' streams have been read to the end, and the playback position reached // the end of the playing period, so advance playback to the next period. - playingPeriod.release(); - setPlayingPeriod(playingPeriod.nextPeriod); + playingPeriodHolder.release(); + setPlayingPeriodHolder(playingPeriodHolder.next); bufferAheadPeriodCount--; - playbackInfo = new PlaybackInfo(playingPeriod.index, playingPeriod.startPositionUs); + playbackInfo = new PlaybackInfo(playingPeriodHolder.index, + playingPeriodHolder.startPositionUs); updatePlaybackPositions(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); } updateTimelineState(); - if (readingPeriod == null) { + if (readingPeriodHolder == null) { // The renderers have their final SampleStreams. return; } @@ -980,10 +1004,10 @@ import java.io.IOException; return; } } - if (readingPeriod.nextPeriod != null && readingPeriod.nextPeriod.prepared) { - TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections; - readingPeriod = readingPeriod.nextPeriod; - TrackSelectionArray newTrackSelections = readingPeriod.trackSelections; + if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { + TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections; + readingPeriodHolder = readingPeriodHolder.next; + TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; TrackSelection oldSelection = oldTrackSelections.get(i); @@ -996,8 +1020,8 @@ import java.io.IOException; for (int j = 0; j < formats.length; j++) { formats[j] = newSelection.getFormat(j); } - renderer.replaceStream(formats, readingPeriod.sampleStreams[i], - readingPeriod.rendererPositionOffsetUs); + renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i], + readingPeriodHolder.rendererPositionOffsetUs); } else { // The renderer will be disabled when transitioning to playing the next period. Mark the // SampleStream as final to play out any remaining data. @@ -1005,8 +1029,8 @@ import java.io.IOException; } } } - } else if (readingPeriod.isLast) { - readingPeriod = null; + } else if (readingPeriodHolder.isLast) { + readingPeriodHolder = null; for (Renderer renderer : enabledRenderers) { renderer.setCurrentStreamIsFinal(); } @@ -1014,18 +1038,19 @@ import java.io.IOException; } private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { - if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { // Stale event. return; } - loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl); - if (playingPeriod == null) { + loadingPeriodHolder.handlePrepared(loadingPeriodHolder.startPositionUs, loadControl); + if (playingPeriodHolder == null) { // This is the first prepared period, so start playing it. - readingPeriod = loadingPeriod; - setPlayingPeriod(readingPeriod); + readingPeriodHolder = loadingPeriodHolder; + setPlayingPeriodHolder(readingPeriodHolder); if (playbackInfo.startPositionUs == C.TIME_UNSET) { // Update the playback info when seeking to a default position. - playbackInfo = new PlaybackInfo(playingPeriod.index, playingPeriod.startPositionUs); + playbackInfo = new PlaybackInfo(playingPeriodHolder.index, + playingPeriodHolder.startPositionUs); resetRendererPosition(playbackInfo.startPositionUs); updatePlaybackPositions(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); @@ -1036,45 +1061,45 @@ import java.io.IOException; } private void handleContinueLoadingRequested(MediaPeriod period) { - if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { return; } maybeContinueLoading(); } private void maybeContinueLoading() { - long nextLoadPositionUs = loadingPeriod.mediaPeriod.getNextLoadPositionUs(); + long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs(); if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { - long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs - + loadingPeriod.startPositionUs; + long loadingPeriodPositionUs = rendererPositionUs + - loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs; long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); setIsLoading(continueLoading); if (continueLoading) { - loadingPeriod.needsContinueLoading = false; - loadingPeriod.mediaPeriod.continueLoading(loadingPeriodPositionUs); + loadingPeriodHolder.needsContinueLoading = false; + loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs); } else { - loadingPeriod.needsContinueLoading = true; + loadingPeriodHolder.needsContinueLoading = true; } } else { setIsLoading(false); } } - private void releasePeriodsFrom(Period period) { - while (period != null) { - period.release(); - period = period.nextPeriod; + private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; } } - private void setPlayingPeriod(Period period) throws ExoPlaybackException { + private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { int enabledRendererCount = 0; boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; - TrackSelection newSelection = period.trackSelections.get(i); + TrackSelection newSelection = periodHolder.trackSelections.get(i); if (newSelection != null) { // The renderer should be enabled when playing the new period. enabledRendererCount++; @@ -1091,17 +1116,18 @@ import java.io.IOException; } } - trackSelector.onSelectionActivated(period.trackSelectionData); - playingPeriod = period; + trackSelector.onSelectionActivated(periodHolder.trackSelectionData); + playingPeriodHolder = periodHolder; enableRenderers(rendererWasEnabledFlags, enabledRendererCount); } private void updateTimelineState() { - long playingPeriodDurationUs = timeline.getPeriodDurationUs(playingPeriod.index); + long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) + .getDurationUs(); isTimelineReady = playingPeriodDurationUs == C.TIME_UNSET || playbackInfo.positionUs < playingPeriodDurationUs - || (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared); - isTimelineEnded = playingPeriod.isLast; + || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared); + isTimelineEnded = playingPeriodHolder.isLast; } private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount) @@ -1110,7 +1136,7 @@ import java.io.IOException; enabledRendererCount = 0; for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; - TrackSelection newSelection = playingPeriod.trackSelections.get(i); + TrackSelection newSelection = playingPeriodHolder.trackSelections.get(i); if (newSelection != null) { enabledRenderers[enabledRendererCount++] = renderer; if (renderer.getState() == Renderer.STATE_DISABLED) { @@ -1124,8 +1150,8 @@ import java.io.IOException; formats[j] = newSelection.getFormat(j); } // Enable the renderer. - renderer.enable(formats, playingPeriod.sampleStreams[i], rendererPositionUs, joining, - playingPeriod.rendererPositionOffsetUs); + renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs, + joining, playingPeriodHolder.rendererPositionOffsetUs); MediaClock mediaClock = renderer.getMediaClock(); if (mediaClock != null) { if (rendererMediaClock != null) { @@ -1145,12 +1171,12 @@ import java.io.IOException; } /** - * Represents a {@link MediaPeriod} with information required to play it as part of a timeline. + * Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */ - private static final class Period { + private static final class MediaPeriodHolder { public final MediaPeriod mediaPeriod; - public final Object id; + public final Object uid; public final SampleStream[] sampleStreams; public final boolean[] mayRetainStreamFlags; @@ -1161,7 +1187,7 @@ import java.io.IOException; public boolean prepared; public boolean hasEnabledTracks; public long rendererPositionOffsetUs; - public Period nextPeriod; + public MediaPeriodHolder next; public boolean needsContinueLoading; private final Renderer[] renderers; @@ -1173,27 +1199,27 @@ import java.io.IOException; private TrackSelectionArray trackSelections; private TrackSelectionArray periodTrackSelections; - public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, - TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object id, + public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, + TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid, long positionUs) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.trackSelector = trackSelector; this.mediaSource = mediaSource; this.mediaPeriod = mediaPeriod; - this.id = Assertions.checkNotNull(id); + this.uid = Assertions.checkNotNull(uid); sampleStreams = new SampleStream[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length]; startPositionUs = positionUs; } - public void setNextPeriod(Period nextPeriod) { - this.nextPeriod = nextPeriod; + public void setNext(MediaPeriodHolder next) { + this.next = next; } - public void setIndex(MediaTimeline timeline, int index) { - this.index = index; - isLast = index == timeline.getPeriodCount() - 1 && !timeline.getPeriodWindow(index).isDynamic; + public void setIndex(Timeline timeline, Timeline.Window window, int periodIndex) { + this.index = periodIndex; + isLast = index == timeline.getPeriodCount() - 1 && !window.isDynamic; } public boolean isFullyBuffered() { diff --git a/library/src/main/java/com/google/android/exoplayer2/MediaTimeline.java b/library/src/main/java/com/google/android/exoplayer2/MediaTimeline.java deleted file mode 100644 index da9cf7046e..0000000000 --- a/library/src/main/java/com/google/android/exoplayer2/MediaTimeline.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2016 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 com.google.android.exoplayer2; - -/** - * The player's timeline consisting of one or more periods. Instances are immutable. - */ -public interface MediaTimeline { - - /** - * Returns the number of periods in the timeline. - */ - int getPeriodCount(); - - /** - * Returns the absolute start time of the timeline in milliseconds. - */ - long getAbsoluteStartTime(); - - /** - * Returns the duration of the period at {@code periodIndex} in the timeline, in milliseconds, or - * {@link C#TIME_UNSET} if not known. - * - * @param periodIndex The index of the period. - * @return The duration of the period in milliseconds, or {@link C#TIME_UNSET}. - */ - long getPeriodDurationMs(int periodIndex); - - /** - * Returns the duration of the period at {@code periodIndex} in the timeline, in microseconds, or - * {@link C#TIME_UNSET} if not known. - * - * @param periodIndex The index of the period. - * @return The duration of the period in microseconds, or {@link C#TIME_UNSET}. - */ - long getPeriodDurationUs(int periodIndex); - - /** - * Returns a unique identifier for the period at {@code periodIndex}, or {@code null} if the - * period at {@code periodIndex} is not known. The identifier is stable across timeline changes. - * - * @param periodIndex A period index. - * @return An identifier for the period, or {@code null} if the period is not known. - */ - Object getPeriodId(int periodIndex); - - /** - * Returns the {@link MediaWindow} to which the period with the specified index belongs. - * - * @param periodIndex The period index. - * @return The corresponding window. - */ - MediaWindow getPeriodWindow(int periodIndex); - - /** - * Returns the index of the window to which the period with the specified index belongs. - * - * @param periodIndex The period index. - * @return The index of the corresponding window. - */ - int getPeriodWindowIndex(int periodIndex); - - /** - * Returns the index of the period identified by {@code id}, or {@link C#INDEX_UNSET} if the - * period is not in the timeline. - * - * @param id An identifier for a period. - * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found. - */ - int getIndexOfPeriod(Object id); - - /** - * Returns the number of windows that can be accessed via {@link #getWindow(int)}. - */ - int getWindowCount(); - - /** - * Returns the {@link MediaWindow} at the specified index. - * - * @param windowIndex The window index. - */ - MediaWindow getWindow(int windowIndex); - - /** - * Returns the index of the first period belonging to the window at the specified index. - * - * @param windowIndex The window index. - * @return The index of the first period in the window. - */ - int getWindowFirstPeriodIndex(int windowIndex); - - /** - * Returns the index of the last period belonging to the window at the specified index. - * - * @param windowIndex The window index. - * @return The index of the last period in the window. - */ - int getWindowLastPeriodIndex(int windowIndex); - - /** - * Returns the start position of the specified window in the first period belonging to it, in - * microseconds. - * - * @param windowIndex The window index. - * @return The start position of the window in the first period belonging to it. - */ - long getWindowOffsetInFirstPeriodUs(int windowIndex); - -} diff --git a/library/src/main/java/com/google/android/exoplayer2/MediaWindow.java b/library/src/main/java/com/google/android/exoplayer2/MediaWindow.java deleted file mode 100644 index 950d1da9ec..0000000000 --- a/library/src/main/java/com/google/android/exoplayer2/MediaWindow.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2016 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 com.google.android.exoplayer2; - -/** - * A window of available media. Instances are immutable. - */ -public final class MediaWindow { - - /** - * Creates a new window consisting of a single period with the specified duration. The default - * start position is zero. - * - * @param durationUs The duration of the window, in microseconds. - * @param isSeekable Whether seeking is supported within the window. - * @param isDynamic Whether this seek window may change when the timeline is updated. - */ - public static MediaWindow createWindowFromZero(long durationUs, boolean isSeekable, - boolean isDynamic) { - return new MediaWindow(durationUs, isSeekable, isDynamic, 0); - } - - /** - * The default position relative to the start of the window at which to start playback, in - * microseconds. - */ - public final long defaultStartPositionUs; - /** - * The duration of the window in microseconds, or {@link C#TIME_UNSET} if unknown. - */ - public final long durationUs; - /** - * Whether it's possible to seek within the window. - */ - public final boolean isSeekable; - /** - * Whether this seek window may change when the timeline is updated. - */ - public final boolean isDynamic; - - /** - * @param durationUs The duration of the window in microseconds, or {@link C#TIME_UNSET} if - * unknown. - * @param isSeekable Whether seeking is supported within the window. - * @param isDynamic Whether this seek window may change when the timeline is updated. - * @param defaultStartPositionUs The default position relative to the start of the window at which - * to start playback, in microseconds. - */ - public MediaWindow(long durationUs, boolean isSeekable, boolean isDynamic, - long defaultStartPositionUs) { - this.durationUs = durationUs; - this.isSeekable = isSeekable; - this.isDynamic = isDynamic; - this.defaultStartPositionUs = defaultStartPositionUs; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (isSeekable ? 1 : 2); - result = 31 * result + (isDynamic ? 1 : 2); - result = 31 * result + (int) defaultStartPositionUs; - result = 31 * result + (int) durationUs; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - MediaWindow other = (MediaWindow) obj; - return other.durationUs == durationUs - && other.isSeekable == isSeekable - && other.isDynamic == isDynamic - && other.defaultStartPositionUs == defaultStartPositionUs; - } - - @Override - public String toString() { - return "MediaWindow[" + durationUs + ", " + defaultStartPositionUs + ", " + isSeekable + ", " - + isDynamic + "]"; - } - -} 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 507f9256ff..71630e3a2d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -391,24 +391,6 @@ public final class SimpleExoPlayer implements ExoPlayer { return player.isLoading(); } - @Override - @Deprecated - public void seekInCurrentPeriod(long positionMs) { - player.seekInCurrentPeriod(positionMs); - } - - @Override - @Deprecated - public void seekToDefaultPositionForPeriod(int periodIndex) { - player.seekToDefaultPositionForPeriod(periodIndex); - } - - @Override - @Deprecated - public void seekInPeriod(int periodIndex, long positionMs) { - player.seekInPeriod(periodIndex, positionMs); - } - @Override public void seekToDefaultPosition() { player.seekToDefaultPosition(); @@ -449,36 +431,6 @@ public final class SimpleExoPlayer implements ExoPlayer { player.blockingSendMessages(messages); } - @Override - @Deprecated - public int getCurrentPeriodIndex() { - return player.getCurrentPeriodIndex(); - } - - @Override - @Deprecated - public long getCurrentPeriodDuration() { - return player.getCurrentPeriodDuration(); - } - - @Override - @Deprecated - public long getCurrentPositionInPeriod() { - return player.getCurrentPositionInPeriod(); - } - - @Override - @Deprecated - public long getBufferedPositionInPeriod() { - return player.getBufferedPositionInPeriod(); - } - - @Override - @Deprecated - public int getBufferedPercentageInPeriod() { - return player.getBufferedPercentageInPeriod(); - } - @Override public int getCurrentWindowIndex() { return player.getCurrentWindowIndex(); @@ -505,7 +457,7 @@ public final class SimpleExoPlayer implements ExoPlayer { } @Override - public MediaTimeline getCurrentTimeline() { + public Timeline getCurrentTimeline() { return player.getCurrentTimeline(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/Timeline.java b/library/src/main/java/com/google/android/exoplayer2/Timeline.java new file mode 100644 index 0000000000..65c208fe24 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/Timeline.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2016 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 com.google.android.exoplayer2; + +/** + * A media timeline. Instances are immutable. + */ +public abstract class Timeline { + + /** + * Returns the number of windows in the timeline. + */ + public abstract int getWindowCount(); + + /** + * Populates a {@link Window} with data for the window at the specified index. Does not populate + * {@link Window#id}. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @return The populated {@link Window}, for convenience. + */ + public final Window getWindow(int windowIndex, Window window) { + return getWindow(windowIndex, window, false); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @return The populated {@link Window}, for convenience. + */ + public abstract Window getWindow(int windowIndex, Window window, boolean setIds); + + /** + * Returns the number of periods in the timeline. + */ + public abstract int getPeriodCount(); + + /** + * Populates a {@link Period} with data for the period at the specified index. Does not populate + * {@link Period#id} and {@link Period#uid}. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @return The populated {@link Period}, for convenience. + */ + public final Period getPeriod(int periodIndex, Period period) { + return getPeriod(periodIndex, period, false); + } + + /** + * Populates a {@link Period} with data for the period at the specified index. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false, + * the fields will be set to null. The caller should pass false for efficiency reasons unless + * the fields are required. + * @return The populated {@link Period}, for convenience. + */ + public abstract Period getPeriod(int periodIndex, Period period, boolean setIds); + + /** + * Returns the index of the period identified by its unique {@code id}, or {@link C#INDEX_UNSET} + * if the period is not in the timeline. + * + * @param uid A unique identifier for a period. + * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found. + */ + public abstract int getIndexOfPeriod(Object uid); + + /** + * Holds information about a window of available media. + */ + public static final class Window { + + /** + * An identifier for the window. Not necessarily unique. + */ + public Object id; + + /** + * The start time of the presentation to which this window belongs in milliseconds since the + * epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes only. + */ + public long presentationStartTimeMs; + + /** + * The windows start time in milliseconds since the epoch, or {@link C#TIME_UNSET} if unknown or + * not applicable. For informational purposes only. + */ + public long windowStartTimeMs; + + /** + * Whether it's possible to seek within this window. + */ + public boolean isSeekable; + + /** + * Whether this window may change when the timeline is updated. + */ + public boolean isDynamic; + + /** + * The index of the first period that belongs to this window. + */ + public int firstPeriodIndex; + + /** + * The index of the last period that belongs to this window. + */ + public int lastPeriodIndex; + + private long defaultStartPositionUs; + private long durationUs; + private long positionInFirstPeriodUs; + + /** + * Sets the data held by this window. + */ + public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs, + boolean isSeekable, boolean isDynamic, long defaultStartPositionUs, long durationUs, + int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { + this.id = id; + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + this.defaultStartPositionUs = defaultStartPositionUs; + this.durationUs = durationUs; + this.firstPeriodIndex = firstPeriodIndex; + this.lastPeriodIndex = lastPeriodIndex; + this.positionInFirstPeriodUs = positionInFirstPeriodUs; + return this; + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in milliseconds. + */ + public long getDefaultStartPositionMs() { + return C.usToMs(defaultStartPositionUs); + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in microseconds. + */ + public long getDefaultStartPositionUs() { + return defaultStartPositionUs; + } + + /** + * Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in milliseconds. + */ + public long getPositionInFirstPeriodMs() { + return C.usToMs(positionInFirstPeriodUs); + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in microseconds. + */ + public long getPositionInFirstPeriodUs() { + return positionInFirstPeriodUs; + } + + } + + /** + * Holds information about a media period. + */ + public static final class Period { + + /** + * An identifier for the period. Not necessarily unique. + */ + public Object id; + + /** + * A unique identifier for the period. + */ + public Object uid; + + /** + * The index of the window to which this period belongs. + */ + public int windowIndex; + + private long durationUs; + private long positionInWindowUs; + + /** + * Sets the data held by this period. + */ + public Period set(Object id, Object uid, int windowIndex, long durationUs, + long positionInWindowUs) { + this.id = id; + this.uid = uid; + this.windowIndex = windowIndex; + this.durationUs = durationUs; + this.positionInWindowUs = positionInWindowUs; + return this; + } + + /** + * Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in milliseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowMs() { + return C.usToMs(positionInWindowUs); + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in microseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowUs() { + return positionInWindowUs; + } + + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 6dedb3556f..5aad9e259e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -17,8 +17,7 @@ package com.google.android.exoplayer2.source; import android.util.Pair; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaTimeline; -import com.google.android.exoplayer2.MediaWindow; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Util; @@ -32,18 +31,18 @@ import java.util.Map; public final class ConcatenatingMediaSource implements MediaSource { private final MediaSource[] mediaSources; - private final MediaTimeline[] timelines; + private final Timeline[] timelines; private final Object[] manifests; private final Map sourceIndexByMediaPeriod; - private ConcatenatedMediaTimeline timeline; + private ConcatenatedTimeline timeline; /** * @param mediaSources The {@link MediaSource}s to concatenate. */ public ConcatenatingMediaSource(MediaSource... mediaSources) { this.mediaSources = mediaSources; - timelines = new MediaTimeline[mediaSources.length]; + timelines = new Timeline[mediaSources.length]; manifests = new Object[mediaSources.length]; sourceIndexByMediaPeriod = new HashMap<>(); } @@ -55,16 +54,16 @@ public final class ConcatenatingMediaSource implements MediaSource { mediaSources[i].prepareSource(new Listener() { @Override - public void onSourceInfoRefreshed(MediaTimeline sourceTimeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) { timelines[index] = sourceTimeline; manifests[index] = manifest; - for (MediaTimeline timeline : timelines) { + for (Timeline timeline : timelines) { if (timeline == null) { // Don't invoke the listener until all sources have timelines. return; } } - timeline = new ConcatenatedMediaTimeline(timelines.clone()); + timeline = new ConcatenatedTimeline(timelines.clone()); listener.onSourceInfoRefreshed(timeline, manifests.clone()); } @@ -105,21 +104,21 @@ public final class ConcatenatingMediaSource implements MediaSource { } /** - * A {@link MediaTimeline} that is the concatenation of one or more {@link MediaTimeline}s. + * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s. */ - private static final class ConcatenatedMediaTimeline implements MediaTimeline { + private static final class ConcatenatedTimeline extends Timeline { - private final MediaTimeline[] timelines; + private final Timeline[] timelines; private final int[] sourcePeriodOffsets; private final int[] sourceWindowOffsets; - public ConcatenatedMediaTimeline(MediaTimeline[] timelines) { + public ConcatenatedTimeline(Timeline[] timelines) { int[] sourcePeriodOffsets = new int[timelines.length]; int[] sourceWindowOffsets = new int[timelines.length]; int periodCount = 0; int windowCount = 0; for (int i = 0; i < timelines.length; i++) { - MediaTimeline timeline = timelines[i]; + Timeline timeline = timelines[i]; periodCount += timeline.getPeriodCount(); sourcePeriodOffsets[i] = periodCount; windowCount += timeline.getWindowCount(); @@ -131,8 +130,19 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public long getAbsoluteStartTime() { - return timelines[0].getAbsoluteStartTime(); + public int getWindowCount() { + return sourceWindowOffsets[sourceWindowOffsets.length - 1]; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds) { + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds); + window.firstPeriodIndex += firstPeriodIndexInSource; + window.lastPeriodIndex += firstPeriodIndexInSource; + return window; } @Override @@ -141,50 +151,28 @@ public final class ConcatenatingMediaSource implements MediaSource { } @Override - public long getPeriodDurationMs(int periodIndex) { + public Period getPeriod(int periodIndex, Period period, boolean setIds) { int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - return timelines[sourceIndex].getPeriodDurationMs(periodIndex - firstPeriodIndexInSource); + timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds); + period.windowIndex += firstWindowIndexInSource; + if (setIds) { + period.uid = Pair.create(sourceIndex, period.uid); + } + return period; } @Override - public long getPeriodDurationUs(int periodIndex) { - int sourceIndex = getSourceIndexForPeriod(periodIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - return timelines[sourceIndex].getPeriodDurationUs(periodIndex - firstPeriodIndexInSource); - } - - @Override - public Object getPeriodId(int periodIndex) { - int sourceIndex = getSourceIndexForPeriod(periodIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex); - Object periodId = timelines[sourceIndex].getPeriodId(periodIndex - firstPeriodIndexInSource); - return Pair.create(sourceIndex, periodId); - } - - @Override - public MediaWindow getPeriodWindow(int periodIndex) { - return getWindow(getPeriodWindowIndex(periodIndex)); - } - - @Override - public int getPeriodWindowIndex(int periodIndex) { - int sourceIndex = getSourceIndexForPeriod(periodIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex); - return (sourceIndex > 0 ? sourceWindowOffsets[sourceIndex - 1] : 0) - + timelines[sourceIndex].getPeriodWindowIndex(periodIndex - firstPeriodIndexInSource); - } - - @Override - public int getIndexOfPeriod(Object id) { - if (!(id instanceof Pair)) { + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { return C.INDEX_UNSET; } - Pair sourceIndexAndPeriodId = (Pair) id; + Pair sourceIndexAndPeriodId = (Pair) uid; if (!(sourceIndexAndPeriodId.first instanceof Integer)) { return C.INDEX_UNSET; } - int sourceIndex = (int) sourceIndexAndPeriodId.first; + int sourceIndex = (Integer) sourceIndexAndPeriodId.first; Object periodId = sourceIndexAndPeriodId.second; if (sourceIndex < 0 || sourceIndex >= timelines.length) { return C.INDEX_UNSET; @@ -194,44 +182,6 @@ public final class ConcatenatingMediaSource implements MediaSource { : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; } - @Override - public int getWindowCount() { - return sourceWindowOffsets[sourceWindowOffsets.length - 1]; - } - - @Override - public MediaWindow getWindow(int windowIndex) { - int sourceIndex = getSourceIndexForWindow(windowIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - return timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource); - } - - @Override - public int getWindowFirstPeriodIndex(int windowIndex) { - int sourceIndex = getSourceIndexForWindow(windowIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - return firstPeriodIndexInSource + timelines[sourceIndex].getWindowFirstPeriodIndex( - windowIndex - firstWindowIndexInSource); - } - - @Override - public int getWindowLastPeriodIndex(int windowIndex) { - int sourceIndex = getSourceIndexForWindow(windowIndex); - int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - return firstPeriodIndexInSource + timelines[sourceIndex].getWindowLastPeriodIndex( - windowIndex - firstWindowIndexInSource); - } - - @Override - public long getWindowOffsetInFirstPeriodUs(int windowIndex) { - int sourceIndex = getSourceIndexForWindow(windowIndex); - int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); - return timelines[sourceIndex].getWindowOffsetInFirstPeriodUs( - windowIndex - firstWindowIndexInSource); - } - private int getSourceIndexForPeriod(int periodIndex) { return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 798449f5f5..a9d4c18db6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -299,7 +299,7 @@ import java.util.Arrays; durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; sourceListener.onSourceInfoRefreshed( - new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null); + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); } } @@ -382,7 +382,7 @@ import java.util.Arrays; tracks = new TrackGroupArray(trackArray); prepared = true; sourceListener.onSourceInfoRefreshed( - new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null); + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); callback.onPrepared(this); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 4ab4403978..936358352e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source; import android.net.Uri; import android.os.Handler; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorsFactory; @@ -92,9 +92,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List private final int minLoadableRetryCount; private final Handler eventHandler; private final EventListener eventListener; + private final Timeline.Period period; private MediaSource.Listener sourceListener; - private MediaTimeline timeline; + private Timeline timeline; + private boolean timelineHasDuration; /** * @param uri The {@link Uri} of the media stream. @@ -130,12 +132,13 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; + period = new Timeline.Period(); } @Override public void prepareSource(MediaSource.Listener listener) { sourceListener = listener; - timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false); + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); listener.onSourceInfoRefreshed(timeline, null); } @@ -166,13 +169,15 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List // MediaSource.Listener implementation. @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { - if (this.timeline.getPeriodDurationUs(0) != C.TIME_UNSET - && timeline.getPeriodDurationUs(0) == C.TIME_UNSET) { + public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { + long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs(); + boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET; + if (timelineHasDuration && !newTimelineHasDuration) { // Suppress source info changes that would make the duration unknown when it is already known. return; } - this.timeline = timeline; + timeline = newTimeline; + timelineHasDuration = newTimelineHasDuration; sourceListener.onSourceInfoRefreshed(timeline, null); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index bf540585e8..4c0686eec1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer2.source; -import com.google.android.exoplayer2.MediaTimeline; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import java.io.IOException; @@ -36,7 +36,7 @@ public interface MediaSource { * @param timeline The source's timeline. * @param manifest The loaded manifest. */ - void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest); + void onSourceInfoRefreshed(Timeline timeline, Object manifest); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 7bb5e3b5ff..d6c976944f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer2.source; -import com.google.android.exoplayer2.MediaTimeline; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.util.Assertions; @@ -31,6 +31,7 @@ public final class MergingMediaSource implements MediaSource { private static final int PERIOD_COUNT_UNSET = -1; private final MediaSource[] mediaSources; + private final Timeline.Window window; private int periodCount; @@ -39,30 +40,26 @@ public final class MergingMediaSource implements MediaSource { */ public MergingMediaSource(MediaSource... mediaSources) { this.mediaSources = mediaSources; + window = new Timeline.Window(); periodCount = PERIOD_COUNT_UNSET; } @Override public void prepareSource(final Listener listener) { mediaSources[0].prepareSource(new Listener() { - @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { checkConsistentTimeline(timeline); - // All source timelines must match. listener.onSourceInfoRefreshed(timeline, manifest); } - }); for (int i = 1; i < mediaSources.length; i++) { mediaSources[i].prepareSource(new Listener() { - @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { checkConsistentTimeline(timeline); } - }); } } @@ -102,10 +99,10 @@ public final class MergingMediaSource implements MediaSource { } } - private void checkConsistentTimeline(MediaTimeline timeline) { + private void checkConsistentTimeline(Timeline timeline) { int windowCount = timeline.getWindowCount(); for (int i = 0; i < windowCount; i++) { - Assertions.checkArgument(!timeline.getWindow(i).isDynamic); + Assertions.checkArgument(!timeline.getWindow(i, window, false).isDynamic); } int periodCount = timeline.getPeriodCount(); if (this.periodCount == PERIOD_COUNT_UNSET) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodMediaTimeline.java b/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodMediaTimeline.java deleted file mode 100644 index 5c45526436..0000000000 --- a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodMediaTimeline.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2016 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 com.google.android.exoplayer2.source; - -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaTimeline; -import com.google.android.exoplayer2.MediaWindow; -import com.google.android.exoplayer2.util.Assertions; - -/** - * A {@link MediaTimeline} consisting of a single period and static window. - */ -public final class SinglePeriodMediaTimeline implements MediaTimeline { - - private static final Object ID = new Object(); - - private final long offsetInFirstPeriodUs; - private final MediaWindow window; - - /** - * Creates a timeline with one period of known duration and a window extending from zero to its - * duration. - * - * @param durationUs The duration of the period, in microseconds. - * @param isSeekable Whether seeking is supported within the period. - */ - public SinglePeriodMediaTimeline(long durationUs, boolean isSeekable) { - this(0, MediaWindow.createWindowFromZero(durationUs, isSeekable, false /* isDynamic */)); - } - - /** - * Creates a timeline with one period of known duration and a window extending from zero to its - * duration. - * - * @param offsetInFirstPeriodUs The offset of the start of the window in the period. - * @param window The available window within the period. - */ - public SinglePeriodMediaTimeline(long offsetInFirstPeriodUs, MediaWindow window) { - this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; - this.window = window; - } - - @Override - public long getAbsoluteStartTime() { - return 0; - } - - @Override - public int getPeriodCount() { - return 1; - } - - @Override - public long getPeriodDurationMs(int periodIndex) { - return C.usToMs(getPeriodDurationUs(periodIndex)); - } - - @Override - public long getPeriodDurationUs(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, 1); - return window.durationUs == C.TIME_UNSET ? C.TIME_UNSET - : (offsetInFirstPeriodUs + window.durationUs); - } - - @Override - public Object getPeriodId(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, 1); - return ID; - } - - @Override - public MediaWindow getPeriodWindow(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, 1); - return window; - } - - @Override - public int getPeriodWindowIndex(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, 1); - return 0; - } - - @Override - public int getIndexOfPeriod(Object id) { - return ID.equals(id) ? 0 : C.INDEX_UNSET; - } - - @Override - public int getWindowCount() { - return 1; - } - - @Override - public MediaWindow getWindow(int windowIndex) { - Assertions.checkIndex(windowIndex, 0, 1); - return window; - } - - @Override - public int getWindowFirstPeriodIndex(int windowIndex) { - Assertions.checkIndex(windowIndex, 0, 1); - return 0; - } - - @Override - public int getWindowLastPeriodIndex(int windowIndex) { - Assertions.checkIndex(windowIndex, 0, 1); - return 0; - } - - @Override - public long getWindowOffsetInFirstPeriodUs(int windowIndex) { - Assertions.checkIndex(windowIndex, 0, 1); - return offsetInFirstPeriodUs; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java b/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java new file mode 100644 index 0000000000..f298d04432 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/source/SinglePeriodTimeline.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 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 com.google.android.exoplayer2.source; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.util.Assertions; + +/** + * A {@link Timeline} consisting of a single period and static window. + */ +public final class SinglePeriodTimeline extends Timeline { + + private static final Object ID = new Object(); + + private final long periodDurationUs; + private final long windowDurationUs; + private final long windowPositionInPeriodUs; + private final long windowDefaultStartPositionUs; + private final boolean isSeekable; + private final boolean isDynamic; + + /** + * Creates a timeline of one period of known duration, and a static window starting at zero and + * extending to that duration. + * + * @param durationUs The duration of the period, in microseconds. + * @param isSeekable Whether seeking is supported within the period. + */ + public SinglePeriodTimeline(long durationUs, boolean isSeekable) { + this(durationUs, durationUs, 0, 0, isSeekable, false); + } + + /** + * Creates a timeline with one period of known duration, and a window of known duration starting + * at a specified position in the period. + * + * @param periodDurationUs The duration of the period in microseconds. + * @param windowDurationUs The duration of the window in microseconds. + * @param windowPositionInPeriodUs The position of the start of the window in the period, in + * microseconds. + * @param windowDefaultStartPositionUs The default position relative to the start of the window at + * which to begin playback, in microseconds. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether the window may change when the timeline is updated. + */ + public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs, + long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable, + boolean isDynamic) { + this.periodDurationUs = periodDurationUs; + this.windowDurationUs = windowDurationUs; + this.windowPositionInPeriodUs = windowPositionInPeriodUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds) { + Assertions.checkIndex(windowIndex, 0, 1); + Object id = setIds ? ID : null; + return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic, + windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); + } + + @Override + public int getPeriodCount() { + return 1; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + Assertions.checkIndex(periodIndex, 0, 1); + Object id = setIds ? ID : null; + return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return ID.equals(uid) ? 0 : C.INDEX_UNSET; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index bbeaf6aaa1..c0902f5a84 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -18,7 +18,7 @@ package com.google.android.exoplayer2.source; import android.net.Uri; import android.os.Handler; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaTimeline; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; @@ -57,7 +57,7 @@ public final class SingleSampleMediaSource implements MediaSource { private final Handler eventHandler; private final EventListener eventListener; private final int eventSourceId; - private final MediaTimeline timeline; + private final Timeline timeline; public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs) { @@ -79,7 +79,7 @@ public final class SingleSampleMediaSource implements MediaSource { this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; - timeline = new SinglePeriodMediaTimeline(durationUs, true); + timeline = new SinglePeriodTimeline(durationUs, true); } // MediaSource implementation. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index ac4aed05bf..3e8668e96c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -21,9 +21,8 @@ import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaTimeline; -import com.google.android.exoplayer2.MediaWindow; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; @@ -71,8 +70,8 @@ public final class DashMediaSource implements MediaSource { public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000; /** * The interval in milliseconds between invocations of - * {@link MediaSource.Listener#onSourceInfoRefreshed(MediaTimeline, Object)} when the source's - * {@link MediaWindow} is changing dynamically (for example, for incomplete live streams). + * {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's + * {@link Timeline} is changing dynamically (for example, for incomplete live streams). */ private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000; @@ -99,7 +98,6 @@ public final class DashMediaSource implements MediaSource { private long manifestLoadEndTimestamp; private DashManifest manifest; private Handler handler; - private MediaWindow window; private long elapsedRealtimeOffsetMs; private int firstPeriodId; @@ -360,12 +358,12 @@ public final class DashMediaSource implements MediaSource { if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) { // The manifest describes an incomplete live stream. Update the start/end times to reflect the // live stream duration and the manifest's time shift buffer depth. - long liveStreamDurationUs = getNowUnixTimeUs() - manifest.availabilityStartTime * 1000; - long liveStreamEndPositionInLastPeriodUs = - liveStreamDurationUs - manifest.getPeriod(lastPeriodIndex).startMs * 1000; + long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime); + long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs + - C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs); currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { - long timeShiftBufferDepthUs = manifest.timeShiftBufferDepth * 1000; + long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth); long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; int periodIndex = lastPeriodIndex; while (offsetInPeriodUs < 0 && periodIndex > 0) { @@ -386,7 +384,7 @@ public final class DashMediaSource implements MediaSource { for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { windowDurationUs += manifest.getPeriodDurationUs(i); } - long defaultInitialTimeUs = 0; + long windowDefaultStartPositionUs = 0; if (manifest.dynamic) { long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs; if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) { @@ -395,7 +393,7 @@ public final class DashMediaSource implements MediaSource { } // Snap the default position to the start of the segment containing it. long initialTimeOffsetInWindowUs = - Math.max(0, windowDurationUs - (liveEdgeOffsetForManifestMs * 1000)); + Math.max(0, windowDurationUs - C.msToUs(liveEdgeOffsetForManifestMs)); int periodIndex = 0; long initialTimeOffsetInPeriodUs = currentStartTimeUs + initialTimeOffsetInWindowUs; long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); @@ -413,16 +411,18 @@ public final class DashMediaSource implements MediaSource { DashSegmentIndex index = period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex(); int segmentNum = index.getSegmentNum(initialTimeOffsetInPeriodUs, periodDurationUs); - defaultInitialTimeUs = + windowDefaultStartPositionUs = initialTimeOffsetInWindowUs - initialTimeOffsetInPeriodUs + index.getTimeUs(segmentNum); } else { - defaultInitialTimeUs = initialTimeOffsetInWindowUs; + windowDefaultStartPositionUs = initialTimeOffsetInWindowUs; } } - window = new MediaWindow(windowDurationUs, true /* isSeekable */, manifest.dynamic, - defaultInitialTimeUs); - sourceListener.onSourceInfoRefreshed(new DashMediaTimeline(firstPeriodId, currentStartTimeUs, - manifest, window), manifest); + long windowStartTimeMs = manifest.availabilityStartTime + + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs); + DashTimeline timeline = new DashTimeline(manifest.availabilityStartTime, windowStartTimeMs, + firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, + manifest); + sourceListener.onSourceInfoRefreshed(timeline, manifest); } private void scheduleManifestRefresh() { @@ -450,9 +450,9 @@ public final class DashMediaSource implements MediaSource { private long getNowUnixTimeUs() { if (elapsedRealtimeOffsetMs != 0) { - return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000; + return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs); } else { - return System.currentTimeMillis() * 1000; + return C.msToUs(System.currentTimeMillis()); } } @@ -462,7 +462,8 @@ public final class DashMediaSource implements MediaSource { private static final class PeriodSeekInfo { - public static PeriodSeekInfo createPeriodSeekInfo(Period period, long durationUs) { + public static PeriodSeekInfo createPeriodSeekInfo( + com.google.android.exoplayer2.source.dash.manifest.Period period, long durationUs) { int adaptationSetCount = period.adaptationSets.size(); long availableStartTimeUs = 0; long availableEndTimeUs = Long.MAX_VALUE; @@ -501,24 +502,27 @@ public final class DashMediaSource implements MediaSource { } - private static final class DashMediaTimeline implements MediaTimeline { + private static final class DashTimeline extends Timeline { + + private final long presentationStartTimeMs; + private final long windowStartTimeMs; private final int firstPeriodId; private final long offsetInFirstPeriodUs; + private final long windowDurationUs; + private final long windowDefaultStartPositionUs; private final DashManifest manifest; - private final MediaWindow window; - public DashMediaTimeline(int firstPeriodId, long offsetInFirstPeriodUs, DashManifest manifest, - MediaWindow window) { + public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, + int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs, + long windowDefaultStartPositionUs, DashManifest manifest) { + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; this.firstPeriodId = firstPeriodId; this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; + this.windowDurationUs = windowDurationUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; this.manifest = manifest; - this.window = window; - } - - @Override - public long getAbsoluteStartTime() { - return manifest.availabilityStartTime + manifest.getPeriod(0).startMs; } @Override @@ -527,42 +531,14 @@ public final class DashMediaSource implements MediaSource { } @Override - public long getPeriodDurationMs(int periodIndex) { + public Period getPeriod(int periodIndex, Period period, boolean setIdentifiers) { Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); - return manifest.getPeriodDurationMs(periodIndex); - } - - @Override - public long getPeriodDurationUs(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); - return manifest.getPeriodDurationUs(periodIndex); - } - - @Override - public Object getPeriodId(int periodIndex) { - return firstPeriodId + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); - } - - @Override - public MediaWindow getPeriodWindow(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); - return window; - } - - @Override - public int getPeriodWindowIndex(int periodIndex) { - Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); - return 0; - } - - @Override - public int getIndexOfPeriod(Object id) { - if (!(id instanceof Integer)) { - return C.INDEX_UNSET; - } - int periodId = (int) id; - return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount() - ? C.INDEX_UNSET : (periodId - firstPeriodId); + Object id = setIdentifiers ? manifest.getPeriod(periodIndex).id : null; + Object uid = setIdentifiers ? firstPeriodId + + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null; + return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex), + C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) + - offsetInFirstPeriodUs); } @Override @@ -571,24 +547,21 @@ public final class DashMediaSource implements MediaSource { } @Override - public MediaWindow getWindow(int windowIndex) { + public Window getWindow(int windowIndex, Window window, boolean setIdentifier) { Assertions.checkIndex(windowIndex, 0, 1); - return window; + return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */, + manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0, + manifest.getPeriodCount() - 1, offsetInFirstPeriodUs); } @Override - public int getWindowFirstPeriodIndex(int windowIndex) { - return 0; - } - - @Override - public int getWindowLastPeriodIndex(int windowIndex) { - return manifest.getPeriodCount() - 1; - } - - @Override - public long getWindowOffsetInFirstPeriodUs(int windowIndex) { - return offsetInFirstPeriodUs; + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Integer)) { + return C.INDEX_UNSET; + } + int periodId = (int) uid; + return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount() + ? C.INDEX_UNSET : (periodId - firstPeriodId); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index 6ca4cb19b0..8d55cd0914 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -19,14 +19,14 @@ import android.net.Uri; import android.text.TextUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.CompositeSequenceableLoader; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SampleStream; -import com.google.android.exoplayer2.source.SinglePeriodMediaTimeline; +import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; @@ -282,7 +282,7 @@ import java.util.List; callback.onPrepared(this); // TODO[playlists]: Calculate the window. - MediaTimeline timeline = new SinglePeriodMediaTimeline(durationUs, !isLive); + Timeline timeline = new SinglePeriodTimeline(durationUs, !isLive); sourceListener.onSourceInfoRefreshed(timeline, playlist); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index db7995152b..72a9fd6c0c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -23,7 +23,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.Eve import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.SinglePeriodMediaTimeline; +import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.util.Assertions; @@ -64,7 +64,7 @@ public final class HlsMediaSource implements MediaSource { public void prepareSource(MediaSource.Listener listener) { sourceListener = listener; // TODO: Defer until the playlist has been loaded. - listener.onSourceInfoRefreshed(new SinglePeriodMediaTimeline(C.TIME_UNSET, false), null); + listener.onSourceInfoRefreshed(new SinglePeriodTimeline(C.TIME_UNSET, false), null); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index b59834b1b2..3987ee08f4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -19,15 +19,14 @@ import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaTimeline; -import com.google.android.exoplayer2.MediaWindow; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.SinglePeriodMediaTimeline; +import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; @@ -157,7 +156,7 @@ public final class SsMediaSource implements MediaSource, for (int i = 0; i < mediaPeriods.size(); i++) { mediaPeriods.get(i).updateManifest(manifest); } - MediaTimeline timeline; + Timeline timeline; if (manifest.isLive) { long startTimeUs = Long.MAX_VALUE; long endTimeUs = Long.MIN_VALUE; @@ -170,21 +169,20 @@ public final class SsMediaSource implements MediaSource, } } if (startTimeUs == Long.MAX_VALUE) { - timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false); + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); } else { if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) { startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); } long durationUs = endTimeUs - startTimeUs; - long defaultInitialStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000)); - MediaWindow window = new MediaWindow(durationUs, true /* isSeekable */, - true /* isDynamic */, defaultInitialStartPositionUs); - timeline = new SinglePeriodMediaTimeline(startTimeUs, window); + long defaultStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000)); + timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, + defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); } } else { boolean isSeekable = manifest.durationUs != C.TIME_UNSET; - timeline = new SinglePeriodMediaTimeline(manifest.durationUs, isSeekable); + timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable); } sourceListener.onSourceInfoRefreshed(timeline, manifest); scheduleManifestRefresh(); diff --git a/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java index 4a064e6d78..b72bc18ef9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -19,8 +19,8 @@ import android.widget.TextView; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.decoder.DecoderCounters; /** @@ -89,7 +89,7 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe } @Override - public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. } 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 d11c9e006b..cc617d8eaf 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 @@ -17,15 +17,14 @@ package com.google.android.exoplayer2.ui; import android.view.View; import android.view.View.OnClickListener; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.MediaTimeline; +import com.google.android.exoplayer2.Timeline; /** * An {@link OnClickListener} that can be passed to * {@link android.widget.MediaController#setPrevNextListeners(OnClickListener, OnClickListener)} to * make the controller's previous and next buttons seek to the previous and next windows in the - * {@link MediaTimeline}. + * {@link Timeline}. */ public class MediaControllerPrevNextClickListener implements OnClickListener { @@ -50,15 +49,15 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { @Override public void onClick(View v) { - int currentWindowIndex = player.getCurrentWindowIndex(); - if (currentWindowIndex == C.INDEX_UNSET) { + Timeline timeline = player.getCurrentTimeline(); + if (timeline == null) { return; } - MediaTimeline timeline = player.getCurrentTimeline(); + int currentWindowIndex = player.getCurrentWindowIndex(); if (isNext) { if (currentWindowIndex < timeline.getWindowCount() - 1) { player.seekToDefaultPosition(currentWindowIndex + 1); - } else if (timeline.getWindow(currentWindowIndex).isDynamic) { + } else if (timeline.getWindow(currentWindowIndex, new Timeline.Window(), false).isDynamic) { // Seek to the live edge. player.seekToDefaultPosition(); } @@ -67,7 +66,7 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { && player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS) { player.seekToDefaultPosition(currentWindowIndex - 1); } else { - player.seekTo(currentWindowIndex, 0); + player.seekTo(0); } } } 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 9a4dd93e38..052de93f8a 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 @@ -24,8 +24,8 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.decoder.DecoderCounters; @@ -215,7 +215,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen } @Override - public final void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { + public final void onSourceInfoRefreshed(Timeline timeline, Object manifest) { // Do nothing. }