Clean-up of player message handling.

Some readability fixes for PlayerMessage and the handling in
ExoPlayerImplInternal.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=180544294
This commit is contained in:
tonihei 2018-01-02 07:12:25 -08:00 committed by Oliver Woodman
parent 884f64017f
commit 22f8ee37d4
8 changed files with 107 additions and 129 deletions

View file

@ -122,7 +122,7 @@ public class VpxPlaybackTest extends InstrumentationTestCase {
player
.createMessage(videoRenderer)
.setType(LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER)
.setMessage(new VpxVideoSurfaceView(context))
.setPayload(new VpxVideoSurfaceView(context))
.send();
player.prepare(mediaSource);
player.setPlayWhenReady(true);

View file

@ -217,7 +217,7 @@ public interface ExoPlayer extends Player {
/**
* Creates a message that can be sent to a {@link PlayerMessage.Target}. By default, the message
* will be delivered immediately without blocking on the playback thread. The default {@link
* PlayerMessage#getType()} is 0 and the default {@link PlayerMessage#getMessage()} is null. If a
* PlayerMessage#getType()} is 0 and the default {@link PlayerMessage#getPayload()} is null. If a
* position is specified with {@link PlayerMessage#setPosition(long)}, the message will be
* delivered at this position in the current window defined by {@link #getCurrentWindowIndex()}.
* Alternatively, the message can be sent at a specific window using {@link

View file

@ -336,7 +336,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void sendMessages(ExoPlayerMessage... messages) {
for (ExoPlayerMessage message : messages) {
createMessage(message.target).setType(message.messageType).setMessage(message.message).send();
createMessage(message.target).setType(message.messageType).setPayload(message.message).send();
}
}
@ -357,7 +357,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
playerMessages.add(
createMessage(message.target)
.setType(message.messageType)
.setMessage(message.message)
.setPayload(message.message)
.send());
}
boolean wasInterrupted = false;

View file

@ -79,6 +79,7 @@ import java.util.Collections;
private static final int MSG_CUSTOM = 12;
private static final int MSG_SET_REPEAT_MODE = 13;
private static final int MSG_SET_SHUFFLE_ENABLED = 14;
private static final int MSG_SEND_MESSAGE_TO_TARGET = 15;
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10;
@ -223,14 +224,13 @@ import java.util.Collections;
}
@Override
public synchronized void sendMessage(
PlayerMessage message, PlayerMessage.Sender.Listener listener) {
public synchronized void sendMessage(PlayerMessage message) {
if (released) {
Log.w(TAG, "Ignoring messages sent after release.");
listener.onMessageDeleted();
message.markAsProcessed(/* isDelivered= */ false);
return;
}
handler.obtainMessage(MSG_CUSTOM, new CustomMessageInfo(message, listener)).sendToTarget();
handler.obtainMessage(MSG_CUSTOM, message).sendToTarget();
}
public synchronized void release() {
@ -338,7 +338,10 @@ import java.util.Collections;
reselectTracksInternal();
break;
case MSG_CUSTOM:
sendMessageInternal((CustomMessageInfo) msg.obj);
sendMessageInternal((PlayerMessage) msg.obj);
break;
case MSG_SEND_MESSAGE_TO_TARGET:
sendCustomMessageToTargetThread((PlayerMessage) msg.obj);
break;
case MSG_RELEASE:
releaseInternal();
@ -838,7 +841,7 @@ import java.util.Collections;
if (resetState) {
mediaPeriodInfoSequence.setTimeline(null);
for (CustomMessageInfo customMessageInfo : customMessageInfos) {
customMessageInfo.listener.onMessageDeleted();
customMessageInfo.message.markAsProcessed(/* isDelivered= */ false);
}
customMessageInfos.clear();
nextCustomMessageInfoIndex = 0;
@ -862,58 +865,54 @@ import java.util.Collections;
}
}
private void sendMessageInternal(CustomMessageInfo customMessageInfo) {
if (customMessageInfo.message.getPositionMs() == C.TIME_UNSET) {
private void sendMessageInternal(PlayerMessage message) {
if (message.getPositionMs() == C.TIME_UNSET) {
// If no delivery time is specified, trigger immediate message delivery.
sendCustomMessagesToTarget(customMessageInfo);
sendCustomMessageToTarget(message);
} else if (playbackInfo.timeline == null) {
// Still waiting for initial timeline to resolve position.
customMessageInfos.add(customMessageInfo);
customMessageInfos.add(new CustomMessageInfo(message));
} else {
CustomMessageInfo customMessageInfo = new CustomMessageInfo(message);
if (resolveCustomMessagePosition(customMessageInfo)) {
customMessageInfos.add(customMessageInfo);
// Ensure new message is inserted according to playback order.
Collections.sort(customMessageInfos);
} else {
customMessageInfo.listener.onMessageDeleted();
message.markAsProcessed(/* isDelivered= */ false);
}
}
}
private void sendCustomMessagesToTarget(final CustomMessageInfo customMessageInfo) {
final Runnable handleMessageRunnable =
new Runnable() {
@Override
public void run() {
try {
customMessageInfo
.message
.getTarget()
.handleMessage(
customMessageInfo.message.getType(), customMessageInfo.message.getMessage());
} catch (ExoPlaybackException e) {
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
} finally {
customMessageInfo.listener.onMessageDelivered();
if (customMessageInfo.message.getDeleteAfterDelivery()) {
customMessageInfo.listener.onMessageDeleted();
}
// The message may have caused something to change that now requires us to do
// work.
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
};
if (customMessageInfo.message.getHandler().getLooper() == handler.getLooper()) {
handleMessageRunnable.run();
private void sendCustomMessageToTarget(PlayerMessage message) {
if (message.getHandler().getLooper() == handler.getLooper()) {
deliverCustomMessage(message);
// The message may have caused something to change that now requires us to do work.
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} else {
handler.post(
new Runnable() {
@Override
public void run() {
customMessageInfo.message.getHandler().post(handleMessageRunnable);
}
});
handler.obtainMessage(MSG_SEND_MESSAGE_TO_TARGET, message).sendToTarget();
}
}
private void sendCustomMessageToTargetThread(final PlayerMessage message) {
message
.getHandler()
.post(
new Runnable() {
@Override
public void run() {
deliverCustomMessage(message);
}
});
}
private void deliverCustomMessage(PlayerMessage message) {
try {
message.getTarget().handleMessage(message.getType(), message.getPayload());
} catch (ExoPlaybackException e) {
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
} finally {
message.markAsProcessed(/* isDelivered= */ true);
}
}
@ -921,7 +920,7 @@ import java.util.Collections;
for (int i = customMessageInfos.size() - 1; i >= 0; i--) {
if (!resolveCustomMessagePosition(customMessageInfos.get(i))) {
// Remove messages if new position can't be resolved.
customMessageInfos.get(i).listener.onMessageDeleted();
customMessageInfos.get(i).message.markAsProcessed(/* isDelivered= */ false);
customMessageInfos.remove(i);
}
}
@ -1003,7 +1002,7 @@ import java.util.Collections;
&& nextInfo.resolvedPeriodIndex == currentPeriodIndex
&& nextInfo.resolvedPeriodTimeUs > oldPeriodPositionUs
&& nextInfo.resolvedPeriodTimeUs <= newPeriodPositionUs) {
sendCustomMessagesToTarget(nextInfo);
sendCustomMessageToTarget(nextInfo.message);
if (nextInfo.message.getDeleteAfterDelivery()) {
customMessageInfos.remove(nextCustomMessageInfoIndex);
} else {
@ -1942,15 +1941,13 @@ import java.util.Collections;
private static final class CustomMessageInfo implements Comparable<CustomMessageInfo> {
public final PlayerMessage message;
public final PlayerMessage.Sender.Listener listener;
public int resolvedPeriodIndex;
public long resolvedPeriodTimeUs;
public @Nullable Object resolvedPeriodUid;
public CustomMessageInfo(PlayerMessage message, PlayerMessage.Sender.Listener listener) {
public CustomMessageInfo(PlayerMessage message) {
this.message = message;
this.listener = listener;
}
public void setResolvedPosition(int periodIndex, long periodTimeUs, Object periodUid) {

View file

@ -32,32 +32,21 @@ public final class PlayerMessage {
* Handles a message delivered to the target.
*
* @param messageType The message type.
* @param message The message.
* @param payload The message payload.
* @throws ExoPlaybackException If an error occurred whilst handling the message.
*/
void handleMessage(int messageType, Object message) throws ExoPlaybackException;
void handleMessage(int messageType, Object payload) throws ExoPlaybackException;
}
/** A sender for messages. */
public interface Sender {
/** A listener for message events triggered by the sender. */
interface Listener {
/** Called when the message has been delivered. */
void onMessageDelivered();
/** Called when the message has been deleted. */
void onMessageDeleted();
}
/**
* Sends a message.
*
* @param message The message to be sent.
* @param listener The listener to listen to message events.
*/
void sendMessage(PlayerMessage message, Listener listener);
void sendMessage(PlayerMessage message);
}
private final Target target;
@ -65,14 +54,14 @@ public final class PlayerMessage {
private final Timeline timeline;
private int type;
private Object message;
private Object payload;
private Handler handler;
private int windowIndex;
private long positionMs;
private boolean deleteAfterDelivery;
private boolean isSent;
private boolean isDelivered;
private boolean isDeleted;
private boolean isProcessed;
/**
* Creates a new message.
@ -112,9 +101,9 @@ public final class PlayerMessage {
}
/**
* Sets a custom message type forwarded to the {@link Target#handleMessage(int, Object)}.
* Sets the message type forwarded to {@link Target#handleMessage(int, Object)}.
*
* @param messageType The custom message type.
* @param messageType The message type.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
@ -124,27 +113,27 @@ public final class PlayerMessage {
return this;
}
/** Returns custom message type forwarded to the {@link Target#handleMessage(int, Object)}. */
/** Returns the message type forwarded to {@link Target#handleMessage(int, Object)}. */
public int getType() {
return type;
}
/**
* Sets a custom message forwarded to the {@link Target#handleMessage(int, Object)}.
* Sets the message payload forwarded to {@link Target#handleMessage(int, Object)}.
*
* @param message The custom message.
* @param payload The message payload.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
public PlayerMessage setMessage(@Nullable Object message) {
public PlayerMessage setPayload(@Nullable Object payload) {
Assertions.checkState(!isSent);
this.message = message;
this.payload = payload;
return this;
}
/** Returns custom message forwarded to the {@link Target#handleMessage(int, Object)}. */
public Object getMessage() {
return message;
/** Returns the message payload forwarded to {@link Target#handleMessage(int, Object)}. */
public Object getPayload() {
return payload;
}
/**
@ -248,25 +237,7 @@ public final class PlayerMessage {
Assertions.checkArgument(deleteAfterDelivery);
}
isSent = true;
sender.sendMessage(
this,
new Sender.Listener() {
@Override
public void onMessageDelivered() {
synchronized (PlayerMessage.this) {
isDelivered = true;
PlayerMessage.this.notifyAll();
}
}
@Override
public void onMessageDeleted() {
synchronized (PlayerMessage.this) {
isDeleted = true;
PlayerMessage.this.notifyAll();
}
}
});
sender.sendMessage(this);
return this;
}
@ -287,9 +258,23 @@ public final class PlayerMessage {
public synchronized boolean blockUntilDelivered() throws InterruptedException {
Assertions.checkState(isSent);
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
while (!isDelivered && !isDeleted) {
while (!isProcessed) {
wait();
}
return isDelivered;
}
/**
* Marks the message as processed. Should only be called by a {@link Sender} and may be called
* multiple times.
*
* @param isDelivered Whether the message has been delivered to its target. The message is
* considered as being delivered when this method has been called with {@code isDelivered} set
* to true at least once.
*/
public synchronized void markAsProcessed(boolean isDelivered) {
this.isDelivered |= isDelivered;
isProcessed = true;
notifyAll();
}
}

View file

@ -41,6 +41,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
@ -168,7 +169,7 @@ public class SimpleExoPlayer implements ExoPlayer {
player
.createMessage(renderer)
.setType(C.MSG_SET_SCALING_MODE)
.setMessage(videoScalingMode)
.setPayload(videoScalingMode)
.send();
}
}
@ -357,7 +358,7 @@ public class SimpleExoPlayer implements ExoPlayer {
player
.createMessage(renderer)
.setType(C.MSG_SET_AUDIO_ATTRIBUTES)
.setMessage(audioAttributes)
.setPayload(audioAttributes)
.send();
}
}
@ -379,7 +380,7 @@ public class SimpleExoPlayer implements ExoPlayer {
this.audioVolume = audioVolume;
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) {
player.createMessage(renderer).setType(C.MSG_SET_VOLUME).setMessage(audioVolume).send();
player.createMessage(renderer).setType(C.MSG_SET_VOLUME).setPayload(audioVolume).send();
}
}
}
@ -911,21 +912,22 @@ public class SimpleExoPlayer implements ExoPlayer {
private void setVideoSurfaceInternal(Surface surface, boolean ownsSurface) {
// Note: We don't turn this method into a no-op if the surface is being replaced with itself
// so as to ensure onRenderedFirstFrame callbacks are still called in this case.
boolean surfaceReplaced = this.surface != null && this.surface != surface;
List<PlayerMessage> messages = new ArrayList<>();
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) {
PlayerMessage message =
player.createMessage(renderer).setType(C.MSG_SET_SURFACE).setMessage(surface).send();
if (surfaceReplaced) {
try {
message.blockUntilDelivered();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
messages.add(
player.createMessage(renderer).setType(C.MSG_SET_SURFACE).setPayload(surface).send());
}
}
if (surfaceReplaced) {
if (this.surface != null && this.surface != surface) {
// We're replacing a surface. Block to ensure that it's not accessed after the method returns.
try {
for (PlayerMessage message : messages) {
message.blockUntilDelivered();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// If we created the previous surface, we are responsible for releasing it.
if (this.ownsSurface) {
this.surface.release();

View file

@ -149,7 +149,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe
player
.createMessage(this)
.setType(MSG_ADD)
.setMessage(new MessageData<>(index, mediaSource, actionOnCompletion))
.setPayload(new MessageData<>(index, mediaSource, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
@ -225,7 +225,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe
player
.createMessage(this)
.setType(MSG_ADD_MULTIPLE)
.setMessage(new MessageData<>(index, mediaSources, actionOnCompletion))
.setPayload(new MessageData<>(index, mediaSources, actionOnCompletion))
.send();
} else if (actionOnCompletion != null){
actionOnCompletion.run();
@ -264,7 +264,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe
player
.createMessage(this)
.setType(MSG_REMOVE)
.setMessage(new MessageData<>(index, null, actionOnCompletion))
.setPayload(new MessageData<>(index, null, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
@ -304,7 +304,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe
player
.createMessage(this)
.setType(MSG_MOVE)
.setMessage(new MessageData<>(currentIndex, newIndex, actionOnCompletion))
.setPayload(new MessageData<>(currentIndex, newIndex, actionOnCompletion))
.send();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
@ -438,7 +438,7 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe
new ConcatenatedTimeline(mediaSourceHolders, windowCount, periodCount, shuffleOrder),
null);
if (actionOnCompletion != null) {
player.createMessage(this).setType(MSG_ON_COMPLETION).setMessage(actionOnCompletion).send();
player.createMessage(this).setType(MSG_ON_COMPLETION).setPayload(actionOnCompletion).send();
}
}
}

View file

@ -24,7 +24,6 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Pair;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlayerMessage;
import com.google.android.exoplayer2.Timeline;
@ -299,22 +298,17 @@ public class MediaSourceTestRunner {
}
@Override
public void sendMessage(PlayerMessage message, Listener listener) {
handler.obtainMessage(0, Pair.create(message, listener)).sendToTarget();
public void sendMessage(PlayerMessage message) {
handler.obtainMessage(0, message).sendToTarget();
}
@Override
@SuppressWarnings("unchecked")
public boolean handleMessage(Message msg) {
Pair<PlayerMessage, Listener> messageAndListener = (Pair<PlayerMessage, Listener>) msg.obj;
PlayerMessage message = (PlayerMessage) msg.obj;
try {
messageAndListener
.first
.getTarget()
.handleMessage(
messageAndListener.first.getType(), messageAndListener.first.getMessage());
messageAndListener.second.onMessageDelivered();
messageAndListener.second.onMessageDeleted();
message.getTarget().handleMessage(message.getType(), message.getPayload());
message.markAsProcessed(/* isDelivered= */ true);
} catch (ExoPlaybackException e) {
fail("Unexpected ExoPlaybackException.");
}