From e1fe3120e29a66ac2dcde6e9960756197bac6444 Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 16 Nov 2022 18:42:27 +0000 Subject: [PATCH] Add setPlaybackLooper ExoPlayer builder method The method allows clients to specify a pre-existing thread to use for playback. This can be used to run multiple ExoPlayer instances on the same playback thread. PiperOrigin-RevId: 488980749 --- RELEASENOTES.md | 2 ++ .../androidx/media3/exoplayer/ExoPlayer.java | 21 ++++++++++++ .../media3/exoplayer/ExoPlayerImpl.java | 3 +- .../exoplayer/ExoPlayerImplInternal.java | 33 ++++++++++++------- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c844584e99..cd3ed77a5e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,6 +39,8 @@ Release notes * Fix bug where removing listeners during the player release can cause an `IllegalStateException` ([#10758](https://github.com/google/ExoPlayer/issues/10758)). + * Add `ExoPlayer.Builder.setPlaybackLooper` that sets a pre-existing + playback thread for a new ExoPlayer instance. * Build: * Avoid publishing block when included in another gradle build. * Downloads: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 84769d802f..eae251688e 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -24,6 +24,7 @@ import android.media.AudioTrack; import android.media.MediaCodec; import android.os.Looper; +import android.os.Process; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -485,6 +486,7 @@ final class Builder { /* package */ long detachSurfaceTimeoutMs; /* package */ boolean pauseAtEndOfMediaItems; /* package */ boolean usePlatformDiagnostics; + @Nullable /* package */ Looper playbackLooper; /* package */ boolean buildCalled; /** @@ -527,6 +529,7 @@ final class Builder { *
  • {@code pauseAtEndOfMediaItems}: {@code false} *
  • {@code usePlatformDiagnostics}: {@code true} *
  • {@link Clock}: {@link Clock#DEFAULT} + *
  • {@code playbackLooper}: {@code null} (create new thread) * * * @param context A {@link Context}. @@ -1134,6 +1137,24 @@ public Builder setClock(Clock clock) { return this; } + /** + * Sets the {@link Looper} that will be used for playback. + * + *

    The backing thread should run with priority {@link Process#THREAD_PRIORITY_AUDIO} and + * should handle messages within 10ms. + * + * @param playbackLooper A {@link looper}. + * @return This builder. + * @throws IllegalStateException If {@link #build()} has already been called. + */ + @CanIgnoreReturnValue + @UnstableApi + public Builder setPlaybackLooper(Looper playbackLooper) { + checkState(!buildCalled); + this.playbackLooper = playbackLooper; + return this; + } + /** * Builds an {@link ExoPlayer} instance. * diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 1d087d23b3..d896bc7654 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -345,7 +345,8 @@ public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) applicationLooper, clock, playbackInfoUpdateListener, - playerId); + playerId, + builder.playbackLooper); volume = 1; repeatMode = Player.REPEAT_MODE_OFF; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 312a9c67ae..d92a5e8b87 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -189,7 +189,7 @@ public interface PlaybackInfoUpdateListener { private final LoadControl loadControl; private final BandwidthMeter bandwidthMeter; private final HandlerWrapper handler; - private final HandlerThread internalPlaybackThread; + @Nullable private final HandlerThread internalPlaybackThread; private final Looper playbackLooper; private final Timeline.Window window; private final Timeline.Period period; @@ -244,7 +244,8 @@ public ExoPlayerImplInternal( Looper applicationLooper, Clock clock, PlaybackInfoUpdateListener playbackInfoUpdateListener, - PlayerId playerId) { + PlayerId playerId, + Looper playbackLooper) { this.playbackInfoUpdateListener = playbackInfoUpdateListener; this.renderers = renderers; this.trackSelector = trackSelector; @@ -285,12 +286,18 @@ public ExoPlayerImplInternal( mediaSourceList = new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId); - // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can - // not normally change to this priority" is incorrect. - internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO); - internalPlaybackThread.start(); - playbackLooper = internalPlaybackThread.getLooper(); - handler = clock.createHandler(playbackLooper, this); + if (playbackLooper != null) { + internalPlaybackThread = null; + this.playbackLooper = playbackLooper; + } else { + // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can + // not normally change to this priority" is incorrect. + internalPlaybackThread = + new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO); + internalPlaybackThread.start(); + this.playbackLooper = internalPlaybackThread.getLooper(); + } + handler = clock.createHandler(this.playbackLooper, this); } public void experimentalSetForegroundModeTimeoutMs(long setForegroundModeTimeoutMs) { @@ -393,7 +400,7 @@ public void setShuffleOrder(ShuffleOrder shuffleOrder) { @Override public synchronized void sendMessage(PlayerMessage message) { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { Log.w(TAG, "Ignoring messages sent after release."); message.markAsProcessed(/* isDelivered= */ false); return; @@ -408,7 +415,7 @@ public synchronized void sendMessage(PlayerMessage message) { * @return Whether the operations succeeded. If false, the operation timed out. */ public synchronized boolean setForegroundMode(boolean foregroundMode) { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { return true; } if (foregroundMode) { @@ -430,7 +437,7 @@ public synchronized boolean setForegroundMode(boolean foregroundMode) { * @return Whether the release succeeded. If false, the release timed out. */ public synchronized boolean release() { - if (released || !internalPlaybackThread.isAlive()) { + if (released || !playbackLooper.getThread().isAlive()) { return true; } handler.sendEmptyMessage(MSG_RELEASE); @@ -1382,7 +1389,9 @@ private void releaseInternal() { /* resetError= */ false); loadControl.onReleased(); setState(Player.STATE_IDLE); - internalPlaybackThread.quit(); + if (internalPlaybackThread != null) { + internalPlaybackThread.quit(); + } synchronized (this) { released = true; notifyAll();