diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3c45c3449a..c7f7ed7bbd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,10 +6,6 @@ * Add optional parameter to `stop` to reset the player when stopping. * Add a reason to `EventListener.onTimelineChanged` to distinguish between initial preparation, reset and dynamic updates. - * Replaced `ExoPlayer.sendMessages` with `ExoPlayer.createMessage` to allow - more customization of the message. Now supports setting a message delivery - playback position and/or a delivery handler. - ([#2189](https://github.com/google/ExoPlayer/issues/2189)). * Buffering: * Allow a back-buffer of media to be retained behind the current playback position, for fast backward seeking. The back-buffer can be configured by 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..0a902e2efe 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 @@ -119,11 +119,9 @@ public class VpxPlaybackTest extends InstrumentationTestCase { new DefaultDataSourceFactory(context, "ExoPlayerExtVp9Test")) .setExtractorsFactory(MatroskaExtractor.FACTORY) .createMediaSource(uri); - player - .createMessage(videoRenderer) - .setType(LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER) - .setMessage(new VpxVideoSurfaceView(context)) - .send(); + player.sendMessages(new ExoPlayer.ExoPlayerMessage(videoRenderer, + LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER, + new VpxVideoSurfaceView(context))); player.prepare(mediaSource); player.setPlayWhenReady(true); Looper.loop(); diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java index 9d8e2dcd9d..40b4b2d383 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -17,13 +17,11 @@ package com.google.android.exoplayer2; import com.google.android.exoplayer2.Player.DefaultEventListener; import com.google.android.exoplayer2.Player.EventListener; -import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.testutil.ActionSchedule; -import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner; import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder; import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer; @@ -944,405 +942,4 @@ public final class ExoPlayerTest extends TestCase { testRunner.assertTimelinesEqual(timeline); testRunner.assertTimelineChangeReasonsEqual(Player.TIMELINE_CHANGE_REASON_PREPARED); } - - public void testSendMessagesDuringPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesAfterPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForTimelineChanged(timeline) - .sendMessage(target, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - } - - public void testMultipleSendMessages() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target50 = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget target80 = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target80, /* positionMs= */ 80) - .sendMessage(target50, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target50.positionMs >= 50); - assertTrue(target80.positionMs >= 80); - assertTrue(target80.positionMs >= target50.positionMs); - } - - public void testMultipleSendMessagesAtSameTime() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target1 = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget target2 = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target1, /* positionMs= */ 50) - .sendMessage(target2, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target1.positionMs >= 50); - assertTrue(target2.positionMs >= 50); - } - - public void testSendMessagesMultiPeriodResolution() throws Exception { - Timeline timeline = - new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 10, /* id= */ 0)); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesAtStartAndEndOfPeriod() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 2); - PositionGrabbingMessageTarget targetStartFirstPeriod = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget targetEndMiddlePeriod = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget targetStartMiddlePeriod = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget targetEndLastPeriod = new PositionGrabbingMessageTarget(); - long duration1Ms = timeline.getWindow(0, new Window()).getDurationMs(); - long duration2Ms = timeline.getWindow(1, new Window()).getDurationMs(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(targetStartFirstPeriod, /* windowIndex= */ 0, /* positionMs= */ 0) - .sendMessage(targetEndMiddlePeriod, /* windowIndex= */ 0, /* positionMs= */ duration1Ms) - .sendMessage(targetStartMiddlePeriod, /* windowIndex= */ 1, /* positionMs= */ 0) - .sendMessage(targetEndLastPeriod, /* windowIndex= */ 1, /* positionMs= */ duration2Ms) - // Add additional prepare at end and wait until it's processed to ensure that - // messages sent at end of playback are received before test ends. - .waitForPlaybackState(Player.STATE_ENDED) - .prepareSource( - new FakeMediaSource(timeline, null), - /* resetPosition= */ false, - /* resetState= */ true) - .waitForPlaybackState(Player.STATE_READY) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilActionScheduleFinished(TIMEOUT_MS) - .blockUntilEnded(TIMEOUT_MS); - assertEquals(0, targetStartFirstPeriod.windowIndex); - assertTrue(targetStartFirstPeriod.positionMs >= 0); - assertEquals(0, targetEndMiddlePeriod.windowIndex); - assertTrue(targetEndMiddlePeriod.positionMs >= duration1Ms); - assertEquals(1, targetStartMiddlePeriod.windowIndex); - assertTrue(targetStartMiddlePeriod.positionMs >= 0); - assertEquals(1, targetEndLastPeriod.windowIndex); - assertTrue(targetEndLastPeriod.positionMs >= duration2Ms); - } - - public void testSendMessagesSeekOnDeliveryTimeDuringPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .seek(/* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesSeekOnDeliveryTimeAfterPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .waitForTimelineChanged(timeline) - .seek(/* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesSeekAfterDeliveryTimeDuringPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .seek(/* positionMs= */ 51) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(C.POSITION_UNSET, target.positionMs); - } - - public void testSendMessagesSeekAfterDeliveryTimeAfterPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .sendMessage(target, /* positionMs= */ 50) - .waitForTimelineChanged(timeline) - .seek(/* positionMs= */ 51) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(C.POSITION_UNSET, target.positionMs); - } - - public void testSendMessagesRepeatDoesNotRepost() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* positionMs= */ 50) - .setRepeatMode(Player.REPEAT_MODE_ALL) - .waitForPositionDiscontinuity() - .setRepeatMode(Player.REPEAT_MODE_OFF) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(1, target.messageCount); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesRepeatWithoutDeletingDoesRepost() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 1); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage( - target, - /* windowIndex= */ 0, - /* positionMs= */ 50, - /* deleteAfterDelivery= */ false) - .setRepeatMode(Player.REPEAT_MODE_ALL) - .waitForPositionDiscontinuity() - .setRepeatMode(Player.REPEAT_MODE_OFF) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(2, target.messageCount); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesMoveCurrentWindowIndex() throws Exception { - Timeline timeline = - new FakeTimeline(new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); - final Timeline secondTimeline = - new FakeTimeline( - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); - final FakeMediaSource mediaSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForTimelineChanged(timeline) - .sendMessage(target, /* positionMs= */ 50) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(secondTimeline, null); - } - }) - .build(); - new ExoPlayerTestRunner.Builder() - .setMediaSource(mediaSource) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - assertEquals(1, target.windowIndex); - } - - public void testSendMessagesMultiWindowDuringPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 3); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(2, target.windowIndex); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesMultiWindowAfterPreparation() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 3); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForTimelineChanged(timeline) - .sendMessage(target, /* windowIndex = */ 2, /* positionMs= */ 50) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(2, target.windowIndex); - assertTrue(target.positionMs >= 50); - } - - public void testSendMessagesMoveWindowIndex() throws Exception { - Timeline timeline = - new FakeTimeline( - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0), - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1)); - final Timeline secondTimeline = - new FakeTimeline( - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 1), - new TimelineWindowDefinition(/* periodCount= */ 1, /* id= */ 0)); - final FakeMediaSource mediaSource = new FakeMediaSource(timeline, null, Builder.VIDEO_FORMAT); - PositionGrabbingMessageTarget target = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForTimelineChanged(timeline) - .sendMessage(target, /* windowIndex = */ 1, /* positionMs= */ 50) - .executeRunnable( - new Runnable() { - @Override - public void run() { - mediaSource.setNewSourceInfo(secondTimeline, null); - } - }) - .waitForTimelineChanged(secondTimeline) - .seek(/* windowIndex= */ 0, /* positionMs= */ 0) - .build(); - new ExoPlayerTestRunner.Builder() - .setMediaSource(mediaSource) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertTrue(target.positionMs >= 50); - assertEquals(0, target.windowIndex); - } - - public void testSendMessagesNonLinearPeriodOrder() throws Exception { - Timeline timeline = new FakeTimeline(/* windowCount= */ 3); - PositionGrabbingMessageTarget target1 = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget target2 = new PositionGrabbingMessageTarget(); - PositionGrabbingMessageTarget target3 = new PositionGrabbingMessageTarget(); - ActionSchedule actionSchedule = - new ActionSchedule.Builder("testSendMessages") - .waitForPlaybackState(Player.STATE_BUFFERING) - .sendMessage(target1, /* windowIndex = */ 0, /* positionMs= */ 50) - .sendMessage(target2, /* windowIndex = */ 1, /* positionMs= */ 50) - .sendMessage(target3, /* windowIndex = */ 2, /* positionMs= */ 50) - .waitForTimelineChanged(timeline) - .seek(/* windowIndex= */ 1, /* positionMs= */ 0) - .waitForPositionDiscontinuity() - .seek(/* windowIndex= */ 0, /* positionMs= */ 0) - .build(); - new ExoPlayerTestRunner.Builder() - .setTimeline(timeline) - .setActionSchedule(actionSchedule) - .build() - .start() - .blockUntilEnded(TIMEOUT_MS); - assertEquals(0, target1.windowIndex); - assertEquals(1, target2.windowIndex); - assertEquals(2, target3.windowIndex); - } - - private static final class PositionGrabbingMessageTarget extends PlayerTarget { - - public int windowIndex; - public long positionMs; - public int messageCount; - - public PositionGrabbingMessageTarget() { - windowIndex = C.INDEX_UNSET; - positionMs = C.POSITION_UNSET; - } - - @Override - public void handleMessage(SimpleExoPlayer player, int messageType, Object message) { - windowIndex = player.getCurrentWindowIndex(); - positionMs = player.getCurrentPosition(); - messageCount++; - } - } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java index 8ee9a13c55..a4103787d1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/BaseRenderer.java @@ -157,7 +157,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { return ADAPTIVE_NOT_SUPPORTED; } - // PlayerMessage.Target implementation. + // ExoPlayerComponent implementation. @Override public void handleMessage(int what, Object object) throws ExoPlaybackException { 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..cc767752be 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 @@ -34,43 +34,40 @@ import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; /** - * An extensible media player that plays {@link MediaSource}s. Instances can be obtained from {@link - * ExoPlayerFactory}. + * An extensible media player that plays {@link MediaSource}s. Instances can be obtained from + * {@link ExoPlayerFactory}. * *

Player components

- * *

ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the * type of the media being played, how and where it is stored, and how it is rendered. Rather than * implementing the loading and rendering of media directly, ExoPlayer implementations delegate this * work to components that are injected when a player is created or when it's prepared for playback. * Components common to all ExoPlayer implementations are: - * *

- * *

An ExoPlayer can be built using the default components provided by the library, but may also * be built using custom implementations if non-standard behaviors are required. For example a * custom LoadControl could be injected to change the player's buffering strategy, or a custom @@ -84,32 +81,30 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; * it's possible to load data from a non-standard source, or through a different network stack. * *

Threading model

- * - *

The figure below shows ExoPlayer's threading model. - * - *

ExoPlayer's threading
- * model + *

The figure below shows ExoPlayer's threading model.

+ *

+ * ExoPlayer's threading model + *

* * */ public interface ExoPlayer extends Player { @@ -120,28 +115,54 @@ public interface ExoPlayer extends Player { @Deprecated interface EventListener extends Player.EventListener {} - /** @deprecated Use {@link PlayerMessage.Target} instead. */ - @Deprecated - interface ExoPlayerComponent extends PlayerMessage.Target {} + /** + * A component of an {@link ExoPlayer} that can receive messages on the playback thread. + *

+ * Messages can be delivered to a component via {@link #sendMessages} and + * {@link #blockingSendMessages}. + */ + interface ExoPlayerComponent { - /** @deprecated Use {@link PlayerMessage} instead. */ - @Deprecated + /** + * Handles a message delivered to the component. Called on the playback thread. + * + * @param messageType The message type. + * @param message The message. + * @throws ExoPlaybackException If an error occurred whilst handling the message. + */ + void handleMessage(int messageType, Object message) throws ExoPlaybackException; + + } + + /** + * Defines a message and a target {@link ExoPlayerComponent} to receive it. + */ final class ExoPlayerMessage { - /** The target to receive the message. */ - public final PlayerMessage.Target target; - /** The type of the message. */ + /** + * The target to receive the message. + */ + public final ExoPlayerComponent target; + /** + * The type of the message. + */ public final int messageType; - /** The message. */ + /** + * The message. + */ public final Object message; - /** @deprecated Use {@link ExoPlayer#createMessage(PlayerMessage.Target)} instead. */ - @Deprecated - public ExoPlayerMessage(PlayerMessage.Target target, int messageType, Object message) { + /** + * @param target The target of the message. + * @param messageType The message type. + * @param message The message. + */ + public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object message) { this.target = target; this.messageType = messageType; this.message = message; } + } /** @@ -215,25 +236,20 @@ public interface ExoPlayer extends Player { void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState); /** - * 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 - * 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 - * PlayerMessage#setPosition(int, long)}. + * Sends messages to their target components. The messages are delivered on the playback thread. + * If a component throws an {@link ExoPlaybackException} then it is propagated out of the player + * as an error. + * + * @param messages The messages to be sent. */ - PlayerMessage createMessage(PlayerMessage.Target target); - - /** @deprecated Use {@link #createMessage(PlayerMessage.Target)} instead. */ - @Deprecated void sendMessages(ExoPlayerMessage... messages); /** - * @deprecated Use {@link #createMessage(PlayerMessage.Target)} with {@link - * PlayerMessage#blockUntilDelivered()}. + * Variant of {@link #sendMessages(ExoPlayerMessage...)} that blocks until after the messages have + * been delivered. + * + * @param messages The messages to be sent. */ - @Deprecated void blockingSendMessages(ExoPlayerMessage... messages); /** 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 afb6428fa5..2869a7668e 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 @@ -22,7 +22,6 @@ import android.os.Message; import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; -import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -32,8 +31,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; /** @@ -48,7 +45,6 @@ import java.util.concurrent.CopyOnWriteArraySet; private final TrackSelectorResult emptyTrackSelectorResult; private final Handler eventHandler; private final ExoPlayerImplInternal internalPlayer; - private final Handler internalPlayerHandler; private final CopyOnWriteArraySet listeners; private final Timeline.Window window; private final Timeline.Period period; @@ -117,7 +113,6 @@ import java.util.concurrent.CopyOnWriteArraySet; shuffleModeEnabled, eventHandler, this); - internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); } @Override @@ -331,47 +326,12 @@ 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(); - } - } - - @Override - public PlayerMessage createMessage(Target target) { - return new PlayerMessage( - internalPlayer, - target, - playbackInfo.timeline, - getCurrentWindowIndex(), - internalPlayerHandler); + internalPlayer.sendMessages(messages); } @Override public void blockingSendMessages(ExoPlayerMessage... messages) { - List playerMessages = new ArrayList<>(); - for (ExoPlayerMessage message : messages) { - playerMessages.add( - createMessage(message.target) - .setType(message.messageType) - .setMessage(message.message) - .send()); - } - boolean wasInterrupted = false; - for (PlayerMessage message : playerMessages) { - boolean blockMessage = true; - while (blockMessage) { - try { - message.blockUntilDelivered(); - blockMessage = false; - } catch (InterruptedException e) { - wasInterrupted = true; - } - } - } - if (wasInterrupted) { - // Restore the interrupted status. - Thread.currentThread().interrupt(); - } + internalPlayer.blockingSendMessages(messages); } @Override 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 f3d0e1794b..09b3231467 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 @@ -22,10 +22,10 @@ import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParameterListener; +import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer2.MediaPeriodInfoSequence.MediaPeriodInfo; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.source.ClippingMediaPeriod; @@ -40,19 +40,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.TraceUtil; -import com.google.android.exoplayer2.util.Util; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -/** Implements the internal behavior of {@link ExoPlayerImpl}. */ -/* package */ final class ExoPlayerImplInternal - implements Handler.Callback, - MediaPeriod.Callback, - TrackSelector.InvalidationListener, - MediaSource.Listener, - PlaybackParameterListener, - PlayerMessage.Sender { +/** + * Implements the internal behavior of {@link ExoPlayerImpl}. + */ +/* package */ final class ExoPlayerImplInternal implements Handler.Callback, + MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener, + PlaybackParameterListener { private static final String TAG = "ExoPlayerImplInternal"; @@ -113,7 +108,6 @@ import java.util.Collections; private final boolean retainBackBufferFromKeyframe; private final DefaultMediaClock mediaClock; private final PlaybackInfoUpdate playbackInfoUpdate; - private final ArrayList customMessageInfos; @SuppressWarnings("unused") private SeekParameters seekParameters; @@ -126,12 +120,13 @@ import java.util.Collections; private boolean rebuffering; private @Player.RepeatMode int repeatMode; private boolean shuffleModeEnabled; + private int customMessagesSent; + private int customMessagesProcessed; private long elapsedRealtimeUs; private int pendingPrepareCount; private SeekPosition pendingInitialSeekPosition; private long rendererPositionUs; - private int nextCustomMessageInfoIndex; private MediaPeriodHolder loadingPeriodHolder; private MediaPeriodHolder readingPeriodHolder; @@ -171,7 +166,6 @@ import java.util.Collections; rendererCapabilities[i] = renderers[i].getCapabilities(); } mediaClock = new DefaultMediaClock(this); - customMessageInfos = new ArrayList<>(); enabledRenderers = new Renderer[0]; window = new Timeline.Window(); period = new Timeline.Period(); @@ -220,15 +214,34 @@ import java.util.Collections; handler.obtainMessage(MSG_STOP, reset ? 1 : 0, 0).sendToTarget(); } - @Override - public synchronized void sendMessage( - PlayerMessage message, PlayerMessage.Sender.Listener listener) { + public void sendMessages(ExoPlayerMessage... messages) { if (released) { Log.w(TAG, "Ignoring messages sent after release."); - listener.onMessageDeleted(); return; } - handler.obtainMessage(MSG_CUSTOM, new CustomMessageInfo(message, listener)).sendToTarget(); + customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + } + + public synchronized void blockingSendMessages(ExoPlayerMessage... messages) { + if (released) { + Log.w(TAG, "Ignoring messages sent after release."); + return; + } + int messageNumber = customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + boolean wasInterrupted = false; + while (customMessagesProcessed <= messageNumber) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } } public synchronized void release() { @@ -336,7 +349,7 @@ import java.util.Collections; reselectTracksInternal(); break; case MSG_CUSTOM: - sendMessageInternal((CustomMessageInfo) msg.obj); + sendMessagesInternal((ExoPlayerMessage[]) msg.obj); break; case MSG_RELEASE: releaseInternal(); @@ -524,9 +537,8 @@ import java.util.Collections; } else { rendererPositionUs = mediaClock.syncAndGetPositionUs(); periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); - maybeTriggerCustomMessages(playbackInfo.positionUs, periodPositionUs); - playbackInfo.positionUs = periodPositionUs; } + playbackInfo.positionUs = periodPositionUs; elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; // Update the buffered position. @@ -644,8 +656,7 @@ import java.util.Collections; boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET; try { - Pair periodPosition = - resolveSeekPosition(seekPosition, /* trySubsequentPeriods= */ true); + Pair periodPosition = resolveSeekPosition(seekPosition); if (periodPosition == null) { // The seek position was valid for the timeline that it was performed into, but the // timeline has changed and a suitable seek position could not be resolved in the new one. @@ -839,11 +850,6 @@ import java.util.Collections; } if (resetState) { mediaPeriodInfoSequence.setTimeline(null); - for (CustomMessageInfo customMessageInfo : customMessageInfos) { - customMessageInfo.listener.onMessageDeleted(); - } - customMessageInfos.clear(); - nextCustomMessageInfoIndex = 0; } playbackInfo = new PlaybackInfo( @@ -864,153 +870,21 @@ import java.util.Collections; } } - private void sendMessageInternal(CustomMessageInfo customMessageInfo) { - if (customMessageInfo.message.getPositionMs() == C.TIME_UNSET) { - // If no delivery time is specified, trigger immediate message delivery. - sendCustomMessagesToTarget(customMessageInfo); - } else if (playbackInfo.timeline == null) { - // Still waiting for initial timeline to resolve position. - customMessageInfos.add(customMessageInfo); - } else { - if (resolveCustomMessagePosition(customMessageInfo)) { - customMessageInfos.add(customMessageInfo); - // Ensure new message is inserted according to playback order. - Collections.sort(customMessageInfos); - } else { - customMessageInfo.listener.onMessageDeleted(); + private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException { + try { + for (ExoPlayerMessage message : messages) { + message.target.handleMessage(message.messageType, message.message); } - } - } - - 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); - } - } - }; - handler.post( - new Runnable() { - @Override - public void run() { - customMessageInfo.message.getHandler().post(handleMessageRunnable); - } - }); - } - - private void resolveCustomMessagePositions() { - 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.remove(i); + if (playbackInfo.playbackState == Player.STATE_READY + || playbackInfo.playbackState == Player.STATE_BUFFERING) { + // The message may have caused something to change that now requires us to do work. + handler.sendEmptyMessage(MSG_DO_SOME_WORK); } - } - // Re-sort messages by playback order. - Collections.sort(customMessageInfos); - } - - private boolean resolveCustomMessagePosition(CustomMessageInfo customMessageInfo) { - if (customMessageInfo.resolvedPeriodUid == null) { - // Position is still unresolved. Try to find window in current timeline. - Pair periodPosition = - resolveSeekPosition( - new SeekPosition( - customMessageInfo.message.getTimeline(), - customMessageInfo.message.getWindowIndex(), - C.msToUs(customMessageInfo.message.getPositionMs())), - /* trySubsequentPeriods= */ false); - if (periodPosition == null) { - return false; + } finally { + synchronized (this) { + customMessagesProcessed++; + notifyAll(); } - customMessageInfo.setResolvedPosition( - periodPosition.first, - periodPosition.second, - playbackInfo.timeline.getPeriod(periodPosition.first, period, true).uid); - } else { - // Position has been resolved for a previous timeline. Try to find the updated period index. - int index = playbackInfo.timeline.getIndexOfPeriod(customMessageInfo.resolvedPeriodUid); - if (index == C.INDEX_UNSET) { - return false; - } - customMessageInfo.resolvedPeriodIndex = index; - } - return true; - } - - private void maybeTriggerCustomMessages(long oldPeriodPositionUs, long newPeriodPositionUs) { - if (customMessageInfos.isEmpty() || playbackInfo.periodId.isAd()) { - return; - } - // If this is the first call from the start position, include oldPeriodPositionUs in potential - // trigger positions. - if (playbackInfo.startPositionUs == oldPeriodPositionUs) { - oldPeriodPositionUs--; - } - // Correct next index if necessary (e.g. after seeking, timeline changes, or new messages) - int currentPeriodIndex = playbackInfo.periodId.periodIndex; - CustomMessageInfo prevInfo = - nextCustomMessageInfoIndex > 0 - ? customMessageInfos.get(nextCustomMessageInfoIndex - 1) - : null; - while (prevInfo != null - && (prevInfo.resolvedPeriodIndex > currentPeriodIndex - || (prevInfo.resolvedPeriodIndex == currentPeriodIndex - && prevInfo.resolvedPeriodTimeUs > oldPeriodPositionUs))) { - nextCustomMessageInfoIndex--; - prevInfo = - nextCustomMessageInfoIndex > 0 - ? customMessageInfos.get(nextCustomMessageInfoIndex - 1) - : null; - } - CustomMessageInfo nextInfo = - nextCustomMessageInfoIndex < customMessageInfos.size() - ? customMessageInfos.get(nextCustomMessageInfoIndex) - : null; - while (nextInfo != null - && nextInfo.resolvedPeriodUid != null - && (nextInfo.resolvedPeriodIndex < currentPeriodIndex - || (nextInfo.resolvedPeriodIndex == currentPeriodIndex - && nextInfo.resolvedPeriodTimeUs <= oldPeriodPositionUs))) { - nextCustomMessageInfoIndex++; - nextInfo = - nextCustomMessageInfoIndex < customMessageInfos.size() - ? customMessageInfos.get(nextCustomMessageInfoIndex) - : null; - } - // Check if any message falls within the covered time span. - while (nextInfo != null - && nextInfo.resolvedPeriodUid != null - && nextInfo.resolvedPeriodIndex == currentPeriodIndex - && nextInfo.resolvedPeriodTimeUs > oldPeriodPositionUs - && nextInfo.resolvedPeriodTimeUs <= newPeriodPositionUs) { - sendCustomMessagesToTarget(nextInfo); - if (nextInfo.message.getDeleteAfterDelivery()) { - customMessageInfos.remove(nextCustomMessageInfoIndex); - } else { - nextCustomMessageInfoIndex++; - } - nextInfo = - nextCustomMessageInfoIndex < customMessageInfos.size() - ? customMessageInfos.get(nextCustomMessageInfoIndex) - : null; } } @@ -1160,14 +1034,12 @@ import java.util.Collections; Object manifest = sourceRefreshInfo.manifest; mediaPeriodInfoSequence.setTimeline(timeline); playbackInfo = playbackInfo.copyWithTimeline(timeline, manifest); - resolveCustomMessagePositions(); if (oldTimeline == null) { playbackInfoUpdate.incrementPendingOperationAcks(pendingPrepareCount); pendingPrepareCount = 0; if (pendingInitialSeekPosition != null) { - Pair periodPosition = - resolveSeekPosition(pendingInitialSeekPosition, /* trySubsequentPeriods= */ true); + Pair periodPosition = resolveSeekPosition(pendingInitialSeekPosition); pendingInitialSeekPosition = null; if (periodPosition == null) { // The seek position was valid for the timeline that it was performed into, but the @@ -1352,14 +1224,11 @@ import java.util.Collections; * internal timeline. * * @param seekPosition The position to resolve. - * @param trySubsequentPeriods Whether the position can be resolved to a subsequent matching - * period if the original period is no longer available. * @return The resolved position, or null if resolution was not successful. * @throws IllegalSeekPositionException If the window index of the seek position is outside the * bounds of the timeline. */ - private Pair resolveSeekPosition( - SeekPosition seekPosition, boolean trySubsequentPeriods) { + private Pair resolveSeekPosition(SeekPosition seekPosition) { Timeline timeline = playbackInfo.timeline; Timeline seekTimeline = seekPosition.timeline; if (seekTimeline.isEmpty()) { @@ -1388,14 +1257,12 @@ import java.util.Collections; // We successfully located the period in the internal timeline. return Pair.create(periodIndex, periodPosition.second); } - if (trySubsequentPeriods) { - // Try and find a subsequent period from the seek timeline in the internal timeline. - periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); - if (periodIndex != C.INDEX_UNSET) { - // We found one. Map the SeekPosition onto the corresponding default position. - return getPeriodPosition( - timeline, timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); - } + // Try and find a subsequent period from the seek timeline in the internal timeline. + periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); + if (periodIndex != C.INDEX_UNSET) { + // We found one. Map the SeekPosition onto the corresponding default position. + return getPeriodPosition(timeline, timeline.getPeriod(periodIndex, period).windowIndex, + C.TIME_UNSET); } // We didn't find one. Give up. return null; @@ -1935,45 +1802,7 @@ import java.util.Collections; this.windowIndex = windowIndex; this.windowPositionUs = windowPositionUs; } - } - 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) { - this.message = message; - this.listener = listener; - } - - public void setResolvedPosition(int periodIndex, long periodTimeUs, Object periodUid) { - resolvedPeriodIndex = periodIndex; - resolvedPeriodTimeUs = periodTimeUs; - resolvedPeriodUid = periodUid; - } - - @Override - public int compareTo(@NonNull CustomMessageInfo other) { - if ((resolvedPeriodUid == null) != (other.resolvedPeriodUid == null)) { - // CustomMessageInfos with a resolved period position are always smaller. - return resolvedPeriodUid != null ? -1 : 1; - } - if (resolvedPeriodUid == null) { - // Don't sort message with unresolved positions. - return 0; - } - // Sort resolved media times by period index and then by period position. - int comparePeriodIndex = resolvedPeriodIndex - other.resolvedPeriodIndex; - if (comparePeriodIndex != 0) { - return comparePeriodIndex; - } - return Util.compareLong(resolvedPeriodTimeUs, other.resolvedPeriodTimeUs); - } } private static final class MediaSourceRefreshInfo { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java index 593d3d1fce..978f4f7a97 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/NoSampleRenderer.java @@ -179,7 +179,7 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities return ADAPTIVE_NOT_SUPPORTED; } - // PlayerMessage.Target implementation. + // ExoPlayerComponent implementation. @Override public void handleMessage(int what, Object object) throws ExoPlaybackException { 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 deleted file mode 100644 index 44a4b0c7c2..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/PlayerMessage.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2; - -import android.os.Handler; -import android.support.annotation.Nullable; -import com.google.android.exoplayer2.util.Assertions; - -/** - * Defines a player message which can be sent with a {@link Sender} and received by a {@link - * Target}. - */ -public final class PlayerMessage { - - /** A target for messages. */ - public interface Target { - - /** - * Handles a message delivered to the target. - * - * @param messageType The message type. - * @param message The message. - * @throws ExoPlaybackException If an error occurred whilst handling the message. - */ - void handleMessage(int messageType, Object message) 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); - } - - private final Target target; - private final Sender sender; - private final Timeline timeline; - - private int type; - private Object message; - private Handler handler; - private int windowIndex; - private long positionMs; - private boolean deleteAfterDelivery; - private boolean isSent; - private boolean isDelivered; - private boolean isDeleted; - - /** - * Creates a new message. - * - * @param sender The {@link Sender} used to send the message. - * @param target The {@link Target} the message is sent to. - * @param timeline The timeline used when setting the position with {@link #setPosition(long)}. If - * 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 - * specified. - */ - public PlayerMessage( - Sender sender, - Target target, - Timeline timeline, - int defaultWindowIndex, - Handler defaultHandler) { - this.sender = sender; - this.target = target; - this.timeline = timeline; - this.handler = defaultHandler; - this.windowIndex = defaultWindowIndex; - this.positionMs = C.TIME_UNSET; - this.deleteAfterDelivery = true; - } - - /** Returns the timeline used for setting the position with {@link #setPosition(long)}. */ - public Timeline getTimeline() { - return timeline; - } - - /** Returns the target the message is sent to. */ - public Target getTarget() { - return target; - } - - /** - * Sets a custom message type forwarded to the {@link Target#handleMessage(int, Object)}. - * - * @param messageType The custom message type. - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setType(int messageType) { - Assertions.checkState(!isSent); - this.type = messageType; - return this; - } - - /** Returns custom message type forwarded to the {@link Target#handleMessage(int, Object)}. */ - public int getType() { - return type; - } - - /** - * Sets a custom message forwarded to the {@link Target#handleMessage(int, Object)}. - * - * @param message The custom message. - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setMessage(@Nullable Object message) { - Assertions.checkState(!isSent); - this.message = message; - return this; - } - - /** Returns custom message forwarded to the {@link Target#handleMessage(int, Object)}. */ - public Object getMessage() { - return message; - } - - /** - * Sets the handler the message is delivered on. - * - * @param handler A {@link Handler}. - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setHandler(Handler handler) { - Assertions.checkState(!isSent); - this.handler = handler; - return this; - } - - /** Returns the handler the message is delivered on. */ - public Handler getHandler() { - return handler; - } - - /** - * Sets a position in the current window at which the message will be delivered. - * - * @param positionMs The position in the current window at which the message will be sent, in - * milliseconds. - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setPosition(long positionMs) { - Assertions.checkState(!isSent); - this.positionMs = positionMs; - return this; - } - - /** - * Returns position in window at {@link #getWindowIndex()} at which the message will be delivered, - * in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. - */ - public long getPositionMs() { - return positionMs; - } - - /** - * Sets a position in a window at which the message will be delivered. - * - * @param windowIndex The index of the window at which the message will be sent. - * @param positionMs The position in the window with index {@code windowIndex} at which the - * message will be sent, in milliseconds. - * @return This message. - * @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not - * empty and the provided window index is not within the bounds of the timeline. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setPosition(int windowIndex, long positionMs) { - Assertions.checkState(!isSent); - Assertions.checkArgument(positionMs != C.TIME_UNSET); - if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { - throw new IllegalSeekPositionException(timeline, windowIndex, positionMs); - } - this.windowIndex = windowIndex; - this.positionMs = positionMs; - return this; - } - - /** Returns window index at which the message will be delivered. */ - public int getWindowIndex() { - return windowIndex; - } - - /** - * Sets whether the message will be deleted after delivery. If false, the message will be resent - * if playback reaches the specified position again. Only allowed to be false if a position is set - * with {@link #setPosition(long)}. - * - * @param deleteAfterDelivery Whether the message is deleted after delivery. - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage setDeleteAfterDelivery(boolean deleteAfterDelivery) { - Assertions.checkState(!isSent); - this.deleteAfterDelivery = deleteAfterDelivery; - return this; - } - - /** Returns whether the message will be deleted after delivery. */ - public boolean getDeleteAfterDelivery() { - return deleteAfterDelivery; - } - - /** - * Sends the message. If the target throws an {@link ExoPlaybackException} then it is propagated - * out of the player as an error using {@link - * Player.EventListener#onPlayerError(ExoPlaybackException)}. - * - * @return This message. - * @throws IllegalStateException If {@link #send()} has already been called. - */ - public PlayerMessage send() { - Assertions.checkState(!isSent); - if (positionMs == C.TIME_UNSET) { - 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(); - } - } - }); - return this; - } - - /** - * Blocks until after the message has been delivered or the player is no longer able to deliver - * the message. - * - *

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. - * - * @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)}. - * @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()); - while (!isDelivered && !isDeleted) { - wait(); - } - return isDelivered; - } -} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index d0a07930e0..6def1591da 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -15,20 +15,22 @@ */ package com.google.android.exoplayer2; +import com.google.android.exoplayer2.ExoPlayer.ExoPlayerComponent; import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.util.MediaClock; import java.io.IOException; /** * Renders media read from a {@link SampleStream}. - * - *

Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is + *

+ * Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is * transitioned through various states as the overall playback state changes. The valid state * transitions are shown below, annotated with the methods that are called during each transition. - * - *

Renderer state transitions + *

+ * Renderer state transitions + *

*/ -public interface Renderer extends PlayerMessage.Target { +public interface Renderer extends ExoPlayerComponent { /** * The renderer is disabled. 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 e2d0ed1422..69369d4229 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 @@ -93,6 +93,8 @@ public class SimpleExoPlayer implements ExoPlayer { private final CopyOnWriteArraySet metadataOutputs; private final CopyOnWriteArraySet videoDebugListeners; private final CopyOnWriteArraySet audioDebugListeners; + private final int videoRendererCount; + private final int audioRendererCount; private Format videoFormat; private Format audioFormat; @@ -122,6 +124,25 @@ public class SimpleExoPlayer implements ExoPlayer { renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener, componentListener, componentListener); + // Obtain counts of video and audio renderers. + int videoRendererCount = 0; + int audioRendererCount = 0; + for (Renderer renderer : renderers) { + switch (renderer.getTrackType()) { + case C.TRACK_TYPE_VIDEO: + videoRendererCount++; + break; + case C.TRACK_TYPE_AUDIO: + audioRendererCount++; + break; + default: + // Don't count other track types. + break; + } + } + this.videoRendererCount = videoRendererCount; + this.audioRendererCount = audioRendererCount; + // Set initial values. audioVolume = 1; audioSessionId = C.AUDIO_SESSION_ID_UNSET; @@ -142,15 +163,15 @@ public class SimpleExoPlayer implements ExoPlayer { */ public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { this.videoScalingMode = videoScalingMode; + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { - player - .createMessage(renderer) - .setType(C.MSG_SET_SCALING_MODE) - .setMessage(videoScalingMode) - .send(); + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SCALING_MODE, + videoScalingMode); } } + player.sendMessages(messages); } /** @@ -331,15 +352,15 @@ public class SimpleExoPlayer implements ExoPlayer { */ public void setAudioAttributes(AudioAttributes audioAttributes) { this.audioAttributes = audioAttributes; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { - player - .createMessage(renderer) - .setType(C.MSG_SET_AUDIO_ATTRIBUTES) - .setMessage(audioAttributes) - .send(); + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_AUDIO_ATTRIBUTES, + audioAttributes); } } + player.sendMessages(messages); } /** @@ -356,11 +377,14 @@ public class SimpleExoPlayer implements ExoPlayer { */ public void setVolume(float audioVolume) { this.audioVolume = audioVolume; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; for (Renderer renderer : renderers) { if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { - player.createMessage(renderer).setType(C.MSG_SET_VOLUME).setMessage(audioVolume).send(); + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_VOLUME, audioVolume); } } + player.sendMessages(messages); } /** @@ -746,11 +770,6 @@ public class SimpleExoPlayer implements ExoPlayer { player.sendMessages(messages); } - @Override - public PlayerMessage createMessage(PlayerMessage.Target target) { - return player.createMessage(target); - } - @Override public void blockingSendMessages(ExoPlayerMessage... messages) { player.blockingSendMessages(messages); @@ -889,25 +908,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; + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; 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[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); } } - 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. + player.blockingSendMessages(messages); // If we created the previous surface, we are responsible for releasing it. if (this.ownsSurface) { this.surface.release(); } + } else { + player.sendMessages(messages); } this.surface = surface; this.ownsSurface = ownsSurface; 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..c410456e7b 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 @@ -23,7 +23,8 @@ import android.util.SparseIntArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.PlayerMessage; +import com.google.android.exoplayer2.ExoPlayer.ExoPlayerComponent; +import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; @@ -41,7 +42,7 @@ import java.util.Map; * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified * during playback. Access to this class is thread-safe. */ -public final class DynamicConcatenatingMediaSource implements MediaSource, PlayerMessage.Target { +public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPlayerComponent { private static final int MSG_ADD = 0; private static final int MSG_ADD_MULTIPLE = 1; @@ -146,11 +147,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe Assertions.checkArgument(!mediaSourcesPublic.contains(mediaSource)); mediaSourcesPublic.add(index, mediaSource); if (player != null) { - player - .createMessage(this) - .setType(MSG_ADD) - .setMessage(new MessageData<>(index, mediaSource, actionOnCompletion)) - .send(); + player.sendMessages(new ExoPlayerMessage(this, MSG_ADD, + new MessageData<>(index, mediaSource, actionOnCompletion))); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -222,11 +220,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe } mediaSourcesPublic.addAll(index, mediaSources); if (player != null && !mediaSources.isEmpty()) { - player - .createMessage(this) - .setType(MSG_ADD_MULTIPLE) - .setMessage(new MessageData<>(index, mediaSources, actionOnCompletion)) - .send(); + player.sendMessages(new ExoPlayerMessage(this, MSG_ADD_MULTIPLE, + new MessageData<>(index, mediaSources, actionOnCompletion))); } else if (actionOnCompletion != null){ actionOnCompletion.run(); } @@ -261,11 +256,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe public synchronized void removeMediaSource(int index, @Nullable Runnable actionOnCompletion) { mediaSourcesPublic.remove(index); if (player != null) { - player - .createMessage(this) - .setType(MSG_REMOVE) - .setMessage(new MessageData<>(index, null, actionOnCompletion)) - .send(); + player.sendMessages(new ExoPlayerMessage(this, MSG_REMOVE, + new MessageData<>(index, null, actionOnCompletion))); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -301,11 +293,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, Playe } mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex)); if (player != null) { - player - .createMessage(this) - .setType(MSG_MOVE) - .setMessage(new MessageData<>(currentIndex, newIndex, actionOnCompletion)) - .send(); + player.sendMessages(new ExoPlayerMessage(this, MSG_MOVE, + new MessageData<>(currentIndex, newIndex, actionOnCompletion))); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -438,7 +427,8 @@ 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.sendMessages( + new ExoPlayerMessage(this, MSG_ON_COMPLETION, actionOnCompletion)); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index a5f5222820..d796e6936f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -561,18 +561,6 @@ public final class Util { return stayInBounds ? Math.min(list.size() - 1, index) : index; } - /** - * Compares two long values and returns the same value as {@code Long.compare(long, long)}. - * - * @param left The left operand. - * @param right The right operand. - * @return 0, if left == right, a negative value if left < right, or a positive value if left - * > right. - */ - public static int compareLong(long left, long right) { - return left < right ? -1 : left == right ? 0 : 1; - } - /** * Parses an xs:duration attribute value, returning the parsed duration in milliseconds. * diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java index 5ec45af29f..ff0b8a6bc0 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/Action.java @@ -18,17 +18,13 @@ package com.google.android.exoplayer2.testutil; import android.os.Handler; import android.util.Log; import android.view.Surface; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.PlayerMessage; -import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.testutil.ActionSchedule.ActionNode; -import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerTarget; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; /** @@ -349,63 +345,7 @@ public abstract class Action { Surface surface) { player.setShuffleModeEnabled(shuffleModeEnabled); } - } - /** Calls {@link ExoPlayer#createMessage(Target)} and {@link PlayerMessage#send()}. */ - public static final class SendMessages extends Action { - - private final Target target; - private final int windowIndex; - private final long positionMs; - private final boolean deleteAfterDelivery; - - /** - * @param tag A tag to use for logging. - * @param target A message target. - * @param positionMs The position at which the message should be sent, in milliseconds. - */ - public SendMessages(String tag, Target target, long positionMs) { - this( - tag, - target, - /* windowIndex= */ C.INDEX_UNSET, - positionMs, - /* deleteAfterDelivery= */ true); - } - - /** - * @param tag A tag to use for logging. - * @param target A message target. - * @param windowIndex The window index at which the message should be sent, or {@link - * C#INDEX_UNSET} for the current window. - * @param positionMs The position at which the message should be sent, in milliseconds. - * @param deleteAfterDelivery Whether the message will be deleted after delivery. - */ - public SendMessages( - String tag, Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { - super(tag, "SendMessages"); - this.target = target; - this.windowIndex = windowIndex; - this.positionMs = positionMs; - this.deleteAfterDelivery = deleteAfterDelivery; - } - - @Override - protected void doActionImpl( - final SimpleExoPlayer player, MappingTrackSelector trackSelector, Surface surface) { - if (target instanceof PlayerTarget) { - ((PlayerTarget) target).setPlayer(player); - } - PlayerMessage message = player.createMessage(target); - if (windowIndex != C.INDEX_UNSET) { - message.setPosition(windowIndex, positionMs); - } else { - message.setPosition(positionMs); - } - message.setHandler(new Handler()); - message.setDeleteAfterDelivery(deleteAfterDelivery); - message.send(); - } } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java index 2ac487c98e..477071f91f 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ActionSchedule.java @@ -20,11 +20,8 @@ import android.os.Looper; import android.support.annotation.Nullable; import android.view.Surface; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.PlayerMessage; -import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource; @@ -32,7 +29,6 @@ import com.google.android.exoplayer2.testutil.Action.ClearVideoSurface; import com.google.android.exoplayer2.testutil.Action.ExecuteRunnable; import com.google.android.exoplayer2.testutil.Action.PrepareSource; import com.google.android.exoplayer2.testutil.Action.Seek; -import com.google.android.exoplayer2.testutil.Action.SendMessages; import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady; import com.google.android.exoplayer2.testutil.Action.SetPlaybackParameters; import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled; @@ -319,44 +315,6 @@ public final class ActionSchedule { return apply(new SetShuffleModeEnabled(tag, shuffleModeEnabled)); } - /** - * Schedules sending a {@link PlayerMessage}. - * - * @param positionMs The position in the current window at which the message should be sent, in - * milliseconds. - * @return The builder, for convenience. - */ - public Builder sendMessage(Target target, long positionMs) { - return apply(new SendMessages(tag, target, positionMs)); - } - - /** - * Schedules sending a {@link PlayerMessage}. - * - * @param target A message target. - * @param windowIndex The window index at which the message should be sent. - * @param positionMs The position at which the message should be sent, in milliseconds. - * @return The builder, for convenience. - */ - public Builder sendMessage(Target target, int windowIndex, long positionMs) { - return apply( - new SendMessages(tag, target, windowIndex, positionMs, /* deleteAfterDelivery= */ true)); - } - - /** - * Schedules to send a {@link PlayerMessage}. - * - * @param target A message target. - * @param windowIndex The window index at which the message should be sent. - * @param positionMs The position at which the message should be sent, in milliseconds. - * @param deleteAfterDelivery Whether the message will be deleted after delivery. - * @return The builder, for convenience. - */ - public Builder sendMessage( - Target target, int windowIndex, long positionMs, boolean deleteAfterDelivery) { - return apply(new SendMessages(tag, target, windowIndex, positionMs, deleteAfterDelivery)); - } - /** * Schedules a delay until the timeline changed to a specified expected timeline. * @@ -407,28 +365,7 @@ public final class ActionSchedule { currentDelayMs = 0; return this; } - } - /** - * Provides a wrapper for a {@link Target} which has access to the player when handling messages. - * Can be used with {@link Builder#sendMessage(Target, long)}. - */ - public abstract static class PlayerTarget implements Target { - - private SimpleExoPlayer player; - - /** Handles the message send to the component and additionally provides access to the player. */ - public abstract void handleMessage(SimpleExoPlayer player, int messageType, Object message); - - /** Sets the player to be passed to {@link #handleMessage(SimpleExoPlayer, int, Object)}. */ - /* package */ void setPlayer(SimpleExoPlayer player) { - this.player = player; - } - - @Override - public final void handleMessage(int messageType, Object message) throws ExoPlaybackException { - handleMessage(player, messageType, message); - } } /** diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java index 797c09d6b6..4a9d79f906 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTimeline.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.testutil; -import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.util.Util; @@ -171,7 +170,7 @@ public final class FakeTimeline extends Timeline { int windowPeriodIndex = periodIndex - periodOffsets[windowIndex]; TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex]; Object id = setIds ? windowPeriodIndex : null; - Object uid = setIds ? Pair.create(windowDefinition.id, windowPeriodIndex) : null; + Object uid = setIds ? periodIndex : null; long periodDurationUs = windowDefinition.durationUs / windowDefinition.periodCount; long positionInWindowUs = periodDurationUs * windowPeriodIndex; if (windowDefinition.adGroupsPerPeriodCount == 0) { @@ -199,13 +198,11 @@ public final class FakeTimeline extends Timeline { @Override public int getIndexOfPeriod(Object uid) { - Period period = new Period(); - for (int i = 0; i < getPeriodCount(); i++) { - if (getPeriod(i, period, true).uid.equals(uid)) { - return i; - } + if (!(uid instanceof Integer)) { + return C.INDEX_UNSET; } - return C.INDEX_UNSET; + int index = (Integer) uid; + return index >= 0 && index < getPeriodCount() ? index : C.INDEX_UNSET; } private static TimelineWindowDefinition[] createDefaultWindowDefinitions(int windowCount) { 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..4f31a8b027 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,9 +24,7 @@ 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; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; @@ -283,8 +281,7 @@ public class MediaSourceTestRunner { } - private static class EventHandlingExoPlayer extends StubExoPlayer - implements Handler.Callback, PlayerMessage.Sender { + private static class EventHandlingExoPlayer extends StubExoPlayer implements Handler.Callback { private final Handler handler; @@ -293,33 +290,23 @@ public class MediaSourceTestRunner { } @Override - public PlayerMessage createMessage(PlayerMessage.Target target) { - return new PlayerMessage( - /* sender= */ this, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler); + public void sendMessages(ExoPlayerMessage... messages) { + handler.obtainMessage(0, messages).sendToTarget(); } @Override - public void sendMessage(PlayerMessage message, Listener listener) { - handler.obtainMessage(0, Pair.create(message, listener)).sendToTarget(); - } - - @Override - @SuppressWarnings("unchecked") public boolean handleMessage(Message msg) { - Pair messageAndListener = (Pair) msg.obj; - try { - messageAndListener - .first - .getTarget() - .handleMessage( - messageAndListener.first.getType(), messageAndListener.first.getMessage()); - messageAndListener.second.onMessageDelivered(); - messageAndListener.second.onMessageDeleted(); - } catch (ExoPlaybackException e) { - fail("Unexpected ExoPlaybackException."); + ExoPlayerMessage[] messages = (ExoPlayerMessage[]) msg.obj; + for (ExoPlayerMessage message : messages) { + try { + message.target.handleMessage(message.messageType, message.message); + } catch (ExoPlaybackException e) { + fail("Unexpected ExoPlaybackException."); + } } return true; } + } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 7164fa13ab..1ea83bf1ec 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -19,7 +19,6 @@ import android.os.Looper; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource; @@ -147,11 +146,6 @@ public abstract class StubExoPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } - @Override - public PlayerMessage createMessage(PlayerMessage.Target target) { - throw new UnsupportedOperationException(); - } - @Override public void sendMessages(ExoPlayerMessage... messages) { throw new UnsupportedOperationException();