Clarify and correct allowed multi-threading for some Player methods

Some Player methods like getting the Looper and adding listeners
were always allowed to be called from any thread, but this is
undocumented. This change makes the threading rules of these
methods more explicit.

Removing listeners was never meant to be called from another thread
and we also don't support it safely because final callbacks may
be triggered from the wrong thread. To find potential issues, we
can assert the correct thread when releasing listeners.

Finally, there is a potential race condition when calling addListener
from a different thread at the same time as release, which may lead to
a registered listener that could receive callbacks after the player is
released.

PiperOrigin-RevId: 493843981
This commit is contained in:
tonihei 2022-12-08 11:02:56 +00:00 committed by Ian Baker
parent 533f5288f4
commit 927b2d6a43
9 changed files with 271 additions and 168 deletions

View File

@ -49,6 +49,10 @@ import java.util.List;
* A media player interface defining traditional high-level functionality, such as the ability to * A media player interface defining traditional high-level functionality, such as the ability to
* play, pause, seek and query properties of the currently playing media. * play, pause, seek and query properties of the currently playing media.
* *
* <p>All methods must be called from a single {@linkplain #getApplicationLooper() application
* thread} unless indicated otherwise. Callbacks in registered listeners are called on the same
* thread.
*
* <p>This interface includes some convenience methods that can be implemented by calling other * <p>This interface includes some convenience methods that can be implemented by calling other
* methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting * methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting
* {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of * {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of
@ -1557,6 +1561,8 @@ public interface Player {
/** /**
* Returns the {@link Looper} associated with the application thread that's used to access the * Returns the {@link Looper} associated with the application thread that's used to access the
* player and on which player events are received. * player and on which player events are received.
*
* <p>This method can be called from any thread.
*/ */
Looper getApplicationLooper(); Looper getApplicationLooper();
@ -1566,6 +1572,8 @@ public interface Player {
* <p>The listener's methods will be called on the thread associated with {@link * <p>The listener's methods will be called on the thread associated with {@link
* #getApplicationLooper()}. * #getApplicationLooper()}.
* *
* <p>This method can be called from any thread.
*
* @param listener The listener to register. * @param listener The listener to register.
*/ */
void addListener(Listener listener); void addListener(Listener listener);

View File

@ -1978,8 +1978,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@Override @Override
public final void removeListener(Listener listener) { public final void removeListener(Listener listener) {
// Don't verify application thread. We allow calls to this method from any thread. verifyApplicationThreadAndInitState();
checkNotNull(listener);
listeners.remove(listener); listeners.remove(listener);
} }

View File

@ -15,9 +15,12 @@
*/ */
package androidx.media3.common.util; package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkState;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import androidx.annotation.CheckResult; import androidx.annotation.CheckResult;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.FlagSet; import androidx.media3.common.FlagSet;
@ -34,6 +37,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* <p>Events are also guaranteed to be only sent to the listeners registered at the time the event * <p>Events are also guaranteed to be only sent to the listeners registered at the time the event
* was enqueued and haven't been removed since. * was enqueued and haven't been removed since.
* *
* <p>All methods must be called on the {@link Looper} passed to the constructor unless indicated
* otherwise.
*
* @param <T> The listener type. * @param <T> The listener type.
*/ */
@UnstableApi @UnstableApi
@ -76,14 +82,18 @@ public final class ListenerSet<T extends @NonNull Object> {
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners; private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
private final ArrayDeque<Runnable> flushingEvents; private final ArrayDeque<Runnable> flushingEvents;
private final ArrayDeque<Runnable> queuedEvents; private final ArrayDeque<Runnable> queuedEvents;
private final Object releasedLock;
@GuardedBy("releasedLock")
private boolean released; private boolean released;
private boolean throwsWhenUsingWrongThread;
/** /**
* Creates a new listener set. * Creates a new listener set.
* *
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used * @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
* to call all other methods of this class. * to call all other methods of this class unless indicated otherwise.
* @param clock A {@link Clock}. * @param clock A {@link Clock}.
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent * @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
* during one {@link Looper} message queue iteration were handled by the listeners. * during one {@link Looper} message queue iteration were handled by the listeners.
@ -100,17 +110,21 @@ public final class ListenerSet<T extends @NonNull Object> {
this.clock = clock; this.clock = clock;
this.listeners = listeners; this.listeners = listeners;
this.iterationFinishedEvent = iterationFinishedEvent; this.iterationFinishedEvent = iterationFinishedEvent;
releasedLock = new Object();
flushingEvents = new ArrayDeque<>(); flushingEvents = new ArrayDeque<>();
queuedEvents = new ArrayDeque<>(); queuedEvents = new ArrayDeque<>();
// It's safe to use "this" because we don't send a message before exiting the constructor. // It's safe to use "this" because we don't send a message before exiting the constructor.
@SuppressWarnings("nullness:methodref.receiver.bound") @SuppressWarnings("nullness:methodref.receiver.bound")
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage); HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
this.handler = handler; this.handler = handler;
throwsWhenUsingWrongThread = true;
} }
/** /**
* Copies the listener set. * Copies the listener set.
* *
* <p>This method can be called from any thread.
*
* @param looper The new {@link Looper} for the copied listener set. * @param looper The new {@link Looper} for the copied listener set.
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events * @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
* sent during one {@link Looper} message queue iteration were handled by the listeners. * sent during one {@link Looper} message queue iteration were handled by the listeners.
@ -124,6 +138,8 @@ public final class ListenerSet<T extends @NonNull Object> {
/** /**
* Copies the listener set. * Copies the listener set.
* *
* <p>This method can be called from any thread.
*
* @param looper The new {@link Looper} for the copied listener set. * @param looper The new {@link Looper} for the copied listener set.
* @param clock The new {@link Clock} for the copied listener set. * @param clock The new {@link Clock} for the copied listener set.
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events * @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
@ -141,14 +157,18 @@ public final class ListenerSet<T extends @NonNull Object> {
* *
* <p>If a listener is already present, it will not be added again. * <p>If a listener is already present, it will not be added again.
* *
* <p>This method can be called from any thread.
*
* @param listener The listener to be added. * @param listener The listener to be added.
*/ */
public void add(T listener) { public void add(T listener) {
if (released) {
return;
}
Assertions.checkNotNull(listener); Assertions.checkNotNull(listener);
listeners.add(new ListenerHolder<>(listener)); synchronized (releasedLock) {
if (released) {
return;
}
listeners.add(new ListenerHolder<>(listener));
}
} }
/** /**
@ -159,6 +179,7 @@ public final class ListenerSet<T extends @NonNull Object> {
* @param listener The listener to be removed. * @param listener The listener to be removed.
*/ */
public void remove(T listener) { public void remove(T listener) {
verifyCurrentThread();
for (ListenerHolder<T> listenerHolder : listeners) { for (ListenerHolder<T> listenerHolder : listeners) {
if (listenerHolder.listener.equals(listener)) { if (listenerHolder.listener.equals(listener)) {
listenerHolder.release(iterationFinishedEvent); listenerHolder.release(iterationFinishedEvent);
@ -169,11 +190,13 @@ public final class ListenerSet<T extends @NonNull Object> {
/** Removes all listeners from the set. */ /** Removes all listeners from the set. */
public void clear() { public void clear() {
verifyCurrentThread();
listeners.clear(); listeners.clear();
} }
/** Returns the number of added listeners. */ /** Returns the number of added listeners. */
public int size() { public int size() {
verifyCurrentThread();
return listeners.size(); return listeners.size();
} }
@ -185,6 +208,7 @@ public final class ListenerSet<T extends @NonNull Object> {
* @param event The event. * @param event The event.
*/ */
public void queueEvent(int eventFlag, Event<T> event) { public void queueEvent(int eventFlag, Event<T> event) {
verifyCurrentThread();
CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners); CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners);
queuedEvents.add( queuedEvents.add(
() -> { () -> {
@ -196,6 +220,7 @@ public final class ListenerSet<T extends @NonNull Object> {
/** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */ /** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */
public void flushEvents() { public void flushEvents() {
verifyCurrentThread();
if (queuedEvents.isEmpty()) { if (queuedEvents.isEmpty()) {
return; return;
} }
@ -234,11 +259,27 @@ public final class ListenerSet<T extends @NonNull Object> {
* <p>This will ensure no events are sent to any listener after this method has been called. * <p>This will ensure no events are sent to any listener after this method has been called.
*/ */
public void release() { public void release() {
verifyCurrentThread();
synchronized (releasedLock) {
released = true;
}
for (ListenerHolder<T> listenerHolder : listeners) { for (ListenerHolder<T> listenerHolder : listeners) {
listenerHolder.release(iterationFinishedEvent); listenerHolder.release(iterationFinishedEvent);
} }
listeners.clear(); listeners.clear();
released = true; }
/**
* Sets whether methods throw when using the wrong thread.
*
* <p>Do not use this method unless to support legacy use cases.
*
* @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread.
* @deprecated Do not use this method and ensure all calls are made from the correct thread.
*/
@Deprecated
public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
} }
private boolean handleMessage(Message message) { private boolean handleMessage(Message message) {
@ -254,6 +295,13 @@ public final class ListenerSet<T extends @NonNull Object> {
return true; return true;
} }
private void verifyCurrentThread() {
if (!throwsWhenUsingWrongThread) {
return;
}
checkState(Thread.currentThread() == handler.getLooper().getThread());
}
private static final class ListenerHolder<T extends @NonNull Object> { private static final class ListenerHolder<T extends @NonNull Object> {
public final T listener; public final T listener;

View File

@ -132,15 +132,15 @@ import java.util.List;
* threading model"> * threading model">
* *
* <ul> * <ul>
* <li>ExoPlayer instances must be accessed from a single application thread. For the vast * <li>ExoPlayer instances must be accessed from a single application thread unless indicated
* majority of cases this should be the application's main thread. Using the application's * otherwise. For the vast majority of cases this should be the application's main thread.
* main thread is also a requirement when using ExoPlayer's UI components or the IMA * Using the application's main thread is also a requirement when using ExoPlayer's UI
* extension. The thread on which an ExoPlayer instance must be accessed can be explicitly * components or the IMA extension. The thread on which an ExoPlayer instance must be accessed
* specified by passing a `Looper` when creating the player. If no `Looper` is specified, then * can be explicitly specified by passing a `Looper` when creating the player. If no `Looper`
* the `Looper` of the thread that the player is created on is used, or if that thread does * is specified, then the `Looper` of the thread that the player is created on is used, or if
* not have a `Looper`, the `Looper` of the application's main thread is used. In all cases * that thread does not have a `Looper`, the `Looper` of the application's main thread is
* the `Looper` of the thread from which the player must be accessed can be queried using * used. In all cases the `Looper` of the thread from which the player must be accessed can be
* {@link #getApplicationLooper()}. * queried using {@link #getApplicationLooper()}.
* <li>Registered listeners are called on the thread associated with {@link * <li>Registered listeners are called on the thread associated with {@link
* #getApplicationLooper()}. Note that this means registered listeners are called on the same * #getApplicationLooper()}. Note that this means registered listeners are called on the same
* thread which must be used to access the player. * thread which must be used to access the player.
@ -1229,6 +1229,8 @@ public interface ExoPlayer extends Player {
/** /**
* Adds a listener to receive audio offload events. * Adds a listener to receive audio offload events.
* *
* <p>This method can be called from any thread.
*
* @param listener The listener to register. * @param listener The listener to register.
*/ */
@UnstableApi @UnstableApi
@ -1249,6 +1251,8 @@ public interface ExoPlayer extends Player {
/** /**
* Adds an {@link AnalyticsListener} to receive analytics events. * Adds an {@link AnalyticsListener} to receive analytics events.
* *
* <p>This method can be called from any thread.
*
* @param listener The listener to be added. * @param listener The listener to be added.
*/ */
void addAnalyticsListener(AnalyticsListener listener); void addAnalyticsListener(AnalyticsListener listener);
@ -1314,11 +1318,19 @@ public interface ExoPlayer extends Player {
@Deprecated @Deprecated
TrackSelectionArray getCurrentTrackSelections(); TrackSelectionArray getCurrentTrackSelections();
/** Returns the {@link Looper} associated with the playback thread. */ /**
* Returns the {@link Looper} associated with the playback thread.
*
* <p>This method may be called from any thread.
*/
@UnstableApi @UnstableApi
Looper getPlaybackLooper(); Looper getPlaybackLooper();
/** Returns the {@link Clock} used for playback. */ /**
* Returns the {@link Clock} used for playback.
*
* <p>This method can be called from any thread.
*/
@UnstableApi @UnstableApi
Clock getClock(); Clock getClock();

View File

@ -89,6 +89,7 @@ import androidx.media3.exoplayer.PlayerMessage.Target;
import androidx.media3.exoplayer.Renderer.MessageType; import androidx.media3.exoplayer.Renderer.MessageType;
import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsCollector;
import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.AnalyticsListener;
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
import androidx.media3.exoplayer.analytics.MediaMetricsListener; import androidx.media3.exoplayer.analytics.MediaMetricsListener;
import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.audio.AudioRendererEventListener; import androidx.media3.exoplayer.audio.AudioRendererEventListener;
@ -480,7 +481,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void removeAudioOffloadListener(AudioOffloadListener listener) { public void removeAudioOffloadListener(AudioOffloadListener listener) {
// Don't verify application thread. We allow calls to this method from any thread. verifyApplicationThread();
audioOffloadListeners.remove(listener); audioOffloadListeners.remove(listener);
} }
@ -1488,7 +1489,7 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void removeAnalyticsListener(AnalyticsListener listener) { public void removeAnalyticsListener(AnalyticsListener listener) {
// Don't verify application thread. We allow calls to this method from any thread. verifyApplicationThread();
analyticsCollector.removeListener(checkNotNull(listener)); analyticsCollector.removeListener(checkNotNull(listener));
} }
@ -1605,9 +1606,8 @@ import java.util.concurrent.TimeoutException;
@Override @Override
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
// Don't verify application thread. We allow calls to this method from any thread. verifyApplicationThread();
checkNotNull(listener); listeners.remove(checkNotNull(listener));
listeners.remove(listener);
} }
@Override @Override
@ -1690,8 +1690,14 @@ import java.util.concurrent.TimeoutException;
return false; return false;
} }
@SuppressWarnings("deprecation") // Calling deprecated methods.
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
if (analyticsCollector instanceof DefaultAnalyticsCollector) {
((DefaultAnalyticsCollector) analyticsCollector)
.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
}
} }
/** /**

View File

@ -96,6 +96,20 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
eventTimes = new SparseArray<>(); eventTimes = new SparseArray<>();
} }
/**
* Sets whether methods throw when using the wrong thread.
*
* <p>Do not use this method unless to support legacy use cases.
*
* @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread.
* @deprecated Do not use this method and ensure all calls are made from the correct thread.
*/
@SuppressWarnings("deprecation") // Calling deprecated method.
@Deprecated
public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
}
@Override @Override
@CallSuper @CallSuper
public void addListener(AnalyticsListener listener) { public void addListener(AnalyticsListener listener) {

View File

@ -12006,10 +12006,20 @@ public final class ExoPlayerTest {
@Test @Test
@Config(sdk = Config.ALL_SDKS) @Config(sdk = Config.ALL_SDKS)
public void builder_inBackgroundThread_doesNotThrow() throws Exception { public void builder_inBackgroundThreadWithAllowedAnyThreadMethods_doesNotThrow()
throws Exception {
Thread builderThread = Thread builderThread =
new Thread( new Thread(
() -> new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build()); () -> {
ExoPlayer player =
new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
player.addListener(new Listener() {});
player.addAnalyticsListener(new AnalyticsListener() {});
player.addAudioOffloadListener(new ExoPlayer.AudioOffloadListener() {});
player.getClock();
player.getApplicationLooper();
player.getPlaybackLooper();
});
AtomicReference<Throwable> builderThrow = new AtomicReference<>(); AtomicReference<Throwable> builderThrow = new AtomicReference<>();
builderThread.setUncaughtExceptionHandler((thread, throwable) -> builderThrow.set(throwable)); builderThread.setUncaughtExceptionHandler((thread, throwable) -> builderThrow.set(throwable));

View File

@ -1720,6 +1720,7 @@ public class MediaController implements Player {
@Override @Override
public Looper getApplicationLooper() { public Looper getApplicationLooper() {
// Don't verify application thread. We allow calls to this method from any thread.
return applicationHandler.getLooper(); return applicationHandler.getLooper();
} }
@ -1744,12 +1745,14 @@ public class MediaController implements Player {
@Override @Override
public void addListener(Player.Listener listener) { public void addListener(Player.Listener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
checkNotNull(listener, "listener must not be null"); checkNotNull(listener, "listener must not be null");
impl.addListener(listener); impl.addListener(listener);
} }
@Override @Override
public void removeListener(Player.Listener listener) { public void removeListener(Player.Listener listener) {
verifyApplicationThread();
checkNotNull(listener, "listener must not be null"); checkNotNull(listener, "listener must not be null");
impl.removeListener(listener); impl.removeListener(listener);
} }

View File

@ -19,6 +19,7 @@ import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_N
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -26,7 +27,6 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.media3.test.session.common.PollingCheck; import androidx.media3.test.session.common.PollingCheck;
import androidx.media3.test.session.common.SurfaceActivity; import androidx.media3.test.session.common.SurfaceActivity;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -37,8 +37,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** Tests for {@link MediaController#setVideoSurface(Surface)}. */ /** Tests for {@link MediaController#setVideoSurface(Surface)}. */
@ -47,13 +45,6 @@ import org.junit.runner.RunWith;
public class MediaControllerSurfaceTest { public class MediaControllerSurfaceTest {
private static final String TAG = "MC_SurfaceTest"; private static final String TAG = "MC_SurfaceTest";
private final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
private final MediaControllerTestRule controllerTestRule =
new MediaControllerTestRule(threadTestRule);
@Rule
public final TestRule chain = RuleChain.outerRule(threadTestRule).around(controllerTestRule);
private SurfaceActivity activity; private SurfaceActivity activity;
private RemoteMediaSession remoteSession; private RemoteMediaSession remoteSession;
@ -76,93 +67,97 @@ public class MediaControllerSurfaceTest {
} }
@Test @Test
public void setVideoSurface() throws Exception { public void setVideoSurface() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurface_withNull_clearsSurface() throws Exception { public void setVideoSurface_withNull_clearsSurface() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(null)); activityRule.runOnUiThread(() -> controller.setVideoSurface(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurface_withTheSameSurface() throws Exception { public void clearVideoSurface_withTheSameSurface() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.clearVideoSurface(testSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurface_withDifferentSurface_doesNothing() throws Exception { public void clearVideoSurface_withDifferentSurface_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
Surface anotherSurface = activity.getSecondSurfaceHolder().getSurface(); Surface anotherSurface = activity.getSecondSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(anotherSurface)); activityRule.runOnUiThread(() -> controller.clearVideoSurface(anotherSurface));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurface_withNull_doesNothing() throws Exception { public void clearVideoSurface_withNull_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface(null)); activityRule.runOnUiThread(() -> controller.clearVideoSurface(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceHolder() throws Exception { public void setVideoSurfaceHolder() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(
.getHandler() () -> {
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder)); controller.setVideoSurfaceHolder(testSurfaceHolder);
});
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceHolder_withNull_clearsSurfaceHolder() throws Exception { public void setVideoSurfaceHolder_withNull_clearsSurfaceHolder() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceHolder(null)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceHolder_whenSurfaceIsDestroyed_surfaceIsClearedFromPlayer() public void setVideoSurfaceHolder_whenSurfaceIsDestroyed_surfaceIsClearedFromPlayer()
throws Throwable { throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
@ -171,15 +166,14 @@ public class MediaControllerSurfaceTest {
}); });
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceHolder_whenSurfaceIsCreated_surfaceIsSetToPlayer() throws Throwable { public void setVideoSurfaceHolder_whenSurfaceIsCreated_surfaceIsSetToPlayer() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
SurfaceView firstSurfaceView = activity.getFirstSurfaceView(); SurfaceView firstSurfaceView = activity.getFirstSurfaceView();
@ -193,79 +187,75 @@ public class MediaControllerSurfaceTest {
}); });
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceHolder_withTheSameSurfaceHolder() throws Exception { public void clearVideoSurfaceHolder_withTheSameSurfaceHolder() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.clearVideoSurfaceHolder(testSurfaceHolder));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceHolder_withDifferentSurfaceHolder_doesNothing() throws Exception { public void clearVideoSurfaceHolder_withDifferentSurfaceHolder_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
SurfaceHolder anotherTestSurfaceHolder = activity.getSecondSurfaceHolder(); SurfaceHolder anotherTestSurfaceHolder = activity.getSecondSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoSurfaceHolder(anotherTestSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.clearVideoSurfaceHolder(anotherTestSurfaceHolder));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceHolder_withNull_doesNothing() throws Exception { public void clearVideoSurfaceHolder_withNull_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurfaceHolder(null)); activityRule.runOnUiThread(() -> controller.clearVideoSurfaceHolder(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceView() throws Exception { public void setVideoSurfaceView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceView_withNull_clearsSurfaceView() throws Exception { public void setVideoSurfaceView_withNull_clearsSurfaceView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(null)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceView_whenSurfaceIsDestroyed_surfaceIsClearedFromPlayer() public void setVideoSurfaceView_whenSurfaceIsDestroyed_surfaceIsClearedFromPlayer()
throws Throwable { throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
@ -273,13 +263,14 @@ public class MediaControllerSurfaceTest {
}); });
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoSurfaceView_whenSurfaceIsCreated_surfaceIsSetToPlayer() throws Throwable { public void setVideoSurfaceView_whenSurfaceIsCreated_surfaceIsSetToPlayer() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
testSurfaceView.setVisibility(View.GONE); testSurfaceView.setVisibility(View.GONE);
@ -291,74 +282,76 @@ public class MediaControllerSurfaceTest {
}); });
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceView_withTheSameSurfaceView() throws Exception { public void clearVideoSurfaceView_withTheSameSurfaceView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoSurfaceView(testSurfaceView));
.getHandler()
.postAndSync(() -> controller.clearVideoSurfaceView(testSurfaceView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceView_withDifferentSurfaceView_doesNothing() throws Exception { public void clearVideoSurfaceView_withDifferentSurfaceView_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
SurfaceView anotherTestSurfaceView = activity.getSecondSurfaceView(); SurfaceView anotherTestSurfaceView = activity.getSecondSurfaceView();
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoSurfaceView(anotherTestSurfaceView));
.getHandler()
.postAndSync(() -> controller.clearVideoSurfaceView(anotherTestSurfaceView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceView_withNull_doesNothing() throws Exception { public void clearVideoSurfaceView_withNull_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
SurfaceView anotherTestSurfaceView = activity.getSecondSurfaceView(); SurfaceView anotherTestSurfaceView = activity.getSecondSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurfaceView(null)); activityRule.runOnUiThread(() -> controller.clearVideoSurfaceView(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoTextureView() throws Exception { public void setVideoTextureView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoTextureView_withNull_clearsTextureView() throws Exception { public void setVideoTextureView_withNull_clearsTextureView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(null)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoTextureView_whenSurfaceTextureIsDestroyed_surfaceIsClearedFromPlayer() public void setVideoTextureView_whenSurfaceTextureIsDestroyed_surfaceIsClearedFromPlayer()
throws Throwable { throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
@ -368,14 +361,15 @@ public class MediaControllerSurfaceTest {
}); });
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void setVideoTextureView_whenSurfaceTextureIsAvailable_surfaceIsSetToPlayer() public void setVideoTextureView_whenSurfaceTextureIsAvailable_surfaceIsSetToPlayer()
throws Throwable { throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
activityRule.runOnUiThread( activityRule.runOnUiThread(
() -> { () -> {
ViewGroup rootViewGroup = activity.getRootViewGroup(); ViewGroup rootViewGroup = activity.getRootViewGroup();
@ -391,89 +385,98 @@ public class MediaControllerSurfaceTest {
}); });
PollingCheck.waitFor(TIMEOUT_MS, () -> remoteSession.getMockPlayer().surfaceExists()); PollingCheck.waitFor(TIMEOUT_MS, () -> remoteSession.getMockPlayer().surfaceExists());
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoTextureView_withTheSameTextureView() throws Exception { public void clearVideoTextureView_withTheSameTextureView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoTextureView(testTextureView));
.getHandler()
.postAndSync(() -> controller.clearVideoTextureView(testTextureView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoTextureView_withDifferentTextureView_doesNothing() throws Exception { public void clearVideoTextureView_withDifferentTextureView_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
TextureView anotherTestTextureView = activity.getSecondTextureView(); TextureView anotherTestTextureView = activity.getSecondTextureView();
threadTestRule activityRule.runOnUiThread(() -> controller.clearVideoTextureView(anotherTestTextureView));
.getHandler()
.postAndSync(() -> controller.clearVideoTextureView(anotherTestTextureView));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoTextureView_withNull_doesNothing() throws Exception { public void clearVideoTextureView_withNull_doesNothing() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoTextureView(null)); activityRule.runOnUiThread(() -> controller.clearVideoTextureView(null));
assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isTrue();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceWithNoArguments_afterSetVideoSurface() throws Exception { public void clearVideoSurfaceWithNoArguments_afterSetVideoSurface() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
Surface testSurface = activity.getFirstSurfaceHolder().getSurface(); Surface testSurface = activity.getFirstSurfaceHolder().getSurface();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurface(testSurface)); activityRule.runOnUiThread(() -> controller.setVideoSurface(testSurface));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface()); activityRule.runOnUiThread(() -> controller.clearVideoSurface());
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceWithNoArguments_afterSetVideoSurfaceHolder() throws Exception { public void clearVideoSurfaceWithNoArguments_afterSetVideoSurfaceHolder() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder(); SurfaceHolder testSurfaceHolder = activity.getFirstSurfaceHolder();
threadTestRule activityRule.runOnUiThread(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
.getHandler()
.postAndSync(() -> controller.setVideoSurfaceHolder(testSurfaceHolder));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface()); activityRule.runOnUiThread(() -> controller.clearVideoSurface());
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceWithNoArguments_afterSetVideoSurfaceView() throws Exception { public void clearVideoSurfaceWithNoArguments_afterSetVideoSurfaceView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
SurfaceView testSurfaceView = activity.getFirstSurfaceView(); SurfaceView testSurfaceView = activity.getFirstSurfaceView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoSurfaceView(testSurfaceView)); activityRule.runOnUiThread(() -> controller.setVideoSurfaceView(testSurfaceView));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface()); activityRule.runOnUiThread(() -> controller.clearVideoSurface());
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
} }
@Test @Test
public void clearVideoSurfaceWithNoArguments_afterSetVideoTextureView() throws Exception { public void clearVideoSurfaceWithNoArguments_afterSetVideoTextureView() throws Throwable {
MediaController controller = controllerTestRule.createController(remoteSession.getToken()); MediaController controller = createController();
TextureView testTextureView = activity.getFirstTextureView(); TextureView testTextureView = activity.getFirstTextureView();
threadTestRule.getHandler().postAndSync(() -> controller.setVideoTextureView(testTextureView)); activityRule.runOnUiThread(() -> controller.setVideoTextureView(testTextureView));
threadTestRule.getHandler().postAndSync(() -> controller.clearVideoSurface()); activityRule.runOnUiThread(() -> controller.clearVideoSurface());
assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse(); assertThat(remoteSession.getMockPlayer().surfaceExists()).isFalse();
activityRule.runOnUiThread(controller::release);
}
private MediaController createController() throws Exception {
return new MediaController.Builder(activity, remoteSession.getToken())
.setApplicationLooper(Looper.getMainLooper())
.buildAsync()
.get();
} }
} }