Commit 2073f3fc by andrewlewis Committed by Oliver Woodman

Expose source indices via ExoPlayer (playlists #5).

ExoPlayer.EventListener.onPositionDiscontinuity is notified during seeking and
transitioning from one source to the next.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=125578976
parent f9fa54cd
...@@ -73,6 +73,11 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb ...@@ -73,6 +73,11 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
Log.d(TAG, "discontinuity [" + sourceIndex + ", " + positionMs + "]");
}
@Override
public void onPlayWhenReadyCommitted() { public void onPlayWhenReadyCommitted() {
// Do nothing. // Do nothing.
} }
......
...@@ -392,6 +392,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -392,6 +392,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
if (mediaController.isShowing()) {
// The MediaController is visible, so force it to show the updated position immediately.
mediaController.show();
}
}
@Override
public void onPlayerError(ExoPlaybackException e) { public void onPlayerError(ExoPlaybackException e) {
String errorString = null; String errorString = null;
if (e.type == ExoPlaybackException.TYPE_RENDERER) { if (e.type == ExoPlaybackException.TYPE_RENDERER) {
......
...@@ -93,6 +93,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { ...@@ -93,6 +93,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
playbackException = error; playbackException = error;
} }
......
...@@ -93,6 +93,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { ...@@ -93,6 +93,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
playbackException = error; playbackException = error;
} }
......
...@@ -112,6 +112,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { ...@@ -112,6 +112,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
playbackException = error; playbackException = error;
} }
......
...@@ -118,6 +118,16 @@ public interface ExoPlayer { ...@@ -118,6 +118,16 @@ public interface ExoPlayer {
*/ */
void onPlayWhenReadyCommitted(); void onPlayWhenReadyCommitted();
// TODO[playlists]: Should source-initiated resets also cause this to be invoked?
/**
* Invoked when the player's position changes due to a discontinuity (seeking or playback
* transitioning to the next source).
*
* @param sourceIndex The index of the source being played.
* @param positionMs The playback position in that source, in milliseconds.
*/
void onPositionDiscontinuity(int sourceIndex, long positionMs);
/** /**
* Invoked when an error occurs. The playback state will transition to * Invoked when an error occurs. The playback state will transition to
* {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance * {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance
...@@ -227,8 +237,9 @@ public interface ExoPlayer { ...@@ -227,8 +237,9 @@ public interface ExoPlayer {
void setSource(SampleSource sampleSource); void setSource(SampleSource sampleSource);
/** /**
* Sets the player's source provider. The player will transition to {@link #STATE_BUFFERING} until * Sets the player's source provider. The player's position will be reset to the start of the
* it is ready to play the first source. * first source and the player will transition to {@link #STATE_BUFFERING} until it is ready to
* play it.
* *
* @param sourceProvider The provider of {@link SampleSource}s to play. * @param sourceProvider The provider of {@link SampleSource}s to play.
*/ */
...@@ -259,13 +270,21 @@ public interface ExoPlayer { ...@@ -259,13 +270,21 @@ public interface ExoPlayer {
boolean isPlayWhenReadyCommitted(); boolean isPlayWhenReadyCommitted();
/** /**
* Seeks to a position specified in milliseconds. * Seeks to a position specified in milliseconds in the current source.
* *
* @param positionMs The seek position. * @param positionMs The seek position.
*/ */
void seekTo(long positionMs); void seekTo(long positionMs);
/** /**
* Seeks to a position specified in milliseconds in the specified source.
*
* @param sourceIndex The index of the source to seek to.
* @param positionMs The seek position relative to the start of the specified source.
*/
void seekTo(int sourceIndex, long positionMs);
/**
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
* is to pause playback. * is to pause playback.
* <p> * <p>
...@@ -312,13 +331,20 @@ public interface ExoPlayer { ...@@ -312,13 +331,20 @@ public interface ExoPlayer {
long getDuration(); long getDuration();
/** /**
* Gets the current playback position in milliseconds. * Gets the playback position in the current source, in milliseconds.
* *
* @return The current playback position in milliseconds. * @return The playback position in the current source, in milliseconds.
*/ */
long getCurrentPosition(); long getCurrentPosition();
/** /**
* Gets the index of the current source.
*
* @return The index of the current source.
*/
int getCurrentSourceIndex();
/**
* Gets an estimate of the absolute position in milliseconds up to which data is buffered. * Gets an estimate of the absolute position in milliseconds up to which data is buffered.
* *
* @return An estimate of the absolute position in milliseconds up to which data is buffered, * @return An estimate of the absolute position in milliseconds up to which data is buffered,
......
...@@ -39,6 +39,15 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -39,6 +39,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
private boolean playWhenReady; private boolean playWhenReady;
private int playbackState; private int playbackState;
private int pendingPlayWhenReadyAcks; private int pendingPlayWhenReadyAcks;
private int pendingSetSourceProviderAndSeekAcks;
// Playback information when there is no pending seek/set source operation.
private ExoPlayerImplInternal.PlaybackInfo playbackInfo;
// Playback information when there is a pending seek/set source operation.
private int sourceIndex;
private long position;
private long duration;
/** /**
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}. * Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
...@@ -68,6 +77,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -68,6 +77,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}; };
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs, internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs,
playWhenReady, eventHandler); playWhenReady, eventHandler);
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
} }
@Override @Override
...@@ -87,12 +97,20 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -87,12 +97,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public void setSource(final SampleSource sampleSource) { public void setSource(final SampleSource sampleSource) {
internalPlayer.setSourceProvider(new SingleSampleSourceProvider(sampleSource)); setSourceProvider(new SingleSampleSourceProvider(sampleSource));
} }
@Override @Override
public void setSourceProvider(SampleSourceProvider sourceProvider) { public void setSourceProvider(SampleSourceProvider sourceProvider) {
duration = ExoPlayer.UNKNOWN_TIME;
position = 0;
sourceIndex = 0;
pendingSetSourceProviderAndSeekAcks++;
internalPlayer.setSourceProvider(sourceProvider); internalPlayer.setSourceProvider(sourceProvider);
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(sourceIndex, position);
}
} }
@Override @Override
...@@ -119,7 +137,20 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -119,7 +137,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public void seekTo(long positionMs) { public void seekTo(long positionMs) {
internalPlayer.seekTo(positionMs); seekTo(getCurrentSourceIndex(), positionMs);
}
@Override
public void seekTo(int sourceIndex, long positionMs) {
duration = sourceIndex == getCurrentSourceIndex() ? getDuration() : ExoPlayer.UNKNOWN_TIME;
position = positionMs;
this.sourceIndex = sourceIndex;
pendingSetSourceProviderAndSeekAcks++;
internalPlayer.seekTo(sourceIndex, position);
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(sourceIndex, position);
}
} }
@Override @Override
...@@ -145,17 +176,33 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -145,17 +176,33 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public long getDuration() { public long getDuration() {
return internalPlayer.getDuration(); if (pendingSetSourceProviderAndSeekAcks == 0) {
long durationUs = playbackInfo.durationUs;
return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : durationUs / 1000;
} else {
return duration;
}
} }
@Override @Override
public long getCurrentPosition() { public long getCurrentPosition() {
return internalPlayer.getCurrentPosition(); return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.positionUs / 1000 : position;
}
@Override
public int getCurrentSourceIndex() {
return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.sourceIndex : sourceIndex;
} }
@Override @Override
public long getBufferedPosition() { public long getBufferedPosition() {
return internalPlayer.getBufferedPosition(); if (pendingSetSourceProviderAndSeekAcks == 0) {
long bufferedPositionUs = playbackInfo.bufferedPositionUs;
return bufferedPositionUs == C.UNSET_TIME_US || bufferedPositionUs == C.END_OF_SOURCE_US
? ExoPlayer.UNKNOWN_TIME : bufferedPositionUs / 1000;
} else {
return position;
}
} }
@Override @Override
...@@ -185,6 +232,20 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -185,6 +232,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
break; break;
} }
case ExoPlayerImplInternal.MSG_SET_SOURCE_PROVIDER_ACK: // Fall through.
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
pendingSetSourceProviderAndSeekAcks--;
break;
}
case ExoPlayerImplInternal.MSG_SOURCE_CHANGED: {
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
if (pendingSetSourceProviderAndSeekAcks == 0) {
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(playbackInfo.sourceIndex, 0);
}
}
break;
}
case ExoPlayerImplInternal.MSG_ERROR: { case ExoPlayerImplInternal.MSG_ERROR: {
ExoPlaybackException exception = (ExoPlaybackException) msg.obj; ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
for (EventListener listener : listeners) { for (EventListener listener : listeners) {
......
...@@ -31,7 +31,6 @@ import android.util.Pair; ...@@ -31,7 +31,6 @@ import android.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Implements the internal behavior of {@link ExoPlayerImpl}. * Implements the internal behavior of {@link ExoPlayerImpl}.
...@@ -40,12 +39,35 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -40,12 +39,35 @@ import java.util.concurrent.atomic.AtomicInteger;
// always propagated properly. // always propagated properly.
/* package */ final class ExoPlayerImplInternal implements Handler.Callback, InvalidationListener { /* package */ final class ExoPlayerImplInternal implements Handler.Callback, InvalidationListener {
/**
* Playback position information which is read on the application's thread by
* {@link ExoPlayerImpl} and read/written internally on the player's thread.
*/
public static final class PlaybackInfo {
public final int sourceIndex;
public volatile long positionUs;
public volatile long bufferedPositionUs;
public volatile long durationUs;
public PlaybackInfo(int sourceIndex) {
this.sourceIndex = sourceIndex;
bufferedPositionUs = C.UNSET_TIME_US;
durationUs = C.UNSET_TIME_US;
}
}
private static final String TAG = "ExoPlayerImplInternal"; private static final String TAG = "ExoPlayerImplInternal";
// External messages // External messages
public static final int MSG_STATE_CHANGED = 1; public static final int MSG_STATE_CHANGED = 1;
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 2; public static final int MSG_SET_PLAY_WHEN_READY_ACK = 2;
public static final int MSG_ERROR = 3; public static final int MSG_SET_SOURCE_PROVIDER_ACK = 3;
public static final int MSG_SEEK_ACK = 4;
public static final int MSG_SOURCE_CHANGED = 5;
public static final int MSG_ERROR = 6;
// Internal messages // Internal messages
private static final int MSG_SET_SOURCE_PROVIDER = 0; private static final int MSG_SET_SOURCE_PROVIDER = 0;
...@@ -69,12 +91,13 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -69,12 +91,13 @@ import java.util.concurrent.atomic.AtomicInteger;
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 AtomicInteger pendingSeekCount;
private final Timeline timeline; private final Timeline timeline;
private PlaybackInfo playbackInfo;
private TrackRenderer rendererMediaClockSource; private TrackRenderer rendererMediaClockSource;
private MediaClock rendererMediaClock; private MediaClock rendererMediaClock;
private SampleSourceProvider sampleSourceProvider; private SampleSourceProvider sampleSourceProvider;
// TODO[playlists]: Use timeline.playingSource.sampleSource instead.
private SampleSource sampleSource; private SampleSource sampleSource;
private TrackRenderer[] enabledRenderers; private TrackRenderer[] enabledRenderers;
private boolean released; private boolean released;
...@@ -83,17 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -83,17 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger;
private int state; private int state;
private int customMessagesSent; private int customMessagesSent;
private int customMessagesProcessed; private int customMessagesProcessed;
private long lastSeekPositionMs;
private int lastSeekSourceIndex;
private long elapsedRealtimeUs; private long elapsedRealtimeUs;
private long sourceOffsetUs; private long sourceOffsetUs;
private long internalPositionUs; private long internalPositionUs;
private int sourceIndex;
private volatile long durationUs;
private volatile long positionUs;
private volatile long bufferedPositionUs;
public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector, public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector,
int minBufferMs, int minRebufferMs, boolean playWhenReady, Handler eventHandler) { int minBufferMs, int minRebufferMs, boolean playWhenReady, Handler eventHandler) {
...@@ -104,17 +120,15 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -104,17 +120,15 @@ import java.util.concurrent.atomic.AtomicInteger;
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.state = ExoPlayer.STATE_IDLE; this.state = ExoPlayer.STATE_IDLE;
this.durationUs = C.UNSET_TIME_US;
this.bufferedPositionUs = C.UNSET_TIME_US;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
renderers[i].setIndex(i); renderers[i].setIndex(i);
} }
standaloneMediaClock = new StandaloneMediaClock(); standaloneMediaClock = new StandaloneMediaClock();
pendingSeekCount = new AtomicInteger();
enabledRenderers = new TrackRenderer[0]; enabledRenderers = new TrackRenderer[0];
timeline = new Timeline(); timeline = new Timeline();
playbackInfo = new PlaybackInfo(0);
trackSelector.init(this); trackSelector.init(this);
...@@ -126,21 +140,6 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -126,21 +140,6 @@ import java.util.concurrent.atomic.AtomicInteger;
handler = new Handler(internalPlaybackThread.getLooper(), this); handler = new Handler(internalPlaybackThread.getLooper(), this);
} }
public long getCurrentPosition() {
return pendingSeekCount.get() > 0 ? lastSeekPositionMs : (positionUs / 1000);
}
public long getBufferedPosition() {
long bufferedPositionUs = this.bufferedPositionUs;
return bufferedPositionUs == C.UNSET_TIME_US || bufferedPositionUs == C.END_OF_SOURCE_US
? ExoPlayer.UNKNOWN_TIME : bufferedPositionUs / 1000;
}
public long getDuration() {
long durationUs = this.durationUs;
return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : durationUs / 1000;
}
public void setSourceProvider(SampleSourceProvider sourceProvider) { public void setSourceProvider(SampleSourceProvider sourceProvider) {
handler.obtainMessage(MSG_SET_SOURCE_PROVIDER, sourceProvider).sendToTarget(); handler.obtainMessage(MSG_SET_SOURCE_PROVIDER, sourceProvider).sendToTarget();
} }
...@@ -149,21 +148,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -149,21 +148,8 @@ import java.util.concurrent.atomic.AtomicInteger;
handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget();
} }
public void seekTo(long positionMs) {
// TODO[playlists]: Move to ExoPlayerImpl.
int sourceIndex;
synchronized (timeline) {
sourceIndex = this.sourceIndex;
}
seekTo(sourceIndex, positionMs);
}
public void seekTo(int sourceIndex, long positionMs) { public void seekTo(int sourceIndex, long positionMs) {
// TODO[playlists]: Expose the current source index and seeking to sources in ExoPlayer. handler.obtainMessage(MSG_SEEK_TO, sourceIndex, -1, positionMs).sendToTarget();
lastSeekSourceIndex = sourceIndex;
lastSeekPositionMs = positionMs;
pendingSeekCount.incrementAndGet();
handler.obtainMessage(MSG_SEEK_TO, lastSeekSourceIndex, -1, positionMs).sendToTarget();
} }
public void stop() { public void stop() {
...@@ -294,17 +280,22 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -294,17 +280,22 @@ import java.util.concurrent.atomic.AtomicInteger;
// TODO[playlists]: Take into account the buffered position in the timeline. // TODO[playlists]: Take into account the buffered position in the timeline.
long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs; long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs;
return minBufferDurationUs <= 0 return minBufferDurationUs <= 0
|| bufferedPositionUs == C.UNSET_TIME_US || playbackInfo.bufferedPositionUs == C.UNSET_TIME_US
|| bufferedPositionUs == C.END_OF_SOURCE_US || playbackInfo.bufferedPositionUs == C.END_OF_SOURCE_US
|| bufferedPositionUs >= positionUs + minBufferDurationUs || playbackInfo.bufferedPositionUs >= playbackInfo.positionUs + minBufferDurationUs
|| (durationUs != C.UNSET_TIME_US && bufferedPositionUs >= durationUs); || (playbackInfo.durationUs != C.UNSET_TIME_US
&& playbackInfo.bufferedPositionUs >= playbackInfo.durationUs);
} }
private void setSourceProviderInternal(SampleSourceProvider sourceProvider) { private void setSourceProviderInternal(SampleSourceProvider sourceProvider) {
try {
resetInternal(); resetInternal();
sampleSourceProvider = sourceProvider; sampleSourceProvider = sourceProvider;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK); handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} finally {
eventHandler.sendEmptyMessage(MSG_SET_SOURCE_PROVIDER_ACK);
}
} }
private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException { private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException {
...@@ -323,7 +314,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -323,7 +314,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
} }
} finally { } finally {
eventHandler.obtainMessage(MSG_SET_PLAY_WHEN_READY_ACK).sendToTarget(); eventHandler.sendEmptyMessage(MSG_SET_PLAY_WHEN_READY_ACK);
} }
} }
...@@ -349,14 +340,15 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -349,14 +340,15 @@ import java.util.concurrent.atomic.AtomicInteger;
} else { } else {
internalPositionUs = standaloneMediaClock.getPositionUs(); internalPositionUs = standaloneMediaClock.getPositionUs();
} }
positionUs = internalPositionUs - sourceOffsetUs; playbackInfo.positionUs = internalPositionUs - sourceOffsetUs;
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
} }
private void updateBufferedPositionUs() { private void updateBufferedPositionUs() {
long sourceBufferedPositionUs = enabledRenderers.length > 0 long sourceBufferedPositionUs = enabledRenderers.length > 0
? sampleSource.getBufferedPositionUs() : C.END_OF_SOURCE_US; ? timeline.playingSource.sampleSource.getBufferedPositionUs() : C.END_OF_SOURCE_US;
bufferedPositionUs = sourceBufferedPositionUs == C.END_OF_SOURCE_US long durationUs = playbackInfo.durationUs;
playbackInfo.bufferedPositionUs = sourceBufferedPositionUs == C.END_OF_SOURCE_US
&& durationUs != C.UNSET_TIME_US ? durationUs : sourceBufferedPositionUs; && durationUs != C.UNSET_TIME_US ? durationUs : sourceBufferedPositionUs;
} }
...@@ -406,7 +398,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -406,7 +398,8 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
boolean timelineIsReady = timeline.isReady(); boolean timelineIsReady = timeline.isReady();
if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) { if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|| playbackInfo.durationUs <= playbackInfo.positionUs)) {
setState(ExoPlayer.STATE_ENDED); setState(ExoPlayer.STATE_ENDED);
stopRenderers(); stopRenderers();
} else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded } else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded
...@@ -444,11 +437,17 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -444,11 +437,17 @@ import java.util.concurrent.atomic.AtomicInteger;
private void seekToInternal(int sourceIndex, long seekPositionMs) throws ExoPlaybackException { private void seekToInternal(int sourceIndex, long seekPositionMs) throws ExoPlaybackException {
try { try {
if (seekPositionMs == (positionUs / 1000)) { if (sourceIndex == playbackInfo.sourceIndex
&& seekPositionMs == (playbackInfo.positionUs / 1000)) {
// Seek is to the current position. Do nothing. // Seek is to the current position. Do nothing.
return; return;
} }
if (sourceIndex != playbackInfo.sourceIndex) {
playbackInfo = new PlaybackInfo(sourceIndex);
eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget();
}
long seekPositionUs = seekPositionMs * 1000; long seekPositionUs = seekPositionMs * 1000;
rebuffering = false; rebuffering = false;
standaloneMediaClock.stop(); standaloneMediaClock.stop();
...@@ -470,7 +469,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -470,7 +469,7 @@ import java.util.concurrent.atomic.AtomicInteger;
setNewSourcePositionInternal(seekPositionUs); setNewSourcePositionInternal(seekPositionUs);
resumeInternal(); resumeInternal();
} finally { } finally {
pendingSeekCount.decrementAndGet(); eventHandler.sendEmptyMessage(MSG_SEEK_ACK);
} }
} }
...@@ -483,7 +482,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -483,7 +482,8 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
updateBufferedPositionUs(); updateBufferedPositionUs();
if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) { if (allRenderersEnded && (playbackInfo.durationUs == C.UNSET_TIME_US
|| playbackInfo.durationUs <= playbackInfo.positionUs)) {
setState(ExoPlayer.STATE_ENDED); setState(ExoPlayer.STATE_ENDED);
} else { } else {
setState(allRenderersReadyOrEnded && haveSufficientBuffer() && timeline.isReady() setState(allRenderersReadyOrEnded && haveSufficientBuffer() && timeline.isReady()
...@@ -507,7 +507,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -507,7 +507,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
private void setNewSourcePositionInternal(long sourcePositionUs) throws ExoPlaybackException { private void setNewSourcePositionInternal(long sourcePositionUs) throws ExoPlaybackException {
positionUs = sourcePositionUs; playbackInfo.positionUs = sourcePositionUs;
internalPositionUs = sourceOffsetUs + sourcePositionUs; internalPositionUs = sourceOffsetUs + sourcePositionUs;
standaloneMediaClock.setPositionUs(internalPositionUs); standaloneMediaClock.setPositionUs(internalPositionUs);
for (TrackRenderer renderer : enabledRenderers) { for (TrackRenderer renderer : enabledRenderers) {
...@@ -636,7 +636,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -636,7 +636,7 @@ import java.util.concurrent.atomic.AtomicInteger;
if (!bufferingSource.prepared) { if (!bufferingSource.prepared) {
// Continue preparation. // Continue preparation.
// TODO[playlists]: Add support for setting the start position to play in a source. // TODO[playlists]: Add support for setting the start position to play in a source.
long startPositionUs = playingSource == null ? positionUs : 0; long startPositionUs = playingSource == null ? playbackInfo.positionUs : 0;
if (bufferingSource.prepare(startPositionUs)) { if (bufferingSource.prepare(startPositionUs)) {
Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers, Pair<TrackSelectionArray, Object> result = trackSelector.selectTracks(renderers,
bufferingSource.sampleSource.getTrackGroups()); bufferingSource.sampleSource.getTrackGroups());
...@@ -710,7 +710,9 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -710,7 +710,9 @@ import java.util.concurrent.atomic.AtomicInteger;
if (readingSource != playingSource && playingSourceEndPositionUs != C.UNSET_TIME_US if (readingSource != playingSource && playingSourceEndPositionUs != C.UNSET_TIME_US
&& internalPositionUs >= playingSourceEndPositionUs) { && internalPositionUs >= playingSourceEndPositionUs) {
playingSource.release(); playingSource.release();
playbackInfo = new PlaybackInfo(readingSource.index);
setPlayingSource(readingSource, playingSourceEndPositionUs); setPlayingSource(readingSource, playingSourceEndPositionUs);
eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget();
} }
return playingSource.sampleSource; return playingSource.sampleSource;
} }
...@@ -727,23 +729,24 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -727,23 +729,24 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
source = source.nextSource; source = source.nextSource;
} }
if (newPlayingSource != null) { if (newPlayingSource != null) {
nextSourceIndex = sourceIndex + 1;
newPlayingSource.nextSource = null; newPlayingSource.nextSource = null;
setPlayingSource(newPlayingSource, sourceOffsetUs); setPlayingSource(newPlayingSource, sourceOffsetUs);
bufferingSource = playingSource; bufferingSource = playingSource;
bufferingSourceOffsetUs = sourceOffsetUs; bufferingSourceOffsetUs = sourceOffsetUs;
nextSourceIndex = sourceIndex + 1;
} else { } else {
// TODO[REFACTOR]: Presumably we need to disable the renderers somewhere in here? // TODO[REFACTOR]: Presumably we need to disable the renderers somewhere in here?
playingSource = null; playingSource = null;
readingSource = null; readingSource = null;
bufferingSource = null; bufferingSource = null;
bufferingSourceOffsetUs = 0; bufferingSourceOffsetUs = 0;
durationUs = C.UNSET_TIME_US;
sampleSource = null; sampleSource = null;
// Set the next source index so that the required source is created in updateSources. // Set the next source index so that the required source is created in updateSources.
nextSourceIndex = sourceIndex; nextSourceIndex = sourceIndex;
} }
return sampleSource; return sampleSource;
} }
...@@ -794,7 +797,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -794,7 +797,7 @@ import java.util.concurrent.atomic.AtomicInteger;
int enabledRendererCount = disableRenderers(false, newTrackSelections); int enabledRendererCount = disableRenderers(false, newTrackSelections);
TrackStream[] newStreams = playingSource.updateTrackStreams(oldStreams, newTrackSelections, TrackStream[] newStreams = playingSource.updateTrackStreams(oldStreams, newTrackSelections,
newSelections, positionUs); newSelections, playbackInfo.positionUs);
trackSelector.onSelectionActivated(trackSelectionData); trackSelector.onSelectionActivated(trackSelectionData);
// Update the stored TrackStreams. // Update the stored TrackStreams.
...@@ -819,32 +822,16 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -819,32 +822,16 @@ import java.util.concurrent.atomic.AtomicInteger;
playingSource = null; playingSource = null;
readingSource = null; readingSource = null;
bufferingSource = null; bufferingSource = null;
durationUs = C.UNSET_TIME_US;
playingSourceEndPositionUs = C.UNSET_TIME_US; playingSourceEndPositionUs = C.UNSET_TIME_US;
nextSourceIndex = 0; nextSourceIndex = 0;
sourceOffsetUs = 0; sourceOffsetUs = 0;
bufferingSourceOffsetUs = 0; bufferingSourceOffsetUs = 0;
} playbackInfo = new PlaybackInfo(0);
eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget();
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Timeline[");
Source source = playingSource != null ? playingSource : bufferingSource;
while (source != null) {
sb.append(source);
source = source.nextSource;
if (source != null) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
} }
private void setPlayingSource(Source source, long offsetUs) throws ExoPlaybackException { private void setPlayingSource(Source source, long offsetUs) throws ExoPlaybackException {
sourceOffsetUs = offsetUs; sourceOffsetUs = offsetUs;
durationUs = source.sampleSource.getDurationUs();
// Disable/enable renderers for the new source. // Disable/enable renderers for the new source.
int enabledRendererCount = disableRenderers(true, source.trackSelections); int enabledRendererCount = disableRenderers(true, source.trackSelections);
...@@ -856,12 +843,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -856,12 +843,11 @@ import java.util.concurrent.atomic.AtomicInteger;
playingSourceEndPositionUs = C.UNSET_TIME_US; playingSourceEndPositionUs = C.UNSET_TIME_US;
enableRenderers(source.trackSelections, enabledRendererCount); enableRenderers(source.trackSelections, enabledRendererCount);
// Update the timeline position for the new source index. // Update playback information.
synchronized (timeline) { playbackInfo.durationUs = source.sampleSource.getDurationUs();
sourceIndex = source.index; updateBufferedPositionUs();
updatePositionUs(); updatePositionUs();
} }
}
private int disableRenderers(boolean sourceTransition, TrackSelectionArray newTrackSelections) private int disableRenderers(boolean sourceTransition, TrackSelectionArray newTrackSelections)
throws ExoPlaybackException { throws ExoPlaybackException {
......
...@@ -344,6 +344,11 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -344,6 +344,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public void seekTo(int sourceIndex, long positionMs) {
player.seekTo(sourceIndex, positionMs);
}
@Override
public void stop() { public void stop() {
player.stop(); player.stop();
} }
...@@ -374,6 +379,11 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -374,6 +379,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public int getCurrentSourceIndex() {
return player.getCurrentSourceIndex();
}
@Override
public long getBufferedPosition() { public long getBufferedPosition() {
return player.getBufferedPosition(); return player.getBufferedPosition();
} }
......
...@@ -81,11 +81,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -81,11 +81,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
} }
private void updateTextView() { private void updateTextView() {
textView.setText(getPlayerStateString() + getBandwidthString() + getVideoString() textView.setText(getPlayerStateString() + getPlayerSourceIndexString() + getBandwidthString()
+ getAudioString()); + getVideoString() + getAudioString());
} }
public String getPlayerStateString() { private String getPlayerStateString() {
String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:";
switch(player.getPlaybackState()) { switch(player.getPlaybackState()) {
case ExoPlayer.STATE_BUFFERING: case ExoPlayer.STATE_BUFFERING:
...@@ -107,6 +107,10 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -107,6 +107,10 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
return text; return text;
} }
private String getPlayerSourceIndexString() {
return " source:" + player.getCurrentSourceIndex();
}
private String getBandwidthString() { private String getBandwidthString() {
BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); BandwidthMeter bandwidthMeter = player.getBandwidthMeter();
if (bandwidthMeter == null if (bandwidthMeter == null
...@@ -160,6 +164,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe ...@@ -160,6 +164,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
} }
@Override @Override
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
updateTextView();
}
@Override
public void onPlayerError(ExoPlaybackException error) { public void onPlayerError(ExoPlaybackException error) {
// Do nothing. // Do nothing.
} }
......
...@@ -204,6 +204,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -204,6 +204,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
// Do nothing. // Do nothing.
} }
@Override
public final void onPositionDiscontinuity(int sourceIndex, long positionMs) {
// Do nothing.
}
// SimpleExoPlayer.DebugListener // SimpleExoPlayer.DebugListener
@Override @Override
......
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