Commit 4cbd4e2e by tonihei Committed by kim-vde

Use Clock to create Handler for delivering messages.

This ensures the message devilery is governed by the clock.

Also replace setting a Handler with a Looper to facilititate this
change.

PiperOrigin-RevId: 353019729
parent a10e9de4
......@@ -138,6 +138,7 @@
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Use
`setVideoSurfaceView` and `clearVideoSurfaceView` instead.
* Replace `PlayerMessage.setHandler` with `PlayerMessage.setLooper`.
* Extractors:
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
allow decoder capability checks based on codec profile/level
......
......@@ -456,6 +456,9 @@ public interface ExoPlayer extends Player {
/** Returns the {@link Looper} associated with the playback thread. */
Looper getPlaybackLooper();
/** Returns the {@link Clock} used for playback. */
Clock getClock();
/** @deprecated Use {@link #prepare()} instead. */
@Deprecated
void retry();
......
......@@ -70,7 +70,6 @@ import java.util.List;
private final Handler playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final ListenerSet<Player.EventListener, Player.Events> listeners;
private final Timeline.Period period;
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
......@@ -79,6 +78,7 @@ import java.util.List;
@Nullable private final AnalyticsCollector analyticsCollector;
private final Looper applicationLooper;
private final BandwidthMeter bandwidthMeter;
private final Clock clock;
@RepeatMode private int repeatMode;
private boolean shuffleModeEnabled;
......@@ -149,6 +149,7 @@ import java.util.List;
this.seekParameters = seekParameters;
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
this.applicationLooper = applicationLooper;
this.clock = clock;
repeatMode = Player.REPEAT_MODE_OFF;
Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this;
listeners =
......@@ -193,7 +194,6 @@ import java.util.List;
applicationLooper,
clock,
playbackInfoUpdateListener);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}
/**
......@@ -261,6 +261,11 @@ import java.util.List;
}
@Override
public Clock getClock() {
return clock;
}
@Override
public void addListener(Player.EventListener listener) {
listeners.add(listener);
}
......@@ -755,7 +760,8 @@ import java.util.List;
target,
playbackInfo.timeline,
getCurrentWindowIndex(),
internalPlayerHandler);
clock,
internalPlayer.getPlaybackLooper());
}
@Override
......
......@@ -1468,7 +1468,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
if (message.getHandler().getLooper() == playbackLooper) {
if (message.getLooper() == playbackLooper) {
deliverMessage(message);
if (playbackInfo.playbackState == Player.STATE_READY
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
......@@ -1481,13 +1481,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void sendMessageToTargetThread(final PlayerMessage message) {
Handler handler = message.getHandler();
if (!handler.getLooper().getThread().isAlive()) {
Looper looper = message.getLooper();
if (!looper.getThread().isAlive()) {
Log.w("TAG", "Trying to send message on a dead thread.");
message.markAsProcessed(/* isDelivered= */ false);
return;
}
handler.post(
clock
.createHandler(looper, /* callback= */ null)
.post(
() -> {
try {
deliverMessage(message);
......
......@@ -16,8 +16,8 @@
package com.google.android.exoplayer2;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import java.util.concurrent.TimeoutException;
......@@ -55,11 +55,12 @@ public final class PlayerMessage {
private final Target target;
private final Sender sender;
private final Clock clock;
private final Timeline timeline;
private int type;
@Nullable private Object payload;
private Handler handler;
private Looper looper;
private int windowIndex;
private long positionMs;
private boolean deleteAfterDelivery;
......@@ -77,7 +78,8 @@ public final class PlayerMessage {
* set to {@link Timeline#EMPTY}, any position can be specified.
* @param defaultWindowIndex The default window index in the {@code timeline} when no other window
* index is specified.
* @param defaultHandler The default handler to send the message on when no other handler is
* @param clock The {@link Clock}.
* @param defaultLooper The default {@link Looper} to send the message on when no other looper is
* specified.
*/
public PlayerMessage(
......@@ -85,11 +87,13 @@ public final class PlayerMessage {
Target target,
Timeline timeline,
int defaultWindowIndex,
Handler defaultHandler) {
Clock clock,
Looper defaultLooper) {
this.sender = sender;
this.target = target;
this.timeline = timeline;
this.handler = defaultHandler;
this.looper = defaultLooper;
this.clock = clock;
this.windowIndex = defaultWindowIndex;
this.positionMs = C.TIME_UNSET;
this.deleteAfterDelivery = true;
......@@ -142,22 +146,28 @@ public final class PlayerMessage {
return payload;
}
/** @deprecated Use {@link #setLooper(Looper)} instead. */
@Deprecated
public PlayerMessage setHandler(Handler handler) {
return setLooper(handler.getLooper());
}
/**
* Sets the handler the message is delivered on.
* Sets the {@link Looper} the message is delivered on.
*
* @param handler A {@link Handler}.
* @param looper A {@link Looper}.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
public PlayerMessage setHandler(Handler handler) {
public PlayerMessage setLooper(Looper looper) {
Assertions.checkState(!isSent);
this.handler = handler;
this.looper = looper;
return this;
}
/** Returns the handler the message is delivered on. */
public Handler getHandler() {
return handler;
/** Returns the {@link Looper} the message is delivered on. */
public Looper getLooper() {
return looper;
}
/**
......@@ -287,19 +297,19 @@ public final class PlayerMessage {
* Blocks until after the message has been delivered or the player is no longer able to deliver
* the message.
*
* <p>Note that this method can't be called if the current thread is the same thread used by the
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
* <p>Note that this method must not be called if the current thread is the same thread used by
* the message {@link #getLooper() looper} as it would cause a deadlock.
*
* @return Whether the message was delivered successfully.
* @throws IllegalStateException If this method is called before {@link #send()}.
* @throws IllegalStateException If this method is called on the same thread used by the message
* handler set with {@link #setHandler(Handler)}.
* {@link #getLooper() looper}.
* @throws InterruptedException If the current thread is interrupted while waiting for the message
* to be delivered.
*/
public synchronized boolean blockUntilDelivered() throws InterruptedException {
Assertions.checkState(isSent);
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
Assertions.checkState(looper.getThread() != Thread.currentThread());
while (!isProcessed) {
wait();
}
......@@ -310,14 +320,14 @@ public final class PlayerMessage {
* Blocks until after the message has been delivered or the player is no longer able to deliver
* the message or the specified timeout elapsed.
*
* <p>Note that this method can't be called if the current thread is the same thread used by the
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
* <p>Note that this method must not be called if the current thread is the same thread used by
* the message {@link #getLooper() looper} as it would cause a deadlock.
*
* @param timeoutMs The timeout in milliseconds.
* @return Whether the message was delivered successfully.
* @throws IllegalStateException If this method is called before {@link #send()}.
* @throws IllegalStateException If this method is called on the same thread used by the message
* handler set with {@link #setHandler(Handler)}.
* {@link #getLooper() looper}.
* @throws TimeoutException If the {@code timeoutMs} elapsed and this message has not been
* delivered and the player is still able to deliver the message.
* @throws InterruptedException If the current thread is interrupted while waiting for the message
......@@ -325,14 +335,8 @@ public final class PlayerMessage {
*/
public synchronized boolean blockUntilDelivered(long timeoutMs)
throws InterruptedException, TimeoutException {
return blockUntilDelivered(timeoutMs, Clock.DEFAULT);
}
@VisibleForTesting()
/* package */ synchronized boolean blockUntilDelivered(long timeoutMs, Clock clock)
throws InterruptedException, TimeoutException {
Assertions.checkState(isSent);
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
Assertions.checkState(looper.getThread() != Thread.currentThread());
long deadlineMs = clock.elapsedRealtime() + timeoutMs;
long remainingMs = timeoutMs;
......@@ -340,11 +344,9 @@ public final class PlayerMessage {
wait(remainingMs);
remainingMs = deadlineMs - clock.elapsedRealtime();
}
if (!isProcessed) {
throw new TimeoutException("Message delivery timed out.");
}
return isDelivered;
}
}
......@@ -1203,6 +1203,11 @@ public class SimpleExoPlayer extends BasePlayer
}
@Override
public Clock getClock() {
return player.getClock();
}
@Override
public void addListener(Player.EventListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
Assertions.checkNotNull(listener);
......
......@@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.os.Handler;
import android.os.HandlerThread;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.util.Clock;
......@@ -55,9 +54,14 @@ public class PlayerMessageTest {
PlayerMessage.Target target = (messageType, payload) -> {};
handlerThread = new HandlerThread("TestHandler");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
message =
new PlayerMessage(sender, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler);
new PlayerMessage(
sender,
target,
Timeline.EMPTY,
/* defaultWindowIndex= */ 0,
clock,
handlerThread.getLooper());
}
@After
......@@ -69,8 +73,7 @@ public class PlayerMessageTest {
public void blockUntilDelivered_timesOut() throws Exception {
when(clock.elapsedRealtime()).thenReturn(0L).thenReturn(TIMEOUT_MS * 2);
assertThrows(
TimeoutException.class, () -> message.send().blockUntilDelivered(TIMEOUT_MS, clock));
assertThrows(TimeoutException.class, () -> message.send().blockUntilDelivered(TIMEOUT_MS));
// Ensure blockUntilDelivered() entered the blocking loop.
verify(clock, Mockito.times(2)).elapsedRealtime();
......@@ -82,7 +85,7 @@ public class PlayerMessageTest {
message.send().markAsProcessed(/* isDelivered= */ true);
assertThat(message.blockUntilDelivered(TIMEOUT_MS, clock)).isTrue();
assertThat(message.blockUntilDelivered(TIMEOUT_MS)).isTrue();
}
@Test
......@@ -110,7 +113,7 @@ public class PlayerMessageTest {
});
try {
assertThat(message.blockUntilDelivered(TIMEOUT_MS, clock)).isTrue();
assertThat(message.blockUntilDelivered(TIMEOUT_MS)).isTrue();
// Ensure blockUntilDelivered() entered the blocking loop.
verify(clock, Mockito.atLeast(2)).elapsedRealtime();
future.get(1, SECONDS);
......
......@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.robolectric;
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil;
import android.os.Handler;
import android.os.Looper;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
......@@ -297,15 +296,17 @@ public class TestPlayerRunHelper {
public static void playUntilPosition(ExoPlayer player, int windowIndex, long positionMs)
throws TimeoutException {
verifyMainTestThread(player);
Handler testHandler = Util.createHandlerForCurrentOrMainLooper();
Looper applicationLooper = Util.getCurrentOrMainLooper();
AtomicBoolean messageHandled = new AtomicBoolean(false);
player
.createMessage(
(messageType, payload) -> {
// Block playback thread until pause command has been sent from test thread.
ConditionVariable blockPlaybackThreadCondition = new ConditionVariable();
testHandler.post(
player
.getClock()
.createHandler(applicationLooper, /* callback= */ null)
.post(
() -> {
player.pause();
messageHandled.set(true);
......@@ -354,7 +355,7 @@ public class TestPlayerRunHelper {
AtomicBoolean receivedMessageCallback = new AtomicBoolean(false);
player
.createMessage((type, data) -> receivedMessageCallback.set(true))
.setHandler(Util.createHandlerForCurrentOrMainLooper())
.setLooper(Util.getCurrentOrMainLooper())
.send();
runMainLooperUntil(receivedMessageCallback::get);
}
......
......@@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer2.testutil;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
......@@ -603,7 +603,7 @@ public abstract class Action {
} else {
message.setPosition(positionMs);
}
message.setHandler(Util.createHandlerForCurrentOrMainLooper());
message.setLooper(Util.getCurrentOrMainLooper());
message.setDeleteAfterDelivery(deleteAfterDelivery);
message.send();
}
......@@ -685,14 +685,17 @@ public abstract class Action {
@Nullable Surface surface,
HandlerWrapper handler,
@Nullable ActionNode nextAction) {
Handler testThreadHandler = Util.createHandlerForCurrentOrMainLooper();
// Schedule a message on the playback thread to ensure the player is paused immediately.
Looper applicationLooper = Util.getCurrentOrMainLooper();
player
.createMessage(
(messageType, payload) -> {
// Block playback thread until pause command has been sent from test thread.
ConditionVariable blockPlaybackThreadCondition = new ConditionVariable();
testThreadHandler.post(
player
.getClock()
.createHandler(applicationLooper, /* callback= */ null)
.post(
() -> {
player.pause();
blockPlaybackThreadCondition.open();
......@@ -712,7 +715,7 @@ public abstract class Action {
(messageType, payload) ->
nextAction.schedule(player, trackSelector, surface, handler))
.setPosition(windowIndex, positionMs)
.setHandler(testThreadHandler)
.setLooper(applicationLooper)
.send();
}
player.play();
......@@ -1049,7 +1052,7 @@ public abstract class Action {
player
.createMessage(
(type, data) -> nextAction.schedule(player, trackSelector, surface, handler))
.setHandler(Util.createHandlerForCurrentOrMainLooper())
.setLooper(Util.getCurrentOrMainLooper())
.send();
}
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
import java.util.List;
/**
......@@ -76,6 +77,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
}
@Override
public Clock getClock() {
throw new UnsupportedOperationException();
}
@Override
public void addListener(Player.EventListener listener) {
throw new UnsupportedOperationException();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment