From 270f68a95e5fb570cce0cfeeb09f3fb89076e471 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 13 Jan 2017 08:11:08 -0800 Subject: [PATCH] Add convenience methods to query current window - Support querying whether the current window is dynamic and seekable. The new methods are similar to getDuration, which is also a convenience method for the current window. - Improve demo app to restore positions in VOD items within playlists where the last item is live. Also restore the position within the window for live items unless the player failed with BehindLiveWindowException. Issue: #2320 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=144443898 --- .../exoplayer2/demo/PlayerActivity.java | 64 ++++++++++++------- .../google/android/exoplayer2/ExoPlayer.java | 16 +++++ .../android/exoplayer2/ExoPlayerImpl.java | 16 +++++ .../android/exoplayer2/SimpleExoPlayer.java | 58 ++++++++++------- 4 files changed, 107 insertions(+), 47 deletions(-) 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 01779c8acb..e61a9ed130 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.drm.UnsupportedDrmException; 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 class PlayerActivity extends Activity implements OnClickListener, ExoPlay @Override public void onNewIntent(Intent intent) { releasePlayer(); - isTimelineStatic = false; + shouldAutoPlay = true; + clearResumePosition(); setIntent(intent); } @@ -264,7 +264,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay @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 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay 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 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } 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 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay 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 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay } } + 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 class PlayerActivity extends Activity implements OnClickListener, ExoPlay @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 class PlayerActivity extends Activity implements OnClickListener, ExoPlay showToast(errorString); } playerNeedsSource = true; + if (isBehindLiveWindow(e)) { + clearResumePosition(); + } else { + updateResumePosition(); + } updateButtonVisibilities(); showControls(); } @@ -535,4 +539,18 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay 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 6c64d2c0f3..083569416c 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 interface ExoPlayer { */ 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 55dd0f57ed..d44d138091 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 @@ import java.util.concurrent.CopyOnWriteArraySet; : (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 5a3e01a109..da9417374e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -545,6 +545,36 @@ public class SimpleExoPlayer implements ExoPlayer { player.blockingSendMessages(messages); } + @Override + public int getRendererCount() { + return player.getRendererCount(); + } + + @Override + public int getRendererType(int index) { + return player.getRendererType(index); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return player.getCurrentTrackGroups(); + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return player.getCurrentTrackSelections(); + } + + @Override + public Timeline getCurrentTimeline() { + return player.getCurrentTimeline(); + } + + @Override + public Object getCurrentManifest() { + return player.getCurrentManifest(); + } + @Override public int getCurrentPeriodIndex() { return player.getCurrentPeriodIndex(); @@ -576,33 +606,13 @@ public class SimpleExoPlayer implements ExoPlayer { } @Override - public int getRendererCount() { - return player.getRendererCount(); + public boolean isCurrentWindowDynamic() { + return player.isCurrentWindowDynamic(); } @Override - public int getRendererType(int index) { - return player.getRendererType(index); - } - - @Override - public TrackGroupArray getCurrentTrackGroups() { - return player.getCurrentTrackGroups(); - } - - @Override - public TrackSelectionArray getCurrentTrackSelections() { - return player.getCurrentTrackSelections(); - } - - @Override - public Timeline getCurrentTimeline() { - return player.getCurrentTimeline(); - } - - @Override - public Object getCurrentManifest() { - return player.getCurrentManifest(); + public boolean isCurrentWindowSeekable() { + return player.isCurrentWindowSeekable(); } // Renderer building.