mirror of
https://github.com/samsonjs/media.git
synced 2026-03-28 09:55:48 +00:00
Fix NPE in MediaController's constructor
MediaController tries to release itself when the binder to the session becomes unavailable. However, if such thing happens while connecting in the constructor, it causes NPE when accessing MediaControllerImpl which is only initialized after it's connected. This fixes random failures in existing tests. PiperOrigin-RevId: 421423381
This commit is contained in:
parent
9a4ad05586
commit
7756d9823d
4 changed files with 26 additions and 5 deletions
|
|
@ -374,6 +374,7 @@ public class MediaController implements Player {
|
|||
@Initialized
|
||||
MediaController thisRef = this;
|
||||
impl = thisRef.createImpl(context, thisRef, token, connectionHints);
|
||||
impl.connect();
|
||||
}
|
||||
|
||||
/* package */ MediaControllerImpl createImpl(
|
||||
|
|
@ -1823,6 +1824,8 @@ public class MediaController implements Player {
|
|||
|
||||
interface MediaControllerImpl {
|
||||
|
||||
void connect();
|
||||
|
||||
void addListener(Player.Listener listener);
|
||||
|
||||
void removeListener(Player.Listener listener);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
|
||||
private final Context context;
|
||||
private final SessionToken token;
|
||||
private final Bundle connectionHints;
|
||||
private final IBinder.DeathRecipient deathRecipient;
|
||||
private final SurfaceCallback surfaceCallback;
|
||||
private final ListenerSet<Listener> listeners;
|
||||
|
|
@ -211,12 +212,24 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
sequencedFutureManager = new SequencedFutureManager();
|
||||
controllerStub = new MediaControllerStub(this);
|
||||
this.token = token;
|
||||
this.connectionHints = connectionHints;
|
||||
deathRecipient =
|
||||
() ->
|
||||
MediaControllerImplBase.this.instance.runOnApplicationLooper(
|
||||
MediaControllerImplBase.this.instance::release);
|
||||
surfaceCallback = new SurfaceCallback();
|
||||
|
||||
serviceConnection =
|
||||
(this.token.getType() == TYPE_SESSION)
|
||||
? null
|
||||
: new SessionServiceConnection(connectionHints);
|
||||
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper());
|
||||
lastReturnedContentPositionMs = C.TIME_UNSET;
|
||||
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
boolean connectionRequested;
|
||||
if (this.token.getType() == TYPE_SESSION) {
|
||||
// Session
|
||||
|
|
@ -229,9 +242,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
if (!connectionRequested) {
|
||||
this.instance.runOnApplicationLooper(MediaControllerImplBase.this.instance::release);
|
||||
}
|
||||
flushCommandQueueHandler = new FlushCommandQueueHandler(instance.getApplicationLooper());
|
||||
lastReturnedContentPositionMs = C.TIME_UNSET;
|
||||
lastSetPlayWhenReadyCalledTimeMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -155,9 +155,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||
this.instance = instance;
|
||||
controllerCompatCallback = new ControllerCompatCallback();
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
if (this.token.getType() == SessionToken.TYPE_SESSION) {
|
||||
browserCompat = null;
|
||||
connectToSession((MediaSessionCompat.Token) checkStateNotNull(this.token.getBinder()));
|
||||
} else {
|
||||
connectToService();
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
|
|
@ -117,11 +118,15 @@ public class MediaSessionAndControllerTest {
|
|||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
Executor mainExecutor = (runnable) -> Util.postOrRun(mainHandler, runnable);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int idx = i;
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setId(TAG).build());
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
// Keep the instance in AtomicReference to prevent GC.
|
||||
// Otherwise ListenableFuture and corresponding controllers can be GCed and disconnected
|
||||
// callback can be ignored.
|
||||
AtomicReference<ListenableFuture<MediaController>> controllerFutureRef =
|
||||
new AtomicReference<>();
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
ListenableFuture<MediaController> controllerFuture =
|
||||
|
|
@ -134,6 +139,7 @@ public class MediaSessionAndControllerTest {
|
|||
}
|
||||
})
|
||||
.buildAsync();
|
||||
controllerFutureRef.set(controllerFuture);
|
||||
controllerFuture.addListener(
|
||||
() -> {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue