From bfd67992f41a7c5698700322e712544432b23bc2 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 15 Oct 2018 12:37:44 -0700 Subject: [PATCH] Add ExoPlayer.setForegroundMode Issue: #2826 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=217189082 --- RELEASENOTES.md | 2 + .../google/android/exoplayer2/ExoPlayer.java | 30 ++++ .../android/exoplayer2/ExoPlayerImpl.java | 9 ++ .../exoplayer2/ExoPlayerImplInternal.java | 132 +++++++++++++++--- .../android/exoplayer2/SimpleExoPlayer.java | 5 + .../exoplayer2/testutil/StubExoPlayer.java | 5 + 6 files changed, 165 insertions(+), 18 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 90ba31c623b..01103a7a222 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Improve decoder re-use between playbacks. TODO: Write and link a blog post + here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). * Improve initial bandwidth meter estimates using the current country and network type. * Do not retry failed loads whose error is `FileNotFoundException`. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 96ddb2eb9c2..5ba2394c3fd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -209,4 +209,34 @@ public ExoPlayerMessage(PlayerMessage.Target target, int messageType, Object mes /** Returns the currently active {@link SeekParameters} of the player. */ SeekParameters getSeekParameters(); + + /** + * Sets whether the player is allowed to keep holding limited resources such as video decoders, + * even when in the idle state. By doing so, the player may be able to reduce latency when + * starting to play another piece of content for which the same resources are required. + * + *

This mode should be used with caution, since holding limited resources may prevent other + * players of media components from acquiring them. It should only be enabled when both + * of the following conditions are true: + * + *

+ * + *

Note that foreground mode is not useful for switching between content without gaps + * between the playbacks. For this use case {@link #stop} does not need to be called, and simply + * calling {@link #prepare} for the new media will cause limited resources to be retained even if + * foreground mode is not enabled. + * + *

If foreground mode is enabled, it's the application's responsibility to disable it when the + * conditions described above no longer hold. + * + * @param foregroundMode Whether the player is allowed to keep limited resources even when in the + * idle state. + */ + void setForegroundMode(boolean foregroundMode); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index e1f942147de..130bf489211 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -71,6 +71,7 @@ private int pendingOperationAcks; private boolean hasPendingPrepare; private boolean hasPendingSeek; + private boolean foregroundMode; private PlaybackParameters playbackParameters; private SeekParameters seekParameters; private @Nullable ExoPlaybackException playbackError; @@ -359,6 +360,14 @@ public SeekParameters getSeekParameters() { return seekParameters; } + @Override + public void setForegroundMode(boolean foregroundMode) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + internalPlayer.setForegroundMode(foregroundMode); + } + } + @Override public void stop(boolean reset) { if (reset) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 0c6c3ca2028..3034ead0322 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -44,6 +44,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; /** Implements the internal behavior of {@link ExoPlayerImpl}. */ /* package */ final class ExoPlayerImplInternal @@ -76,9 +77,10 @@ private static final int MSG_TRACK_SELECTION_INVALIDATED = 11; private static final int MSG_SET_REPEAT_MODE = 12; private static final int MSG_SET_SHUFFLE_ENABLED = 13; - private static final int MSG_SEND_MESSAGE = 14; - private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15; - private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16; + private static final int MSG_SET_FOREGROUND_MODE = 14; + private static final int MSG_SEND_MESSAGE = 15; + private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 16; + private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 17; private static final int PREPARING_SOURCE_INTERVAL_MS = 10; private static final int RENDERING_INTERVAL_MS = 10; @@ -115,6 +117,7 @@ private boolean rebuffering; @Player.RepeatMode private int repeatMode; private boolean shuffleModeEnabled; + private boolean foregroundMode; private int pendingPrepareCount; private SeekPosition pendingInitialSeekPosition; @@ -218,6 +221,29 @@ public synchronized void sendMessage(PlayerMessage message) { handler.obtainMessage(MSG_SEND_MESSAGE, message).sendToTarget(); } + public synchronized void setForegroundMode(boolean foregroundMode) { + if (foregroundMode) { + handler.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 1, 0).sendToTarget(); + } else { + AtomicBoolean processedFlag = new AtomicBoolean(); + handler + .obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedFlag) + .sendToTarget(); + boolean wasInterrupted = false; + while (!processedFlag.get() && !released) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + } + } + public synchronized void release() { if (released) { return; @@ -311,8 +337,15 @@ public boolean handleMessage(Message msg) { case MSG_SET_SEEK_PARAMETERS: setSeekParametersInternal((SeekParameters) msg.obj); break; + case MSG_SET_FOREGROUND_MODE: + setForegroundModeInternal( + /* foregroundMode= */ msg.arg1 != 0, /* processedFlag= */ (AtomicBoolean) msg.obj); + break; case MSG_STOP: - stopInternal(/* reset= */ msg.arg1 != 0, /* acknowledgeStop= */ true); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ msg.arg1 != 0, + /* acknowledgeStop= */ true); break; case MSG_PERIOD_PREPARED: handlePeriodPrepared((MediaPeriod) msg.obj); @@ -345,17 +378,26 @@ public boolean handleMessage(Message msg) { maybeNotifyPlaybackInfoChanged(); } catch (ExoPlaybackException e) { Log.e(TAG, "Playback error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (IOException e) { Log.e(TAG, "Source error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ false, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); maybeNotifyPlaybackInfoChanged(); } catch (RuntimeException e) { Log.e(TAG, "Internal runtime error.", e); - stopInternal(/* reset= */ false, /* acknowledgeStop= */ false); + stopInternal( + /* forceResetRenderers= */ true, + /* resetPositionAndState= */ false, + /* acknowledgeStop= */ false); eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) .sendToTarget(); maybeNotifyPlaybackInfoChanged(); @@ -394,7 +436,8 @@ private void maybeNotifyPlaybackInfoChanged() { private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) { pendingPrepareCount++; - resetInternal(/* releaseMediaSource= */ true, resetPosition, resetState); + resetInternal( + /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState); loadControl.onPrepared(); this.mediaSource = mediaSource; setState(Player.STATE_BUFFERING); @@ -631,7 +674,10 @@ private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackExcepti // End playback, as we didn't manage to find a valid seek position. setState(Player.STATE_ENDED); resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } else { // Execute the seek in the current media periods. long newPeriodPositionUs = periodPositionUs; @@ -738,9 +784,33 @@ private void setSeekParametersInternal(SeekParameters seekParameters) { this.seekParameters = seekParameters; } - private void stopInternal(boolean reset, boolean acknowledgeStop) { + private void setForegroundModeInternal( + boolean foregroundMode, @Nullable AtomicBoolean processedFlag) { + if (this.foregroundMode != foregroundMode) { + this.foregroundMode = foregroundMode; + if (!foregroundMode) { + for (Renderer renderer : renderers) { + if (renderer.getState() == Renderer.STATE_DISABLED) { + renderer.reset(); + } + } + } + } + if (processedFlag != null) { + synchronized (this) { + processedFlag.set(true); + notifyAll(); + } + } + } + + private void stopInternal( + boolean forceResetRenderers, boolean resetPositionAndState, boolean acknowledgeStop) { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ reset, /* resetState= */ reset); + /* resetRenderers= */ forceResetRenderers || !foregroundMode, + /* releaseMediaSource= */ true, + /* resetPosition= */ resetPositionAndState, + /* resetState= */ resetPositionAndState); playbackInfoUpdate.incrementPendingOperationAcks( pendingPrepareCount + (acknowledgeStop ? 1 : 0)); pendingPrepareCount = 0; @@ -750,7 +820,10 @@ private void stopInternal(boolean reset, boolean acknowledgeStop) { private void releaseInternal() { resetInternal( - /* releaseMediaSource= */ true, /* resetPosition= */ true, /* resetState= */ true); + /* resetRenderers= */ true, + /* releaseMediaSource= */ true, + /* resetPosition= */ true, + /* resetState= */ true); loadControl.onReleased(); setState(Player.STATE_IDLE); internalPlaybackThread.quit(); @@ -772,7 +845,10 @@ private MediaPeriodId getFirstMediaPeriodId() { } private void resetInternal( - boolean releaseMediaSource, boolean resetPosition, boolean resetState) { + boolean resetRenderers, + boolean releaseMediaSource, + boolean resetPosition, + boolean resetState) { handler.removeMessages(MSG_DO_SOME_WORK); rebuffering = false; mediaClock.stop(); @@ -782,7 +858,17 @@ private void resetInternal( disableRenderer(renderer); } catch (ExoPlaybackException | RuntimeException e) { // There's nothing we can do. - Log.e(TAG, "Stop failed.", e); + Log.e(TAG, "Disable failed.", e); + } + } + if (resetRenderers) { + for (Renderer renderer : renderers) { + try { + renderer.reset(); + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Reset failed.", e); + } } } enabledRenderers = new Renderer[0]; @@ -989,7 +1075,6 @@ private void disableRenderer(Renderer renderer) throws ExoPlaybackException { mediaClock.onRendererDisabled(renderer); ensureStopped(renderer); renderer.disable(); - renderer.reset(); } private void reselectTracksInternal() throws ExoPlaybackException { @@ -1294,7 +1379,10 @@ private void handleSourceInfoRefreshEndedPlayback() { setState(Player.STATE_ENDED); // Reset, but retain the source so that it can still be used should a seek occur. resetInternal( - /* releaseMediaSource= */ false, /* resetPosition= */ true, /* resetState= */ false); + /* resetRenderers= */ false, + /* releaseMediaSource= */ false, + /* resetPosition= */ true, + /* resetState= */ false); } /** @@ -1639,9 +1727,17 @@ private void enableRenderers(boolean[] rendererWasEnabledFlags, int totalEnabled throws ExoPlaybackException { enabledRenderers = new Renderer[totalEnabledRendererCount]; int enabledRendererCount = 0; - MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); + TrackSelectorResult trackSelectorResult = queue.getPlayingPeriod().getTrackSelectorResult(); + // Reset all disabled renderers before enabling any new ones. This makes sure resources released + // by the disabled renderers will be available to renderers that are being enabled. + for (int i = 0; i < renderers.length; i++) { + if (!trackSelectorResult.isRendererEnabled(i)) { + renderers[i].reset(); + } + } + // Enable the renderers. for (int i = 0; i < renderers.length; i++) { - if (playingPeriodHolder.getTrackSelectorResult().isRendererEnabled(i)) { + if (trackSelectorResult.isRendererEnabled(i)) { enableRenderer(i, rendererWasEnabledFlags[i], enabledRendererCount++); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 85175568872..4ca6b51ce2f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -958,6 +958,11 @@ public SeekParameters getSeekParameters() { return player.getSeekParameters(); } + @Override + public void setForegroundMode(boolean foregroundMode) { + player.setForegroundMode(foregroundMode); + } + @Override public void stop(boolean reset) { verifyApplicationThread(); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 156b573df8b..1ac19591c08 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -267,4 +267,9 @@ public long getContentPosition() { public long getContentBufferedPosition() { throw new UnsupportedOperationException(); } + + @Override + public void setForegroundMode(boolean foregroundMode) { + throw new UnsupportedOperationException(); + } }