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 a842e736e1..6889337910 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 @@ -24,6 +24,7 @@ import android.media.AudioDeviceInfo; 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; @@ -473,6 +474,7 @@ public interface ExoPlayer extends Player { /* package */ long detachSurfaceTimeoutMs; /* package */ boolean pauseAtEndOfMediaItems; /* package */ boolean usePlatformDiagnostics; + @Nullable /* package */ Looper playbackLooper; /* package */ boolean buildCalled; /** @@ -515,6 +517,7 @@ public interface ExoPlayer extends Player { *
  • {@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}. @@ -1097,6 +1100,23 @@ public interface ExoPlayer extends Player { 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 + public Builder setPlaybackLooper(Looper playbackLooper) { + checkState(!buildCalled); + this.playbackLooper = playbackLooper; + return this; + } + /** * Builds an {@link ExoPlayer} instance. * 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 0a91d7b693..da7e5eb31c 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 @@ -334,7 +334,8 @@ import java.util.concurrent.TimeoutException; applicationLooper, clock, playbackInfoUpdateListener, - playerId); + playerId, + builder.playbackLooper); volume = 1; repeatMode = Player.REPEAT_MODE_OFF; 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 bddba18093..7680e6a0a5 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 @@ -181,7 +181,7 @@ import java.util.concurrent.atomic.AtomicBoolean; 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; @@ -236,7 +236,8 @@ import java.util.concurrent.atomic.AtomicBoolean; Looper applicationLooper, Clock clock, PlaybackInfoUpdateListener playbackInfoUpdateListener, - PlayerId playerId) { + PlayerId playerId, + Looper playbackLooper) { this.playbackInfoUpdateListener = playbackInfoUpdateListener; this.renderers = renderers; this.trackSelector = trackSelector; @@ -277,12 +278,18 @@ import java.util.concurrent.atomic.AtomicBoolean; 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) { @@ -385,7 +392,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @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; @@ -400,7 +407,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @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) { @@ -422,7 +429,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @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); @@ -1374,7 +1381,9 @@ import java.util.concurrent.atomic.AtomicBoolean; /* resetError= */ false); loadControl.onReleased(); setState(Player.STATE_IDLE); - internalPlaybackThread.quit(); + if (internalPlaybackThread != null) { + internalPlaybackThread.quit(); + } synchronized (this) { released = true; notifyAll();