From 2073f3fce374d2af5152550cd7228f0772c518d5 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 22 Jun 2016 10:21:07 -0700 Subject: [PATCH] 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 --- .../android/exoplayer/demo/EventLogger.java | 5 + .../exoplayer/demo/PlayerActivity.java | 8 + .../exoplayer/ext/flac/FlacPlaybackTest.java | 5 + .../exoplayer/ext/opus/OpusPlaybackTest.java | 5 + .../exoplayer/ext/vp9/VpxPlaybackTest.java | 5 + .../google/android/exoplayer/ExoPlayer.java | 36 +++- .../android/exoplayer/ExoPlayerImpl.java | 71 +++++++- .../exoplayer/ExoPlayerImplInternal.java | 158 ++++++++---------- .../android/exoplayer/SimpleExoPlayer.java | 10 ++ .../exoplayer/util/DebugTextViewHelper.java | 15 +- .../playbacktests/util/ExoHostedTest.java | 5 + 11 files changed, 224 insertions(+), 99 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index 5ab6f038f6..c0160cac8d 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -72,6 +72,11 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb + getStateString(state) + "]"); } + @Override + public void onPositionDiscontinuity(int sourceIndex, long positionMs) { + Log.d(TAG, "discontinuity [" + sourceIndex + ", " + positionMs + "]"); + } + @Override public void onPlayWhenReadyCommitted() { // Do nothing. diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 8f0b6c5d89..f9bc64d3e4 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -391,6 +391,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, // 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 public void onPlayerError(ExoPlaybackException e) { String errorString = null; diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java index 0310fa2c78..7251e57d70 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer/ext/flac/FlacPlaybackTest.java @@ -92,6 +92,11 @@ public class FlacPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPositionDiscontinuity(int sourceIndex, long positionMs) { + // Do nothing. + } + @Override public void onPlayerError(ExoPlaybackException error) { playbackException = error; diff --git a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java index 39e45af7bb..88a350c295 100644 --- a/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java +++ b/extensions/opus/src/androidTest/java/com/google/android/exoplayer/ext/opus/OpusPlaybackTest.java @@ -92,6 +92,11 @@ public class OpusPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPositionDiscontinuity(int sourceIndex, long positionMs) { + // Do nothing. + } + @Override public void onPlayerError(ExoPlaybackException error) { playbackException = error; diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java index c55f7f7e9f..d617f13554 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer/ext/vp9/VpxPlaybackTest.java @@ -111,6 +111,11 @@ public class VpxPlaybackTest extends InstrumentationTestCase { // Do nothing. } + @Override + public void onPositionDiscontinuity(int sourceIndex, long positionMs) { + // Do nothing. + } + @Override public void onPlayerError(ExoPlaybackException error) { playbackException = error; diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index affb59a764..a97519ca4b 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -118,6 +118,16 @@ public interface ExoPlayer { */ 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 * {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance @@ -227,8 +237,9 @@ public interface ExoPlayer { void setSource(SampleSource sampleSource); /** - * Sets the player's source provider. The player will transition to {@link #STATE_BUFFERING} until - * it is ready to play the first source. + * Sets the player's source provider. The player's position will be reset to the start of the + * 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. */ @@ -259,12 +270,20 @@ public interface ExoPlayer { 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. */ 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 * is to pause playback. @@ -312,12 +331,19 @@ public interface ExoPlayer { 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(); + /** + * 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. * diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java index d98689d426..f5b3208c64 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -39,6 +39,15 @@ import java.util.concurrent.CopyOnWriteArraySet; private boolean playWhenReady; private int playbackState; 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}. @@ -68,6 +77,7 @@ import java.util.concurrent.CopyOnWriteArraySet; }; internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs, playWhenReady, eventHandler); + playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0); } @Override @@ -87,12 +97,20 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override public void setSource(final SampleSource sampleSource) { - internalPlayer.setSourceProvider(new SingleSampleSourceProvider(sampleSource)); + setSourceProvider(new SingleSampleSourceProvider(sampleSource)); } @Override public void setSourceProvider(SampleSourceProvider sourceProvider) { + duration = ExoPlayer.UNKNOWN_TIME; + position = 0; + sourceIndex = 0; + + pendingSetSourceProviderAndSeekAcks++; internalPlayer.setSourceProvider(sourceProvider); + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(sourceIndex, position); + } } @Override @@ -119,7 +137,20 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override 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 @@ -145,17 +176,33 @@ import java.util.concurrent.CopyOnWriteArraySet; @Override 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 public long getCurrentPosition() { - return internalPlayer.getCurrentPosition(); + return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.positionUs / 1000 : position; + } + + @Override + public int getCurrentSourceIndex() { + return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.sourceIndex : sourceIndex; } @Override 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 @@ -185,6 +232,20 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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: { ExoPlaybackException exception = (ExoPlaybackException) msg.obj; for (EventListener listener : listeners) { diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index af2f85b003..2faf25f324 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -31,7 +31,6 @@ import android.util.Pair; import java.io.IOException; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicInteger; /** * Implements the internal behavior of {@link ExoPlayerImpl}. @@ -40,12 +39,35 @@ import java.util.concurrent.atomic.AtomicInteger; // always propagated properly. /* 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"; // External messages public static final int MSG_STATE_CHANGED = 1; 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 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 HandlerThread internalPlaybackThread; private final Handler eventHandler; - private final AtomicInteger pendingSeekCount; private final Timeline timeline; + private PlaybackInfo playbackInfo; private TrackRenderer rendererMediaClockSource; private MediaClock rendererMediaClock; private SampleSourceProvider sampleSourceProvider; + // TODO[playlists]: Use timeline.playingSource.sampleSource instead. private SampleSource sampleSource; private TrackRenderer[] enabledRenderers; private boolean released; @@ -83,17 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger; private int state; private int customMessagesSent; private int customMessagesProcessed; - private long lastSeekPositionMs; - private int lastSeekSourceIndex; private long elapsedRealtimeUs; private long sourceOffsetUs; private long internalPositionUs; - private int sourceIndex; - - private volatile long durationUs; - private volatile long positionUs; - private volatile long bufferedPositionUs; public ExoPlayerImplInternal(TrackRenderer[] renderers, TrackSelector trackSelector, int minBufferMs, int minRebufferMs, boolean playWhenReady, Handler eventHandler) { @@ -104,17 +120,15 @@ import java.util.concurrent.atomic.AtomicInteger; this.playWhenReady = playWhenReady; this.eventHandler = eventHandler; 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++) { renderers[i].setIndex(i); } standaloneMediaClock = new StandaloneMediaClock(); - pendingSeekCount = new AtomicInteger(); enabledRenderers = new TrackRenderer[0]; timeline = new Timeline(); + playbackInfo = new PlaybackInfo(0); trackSelector.init(this); @@ -126,21 +140,6 @@ import java.util.concurrent.atomic.AtomicInteger; 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) { 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(); } - 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) { - // TODO[playlists]: Expose the current source index and seeking to sources in ExoPlayer. - lastSeekSourceIndex = sourceIndex; - lastSeekPositionMs = positionMs; - pendingSeekCount.incrementAndGet(); - handler.obtainMessage(MSG_SEEK_TO, lastSeekSourceIndex, -1, positionMs).sendToTarget(); + handler.obtainMessage(MSG_SEEK_TO, sourceIndex, -1, positionMs).sendToTarget(); } public void stop() { @@ -294,17 +280,22 @@ import java.util.concurrent.atomic.AtomicInteger; // TODO[playlists]: Take into account the buffered position in the timeline. long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs; return minBufferDurationUs <= 0 - || bufferedPositionUs == C.UNSET_TIME_US - || bufferedPositionUs == C.END_OF_SOURCE_US - || bufferedPositionUs >= positionUs + minBufferDurationUs - || (durationUs != C.UNSET_TIME_US && bufferedPositionUs >= durationUs); + || playbackInfo.bufferedPositionUs == C.UNSET_TIME_US + || playbackInfo.bufferedPositionUs == C.END_OF_SOURCE_US + || playbackInfo.bufferedPositionUs >= playbackInfo.positionUs + minBufferDurationUs + || (playbackInfo.durationUs != C.UNSET_TIME_US + && playbackInfo.bufferedPositionUs >= playbackInfo.durationUs); } private void setSourceProviderInternal(SampleSourceProvider sourceProvider) { - resetInternal(); - sampleSourceProvider = sourceProvider; - setState(ExoPlayer.STATE_BUFFERING); - handler.sendEmptyMessage(MSG_DO_SOME_WORK); + try { + resetInternal(); + sampleSourceProvider = sourceProvider; + 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 { @@ -323,7 +314,7 @@ import java.util.concurrent.atomic.AtomicInteger; } } } 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 { internalPositionUs = standaloneMediaClock.getPositionUs(); } - positionUs = internalPositionUs - sourceOffsetUs; + playbackInfo.positionUs = internalPositionUs - sourceOffsetUs; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; } private void updateBufferedPositionUs() { long sourceBufferedPositionUs = enabledRenderers.length > 0 - ? sampleSource.getBufferedPositionUs() : C.END_OF_SOURCE_US; - bufferedPositionUs = sourceBufferedPositionUs == C.END_OF_SOURCE_US + ? timeline.playingSource.sampleSource.getBufferedPositionUs() : C.END_OF_SOURCE_US; + long durationUs = playbackInfo.durationUs; + playbackInfo.bufferedPositionUs = sourceBufferedPositionUs == C.END_OF_SOURCE_US && durationUs != C.UNSET_TIME_US ? durationUs : sourceBufferedPositionUs; } @@ -406,7 +398,8 @@ import java.util.concurrent.atomic.AtomicInteger; } 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); stopRenderers(); } 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 { try { - if (seekPositionMs == (positionUs / 1000)) { + if (sourceIndex == playbackInfo.sourceIndex + && seekPositionMs == (playbackInfo.positionUs / 1000)) { // Seek is to the current position. Do nothing. return; } + if (sourceIndex != playbackInfo.sourceIndex) { + playbackInfo = new PlaybackInfo(sourceIndex); + eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget(); + } + long seekPositionUs = seekPositionMs * 1000; rebuffering = false; standaloneMediaClock.stop(); @@ -470,7 +469,7 @@ import java.util.concurrent.atomic.AtomicInteger; setNewSourcePositionInternal(seekPositionUs); resumeInternal(); } finally { - pendingSeekCount.decrementAndGet(); + eventHandler.sendEmptyMessage(MSG_SEEK_ACK); } } @@ -483,7 +482,8 @@ import java.util.concurrent.atomic.AtomicInteger; } 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); } else { setState(allRenderersReadyOrEnded && haveSufficientBuffer() && timeline.isReady() @@ -507,7 +507,7 @@ import java.util.concurrent.atomic.AtomicInteger; } private void setNewSourcePositionInternal(long sourcePositionUs) throws ExoPlaybackException { - positionUs = sourcePositionUs; + playbackInfo.positionUs = sourcePositionUs; internalPositionUs = sourceOffsetUs + sourcePositionUs; standaloneMediaClock.setPositionUs(internalPositionUs); for (TrackRenderer renderer : enabledRenderers) { @@ -636,7 +636,7 @@ import java.util.concurrent.atomic.AtomicInteger; if (!bufferingSource.prepared) { // Continue preparation. // 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)) { Pair result = trackSelector.selectTracks(renderers, bufferingSource.sampleSource.getTrackGroups()); @@ -710,7 +710,9 @@ import java.util.concurrent.atomic.AtomicInteger; if (readingSource != playingSource && playingSourceEndPositionUs != C.UNSET_TIME_US && internalPositionUs >= playingSourceEndPositionUs) { playingSource.release(); + playbackInfo = new PlaybackInfo(readingSource.index); setPlayingSource(readingSource, playingSourceEndPositionUs); + eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget(); } return playingSource.sampleSource; } @@ -727,23 +729,24 @@ import java.util.concurrent.atomic.AtomicInteger; } source = source.nextSource; } + if (newPlayingSource != null) { - nextSourceIndex = sourceIndex + 1; newPlayingSource.nextSource = null; setPlayingSource(newPlayingSource, sourceOffsetUs); bufferingSource = playingSource; bufferingSourceOffsetUs = sourceOffsetUs; + nextSourceIndex = sourceIndex + 1; } else { // TODO[REFACTOR]: Presumably we need to disable the renderers somewhere in here? playingSource = null; readingSource = null; bufferingSource = null; bufferingSourceOffsetUs = 0; - durationUs = C.UNSET_TIME_US; sampleSource = null; // Set the next source index so that the required source is created in updateSources. nextSourceIndex = sourceIndex; } + return sampleSource; } @@ -794,7 +797,7 @@ import java.util.concurrent.atomic.AtomicInteger; int enabledRendererCount = disableRenderers(false, newTrackSelections); TrackStream[] newStreams = playingSource.updateTrackStreams(oldStreams, newTrackSelections, - newSelections, positionUs); + newSelections, playbackInfo.positionUs); trackSelector.onSelectionActivated(trackSelectionData); // Update the stored TrackStreams. @@ -819,32 +822,16 @@ import java.util.concurrent.atomic.AtomicInteger; playingSource = null; readingSource = null; bufferingSource = null; - durationUs = C.UNSET_TIME_US; playingSourceEndPositionUs = C.UNSET_TIME_US; nextSourceIndex = 0; sourceOffsetUs = 0; bufferingSourceOffsetUs = 0; - } - - @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(); + playbackInfo = new PlaybackInfo(0); + eventHandler.obtainMessage(MSG_SOURCE_CHANGED, playbackInfo).sendToTarget(); } private void setPlayingSource(Source source, long offsetUs) throws ExoPlaybackException { sourceOffsetUs = offsetUs; - durationUs = source.sampleSource.getDurationUs(); // Disable/enable renderers for the new source. int enabledRendererCount = disableRenderers(true, source.trackSelections); @@ -856,11 +843,10 @@ import java.util.concurrent.atomic.AtomicInteger; playingSourceEndPositionUs = C.UNSET_TIME_US; enableRenderers(source.trackSelections, enabledRendererCount); - // Update the timeline position for the new source index. - synchronized (timeline) { - sourceIndex = source.index; - updatePositionUs(); - } + // Update playback information. + playbackInfo.durationUs = source.sampleSource.getDurationUs(); + updateBufferedPositionUs(); + updatePositionUs(); } private int disableRenderers(boolean sourceTransition, TrackSelectionArray newTrackSelections) diff --git a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java index 7cc68074e1..c4bb467970 100644 --- a/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/SimpleExoPlayer.java @@ -343,6 +343,11 @@ public final class SimpleExoPlayer implements ExoPlayer { player.seekTo(positionMs); } + @Override + public void seekTo(int sourceIndex, long positionMs) { + player.seekTo(sourceIndex, positionMs); + } + @Override public void stop() { player.stop(); @@ -373,6 +378,11 @@ public final class SimpleExoPlayer implements ExoPlayer { return player.getCurrentPosition(); } + @Override + public int getCurrentSourceIndex() { + return player.getCurrentSourceIndex(); + } + @Override public long getBufferedPosition() { return player.getBufferedPosition(); diff --git a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java index 97ee4e08ad..cb33823dc9 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java @@ -81,11 +81,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe } private void updateTextView() { - textView.setText(getPlayerStateString() + getBandwidthString() + getVideoString() - + getAudioString()); + textView.setText(getPlayerStateString() + getPlayerSourceIndexString() + getBandwidthString() + + getVideoString() + getAudioString()); } - public String getPlayerStateString() { + private String getPlayerStateString() { String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; switch(player.getPlaybackState()) { case ExoPlayer.STATE_BUFFERING: @@ -107,6 +107,10 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe return text; } + private String getPlayerSourceIndexString() { + return " source:" + player.getCurrentSourceIndex(); + } + private String getBandwidthString() { BandwidthMeter bandwidthMeter = player.getBandwidthMeter(); if (bandwidthMeter == null @@ -159,6 +163,11 @@ public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListe // Do nothing. } + @Override + public void onPositionDiscontinuity(int sourceIndex, long positionMs) { + updateTextView(); + } + @Override public void onPlayerError(ExoPlaybackException error) { // Do nothing. diff --git a/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java index b584f4f201..66b8f320bf 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer/playbacktests/util/ExoHostedTest.java @@ -204,6 +204,11 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen // Do nothing. } + @Override + public final void onPositionDiscontinuity(int sourceIndex, long positionMs) { + // Do nothing. + } + // SimpleExoPlayer.DebugListener @Override