diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index 01779c8acbd..e61a9ed1302 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ExtractorMediaSource; import com.google.android.exoplayer2.source.MediaSource; @@ -100,7 +101,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } private Handler mainHandler; - private Timeline.Window window; private EventLogger eventLogger; private SimpleExoPlayerView simpleExoPlayerView; private LinearLayout debugRootView; @@ -115,9 +115,8 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay private boolean playerNeedsSource; private boolean shouldAutoPlay; - private boolean isTimelineStatic; - private int playerWindow; - private long playerPosition; + private int resumeWindow; + private long resumePosition; // Activity lifecycle @@ -125,9 +124,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); shouldAutoPlay = true; + clearResumePosition(); mediaDataSourceFactory = buildDataSourceFactory(true); mainHandler = new Handler(); - window = new Timeline.Window(); if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); } @@ -148,7 +147,8 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onNewIntent(Intent intent) { releasePlayer(); - isTimelineStatic = false; + shouldAutoPlay = true; + clearResumePosition(); setIntent(intent); } @@ -264,7 +264,7 @@ private void initializePlayer() { @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode = ((DemoApplication) getApplication()).useExtensionRenderers() ? (preferExtensionDecoders ? SimpleExoPlayer.EXTENSION_RENDERER_MODE_PREFER - : SimpleExoPlayer.EXTENSION_RENDERER_MODE_ON) + : SimpleExoPlayer.EXTENSION_RENDERER_MODE_ON) : SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF; TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); @@ -281,13 +281,6 @@ private void initializePlayer() { player.setMetadataOutput(eventLogger); simpleExoPlayerView.setPlayer(player); - if (isTimelineStatic) { - if (playerPosition == C.TIME_UNSET) { - player.seekToDefaultPosition(playerWindow); - } else { - player.seekTo(playerWindow, playerPosition); - } - } player.setPlayWhenReady(shouldAutoPlay); debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); @@ -324,7 +317,8 @@ private void initializePlayer() { } MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); - player.prepare(mediaSource, !isTimelineStatic, !isTimelineStatic); + player.seekTo(resumeWindow, resumePosition); + player.prepare(mediaSource, false, false); playerNeedsSource = false; updateButtonVisibilities(); } @@ -367,12 +361,7 @@ private void releasePlayer() { debugViewHelper.stop(); debugViewHelper = null; shouldAutoPlay = player.getPlayWhenReady(); - playerWindow = player.getCurrentWindowIndex(); - playerPosition = C.TIME_UNSET; - Timeline timeline = player.getCurrentTimeline(); - if (!timeline.isEmpty() && timeline.getWindow(playerWindow, window).isSeekable) { - playerPosition = player.getCurrentPosition(); - } + updateResumePosition(); player.release(); player = null; trackSelector = null; @@ -381,6 +370,17 @@ private void releasePlayer() { } } + private void updateResumePosition() { + resumeWindow = player.getCurrentWindowIndex(); + resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition()) + : C.TIME_UNSET; + } + + private void clearResumePosition() { + resumeWindow = 0; + resumePosition = C.TIME_UNSET; + } + /** * Returns a new DataSource factory. * @@ -427,8 +427,7 @@ public void onPositionDiscontinuity() { @Override public void onTimelineChanged(Timeline timeline, Object manifest) { - isTimelineStatic = !timeline.isEmpty() - && !timeline.getWindow(timeline.getWindowCount() - 1, window).isDynamic; + // Do nothing. } @Override @@ -460,6 +459,11 @@ public void onPlayerError(ExoPlaybackException e) { showToast(errorString); } playerNeedsSource = true; + if (isBehindLiveWindow(e)) { + clearResumePosition(); + } else { + updateResumePosition(); + } updateButtonVisibilities(); showControls(); } @@ -535,4 +539,18 @@ private void showToast(String message) { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } + private static boolean isBehindLiveWindow(ExoPlaybackException e) { + if (e.type != ExoPlaybackException.TYPE_SOURCE) { + return false; + } + Throwable cause = e.getSourceException(); + while (cause != null) { + if (cause instanceof BehindLiveWindowException) { + return true; + } + cause = cause.getCause(); + } + return false; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 6c64d2c0f37..083569416cd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -447,4 +447,20 @@ public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object messa */ int getBufferedPercentage(); + /** + * Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isDynamic + */ + boolean isCurrentWindowDynamic(); + + /** + * Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is + * empty. + * + * @see Timeline.Window#isSeekable + */ + boolean isCurrentWindowSeekable(); + } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 55dd0f57edb..d44d1380910 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -271,6 +271,22 @@ public int getBufferedPercentage() { : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); } + @Override + public boolean isCurrentWindowDynamic() { + if (timeline.isEmpty()) { + return false; + } + return timeline.getWindow(getCurrentWindowIndex(), window).isDynamic; + } + + @Override + public boolean isCurrentWindowSeekable() { + if (timeline.isEmpty()) { + return false; + } + return timeline.getWindow(getCurrentWindowIndex(), window).isSeekable; + } + @Override public int getRendererCount() { return renderers.length; diff --git a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 5a3e01a1097..da9417374e4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -546,63 +546,73 @@ public void blockingSendMessages(ExoPlayerMessage... messages) { } @Override - public int getCurrentPeriodIndex() { - return player.getCurrentPeriodIndex(); + public int getRendererCount() { + return player.getRendererCount(); } @Override - public int getCurrentWindowIndex() { - return player.getCurrentWindowIndex(); + public int getRendererType(int index) { + return player.getRendererType(index); } @Override - public long getDuration() { - return player.getDuration(); + public TrackGroupArray getCurrentTrackGroups() { + return player.getCurrentTrackGroups(); } @Override - public long getCurrentPosition() { - return player.getCurrentPosition(); + public TrackSelectionArray getCurrentTrackSelections() { + return player.getCurrentTrackSelections(); } @Override - public long getBufferedPosition() { - return player.getBufferedPosition(); + public Timeline getCurrentTimeline() { + return player.getCurrentTimeline(); } @Override - public int getBufferedPercentage() { - return player.getBufferedPercentage(); + public Object getCurrentManifest() { + return player.getCurrentManifest(); } @Override - public int getRendererCount() { - return player.getRendererCount(); + public int getCurrentPeriodIndex() { + return player.getCurrentPeriodIndex(); } @Override - public int getRendererType(int index) { - return player.getRendererType(index); + public int getCurrentWindowIndex() { + return player.getCurrentWindowIndex(); } @Override - public TrackGroupArray getCurrentTrackGroups() { - return player.getCurrentTrackGroups(); + public long getDuration() { + return player.getDuration(); } @Override - public TrackSelectionArray getCurrentTrackSelections() { - return player.getCurrentTrackSelections(); + public long getCurrentPosition() { + return player.getCurrentPosition(); } @Override - public Timeline getCurrentTimeline() { - return player.getCurrentTimeline(); + public long getBufferedPosition() { + return player.getBufferedPosition(); } @Override - public Object getCurrentManifest() { - return player.getCurrentManifest(); + public int getBufferedPercentage() { + return player.getBufferedPercentage(); + } + + @Override + public boolean isCurrentWindowDynamic() { + return player.isCurrentWindowDynamic(); + } + + @Override + public boolean isCurrentWindowSeekable() { + return player.isCurrentWindowSeekable(); } // Renderer building.