diff --git a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java index 0f8df65959..c5485d3f96 100644 --- a/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java +++ b/extensions/vp9/src/androidTest/java/com/google/android/exoplayer2/ext/vp9/VpxPlaybackTest.java @@ -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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 4bd28150bc..a9980f9803 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 4e97a47924..b5f6e623eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 1c680d4aba..65f43ae684 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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 { 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) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java index 420eb60a48..1e8a89e102 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java @@ -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(); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index d4346a65e1..ec53e5a964 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -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 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(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java index 54537ba548..c2e208afbe 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSource.java @@ -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(); } } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 93c14afc8f..635d0dd835 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -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 messageAndListener = (Pair) 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."); }