Add PlaybackLooperProvider and make it injectable for ExoPlayer
PiperOrigin-RevId: 683607682
This commit is contained in:
parent
abfeea518e
commit
fd48dd9ce8
@ -504,7 +504,7 @@ public interface ExoPlayer extends Player {
|
||||
/* package */ long detachSurfaceTimeoutMs;
|
||||
/* package */ boolean pauseAtEndOfMediaItems;
|
||||
/* package */ boolean usePlatformDiagnostics;
|
||||
@Nullable /* package */ Looper playbackLooper;
|
||||
@Nullable /* package */ PlaybackLooperProvider playbackLooperProvider;
|
||||
/* package */ boolean buildCalled;
|
||||
/* package */ boolean suppressPlaybackOnUnsuitableOutput;
|
||||
/* package */ String playerName;
|
||||
@ -1299,7 +1299,23 @@ public interface ExoPlayer extends Player {
|
||||
@UnstableApi
|
||||
public Builder setPlaybackLooper(Looper playbackLooper) {
|
||||
checkState(!buildCalled);
|
||||
this.playbackLooper = playbackLooper;
|
||||
this.playbackLooperProvider = new PlaybackLooperProvider(playbackLooper);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PlaybackLooperProvider} that will be used for playback.
|
||||
*
|
||||
* @param playbackLooperProvider A {@link PlaybackLooperProvider}.
|
||||
* @return This builder.
|
||||
* @throws IllegalStateException If {@link #build()} has already been called.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
public Builder setPlaybackLooperProvider(PlaybackLooperProvider playbackLooperProvider) {
|
||||
checkState(!buildCalled);
|
||||
this.playbackLooperProvider = playbackLooperProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -375,7 +375,7 @@ import java.util.concurrent.TimeoutException;
|
||||
clock,
|
||||
playbackInfoUpdateListener,
|
||||
playerId,
|
||||
builder.playbackLooper,
|
||||
builder.playbackLooperProvider,
|
||||
preloadConfiguration);
|
||||
|
||||
volume = 1;
|
||||
|
@ -27,10 +27,8 @@ import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -192,7 +190,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private final LoadControl loadControl;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final HandlerWrapper handler;
|
||||
@Nullable private final HandlerThread internalPlaybackThread;
|
||||
private final PlaybackLooperProvider playbackLooperProvider;
|
||||
private final Looper playbackLooper;
|
||||
private final Timeline.Window window;
|
||||
private final Timeline.Period period;
|
||||
@ -257,7 +255,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
Clock clock,
|
||||
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
||||
PlayerId playerId,
|
||||
@Nullable Looper playbackLooper,
|
||||
@Nullable PlaybackLooperProvider playbackLooperProvider,
|
||||
PreloadConfiguration preloadConfiguration) {
|
||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||
this.renderers = renderers;
|
||||
@ -318,17 +316,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
new MediaSourceList(
|
||||
/* listener= */ this, analyticsCollector, applicationLooperHandler, playerId);
|
||||
|
||||
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();
|
||||
}
|
||||
this.playbackLooperProvider =
|
||||
(playbackLooperProvider == null) ? new PlaybackLooperProvider() : playbackLooperProvider;
|
||||
this.playbackLooper = this.playbackLooperProvider.obtainLooper();
|
||||
handler = clock.createHandler(this.playbackLooper, this);
|
||||
}
|
||||
|
||||
@ -1564,9 +1554,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
loadControl.onReleased(playerId);
|
||||
setState(Player.STATE_IDLE);
|
||||
} finally {
|
||||
if (internalPlaybackThread != null) {
|
||||
internalPlaybackThread.quit();
|
||||
}
|
||||
playbackLooperProvider.releaseLooper();
|
||||
synchronized (this) {
|
||||
released = true;
|
||||
notifyAll();
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.exoplayer;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.RestrictTo;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Provides a {@link Looper} for multiple {@link ExoPlayer} instances with reference counting in
|
||||
* order to properly manage the lifecycle of the thread that the {@link Looper} is associated with.
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP)
|
||||
@UnstableApi
|
||||
public final class PlaybackLooperProvider {
|
||||
|
||||
private final Object lock;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private @Nullable Looper playbackLooper;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private @Nullable HandlerThread internalPlaybackThread;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private int referenceCount;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>The {@link PlaybackLooperProvider} instance will create a {@link HandlerThread} internally
|
||||
* and manage its lifecycle.
|
||||
*/
|
||||
public PlaybackLooperProvider() {
|
||||
this(/* looper= */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param looper The {@linkplain Looper playback looper}. If non-null, the caller is responsible
|
||||
* for managing the lifecycle of the thread that the {@code looper} is associated with.
|
||||
* Otherwise, the {@link PlaybackLooperProvider} instance will create a {@linkplain
|
||||
* HandlerThread playback thread} internally and manage its lifecycle.
|
||||
*/
|
||||
public PlaybackLooperProvider(@Nullable Looper looper) {
|
||||
lock = new Object();
|
||||
playbackLooper = looper;
|
||||
internalPlaybackThread = null;
|
||||
referenceCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@linkplain Looper playback looper} by increasing the reference count.
|
||||
*
|
||||
* @return The playback looper. It will either be the {@code looper} injected via the constructor,
|
||||
* or the {@linkplain HandlerThread#getLooper() looper} associated with the internal {@link
|
||||
* HandlerThread playback thread}.
|
||||
*/
|
||||
public Looper obtainLooper() {
|
||||
synchronized (lock) {
|
||||
if (playbackLooper == null) {
|
||||
checkState(referenceCount == 0 && internalPlaybackThread == null);
|
||||
// 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();
|
||||
}
|
||||
referenceCount++;
|
||||
return playbackLooper;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the {@linkplain Looper playback looper} by decreasing the reference count.
|
||||
*
|
||||
* <p>If the playback looper was not provided by the caller, the {@link PlaybackLooperProvider}
|
||||
* instance will automatically stop the internal {@link HandlerThread playback thread}.
|
||||
*/
|
||||
public void releaseLooper() {
|
||||
synchronized (lock) {
|
||||
checkState(referenceCount > 0);
|
||||
referenceCount--;
|
||||
if (referenceCount == 0 && internalPlaybackThread != null) {
|
||||
internalPlaybackThread.quit();
|
||||
internalPlaybackThread = null;
|
||||
playbackLooper = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user