mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
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
This commit is contained in:
parent
a10e9de484
commit
4cbd4e2e2a
10 changed files with 102 additions and 70 deletions
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -260,6 +260,11 @@ import java.util.List;
|
|||
return applicationLooper;
|
||||
}
|
||||
|
||||
@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,21 +1481,23 @@ 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(
|
||||
() -> {
|
||||
try {
|
||||
deliverMessage(message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
clock
|
||||
.createHandler(looper, /* callback= */ null)
|
||||
.post(
|
||||
() -> {
|
||||
try {
|
||||
deliverMessage(message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverMessage(PlayerMessage message) throws ExoPlaybackException {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1202,6 +1202,11 @@ public class SimpleExoPlayer extends BasePlayer
|
|||
return player.getApplicationLooper();
|
||||
}
|
||||
|
||||
@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.
|
||||
|
|
|
|||
|
|
@ -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,20 +296,22 @@ 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.pause();
|
||||
messageHandled.set(true);
|
||||
blockPlaybackThreadCondition.open();
|
||||
});
|
||||
player
|
||||
.getClock()
|
||||
.createHandler(applicationLooper, /* callback= */ null)
|
||||
.post(
|
||||
() -> {
|
||||
player.pause();
|
||||
messageHandled.set(true);
|
||||
blockPlaybackThreadCondition.open();
|
||||
});
|
||||
try {
|
||||
blockPlaybackThreadCondition.block();
|
||||
} catch (InterruptedException e) {
|
||||
|
|
@ -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,18 +685,21 @@ 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.pause();
|
||||
blockPlaybackThreadCondition.open();
|
||||
});
|
||||
player
|
||||
.getClock()
|
||||
.createHandler(applicationLooper, /* callback= */ null)
|
||||
.post(
|
||||
() -> {
|
||||
player.pause();
|
||||
blockPlaybackThreadCondition.open();
|
||||
});
|
||||
try {
|
||||
blockPlaybackThreadCondition.block();
|
||||
} catch (InterruptedException e) {
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
@ -75,6 +76,11 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock getClock() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
|||
Loading…
Reference in a new issue