Commit 5f1a2c71 by olly Committed by Oliver Woodman

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
parent 298464eb
Showing with 846 additions and 1008 deletions
...@@ -22,8 +22,8 @@ import com.google.android.exoplayer2.C; ...@@ -22,8 +22,8 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
...@@ -57,16 +57,22 @@ import java.util.Locale; ...@@ -57,16 +57,22 @@ import java.util.Locale;
MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> { MappingTrackSelector.EventListener, MetadataRenderer.Output<List<Id3Frame>> {
private static final String TAG = "EventLogger"; private static final String TAG = "EventLogger";
private static final int MAX_TIMELINE_ITEM_LINES = 3;
private static final NumberFormat TIME_FORMAT; private static final NumberFormat TIME_FORMAT;
static { static {
TIME_FORMAT = NumberFormat.getInstance(Locale.US); TIME_FORMAT = NumberFormat.getInstance(Locale.US);
TIME_FORMAT.setMinimumFractionDigits(2); TIME_FORMAT.setMinimumFractionDigits(2);
TIME_FORMAT.setMaximumFractionDigits(2); TIME_FORMAT.setMaximumFractionDigits(2);
TIME_FORMAT.setGroupingUsed(false);
} }
private final Timeline.Window window;
private final Timeline.Period period;
private final long startTimeMs; private final long startTimeMs;
public EventLogger() { public EventLogger() {
window = new Timeline.Window();
period = new Timeline.Period();
startTimeMs = SystemClock.elapsedRealtime(); startTimeMs = SystemClock.elapsedRealtime();
} }
...@@ -89,13 +95,24 @@ import java.util.Locale; ...@@ -89,13 +95,24 @@ import java.util.Locale;
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
int periodCount = timeline.getPeriodCount(); int periodCount = timeline.getPeriodCount();
int windowCount = timeline.getWindowCount(); int windowCount = timeline.getWindowCount();
Log.d(TAG, "sourceInfo[startTime=" + timeline.getAbsoluteStartTime() + ", periodCount=" Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount);
+ periodCount + ", windows: " + windowCount); for (int i = 0; i < Math.min(periodCount, MAX_TIMELINE_ITEM_LINES); i++) {
for (int windowIndex = 0; windowIndex < windowCount; windowIndex++) { timeline.getPeriod(i, period);
Log.d(TAG, " " + timeline.getWindow(windowIndex)); 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, "]"); Log.d(TAG, "]");
} }
...@@ -333,7 +350,7 @@ import java.util.Locale; ...@@ -333,7 +350,7 @@ import java.util.Locale;
} }
private static String getTimeString(long timeMs) { 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) { private static String getStateString(int state) {
......
...@@ -40,9 +40,8 @@ import com.google.android.exoplayer2.DefaultLoadControl; ...@@ -40,9 +40,8 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; 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.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager; import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
...@@ -127,7 +126,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -127,7 +126,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
private boolean playerNeedsSource; private boolean playerNeedsSource;
private boolean shouldRestorePosition; private boolean shouldRestorePosition;
private int playerPeriod; private int playerWindow;
private long playerPosition; private long playerPosition;
// Activity lifecycle // Activity lifecycle
...@@ -283,9 +282,9 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -283,9 +282,9 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
player.setVideoSurfaceHolder(surfaceView.getHolder()); player.setVideoSurfaceHolder(surfaceView.getHolder());
if (shouldRestorePosition) { if (shouldRestorePosition) {
if (playerPosition == C.TIME_UNSET) { if (playerPosition == C.TIME_UNSET) {
player.seekToDefaultPositionForPeriod(playerPeriod); player.seekToDefaultPosition(playerWindow);
} else { } else {
player.seekInPeriod(playerPeriod, playerPosition); player.seekTo(playerWindow, playerPosition);
} }
} }
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
...@@ -376,13 +375,13 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -376,13 +375,13 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
debugViewHelper.stop(); debugViewHelper.stop();
debugViewHelper = null; debugViewHelper = null;
shouldRestorePosition = false; shouldRestorePosition = false;
MediaTimeline playerTimeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (playerTimeline != null) { if (timeline != null) {
MediaWindow window = playerTimeline.getWindow(player.getCurrentWindowIndex()); playerWindow = player.getCurrentWindowIndex();
Timeline.Window window = timeline.getWindow(playerWindow, new Timeline.Window());
if (!window.isDynamic) { if (!window.isDynamic) {
shouldRestorePosition = true; shouldRestorePosition = true;
playerPeriod = player.getCurrentPeriodIndex(); playerPosition = window.isSeekable ? player.getCurrentPosition() : C.TIME_UNSET;
playerPosition = window.isSeekable ? player.getCurrentPositionInPeriod() : C.TIME_UNSET;
} }
} }
player.release(); player.release();
...@@ -417,7 +416,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -417,7 +416,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; ...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.Renderer; 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.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
...@@ -96,7 +96,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -96,7 +96,7 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; ...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.Renderer; 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.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
...@@ -96,7 +96,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -96,7 +96,7 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase; ...@@ -22,8 +22,8 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.Renderer; 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.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
...@@ -115,7 +115,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -115,7 +115,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -117,10 +117,10 @@ public interface ExoPlayer { ...@@ -117,10 +117,10 @@ public interface ExoPlayer {
*/ */
void onPlayerStateChanged(boolean playWhenReady, int playbackState); 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 * Called when the player's position changes due to a discontinuity (i.e. due to seeking,
* transitioning to the next period). * playback transitioning to the next window, or a source induced discontinuity).
* *
* @param periodIndex The index of the period being played. * @param periodIndex The index of the period being played.
* @param positionMs The playback position in that period, in milliseconds. * @param positionMs The playback position in that period, in milliseconds.
...@@ -133,7 +133,7 @@ public interface ExoPlayer { ...@@ -133,7 +133,7 @@ public interface ExoPlayer {
* @param timeline The source's timeline. * @param timeline The source's timeline.
* @param manifest The loaded manifest. * @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} * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE}
...@@ -249,7 +249,7 @@ public interface ExoPlayer { ...@@ -249,7 +249,7 @@ public interface ExoPlayer {
* @param mediaSource The {@link MediaSource} to play. * @param mediaSource The {@link MediaSource} to play.
* @param resetPosition Whether the playback position should be reset to the source's default * @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 * 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); void setMediaSource(MediaSource mediaSource, boolean resetPosition);
...@@ -278,35 +278,6 @@ public interface ExoPlayer { ...@@ -278,35 +278,6 @@ public interface ExoPlayer {
boolean isLoading(); 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 * 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 * the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
* typically be the live edge of the window. For other streams it will typically be the start of * typically be the live edge of the window. For other streams it will typically be the start of
...@@ -333,7 +304,7 @@ public interface ExoPlayer { ...@@ -333,7 +304,7 @@ public interface ExoPlayer {
void seekTo(long positionMs); 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 windowIndex The index of the window.
* @param positionMs The seek position relative to the start of the window. * @param positionMs The seek position relative to the start of the window.
...@@ -376,9 +347,9 @@ public interface ExoPlayer { ...@@ -376,9 +347,9 @@ public interface ExoPlayer {
void blockingSendMessages(ExoPlayerMessage... messages); 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 * Returns the current manifest. The type depends on the {@link MediaSource} passed to
...@@ -386,46 +357,9 @@ public interface ExoPlayer { ...@@ -386,46 +357,9 @@ public interface ExoPlayer {
*/ */
Object getCurrentManifest(); 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 * Returns the index of the window associated with the current period, or {@link C#INDEX_UNSET} if
* {@link C#INDEX_UNSET} if the timeline is not set. * the timeline is not set.
*/ */
int getCurrentWindowIndex(); int getCurrentWindowIndex();
...@@ -436,8 +370,8 @@ public interface ExoPlayer { ...@@ -436,8 +370,8 @@ public interface ExoPlayer {
long getDuration(); long getDuration();
/** /**
* Returns the playback position in the current seek window, in milliseconds, or * Returns the playback position in the current window, in milliseconds, or {@link C#TIME_UNSET}
* {@link C#TIME_UNSET} if the timeline is not set. * if the timeline is not set.
*/ */
long getCurrentPosition(); long getCurrentPosition();
......
...@@ -37,12 +37,14 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -37,12 +37,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final Handler eventHandler; private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer; private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<EventListener> listeners; private final CopyOnWriteArraySet<EventListener> listeners;
private final Timeline.Window window;
private final Timeline.Period period;
private boolean playWhenReady; private boolean playWhenReady;
private int playbackState; private int playbackState;
private int pendingSeekAcks; private int pendingSeekAcks;
private boolean isLoading; private boolean isLoading;
private MediaTimeline timeline; private Timeline timeline;
private Object manifest; private Object manifest;
// Playback information when there is no pending seek/set source operation. // Playback information when there is no pending seek/set source operation.
...@@ -68,6 +70,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -68,6 +70,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.playWhenReady = false; this.playWhenReady = false;
this.playbackState = STATE_IDLE; this.playbackState = STATE_IDLE;
this.listeners = new CopyOnWriteArraySet<>(); this.listeners = new CopyOnWriteArraySet<>();
window = new Timeline.Window();
period = new Timeline.Period();
eventHandler = new Handler() { eventHandler = new Handler() {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
...@@ -127,30 +131,6 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -127,30 +131,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @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() { public void seekToDefaultPosition() {
seekToDefaultPosition(getCurrentWindowIndex()); seekToDefaultPosition(getCurrentWindowIndex());
} }
...@@ -158,38 +138,38 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -158,38 +138,38 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public void seekToDefaultPosition(int windowIndex) { public void seekToDefaultPosition(int windowIndex) {
if (timeline == null) { 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()); Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
seekToDefaultPositionForPeriod(timeline.getWindowFirstPeriodIndex(windowIndex)); int periodIndex = timeline.getWindow(windowIndex, window).firstPeriodIndex;
seekToDefaultPositionForPeriod(periodIndex);
} }
@Override @Override
public void seekTo(long positionMs) { public void seekTo(long positionMs) {
MediaTimeline timeline = getCurrentTimeline();
if (timeline == null) { 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(getCurrentWindowIndex(), positionMs);
seekTo(windowIndex, positionMs);
} }
@Override @Override
public void seekTo(int windowIndex, long positionMs) { public void seekTo(int windowIndex, long positionMs) {
if (timeline == null) { 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()); Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); timeline.getWindow(windowIndex, window);
int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); int periodIndex = window.firstPeriodIndex;
int periodIndex = firstPeriodIndex; long periodPositionMs = window.getPositionInFirstPeriodMs() + positionMs;
long periodPositionMs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000 long periodDurationMs = timeline.getPeriod(periodIndex, period).getDurationMs();
+ positionMs;
long periodDurationMs = timeline.getPeriodDurationMs(periodIndex);
while (periodDurationMs != C.TIME_UNSET && periodPositionMs >= periodDurationMs while (periodDurationMs != C.TIME_UNSET && periodPositionMs >= periodDurationMs
&& periodIndex < lastPeriodIndex) { && periodIndex < window.lastPeriodIndex) {
periodPositionMs -= periodDurationMs; periodPositionMs -= periodDurationMs;
periodDurationMs = timeline.getPeriodDurationMs(++periodIndex); periodDurationMs = timeline.getPeriod(++periodIndex, period).getDurationMs();
} }
seekInPeriod(periodIndex, periodPositionMs); seekInPeriod(periodIndex, periodPositionMs);
} }
...@@ -216,54 +196,11 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -216,54 +196,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @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() { public int getCurrentWindowIndex() {
if (timeline == null) { if (timeline == null) {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
return timeline.getPeriodWindowIndex(getCurrentPeriodIndex()); return timeline.getPeriod(getCurrentPeriodIndex(), period).windowIndex;
} }
@Override @Override
...@@ -271,7 +208,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -271,7 +208,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline == null) { if (timeline == null) {
return C.TIME_UNSET; return C.TIME_UNSET;
} }
return C.usToMs(timeline.getWindow(getCurrentWindowIndex()).durationUs); return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
} }
@Override @Override
...@@ -279,14 +216,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -279,14 +216,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (timeline == null) { if (timeline == null) {
return C.TIME_UNSET; return C.TIME_UNSET;
} }
int periodIndex = getCurrentPeriodIndex(); timeline.getPeriod(getCurrentPeriodIndex(), period);
int windowIndex = timeline.getPeriodWindowIndex(periodIndex); return period.getPositionInWindowMs() + getCurrentPositionInPeriod();
long positionMs = getCurrentPositionInPeriod();
for (int i = timeline.getWindowFirstPeriodIndex(windowIndex); i < periodIndex; i++) {
positionMs += timeline.getPeriodDurationMs(i);
}
positionMs -= timeline.getWindowOffsetInFirstPeriodUs(windowIndex) / 1000;
return positionMs;
} }
@Override @Override
...@@ -296,13 +227,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -296,13 +227,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
return C.TIME_UNSET; return C.TIME_UNSET;
} }
int periodIndex = getCurrentPeriodIndex(); int periodIndex = getCurrentPeriodIndex();
int windowIndex = timeline.getPeriodWindowIndex(periodIndex); timeline.getPeriod(periodIndex, period);
MediaWindow window = timeline.getWindow(windowIndex); int windowIndex = period.windowIndex;
int firstPeriodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); timeline.getWindow(windowIndex, window);
int lastPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); if (window.firstPeriodIndex == periodIndex && window.lastPeriodIndex == periodIndex
if (firstPeriodIndex == periodIndex && lastPeriodIndex == periodIndex && window.getPositionInFirstPeriodUs() == 0
&& timeline.getWindowOffsetInFirstPeriodUs(windowIndex) == 0 && window.getDurationUs() == period.getDurationUs()) {
&& window.durationUs == timeline.getPeriodDurationUs(periodIndex)) {
return getBufferedPositionInPeriod(); return getBufferedPositionInPeriod();
} }
return getCurrentPosition(); return getCurrentPosition();
...@@ -320,7 +250,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -320,7 +250,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public MediaTimeline getCurrentTimeline() { public Timeline getCurrentTimeline() {
return timeline; return timeline;
} }
...@@ -329,6 +259,44 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -329,6 +259,44 @@ import java.util.concurrent.CopyOnWriteArraySet;
return manifest; 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. // Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) { /* package */ void handleEvent(Message msg) {
switch (msg.what) { switch (msg.what) {
...@@ -361,16 +329,16 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -361,16 +329,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
if (pendingSeekAcks == 0) { if (pendingSeekAcks == 0) {
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
long positionMs = C.usToMs(playbackInfo.startPositionUs);
for (EventListener listener : listeners) { for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(playbackInfo.periodIndex, listener.onPositionDiscontinuity(playbackInfo.periodIndex, positionMs);
C.usToMs(playbackInfo.startPositionUs));
} }
} }
break; break;
} }
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Pair<MediaTimeline, Object> timelineAndManifest = (Pair<MediaTimeline, Object>) msg.obj; Pair<Timeline, Object> timelineAndManifest = (Pair<Timeline, Object>) msg.obj;
timeline = timelineAndManifest.first; timeline = timelineAndManifest.first;
manifest = timelineAndManifest.second; manifest = timelineAndManifest.second;
for (EventListener listener : listeners) { for (EventListener listener : listeners) {
......
...@@ -106,6 +106,8 @@ import java.io.IOException; ...@@ -106,6 +106,8 @@ import java.io.IOException;
private final Handler handler; private final Handler handler;
private final HandlerThread internalPlaybackThread; private final HandlerThread internalPlaybackThread;
private final Handler eventHandler; private final Handler eventHandler;
private final Timeline.Window window;
private final Timeline.Period period;
private PlaybackInfo playbackInfo; private PlaybackInfo playbackInfo;
private Renderer rendererMediaClockSource; private Renderer rendererMediaClockSource;
...@@ -126,11 +128,11 @@ import java.io.IOException; ...@@ -126,11 +128,11 @@ import java.io.IOException;
private boolean isTimelineReady; private boolean isTimelineReady;
private boolean isTimelineEnded; private boolean isTimelineEnded;
private int bufferAheadPeriodCount; private int bufferAheadPeriodCount;
private Period playingPeriod; private MediaPeriodHolder playingPeriodHolder;
private Period readingPeriod; private MediaPeriodHolder readingPeriodHolder;
private Period loadingPeriod; private MediaPeriodHolder loadingPeriodHolder;
private MediaTimeline timeline; private Timeline timeline;
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl, boolean playWhenReady, Handler eventHandler, LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
...@@ -150,6 +152,8 @@ import java.io.IOException; ...@@ -150,6 +152,8 @@ import java.io.IOException;
} }
standaloneMediaClock = new StandaloneMediaClock(); standaloneMediaClock = new StandaloneMediaClock();
enabledRenderers = new Renderer[0]; enabledRenderers = new Renderer[0];
window = new Timeline.Window();
period = new Timeline.Period();
trackSelector.init(this); trackSelector.init(this);
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
...@@ -220,7 +224,7 @@ import java.io.IOException; ...@@ -220,7 +224,7 @@ import java.io.IOException;
// MediaSource.Listener implementation. // MediaSource.Listener implementation.
@Override @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(); handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget();
} }
...@@ -278,7 +282,7 @@ import java.io.IOException; ...@@ -278,7 +282,7 @@ import java.io.IOException;
return true; return true;
} }
case MSG_REFRESH_SOURCE_INFO: { case MSG_REFRESH_SOURCE_INFO: {
handleSourceInfoRefreshed((Pair<MediaTimeline, Object>) msg.obj); handleSourceInfoRefreshed((Pair<Timeline, Object>) msg.obj);
return true; return true;
} }
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: { case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: {
...@@ -375,13 +379,12 @@ import java.io.IOException; ...@@ -375,13 +379,12 @@ import java.io.IOException;
} }
private void updatePlaybackPositions() throws ExoPlaybackException { private void updatePlaybackPositions() throws ExoPlaybackException {
if (playingPeriod == null) { if (playingPeriodHolder == null) {
return; return;
} }
MediaPeriod mediaPeriod = playingPeriod.mediaPeriod;
// Update the playback position. // Update the playback position.
long periodPositionUs = mediaPeriod.readDiscontinuity(); long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity();
if (periodPositionUs != C.TIME_UNSET) { if (periodPositionUs != C.TIME_UNSET) {
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
} else { } else {
...@@ -391,23 +394,24 @@ import java.io.IOException; ...@@ -391,23 +394,24 @@ import java.io.IOException;
} else { } else {
rendererPositionUs = standaloneMediaClock.getPositionUs(); rendererPositionUs = standaloneMediaClock.getPositionUs();
} }
periodPositionUs = rendererPositionUs - playingPeriod.rendererPositionOffsetUs; periodPositionUs = rendererPositionUs - playingPeriodHolder.rendererPositionOffsetUs;
} }
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
// Update the buffered position. // Update the buffered position.
long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE
: mediaPeriod.getBufferedPositionUs(); : playingPeriodHolder.mediaPeriod.getBufferedPositionUs();
playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE 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 { private void doSomeWork() throws ExoPlaybackException, IOException {
long operationStartTimeMs = SystemClock.elapsedRealtime(); long operationStartTimeMs = SystemClock.elapsedRealtime();
updatePeriods(); updatePeriods();
if (playingPeriod == null) { if (playingPeriodHolder == null) {
// We're still waiting for the first period to be prepared. // We're still waiting for the first period to be prepared.
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
...@@ -438,10 +442,11 @@ import java.io.IOException; ...@@ -438,10 +442,11 @@ import java.io.IOException;
maybeThrowPeriodPrepareError(); maybeThrowPeriodPrepareError();
} }
long playingPeriodDuration = timeline.getPeriodDurationUs(playingPeriod.index); long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
.getDurationUs();
if (allRenderersEnded if (allRenderersEnded
&& (playingPeriodDuration == C.TIME_UNSET && (playingPeriodDurationUs == C.TIME_UNSET
|| playingPeriodDuration <= playbackInfo.positionUs) || playingPeriodDurationUs <= playbackInfo.positionUs)
&& isTimelineEnded) { && isTimelineEnded) {
setState(ExoPlayer.STATE_ENDED); setState(ExoPlayer.STATE_ENDED);
stopRenderers(); stopRenderers();
...@@ -526,27 +531,27 @@ import java.io.IOException; ...@@ -526,27 +531,27 @@ import java.io.IOException;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
if (periodPositionUs == C.TIME_UNSET if (periodPositionUs == C.TIME_UNSET
|| (readingPeriod != playingPeriod && (periodIndex == playingPeriod.index || (readingPeriodHolder != playingPeriodHolder && (periodIndex == playingPeriodHolder.index
|| (readingPeriod != null && periodIndex == readingPeriod.index)))) { || (readingPeriodHolder != null && periodIndex == readingPeriodHolder.index)))) {
// Clear the timeline because either the seek position is not known, or a renderer is reading // 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. // ahead to the next period and the seek is to either the playing or reading period.
periodIndex = C.INDEX_UNSET; periodIndex = C.INDEX_UNSET;
} }
// Clear the timeline, but keep the requested period if it is already prepared. // Clear the timeline, but keep the requested period if it is already prepared.
Period period = playingPeriod; MediaPeriodHolder periodHolder = playingPeriodHolder;
Period newPlayingPeriod = null; MediaPeriodHolder newPlayingPeriodHolder = null;
while (period != null) { while (periodHolder != null) {
if (period.index == periodIndex && period.prepared) { if (periodHolder.index == periodIndex && periodHolder.prepared) {
newPlayingPeriod = period; newPlayingPeriodHolder = periodHolder;
} else { } else {
period.release(); periodHolder.release();
} }
period = period.nextPeriod; periodHolder = periodHolder.next;
} }
// Disable all the renderers if the period is changing. // Disable all the renderers if the period is changing.
if (newPlayingPeriod != playingPeriod) { if (newPlayingPeriodHolder != playingPeriodHolder) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.disable(); renderer.disable();
} }
...@@ -557,21 +562,21 @@ import java.io.IOException; ...@@ -557,21 +562,21 @@ import java.io.IOException;
// Update loaded periods. // Update loaded periods.
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
if (newPlayingPeriod != null) { if (newPlayingPeriodHolder != null) {
newPlayingPeriod.nextPeriod = null; newPlayingPeriodHolder.next = null;
setPlayingPeriod(newPlayingPeriod); setPlayingPeriodHolder(newPlayingPeriodHolder);
updateTimelineState(); updateTimelineState();
readingPeriod = playingPeriod; readingPeriodHolder = playingPeriodHolder;
loadingPeriod = playingPeriod; loadingPeriodHolder = playingPeriodHolder;
if (playingPeriod.hasEnabledTracks) { if (playingPeriodHolder.hasEnabledTracks) {
periodPositionUs = playingPeriod.mediaPeriod.seekToUs(periodPositionUs); periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs);
} }
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
maybeContinueLoading(); maybeContinueLoading();
} else { } else {
playingPeriod = null; playingPeriodHolder = null;
readingPeriod = null; readingPeriodHolder = null;
loadingPeriod = null; loadingPeriodHolder = null;
if (periodPositionUs != C.TIME_UNSET) { if (periodPositionUs != C.TIME_UNSET) {
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
} }
...@@ -582,7 +587,8 @@ import java.io.IOException; ...@@ -582,7 +587,8 @@ import java.io.IOException;
} }
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
long periodOffsetUs = playingPeriod == null ? 0 : playingPeriod.rendererPositionOffsetUs; long periodOffsetUs = playingPeriodHolder == null ? 0
: playingPeriodHolder.rendererPositionOffsetUs;
rendererPositionUs = periodOffsetUs + periodPositionUs; rendererPositionUs = periodOffsetUs + periodPositionUs;
standaloneMediaClock.setPositionUs(rendererPositionUs); standaloneMediaClock.setPositionUs(rendererPositionUs);
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
...@@ -624,12 +630,13 @@ import java.io.IOException; ...@@ -624,12 +630,13 @@ import java.io.IOException;
mediaSource.releaseSource(); mediaSource.releaseSource();
mediaSource = null; mediaSource = null;
} }
releasePeriodsFrom(playingPeriod != null ? playingPeriod : loadingPeriod); releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder
: loadingPeriodHolder);
isTimelineReady = false; isTimelineReady = false;
isTimelineEnded = false; isTimelineEnded = false;
playingPeriod = null; playingPeriodHolder = null;
readingPeriod = null; readingPeriodHolder = null;
loadingPeriod = null; loadingPeriodHolder = null;
timeline = null; timeline = null;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
loadControl.onTracksDisabled(); loadControl.onTracksDisabled();
...@@ -660,43 +667,43 @@ import java.io.IOException; ...@@ -660,43 +667,43 @@ import java.io.IOException;
} }
private void reselectTracksInternal() throws ExoPlaybackException { private void reselectTracksInternal() throws ExoPlaybackException {
if (playingPeriod == null) { if (playingPeriodHolder == null) {
// We don't have tracks yet, so we don't care. // We don't have tracks yet, so we don't care.
return; return;
} }
// Reselect tracks on each period in turn, until the selection changes. // Reselect tracks on each period in turn, until the selection changes.
Period period = playingPeriod; MediaPeriodHolder periodHolder = playingPeriodHolder;
boolean selectionsChangedForReadPeriod = true; boolean selectionsChangedForReadPeriod = true;
while (true) { while (true) {
if (period == null || !period.prepared) { if (periodHolder == null || !periodHolder.prepared) {
// The reselection did not change any prepared periods. // The reselection did not change any prepared periods.
return; return;
} }
if (period.selectTracks()) { if (periodHolder.selectTracks()) {
// Selected tracks have changed for this period. // Selected tracks have changed for this period.
break; break;
} }
if (period == readingPeriod) { if (periodHolder == readingPeriodHolder) {
// The track reselection didn't affect any period that has been read. // The track reselection didn't affect any period that has been read.
selectionsChangedForReadPeriod = false; selectionsChangedForReadPeriod = false;
} }
period = period.nextPeriod; periodHolder = periodHolder.next;
} }
if (selectionsChangedForReadPeriod) { if (selectionsChangedForReadPeriod) {
// Release everything after the playing period because a renderer may have read data from a // Release everything after the playing period because a renderer may have read data from a
// track whose selection has now changed. // track whose selection has now changed.
releasePeriodsFrom(playingPeriod.nextPeriod); releasePeriodHoldersFrom(playingPeriodHolder.next);
playingPeriod.nextPeriod = null; playingPeriodHolder.next = null;
readingPeriod = playingPeriod; readingPeriodHolder = playingPeriodHolder;
loadingPeriod = playingPeriod; loadingPeriodHolder = playingPeriodHolder;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
// Update streams for the new selection, recreating all streams if reading ahead. // 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]; boolean[] streamResetFlags = new boolean[renderers.length];
long periodPositionUs = playingPeriod.updatePeriodTrackSelection(playbackInfo.positionUs, long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection(
loadControl, recreateStreams, streamResetFlags); playbackInfo.positionUs, loadControl, recreateStreams, streamResetFlags);
if (periodPositionUs != playbackInfo.positionUs) { if (periodPositionUs != playbackInfo.positionUs) {
playbackInfo.positionUs = periodPositionUs; playbackInfo.positionUs = periodPositionUs;
resetRendererPosition(periodPositionUs); resetRendererPosition(periodPositionUs);
...@@ -707,7 +714,7 @@ import java.io.IOException; ...@@ -707,7 +714,7 @@ import java.io.IOException;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
SampleStream sampleStream = playingPeriod.sampleStreams[i]; SampleStream sampleStream = playingPeriodHolder.sampleStreams[i];
if (sampleStream != null) { if (sampleStream != null) {
enabledRendererCount++; enabledRendererCount++;
} }
...@@ -732,21 +739,21 @@ import java.io.IOException; ...@@ -732,21 +739,21 @@ import java.io.IOException;
} }
} }
} }
trackSelector.onSelectionActivated(playingPeriod.trackSelectionData); trackSelector.onSelectionActivated(playingPeriodHolder.trackSelectionData);
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else { } else {
// Release and re-prepare/buffer periods after the one whose selection changed. // Release and re-prepare/buffer periods after the one whose selection changed.
loadingPeriod = period; loadingPeriodHolder = periodHolder;
period = loadingPeriod.nextPeriod; periodHolder = loadingPeriodHolder.next;
while (period != null) { while (periodHolder != null) {
period.release(); periodHolder.release();
period = period.nextPeriod; periodHolder = periodHolder.next;
bufferAheadPeriodCount--; bufferAheadPeriodCount--;
} }
loadingPeriod.nextPeriod = null; loadingPeriodHolder.next = null;
long loadingPeriodPositionUs = Math.max(0, long loadingPeriodPositionUs = Math.max(0,
rendererPositionUs - loadingPeriod.rendererPositionOffsetUs); rendererPositionUs - loadingPeriodHolder.rendererPositionOffsetUs);
loadingPeriod.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false); loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, loadControl, false);
} }
maybeContinueLoading(); maybeContinueLoading();
updatePlaybackPositions(); updatePlaybackPositions();
...@@ -754,67 +761,72 @@ import java.io.IOException; ...@@ -754,67 +761,72 @@ import java.io.IOException;
} }
private boolean haveSufficientBuffer(boolean rebuffering) { private boolean haveSufficientBuffer(boolean rebuffering) {
if (loadingPeriod == null) { if (loadingPeriodHolder == null) {
return false; return false;
} }
long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs; long loadingPeriodPositionUs = rendererPositionUs
- loadingPeriodHolder.rendererPositionOffsetUs;
long loadingPeriodBufferedPositionUs = long loadingPeriodBufferedPositionUs =
!loadingPeriod.prepared ? 0 : loadingPeriod.mediaPeriod.getBufferedPositionUs(); !loadingPeriodHolder.prepared ? 0 : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) { if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) {
if (loadingPeriod.isLast) { if (loadingPeriodHolder.isLast) {
return true; return true;
} }
loadingPeriodBufferedPositionUs = timeline.getPeriodDurationUs(loadingPeriod.index); loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period)
.getDurationUs();
} }
return loadControl.shouldStartPlayback( return loadControl.shouldStartPlayback(
loadingPeriodBufferedPositionUs - loadingPeriodPositionUs, rebuffering); loadingPeriodBufferedPositionUs - loadingPeriodPositionUs, rebuffering);
} }
private void maybeThrowPeriodPrepareError() throws IOException { private void maybeThrowPeriodPrepareError() throws IOException {
if (loadingPeriod != null && !loadingPeriod.prepared if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared
&& (readingPeriod == null || readingPeriod.nextPeriod == loadingPeriod)) { && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) {
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) { if (!renderer.hasReadStreamToEnd()) {
return; return;
} }
} }
loadingPeriod.mediaPeriod.maybeThrowPrepareError(); loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError();
} }
} }
private void handleSourceInfoRefreshed(Pair<MediaTimeline, Object> timelineAndManifest) private void handleSourceInfoRefreshed(Pair<Timeline, Object> timelineAndManifest)
throws ExoPlaybackException, IOException { throws ExoPlaybackException, IOException {
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget(); eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, timelineAndManifest).sendToTarget();
MediaTimeline oldTimeline = this.timeline; Timeline oldTimeline = this.timeline;
this.timeline = timelineAndManifest.first; this.timeline = timelineAndManifest.first;
// Update the loaded periods to take into account the new timeline. // Update the loaded periods to take into account the new timeline.
if (playingPeriod != null) { if (playingPeriodHolder != null) {
int index = timeline.getIndexOfPeriod(playingPeriod.id); int index = timeline.getIndexOfPeriod(playingPeriodHolder.uid);
if (index == C.INDEX_UNSET) { if (index == C.INDEX_UNSET) {
attemptRestart(timeline, oldTimeline, playingPeriod.index); attemptRestart(timeline, oldTimeline, playingPeriodHolder.index);
return; return;
} }
// The playing period is also in the new timeline. Update the index for each loaded period // 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. // 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; boolean seenReadingPeriod = false;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
while (previousPeriod.nextPeriod != null) { while (previousPeriod.next != null) {
Period period = previousPeriod.nextPeriod; MediaPeriodHolder periodHolder = previousPeriod.next;
index++; index++;
if (!period.id.equals(timeline.getPeriodId(index))) { timeline.getPeriod(index, period, true);
if (!periodHolder.uid.equals(period.uid)) {
if (!seenReadingPeriod) { if (!seenReadingPeriod) {
// Renderers may have read a period that has been removed, so release all loaded periods // 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. // and seek to the current position of the playing period index.
index = playingPeriod.index; index = playingPeriodHolder.index;
releasePeriodsFrom(playingPeriod); releasePeriodHoldersFrom(playingPeriodHolder);
playingPeriod = null; playingPeriodHolder = null;
readingPeriod = null; readingPeriodHolder = null;
loadingPeriod = null; loadingPeriodHolder = null;
long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs); long newPositionUs = seekToPeriodPosition(index, playbackInfo.positionUs);
if (newPositionUs != playbackInfo.positionUs) { if (newPositionUs != playbackInfo.positionUs) {
playbackInfo = new PlaybackInfo(index, newPositionUs); playbackInfo = new PlaybackInfo(index, newPositionUs);
...@@ -824,36 +836,39 @@ import java.io.IOException; ...@@ -824,36 +836,39 @@ import java.io.IOException;
} }
// Update the loading period to be the latest period that is still valid. // Update the loading period to be the latest period that is still valid.
loadingPeriod = previousPeriod; loadingPeriodHolder = previousPeriod;
loadingPeriod.nextPeriod = null; loadingPeriodHolder.next = null;
// Release the rest of the timeline. // Release the rest of the timeline.
releasePeriodsFrom(period); releasePeriodHoldersFrom(periodHolder);
break; break;
} }
bufferAheadPeriodCount++; bufferAheadPeriodCount++;
period.setIndex(timeline, index); int windowIndex = timeline.getPeriod(index, period).windowIndex;
if (period == readingPeriod) { periodHolder.setIndex(timeline, timeline.getWindow(windowIndex, window), index);
if (periodHolder == readingPeriodHolder) {
seenReadingPeriod = true; seenReadingPeriod = true;
} }
previousPeriod = period; previousPeriod = periodHolder;
} }
} else if (loadingPeriod != null) { } else if (loadingPeriodHolder != null) {
Object id = loadingPeriod.id; Object uid = loadingPeriodHolder.uid;
int index = timeline.getIndexOfPeriod(id); int index = timeline.getIndexOfPeriod(uid);
if (index == C.INDEX_UNSET) { if (index == C.INDEX_UNSET) {
attemptRestart(timeline, oldTimeline, loadingPeriod.index); attemptRestart(timeline, oldTimeline, loadingPeriodHolder.index);
return; return;
} else { } 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. // TODO[playlists]: Signal the identifier discontinuity, even if the index hasn't changed.
if (oldTimeline != null) { if (oldTimeline != null) {
int newPlayingIndex = playingPeriod != null ? playingPeriod.index int newPlayingIndex = playingPeriodHolder != null ? playingPeriodHolder.index
: loadingPeriod != null ? loadingPeriod.index : C.INDEX_UNSET; : loadingPeriodHolder != null ? loadingPeriodHolder.index : C.INDEX_UNSET;
if (newPlayingIndex != C.INDEX_UNSET if (newPlayingIndex != C.INDEX_UNSET
&& newPlayingIndex != playbackInfo.periodIndex) { && newPlayingIndex != playbackInfo.periodIndex) {
playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs); playbackInfo = new PlaybackInfo(newPlayingIndex, playbackInfo.positionUs);
...@@ -863,12 +878,13 @@ import java.io.IOException; ...@@ -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 oldPeriodIndex) throws ExoPlaybackException {
int newPeriodIndex = C.INDEX_UNSET; int newPeriodIndex = C.INDEX_UNSET;
while (newPeriodIndex == C.INDEX_UNSET while (newPeriodIndex == C.INDEX_UNSET
&& oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
newPeriodIndex = newTimeline.getIndexOfPeriod(oldTimeline.getPeriodId(++oldPeriodIndex)); newPeriodIndex =
newTimeline.getIndexOfPeriod(oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid);
} }
if (newPeriodIndex == C.INDEX_UNSET) { if (newPeriodIndex == C.INDEX_UNSET) {
// We failed to find a replacement period. Stop the player. // We failed to find a replacement period. Stop the player.
...@@ -877,11 +893,11 @@ import java.io.IOException; ...@@ -877,11 +893,11 @@ import java.io.IOException;
} }
// Release all loaded periods. // Release all loaded periods.
releasePeriodsFrom(playingPeriod); releasePeriodHoldersFrom(playingPeriodHolder);
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
playingPeriod = null; playingPeriodHolder = null;
readingPeriod = null; readingPeriodHolder = null;
loadingPeriod = null; loadingPeriodHolder = null;
// Find the default initial position in the window and seek to it. // Find the default initial position in the window and seek to it.
Pair<Integer, Long> defaultPosition = getDefaultPosition(newPeriodIndex); Pair<Integer, Long> defaultPosition = getDefaultPosition(newPeriodIndex);
...@@ -893,14 +909,16 @@ import java.io.IOException; ...@@ -893,14 +909,16 @@ import java.io.IOException;
} }
private Pair<Integer, Long> getDefaultPosition(int periodIndex) { private Pair<Integer, Long> getDefaultPosition(int periodIndex) {
int windowIndex = timeline.getPeriodWindowIndex(periodIndex); timeline.getPeriod(periodIndex, period);
periodIndex = timeline.getWindowFirstPeriodIndex(windowIndex); timeline.getWindow(period.windowIndex, window);
int maxPeriodIndex = timeline.getWindowLastPeriodIndex(windowIndex); periodIndex = window.firstPeriodIndex;
long periodPositionUs = timeline.getWindowOffsetInFirstPeriodUs(windowIndex) long periodPositionUs = window.getPositionInFirstPeriodUs()
+ timeline.getWindow(windowIndex).defaultStartPositionUs; + window.getDefaultStartPositionUs();
while (periodIndex < maxPeriodIndex timeline.getPeriod(periodIndex, period);
&& periodPositionUs > timeline.getPeriodDurationUs(periodIndex)) { while (periodIndex < window.lastPeriodIndex
periodPositionUs -= timeline.getPeriodDurationUs(periodIndex++); && periodPositionUs > period.getDurationMs()) {
periodPositionUs -= period.getDurationUs();
timeline.getPeriod(periodIndex++, period);
} }
return Pair.create(periodIndex, periodPositionUs); return Pair.create(periodIndex, periodPositionUs);
} }
...@@ -912,18 +930,21 @@ import java.io.IOException; ...@@ -912,18 +930,21 @@ import java.io.IOException;
return; return;
} }
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast if (loadingPeriodHolder == null
|| (loadingPeriodHolder.isFullyBuffered() && !loadingPeriodHolder.isLast
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) { && bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
// We don't have a loading period or it's fully loaded, so try and create the next one. // 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 int newLoadingPeriodIndex = loadingPeriodHolder == null ? playbackInfo.periodIndex
: loadingPeriod.index + 1; : loadingPeriodHolder.index + 1;
if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
// The period is not available yet. // The period is not available yet.
mediaSource.maybeThrowSourceInfoRefreshError(); mediaSource.maybeThrowSourceInfoRefreshError();
} else { } else {
long periodStartPositionUs = loadingPeriod == null ? playbackInfo.positionUs int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
: (newLoadingPeriodIndex == timeline.getWindowFirstPeriodIndex(newLoadingPeriodIndex) boolean isFirstPeriodInWindow = newLoadingPeriodIndex
? C.TIME_UNSET : 0); == timeline.getWindow(windowIndex, window).firstPeriodIndex;
long periodStartPositionUs = loadingPeriodHolder == null ? playbackInfo.positionUs
: (isFirstPeriodInWindow ? C.TIME_UNSET : 0);
if (periodStartPositionUs == C.TIME_UNSET) { 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 // 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. // the default position for the window.
...@@ -931,47 +952,50 @@ import java.io.IOException; ...@@ -931,47 +952,50 @@ import java.io.IOException;
newLoadingPeriodIndex = defaultPosition.first; newLoadingPeriodIndex = defaultPosition.first;
periodStartPositionUs = defaultPosition.second; 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); loadControl.getAllocator(), periodStartPositionUs);
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaSource, MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
mediaPeriod, timeline.getPeriodId(newLoadingPeriodIndex), periodStartPositionUs); trackSelector, mediaSource, newMediaPeriod, newPeriodUid, periodStartPositionUs);
newPeriod.setIndex(timeline, newLoadingPeriodIndex); timeline.getWindow(windowIndex, window);
if (loadingPeriod != null) { newPeriodHolder.setIndex(timeline, window, newLoadingPeriodIndex);
loadingPeriod.setNextPeriod(newPeriod); if (loadingPeriodHolder != null) {
newPeriod.rendererPositionOffsetUs = loadingPeriod.rendererPositionOffsetUs loadingPeriodHolder.setNext(newPeriodHolder);
+ timeline.getPeriodDurationUs(loadingPeriod.index); newPeriodHolder.rendererPositionOffsetUs = loadingPeriodHolder.rendererPositionOffsetUs
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs();
} }
bufferAheadPeriodCount++; bufferAheadPeriodCount++;
loadingPeriod = newPeriod; loadingPeriodHolder = newPeriodHolder;
setIsLoading(true); setIsLoading(true);
} }
} }
if (loadingPeriod == null || loadingPeriod.isFullyBuffered()) { if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
setIsLoading(false); setIsLoading(false);
} else if (loadingPeriod != null && loadingPeriod.needsContinueLoading) { } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) {
maybeContinueLoading(); maybeContinueLoading();
} }
if (playingPeriod == null) { if (playingPeriodHolder == null) {
// We're waiting for the first period to be prepared. // We're waiting for the first period to be prepared.
return; return;
} }
// Update the playing and reading periods. // Update the playing and reading periods.
while (playingPeriod != readingPeriod && playingPeriod.nextPeriod != null while (playingPeriodHolder != readingPeriodHolder && playingPeriodHolder.next != null
&& rendererPositionUs >= playingPeriod.nextPeriod.rendererPositionOffsetUs) { && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) {
// All enabled renderers' streams have been read to the end, and the playback position reached // 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. // the end of the playing period, so advance playback to the next period.
playingPeriod.release(); playingPeriodHolder.release();
setPlayingPeriod(playingPeriod.nextPeriod); setPlayingPeriodHolder(playingPeriodHolder.next);
bufferAheadPeriodCount--; bufferAheadPeriodCount--;
playbackInfo = new PlaybackInfo(playingPeriod.index, playingPeriod.startPositionUs); playbackInfo = new PlaybackInfo(playingPeriodHolder.index,
playingPeriodHolder.startPositionUs);
updatePlaybackPositions(); updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
} }
updateTimelineState(); updateTimelineState();
if (readingPeriod == null) { if (readingPeriodHolder == null) {
// The renderers have their final SampleStreams. // The renderers have their final SampleStreams.
return; return;
} }
...@@ -980,10 +1004,10 @@ import java.io.IOException; ...@@ -980,10 +1004,10 @@ import java.io.IOException;
return; return;
} }
} }
if (readingPeriod.nextPeriod != null && readingPeriod.nextPeriod.prepared) { if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections; TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
readingPeriod = readingPeriod.nextPeriod; readingPeriodHolder = readingPeriodHolder.next;
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections; TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i); TrackSelection oldSelection = oldTrackSelections.get(i);
...@@ -996,8 +1020,8 @@ import java.io.IOException; ...@@ -996,8 +1020,8 @@ import java.io.IOException;
for (int j = 0; j < formats.length; j++) { for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.getFormat(j); formats[j] = newSelection.getFormat(j);
} }
renderer.replaceStream(formats, readingPeriod.sampleStreams[i], renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
readingPeriod.rendererPositionOffsetUs); readingPeriodHolder.rendererPositionOffsetUs);
} else { } else {
// The renderer will be disabled when transitioning to playing the next period. Mark the // The renderer will be disabled when transitioning to playing the next period. Mark the
// SampleStream as final to play out any remaining data. // SampleStream as final to play out any remaining data.
...@@ -1005,8 +1029,8 @@ import java.io.IOException; ...@@ -1005,8 +1029,8 @@ import java.io.IOException;
} }
} }
} }
} else if (readingPeriod.isLast) { } else if (readingPeriodHolder.isLast) {
readingPeriod = null; readingPeriodHolder = null;
for (Renderer renderer : enabledRenderers) { for (Renderer renderer : enabledRenderers) {
renderer.setCurrentStreamIsFinal(); renderer.setCurrentStreamIsFinal();
} }
...@@ -1014,18 +1038,19 @@ import java.io.IOException; ...@@ -1014,18 +1038,19 @@ import java.io.IOException;
} }
private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
// Stale event. // Stale event.
return; return;
} }
loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl); loadingPeriodHolder.handlePrepared(loadingPeriodHolder.startPositionUs, loadControl);
if (playingPeriod == null) { if (playingPeriodHolder == null) {
// This is the first prepared period, so start playing it. // This is the first prepared period, so start playing it.
readingPeriod = loadingPeriod; readingPeriodHolder = loadingPeriodHolder;
setPlayingPeriod(readingPeriod); setPlayingPeriodHolder(readingPeriodHolder);
if (playbackInfo.startPositionUs == C.TIME_UNSET) { if (playbackInfo.startPositionUs == C.TIME_UNSET) {
// Update the playback info when seeking to a default position. // 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); resetRendererPosition(playbackInfo.startPositionUs);
updatePlaybackPositions(); updatePlaybackPositions();
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
...@@ -1036,45 +1061,45 @@ import java.io.IOException; ...@@ -1036,45 +1061,45 @@ import java.io.IOException;
} }
private void handleContinueLoadingRequested(MediaPeriod period) { private void handleContinueLoadingRequested(MediaPeriod period) {
if (loadingPeriod == null || loadingPeriod.mediaPeriod != period) { if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
return; return;
} }
maybeContinueLoading(); maybeContinueLoading();
} }
private void maybeContinueLoading() { private void maybeContinueLoading() {
long nextLoadPositionUs = loadingPeriod.mediaPeriod.getNextLoadPositionUs(); long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) { if (nextLoadPositionUs != C.TIME_END_OF_SOURCE) {
long loadingPeriodPositionUs = rendererPositionUs - loadingPeriod.rendererPositionOffsetUs long loadingPeriodPositionUs = rendererPositionUs
+ loadingPeriod.startPositionUs; - loadingPeriodHolder.rendererPositionOffsetUs + loadingPeriodHolder.startPositionUs;
long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs;
boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs);
setIsLoading(continueLoading); setIsLoading(continueLoading);
if (continueLoading) { if (continueLoading) {
loadingPeriod.needsContinueLoading = false; loadingPeriodHolder.needsContinueLoading = false;
loadingPeriod.mediaPeriod.continueLoading(loadingPeriodPositionUs); loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs);
} else { } else {
loadingPeriod.needsContinueLoading = true; loadingPeriodHolder.needsContinueLoading = true;
} }
} else { } else {
setIsLoading(false); setIsLoading(false);
} }
} }
private void releasePeriodsFrom(Period period) { private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) {
while (period != null) { while (periodHolder != null) {
period.release(); periodHolder.release();
period = period.nextPeriod; periodHolder = periodHolder.next;
} }
} }
private void setPlayingPeriod(Period period) throws ExoPlaybackException { private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
int enabledRendererCount = 0; int enabledRendererCount = 0;
boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
TrackSelection newSelection = period.trackSelections.get(i); TrackSelection newSelection = periodHolder.trackSelections.get(i);
if (newSelection != null) { if (newSelection != null) {
// The renderer should be enabled when playing the new period. // The renderer should be enabled when playing the new period.
enabledRendererCount++; enabledRendererCount++;
...@@ -1091,17 +1116,18 @@ import java.io.IOException; ...@@ -1091,17 +1116,18 @@ import java.io.IOException;
} }
} }
trackSelector.onSelectionActivated(period.trackSelectionData); trackSelector.onSelectionActivated(periodHolder.trackSelectionData);
playingPeriod = period; playingPeriodHolder = periodHolder;
enableRenderers(rendererWasEnabledFlags, enabledRendererCount); enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} }
private void updateTimelineState() { private void updateTimelineState() {
long playingPeriodDurationUs = timeline.getPeriodDurationUs(playingPeriod.index); long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
.getDurationUs();
isTimelineReady = playingPeriodDurationUs == C.TIME_UNSET isTimelineReady = playingPeriodDurationUs == C.TIME_UNSET
|| playbackInfo.positionUs < playingPeriodDurationUs || playbackInfo.positionUs < playingPeriodDurationUs
|| (playingPeriod.nextPeriod != null && playingPeriod.nextPeriod.prepared); || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared);
isTimelineEnded = playingPeriod.isLast; isTimelineEnded = playingPeriodHolder.isLast;
} }
private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount) private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount)
...@@ -1110,7 +1136,7 @@ import java.io.IOException; ...@@ -1110,7 +1136,7 @@ import java.io.IOException;
enabledRendererCount = 0; enabledRendererCount = 0;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection newSelection = playingPeriod.trackSelections.get(i); TrackSelection newSelection = playingPeriodHolder.trackSelections.get(i);
if (newSelection != null) { if (newSelection != null) {
enabledRenderers[enabledRendererCount++] = renderer; enabledRenderers[enabledRendererCount++] = renderer;
if (renderer.getState() == Renderer.STATE_DISABLED) { if (renderer.getState() == Renderer.STATE_DISABLED) {
...@@ -1124,8 +1150,8 @@ import java.io.IOException; ...@@ -1124,8 +1150,8 @@ import java.io.IOException;
formats[j] = newSelection.getFormat(j); formats[j] = newSelection.getFormat(j);
} }
// Enable the renderer. // Enable the renderer.
renderer.enable(formats, playingPeriod.sampleStreams[i], rendererPositionUs, joining, renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs,
playingPeriod.rendererPositionOffsetUs); joining, playingPeriodHolder.rendererPositionOffsetUs);
MediaClock mediaClock = renderer.getMediaClock(); MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) { if (mediaClock != null) {
if (rendererMediaClock != null) { if (rendererMediaClock != null) {
...@@ -1145,12 +1171,12 @@ import java.io.IOException; ...@@ -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 MediaPeriod mediaPeriod;
public final Object id; public final Object uid;
public final SampleStream[] sampleStreams; public final SampleStream[] sampleStreams;
public final boolean[] mayRetainStreamFlags; public final boolean[] mayRetainStreamFlags;
...@@ -1161,7 +1187,7 @@ import java.io.IOException; ...@@ -1161,7 +1187,7 @@ import java.io.IOException;
public boolean prepared; public boolean prepared;
public boolean hasEnabledTracks; public boolean hasEnabledTracks;
public long rendererPositionOffsetUs; public long rendererPositionOffsetUs;
public Period nextPeriod; public MediaPeriodHolder next;
public boolean needsContinueLoading; public boolean needsContinueLoading;
private final Renderer[] renderers; private final Renderer[] renderers;
...@@ -1173,27 +1199,27 @@ import java.io.IOException; ...@@ -1173,27 +1199,27 @@ import java.io.IOException;
private TrackSelectionArray trackSelections; private TrackSelectionArray trackSelections;
private TrackSelectionArray periodTrackSelections; private TrackSelectionArray periodTrackSelections;
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object id, TrackSelector trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid,
long positionUs) { long positionUs) {
this.renderers = renderers; this.renderers = renderers;
this.rendererCapabilities = rendererCapabilities; this.rendererCapabilities = rendererCapabilities;
this.trackSelector = trackSelector; this.trackSelector = trackSelector;
this.mediaSource = mediaSource; this.mediaSource = mediaSource;
this.mediaPeriod = mediaPeriod; this.mediaPeriod = mediaPeriod;
this.id = Assertions.checkNotNull(id); this.uid = Assertions.checkNotNull(uid);
sampleStreams = new SampleStream[renderers.length]; sampleStreams = new SampleStream[renderers.length];
mayRetainStreamFlags = new boolean[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length];
startPositionUs = positionUs; startPositionUs = positionUs;
} }
public void setNextPeriod(Period nextPeriod) { public void setNext(MediaPeriodHolder next) {
this.nextPeriod = nextPeriod; this.next = next;
} }
public void setIndex(MediaTimeline timeline, int index) { public void setIndex(Timeline timeline, Timeline.Window window, int periodIndex) {
this.index = index; this.index = periodIndex;
isLast = index == timeline.getPeriodCount() - 1 && !timeline.getPeriodWindow(index).isDynamic; isLast = index == timeline.getPeriodCount() - 1 && !window.isDynamic;
} }
public boolean isFullyBuffered() { public boolean isFullyBuffered() {
......
/*
* 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);
}
/*
* 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 + "]";
}
}
...@@ -392,24 +392,6 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -392,24 +392,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @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() { public void seekToDefaultPosition() {
player.seekToDefaultPosition(); player.seekToDefaultPosition();
} }
...@@ -450,36 +432,6 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -450,36 +432,6 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @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() { public int getCurrentWindowIndex() {
return player.getCurrentWindowIndex(); return player.getCurrentWindowIndex();
} }
...@@ -505,7 +457,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -505,7 +457,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public MediaTimeline getCurrentTimeline() { public Timeline getCurrentTimeline() {
return player.getCurrentTimeline(); return player.getCurrentTimeline();
} }
......
/*
* 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;
}
}
}
...@@ -17,8 +17,7 @@ package com.google.android.exoplayer2.source; ...@@ -17,8 +17,7 @@ package com.google.android.exoplayer2.source;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaTimeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.MediaWindow;
import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -32,18 +31,18 @@ import java.util.Map; ...@@ -32,18 +31,18 @@ import java.util.Map;
public final class ConcatenatingMediaSource implements MediaSource { public final class ConcatenatingMediaSource implements MediaSource {
private final MediaSource[] mediaSources; private final MediaSource[] mediaSources;
private final MediaTimeline[] timelines; private final Timeline[] timelines;
private final Object[] manifests; private final Object[] manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod; private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private ConcatenatedMediaTimeline timeline; private ConcatenatedTimeline timeline;
/** /**
* @param mediaSources The {@link MediaSource}s to concatenate. * @param mediaSources The {@link MediaSource}s to concatenate.
*/ */
public ConcatenatingMediaSource(MediaSource... mediaSources) { public ConcatenatingMediaSource(MediaSource... mediaSources) {
this.mediaSources = mediaSources; this.mediaSources = mediaSources;
timelines = new MediaTimeline[mediaSources.length]; timelines = new Timeline[mediaSources.length];
manifests = new Object[mediaSources.length]; manifests = new Object[mediaSources.length];
sourceIndexByMediaPeriod = new HashMap<>(); sourceIndexByMediaPeriod = new HashMap<>();
} }
...@@ -55,16 +54,16 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -55,16 +54,16 @@ public final class ConcatenatingMediaSource implements MediaSource {
mediaSources[i].prepareSource(new Listener() { mediaSources[i].prepareSource(new Listener() {
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline sourceTimeline, Object manifest) { public void onSourceInfoRefreshed(Timeline sourceTimeline, Object manifest) {
timelines[index] = sourceTimeline; timelines[index] = sourceTimeline;
manifests[index] = manifest; manifests[index] = manifest;
for (MediaTimeline timeline : timelines) { for (Timeline timeline : timelines) {
if (timeline == null) { if (timeline == null) {
// Don't invoke the listener until all sources have timelines. // Don't invoke the listener until all sources have timelines.
return; return;
} }
} }
timeline = new ConcatenatedMediaTimeline(timelines.clone()); timeline = new ConcatenatedTimeline(timelines.clone());
listener.onSourceInfoRefreshed(timeline, manifests.clone()); listener.onSourceInfoRefreshed(timeline, manifests.clone());
} }
...@@ -105,21 +104,21 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -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[] sourcePeriodOffsets;
private final int[] sourceWindowOffsets; private final int[] sourceWindowOffsets;
public ConcatenatedMediaTimeline(MediaTimeline[] timelines) { public ConcatenatedTimeline(Timeline[] timelines) {
int[] sourcePeriodOffsets = new int[timelines.length]; int[] sourcePeriodOffsets = new int[timelines.length];
int[] sourceWindowOffsets = new int[timelines.length]; int[] sourceWindowOffsets = new int[timelines.length];
int periodCount = 0; int periodCount = 0;
int windowCount = 0; int windowCount = 0;
for (int i = 0; i < timelines.length; i++) { for (int i = 0; i < timelines.length; i++) {
MediaTimeline timeline = timelines[i]; Timeline timeline = timelines[i];
periodCount += timeline.getPeriodCount(); periodCount += timeline.getPeriodCount();
sourcePeriodOffsets[i] = periodCount; sourcePeriodOffsets[i] = periodCount;
windowCount += timeline.getWindowCount(); windowCount += timeline.getWindowCount();
...@@ -131,60 +130,49 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -131,60 +130,49 @@ public final class ConcatenatingMediaSource implements MediaSource {
} }
@Override @Override
public long getAbsoluteStartTime() { public int getWindowCount() {
return timelines[0].getAbsoluteStartTime(); return sourceWindowOffsets[sourceWindowOffsets.length - 1];
}
@Override
public int getPeriodCount() {
return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
}
@Override
public long getPeriodDurationMs(int periodIndex) {
int sourceIndex = getSourceIndexForPeriod(periodIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
return timelines[sourceIndex].getPeriodDurationMs(periodIndex - firstPeriodIndexInSource);
} }
@Override @Override
public long getPeriodDurationUs(int periodIndex) { public Window getWindow(int windowIndex, Window window, boolean setIds) {
int sourceIndex = getSourceIndexForPeriod(periodIndex); int sourceIndex = getSourceIndexForWindow(windowIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
return timelines[sourceIndex].getPeriodDurationUs(periodIndex - firstPeriodIndexInSource); timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
window.firstPeriodIndex += firstPeriodIndexInSource;
window.lastPeriodIndex += firstPeriodIndexInSource;
return window;
} }
@Override @Override
public Object getPeriodId(int periodIndex) { public int getPeriodCount() {
int sourceIndex = getSourceIndexForPeriod(periodIndex); return sourcePeriodOffsets[sourcePeriodOffsets.length - 1];
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 @Override
public int getPeriodWindowIndex(int periodIndex) { public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int sourceIndex = getSourceIndexForPeriod(periodIndex); int sourceIndex = getSourceIndexForPeriod(periodIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(periodIndex); int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
return (sourceIndex > 0 ? sourceWindowOffsets[sourceIndex - 1] : 0) int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
+ timelines[sourceIndex].getPeriodWindowIndex(periodIndex - firstPeriodIndexInSource); timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
period.windowIndex += firstWindowIndexInSource;
if (setIds) {
period.uid = Pair.create(sourceIndex, period.uid);
}
return period;
} }
@Override @Override
public int getIndexOfPeriod(Object id) { public int getIndexOfPeriod(Object uid) {
if (!(id instanceof Pair)) { if (!(uid instanceof Pair)) {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
Pair sourceIndexAndPeriodId = (Pair) id; Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
if (!(sourceIndexAndPeriodId.first instanceof Integer)) { if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
int sourceIndex = (int) sourceIndexAndPeriodId.first; int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
Object periodId = sourceIndexAndPeriodId.second; Object periodId = sourceIndexAndPeriodId.second;
if (sourceIndex < 0 || sourceIndex >= timelines.length) { if (sourceIndex < 0 || sourceIndex >= timelines.length) {
return C.INDEX_UNSET; return C.INDEX_UNSET;
...@@ -194,44 +182,6 @@ public final class ConcatenatingMediaSource implements MediaSource { ...@@ -194,44 +182,6 @@ public final class ConcatenatingMediaSource implements MediaSource {
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; : 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) { private int getSourceIndexForPeriod(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
} }
......
...@@ -299,7 +299,7 @@ import java.util.Arrays; ...@@ -299,7 +299,7 @@ import java.util.Arrays;
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
sourceListener.onSourceInfoRefreshed( sourceListener.onSourceInfoRefreshed(
new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null); new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
} }
} }
...@@ -382,7 +382,7 @@ import java.util.Arrays; ...@@ -382,7 +382,7 @@ import java.util.Arrays;
tracks = new TrackGroupArray(trackArray); tracks = new TrackGroupArray(trackArray);
prepared = true; prepared = true;
sourceListener.onSourceInfoRefreshed( sourceListener.onSourceInfoRefreshed(
new SinglePeriodMediaTimeline(durationUs, seekMap.isSeekable()), null); new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
callback.onPrepared(this); callback.onPrepared(this);
} }
......
...@@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source; ...@@ -18,8 +18,8 @@ package com.google.android.exoplayer2.source;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.ParserException; 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.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
...@@ -92,9 +92,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List ...@@ -92,9 +92,11 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private final Timeline.Period period;
private MediaSource.Listener sourceListener; private MediaSource.Listener sourceListener;
private MediaTimeline timeline; private Timeline timeline;
private boolean timelineHasDuration;
/** /**
* @param uri The {@link Uri} of the media stream. * @param uri The {@link Uri} of the media stream.
...@@ -130,12 +132,13 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List ...@@ -130,12 +132,13 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
period = new Timeline.Period();
} }
@Override @Override
public void prepareSource(MediaSource.Listener listener) { public void prepareSource(MediaSource.Listener listener) {
sourceListener = listener; sourceListener = listener;
timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false); timeline = new SinglePeriodTimeline(C.TIME_UNSET, false);
listener.onSourceInfoRefreshed(timeline, null); listener.onSourceInfoRefreshed(timeline, null);
} }
...@@ -166,13 +169,15 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List ...@@ -166,13 +169,15 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
// MediaSource.Listener implementation. // MediaSource.Listener implementation.
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) {
if (this.timeline.getPeriodDurationUs(0) != C.TIME_UNSET long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs();
&& timeline.getPeriodDurationUs(0) == C.TIME_UNSET) { boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET;
if (timelineHasDuration && !newTimelineHasDuration) {
// Suppress source info changes that would make the duration unknown when it is already known. // Suppress source info changes that would make the duration unknown when it is already known.
return; return;
} }
this.timeline = timeline; timeline = newTimeline;
timelineHasDuration = newTimelineHasDuration;
sourceListener.onSourceInfoRefreshed(timeline, null); sourceListener.onSourceInfoRefreshed(timeline, null);
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.source; 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.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException; import java.io.IOException;
...@@ -36,7 +36,7 @@ public interface MediaSource { ...@@ -36,7 +36,7 @@ public interface MediaSource {
* @param timeline The source's timeline. * @param timeline The source's timeline.
* @param manifest The loaded manifest. * @param manifest The loaded manifest.
*/ */
void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest); void onSourceInfoRefreshed(Timeline timeline, Object manifest);
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer2.source; 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.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -31,6 +31,7 @@ public final class MergingMediaSource implements MediaSource { ...@@ -31,6 +31,7 @@ public final class MergingMediaSource implements MediaSource {
private static final int PERIOD_COUNT_UNSET = -1; private static final int PERIOD_COUNT_UNSET = -1;
private final MediaSource[] mediaSources; private final MediaSource[] mediaSources;
private final Timeline.Window window;
private int periodCount; private int periodCount;
...@@ -39,30 +40,26 @@ public final class MergingMediaSource implements MediaSource { ...@@ -39,30 +40,26 @@ public final class MergingMediaSource implements MediaSource {
*/ */
public MergingMediaSource(MediaSource... mediaSources) { public MergingMediaSource(MediaSource... mediaSources) {
this.mediaSources = mediaSources; this.mediaSources = mediaSources;
window = new Timeline.Window();
periodCount = PERIOD_COUNT_UNSET; periodCount = PERIOD_COUNT_UNSET;
} }
@Override @Override
public void prepareSource(final Listener listener) { public void prepareSource(final Listener listener) {
mediaSources[0].prepareSource(new Listener() { mediaSources[0].prepareSource(new Listener() {
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
checkConsistentTimeline(timeline); checkConsistentTimeline(timeline);
// All source timelines must match. // All source timelines must match.
listener.onSourceInfoRefreshed(timeline, manifest); listener.onSourceInfoRefreshed(timeline, manifest);
} }
}); });
for (int i = 1; i < mediaSources.length; i++) { for (int i = 1; i < mediaSources.length; i++) {
mediaSources[i].prepareSource(new Listener() { mediaSources[i].prepareSource(new Listener() {
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
checkConsistentTimeline(timeline); checkConsistentTimeline(timeline);
} }
}); });
} }
} }
...@@ -102,10 +99,10 @@ public final class MergingMediaSource implements MediaSource { ...@@ -102,10 +99,10 @@ public final class MergingMediaSource implements MediaSource {
} }
} }
private void checkConsistentTimeline(MediaTimeline timeline) { private void checkConsistentTimeline(Timeline timeline) {
int windowCount = timeline.getWindowCount(); int windowCount = timeline.getWindowCount();
for (int i = 0; i < windowCount; i++) { 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(); int periodCount = timeline.getPeriodCount();
if (this.periodCount == PERIOD_COUNT_UNSET) { if (this.periodCount == PERIOD_COUNT_UNSET) {
......
/*
* 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;
}
}
/*
* 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;
}
}
...@@ -18,7 +18,7 @@ package com.google.android.exoplayer2.source; ...@@ -18,7 +18,7 @@ package com.google.android.exoplayer2.source;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; 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.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
...@@ -57,7 +57,7 @@ public final class SingleSampleMediaSource implements MediaSource { ...@@ -57,7 +57,7 @@ public final class SingleSampleMediaSource implements MediaSource {
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private final int eventSourceId; private final int eventSourceId;
private final MediaTimeline timeline; private final Timeline timeline;
public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format,
long durationUs) { long durationUs) {
...@@ -79,7 +79,7 @@ public final class SingleSampleMediaSource implements MediaSource { ...@@ -79,7 +79,7 @@ public final class SingleSampleMediaSource implements MediaSource {
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.eventSourceId = eventSourceId; this.eventSourceId = eventSourceId;
timeline = new SinglePeriodMediaTimeline(durationUs, true); timeline = new SinglePeriodTimeline(durationUs, true);
} }
// MediaSource implementation. // MediaSource implementation.
......
...@@ -21,9 +21,8 @@ import android.os.SystemClock; ...@@ -21,9 +21,8 @@ import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C; 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.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
...@@ -71,8 +70,8 @@ public final class DashMediaSource implements MediaSource { ...@@ -71,8 +70,8 @@ public final class DashMediaSource implements MediaSource {
public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000; public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000;
/** /**
* The interval in milliseconds between invocations of * The interval in milliseconds between invocations of
* {@link MediaSource.Listener#onSourceInfoRefreshed(MediaTimeline, Object)} when the source's * {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's
* {@link MediaWindow} is changing dynamically (for example, for incomplete live streams). * {@link Timeline} is changing dynamically (for example, for incomplete live streams).
*/ */
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000; private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
...@@ -99,7 +98,6 @@ public final class DashMediaSource implements MediaSource { ...@@ -99,7 +98,6 @@ public final class DashMediaSource implements MediaSource {
private long manifestLoadEndTimestamp; private long manifestLoadEndTimestamp;
private DashManifest manifest; private DashManifest manifest;
private Handler handler; private Handler handler;
private MediaWindow window;
private long elapsedRealtimeOffsetMs; private long elapsedRealtimeOffsetMs;
private int firstPeriodId; private int firstPeriodId;
...@@ -360,12 +358,12 @@ public final class DashMediaSource implements MediaSource { ...@@ -360,12 +358,12 @@ public final class DashMediaSource implements MediaSource {
if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) { if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) {
// The manifest describes an incomplete live stream. Update the start/end times to reflect the // 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. // live stream duration and the manifest's time shift buffer depth.
long liveStreamDurationUs = getNowUnixTimeUs() - manifest.availabilityStartTime * 1000; long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime);
long liveStreamEndPositionInLastPeriodUs = long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs
liveStreamDurationUs - manifest.getPeriod(lastPeriodIndex).startMs * 1000; - C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs);
currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs);
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
long timeShiftBufferDepthUs = manifest.timeShiftBufferDepth * 1000; long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth);
long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs;
int periodIndex = lastPeriodIndex; int periodIndex = lastPeriodIndex;
while (offsetInPeriodUs < 0 && periodIndex > 0) { while (offsetInPeriodUs < 0 && periodIndex > 0) {
...@@ -386,7 +384,7 @@ public final class DashMediaSource implements MediaSource { ...@@ -386,7 +384,7 @@ public final class DashMediaSource implements MediaSource {
for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { for (int i = 0; i < manifest.getPeriodCount() - 1; i++) {
windowDurationUs += manifest.getPeriodDurationUs(i); windowDurationUs += manifest.getPeriodDurationUs(i);
} }
long defaultInitialTimeUs = 0; long windowDefaultStartPositionUs = 0;
if (manifest.dynamic) { if (manifest.dynamic) {
long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs; long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs;
if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) { if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) {
...@@ -395,7 +393,7 @@ public final class DashMediaSource implements MediaSource { ...@@ -395,7 +393,7 @@ public final class DashMediaSource implements MediaSource {
} }
// Snap the default position to the start of the segment containing it. // Snap the default position to the start of the segment containing it.
long initialTimeOffsetInWindowUs = long initialTimeOffsetInWindowUs =
Math.max(0, windowDurationUs - (liveEdgeOffsetForManifestMs * 1000)); Math.max(0, windowDurationUs - C.msToUs(liveEdgeOffsetForManifestMs));
int periodIndex = 0; int periodIndex = 0;
long initialTimeOffsetInPeriodUs = currentStartTimeUs + initialTimeOffsetInWindowUs; long initialTimeOffsetInPeriodUs = currentStartTimeUs + initialTimeOffsetInWindowUs;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
...@@ -413,16 +411,18 @@ public final class DashMediaSource implements MediaSource { ...@@ -413,16 +411,18 @@ public final class DashMediaSource implements MediaSource {
DashSegmentIndex index = DashSegmentIndex index =
period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex(); period.adaptationSets.get(videoAdaptationSetIndex).representations.get(0).getIndex();
int segmentNum = index.getSegmentNum(initialTimeOffsetInPeriodUs, periodDurationUs); int segmentNum = index.getSegmentNum(initialTimeOffsetInPeriodUs, periodDurationUs);
defaultInitialTimeUs = windowDefaultStartPositionUs =
initialTimeOffsetInWindowUs - initialTimeOffsetInPeriodUs + index.getTimeUs(segmentNum); initialTimeOffsetInWindowUs - initialTimeOffsetInPeriodUs + index.getTimeUs(segmentNum);
} else { } else {
defaultInitialTimeUs = initialTimeOffsetInWindowUs; windowDefaultStartPositionUs = initialTimeOffsetInWindowUs;
} }
} }
window = new MediaWindow(windowDurationUs, true /* isSeekable */, manifest.dynamic, long windowStartTimeMs = manifest.availabilityStartTime
defaultInitialTimeUs); + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs);
sourceListener.onSourceInfoRefreshed(new DashMediaTimeline(firstPeriodId, currentStartTimeUs, DashTimeline timeline = new DashTimeline(manifest.availabilityStartTime, windowStartTimeMs,
manifest, window), manifest); firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs,
manifest);
sourceListener.onSourceInfoRefreshed(timeline, manifest);
} }
private void scheduleManifestRefresh() { private void scheduleManifestRefresh() {
...@@ -450,9 +450,9 @@ public final class DashMediaSource implements MediaSource { ...@@ -450,9 +450,9 @@ public final class DashMediaSource implements MediaSource {
private long getNowUnixTimeUs() { private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetMs != 0) { if (elapsedRealtimeOffsetMs != 0) {
return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000; return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs);
} else { } else {
return System.currentTimeMillis() * 1000; return C.msToUs(System.currentTimeMillis());
} }
} }
...@@ -462,7 +462,8 @@ public final class DashMediaSource implements MediaSource { ...@@ -462,7 +462,8 @@ public final class DashMediaSource implements MediaSource {
private static final class PeriodSeekInfo { 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(); int adaptationSetCount = period.adaptationSets.size();
long availableStartTimeUs = 0; long availableStartTimeUs = 0;
long availableEndTimeUs = Long.MAX_VALUE; long availableEndTimeUs = Long.MAX_VALUE;
...@@ -501,24 +502,27 @@ public final class DashMediaSource implements MediaSource { ...@@ -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 int firstPeriodId;
private final long offsetInFirstPeriodUs; private final long offsetInFirstPeriodUs;
private final long windowDurationUs;
private final long windowDefaultStartPositionUs;
private final DashManifest manifest; private final DashManifest manifest;
private final MediaWindow window;
public DashMediaTimeline(int firstPeriodId, long offsetInFirstPeriodUs, DashManifest manifest, public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs,
MediaWindow window) { int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs,
long windowDefaultStartPositionUs, DashManifest manifest) {
this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs;
this.firstPeriodId = firstPeriodId; this.firstPeriodId = firstPeriodId;
this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; this.offsetInFirstPeriodUs = offsetInFirstPeriodUs;
this.windowDurationUs = windowDurationUs;
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
this.manifest = manifest; this.manifest = manifest;
this.window = window;
}
@Override
public long getAbsoluteStartTime() {
return manifest.availabilityStartTime + manifest.getPeriod(0).startMs;
} }
@Override @Override
...@@ -527,70 +531,39 @@ public final class DashMediaSource implements MediaSource { ...@@ -527,70 +531,39 @@ public final class DashMediaSource implements MediaSource {
} }
@Override @Override
public long getPeriodDurationMs(int periodIndex) { public Period getPeriod(int periodIndex, Period period, boolean setIdentifiers) {
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount());
return manifest.getPeriodDurationMs(periodIndex); 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 @Override
public long getPeriodDurationUs(int periodIndex) { public int getWindowCount() {
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); return 1;
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 @Override
public int getPeriodWindowIndex(int periodIndex) { public Window getWindow(int windowIndex, Window window, boolean setIdentifier) {
Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); Assertions.checkIndex(windowIndex, 0, 1);
return 0; return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */,
manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0,
manifest.getPeriodCount() - 1, offsetInFirstPeriodUs);
} }
@Override @Override
public int getIndexOfPeriod(Object id) { public int getIndexOfPeriod(Object uid) {
if (!(id instanceof Integer)) { if (!(uid instanceof Integer)) {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
int periodId = (int) id; int periodId = (int) uid;
return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount() return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount()
? C.INDEX_UNSET : (periodId - firstPeriodId); ? C.INDEX_UNSET : (periodId - firstPeriodId);
} }
@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) {
return 0;
}
@Override
public int getWindowLastPeriodIndex(int windowIndex) {
return manifest.getPeriodCount() - 1;
}
@Override
public long getWindowOffsetInFirstPeriodUs(int windowIndex) {
return offsetInFirstPeriodUs;
}
} }
private final class ManifestCallback implements private final class ManifestCallback implements
......
...@@ -19,14 +19,14 @@ import android.net.Uri; ...@@ -19,14 +19,14 @@ import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.ParserException; 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.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.CompositeSequenceableLoader; import com.google.android.exoplayer2.source.CompositeSequenceableLoader;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream; 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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
...@@ -282,7 +282,7 @@ import java.util.List; ...@@ -282,7 +282,7 @@ import java.util.List;
callback.onPrepared(this); callback.onPrepared(this);
// TODO[playlists]: Calculate the window. // TODO[playlists]: Calculate the window.
MediaTimeline timeline = new SinglePeriodMediaTimeline(durationUs, !isLive); Timeline timeline = new SinglePeriodTimeline(durationUs, !isLive);
sourceListener.onSourceInfoRefreshed(timeline, playlist); sourceListener.onSourceInfoRefreshed(timeline, playlist);
} }
......
...@@ -23,7 +23,7 @@ import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.Eve ...@@ -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;
import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource; 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.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -64,7 +64,7 @@ public final class HlsMediaSource implements MediaSource { ...@@ -64,7 +64,7 @@ public final class HlsMediaSource implements MediaSource {
public void prepareSource(MediaSource.Listener listener) { public void prepareSource(MediaSource.Listener listener) {
sourceListener = listener; sourceListener = listener;
// TODO: Defer until the playlist has been loaded. // 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 @Override
......
...@@ -19,15 +19,14 @@ import android.net.Uri; ...@@ -19,15 +19,14 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.android.exoplayer2.C; 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.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaPeriod.Callback; import com.google.android.exoplayer2.source.MediaPeriod.Callback;
import com.google.android.exoplayer2.source.MediaSource; 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;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
...@@ -157,7 +156,7 @@ public final class SsMediaSource implements MediaSource, ...@@ -157,7 +156,7 @@ public final class SsMediaSource implements MediaSource,
for (int i = 0; i < mediaPeriods.size(); i++) { for (int i = 0; i < mediaPeriods.size(); i++) {
mediaPeriods.get(i).updateManifest(manifest); mediaPeriods.get(i).updateManifest(manifest);
} }
MediaTimeline timeline; Timeline timeline;
if (manifest.isLive) { if (manifest.isLive) {
long startTimeUs = Long.MAX_VALUE; long startTimeUs = Long.MAX_VALUE;
long endTimeUs = Long.MIN_VALUE; long endTimeUs = Long.MIN_VALUE;
...@@ -170,21 +169,20 @@ public final class SsMediaSource implements MediaSource, ...@@ -170,21 +169,20 @@ public final class SsMediaSource implements MediaSource,
} }
} }
if (startTimeUs == Long.MAX_VALUE) { if (startTimeUs == Long.MAX_VALUE) {
timeline = new SinglePeriodMediaTimeline(C.TIME_UNSET, false); timeline = new SinglePeriodTimeline(C.TIME_UNSET, false);
} else { } else {
if (manifest.dvrWindowLengthUs != C.TIME_UNSET if (manifest.dvrWindowLengthUs != C.TIME_UNSET
&& manifest.dvrWindowLengthUs > 0) { && manifest.dvrWindowLengthUs > 0) {
startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs);
} }
long durationUs = endTimeUs - startTimeUs; long durationUs = endTimeUs - startTimeUs;
long defaultInitialStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000)); long defaultStartPositionUs = Math.max(0, durationUs - (liveEdgeOffsetMs * 1000));
MediaWindow window = new MediaWindow(durationUs, true /* isSeekable */, timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs,
true /* isDynamic */, defaultInitialStartPositionUs); defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */);
timeline = new SinglePeriodMediaTimeline(startTimeUs, window);
} }
} else { } else {
boolean isSeekable = manifest.durationUs != C.TIME_UNSET; boolean isSeekable = manifest.durationUs != C.TIME_UNSET;
timeline = new SinglePeriodMediaTimeline(manifest.durationUs, isSeekable); timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable);
} }
sourceListener.onSourceInfoRefreshed(timeline, manifest); sourceListener.onSourceInfoRefreshed(timeline, manifest);
scheduleManifestRefresh(); scheduleManifestRefresh();
......
...@@ -19,8 +19,8 @@ import android.widget.TextView; ...@@ -19,8 +19,8 @@ import android.widget.TextView;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
/** /**
...@@ -89,7 +89,7 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -89,7 +89,7 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
} }
@Override @Override
public void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
...@@ -17,15 +17,14 @@ package com.google.android.exoplayer2.ui; ...@@ -17,15 +17,14 @@ package com.google.android.exoplayer2.ui;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; 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 * An {@link OnClickListener} that can be passed to
* {@link android.widget.MediaController#setPrevNextListeners(OnClickListener, OnClickListener)} 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 * 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 { public class MediaControllerPrevNextClickListener implements OnClickListener {
...@@ -50,15 +49,15 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { ...@@ -50,15 +49,15 @@ public class MediaControllerPrevNextClickListener implements OnClickListener {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
int currentWindowIndex = player.getCurrentWindowIndex(); Timeline timeline = player.getCurrentTimeline();
if (currentWindowIndex == C.INDEX_UNSET) { if (timeline == null) {
return; return;
} }
MediaTimeline timeline = player.getCurrentTimeline(); int currentWindowIndex = player.getCurrentWindowIndex();
if (isNext) { if (isNext) {
if (currentWindowIndex < timeline.getWindowCount() - 1) { if (currentWindowIndex < timeline.getWindowCount() - 1) {
player.seekToDefaultPosition(currentWindowIndex + 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. // Seek to the live edge.
player.seekToDefaultPosition(); player.seekToDefaultPosition();
} }
...@@ -67,7 +66,7 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { ...@@ -67,7 +66,7 @@ public class MediaControllerPrevNextClickListener implements OnClickListener {
&& player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS) { && player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS) {
player.seekToDefaultPosition(currentWindowIndex - 1); player.seekToDefaultPosition(currentWindowIndex - 1);
} else { } else {
player.seekTo(currentWindowIndex, 0); player.seekTo(0);
} }
} }
} }
......
...@@ -24,8 +24,8 @@ import com.google.android.exoplayer2.ExoPlaybackException; ...@@ -24,8 +24,8 @@ import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaTimeline;
import com.google.android.exoplayer2.SimpleExoPlayer; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
...@@ -215,7 +215,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -215,7 +215,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
} }
@Override @Override
public final void onSourceInfoRefreshed(MediaTimeline timeline, Object manifest) { public final void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
// Do nothing. // Do nothing.
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment