mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
f9fa54cd5d
commit
2073f3fce3
11 changed files with 224 additions and 99 deletions
|
|
@ -72,6 +72,11 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
|
||||||
+ getStateString(state) + "]");
|
+ getStateString(state) + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
Log.d(TAG, "discontinuity [" + sourceIndex + ", " + positionMs + "]");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayWhenReadyCommitted() {
|
public void onPlayWhenReadyCommitted() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException e) {
|
public void onPlayerError(ExoPlaybackException e) {
|
||||||
String errorString = null;
|
String errorString = null;
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException error) {
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
playbackException = error;
|
playbackException = error;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
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,12 +270,20 @@ 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.
|
||||||
|
|
@ -312,12 +331,19 @@ 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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
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;
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
@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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
// 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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
// 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) {
|
||||||
resetInternal();
|
try {
|
||||||
sampleSourceProvider = sourceProvider;
|
resetInternal();
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
sampleSourceProvider = sourceProvider;
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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;
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
|
||||||
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;
|
||||||
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,11 +843,10 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -343,6 +343,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
player.seekTo(positionMs);
|
player.seekTo(positionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(int sourceIndex, long positionMs) {
|
||||||
|
player.seekTo(sourceIndex, positionMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
player.stop();
|
player.stop();
|
||||||
|
|
@ -373,6 +378,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
||||||
return player.getCurrentPosition();
|
return player.getCurrentPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentSourceIndex() {
|
||||||
|
return player.getCurrentSourceIndex();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
|
|
@ -159,6 +163,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
updateTextView();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onPositionDiscontinuity(int sourceIndex, long positionMs) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
// SimpleExoPlayer.DebugListener
|
// SimpleExoPlayer.DebugListener
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue