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:
- *
*
* - A {@link MediaSource} that defines the media to be played, loads the media, and from
- * which the loaded media can be read. A MediaSource is injected via {@link
- * #prepare(MediaSource)} at the start of playback. The library modules provide default
- * implementations for regular media files ({@link ExtractorMediaSource}), DASH
- * (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS (HlsMediaSource), an
- * implementation for loading single media samples ({@link SingleSampleMediaSource}) that's
- * most often used for side-loaded subtitle files, and implementations for building more
- * complex MediaSources from simpler ones ({@link MergingMediaSource}, {@link
- * ConcatenatingMediaSource}, {@link DynamicConcatenatingMediaSource}, {@link
- * LoopingMediaSource} and {@link ClippingMediaSource}).
+ * which the loaded media can be read. A MediaSource is injected via {@link #prepare(MediaSource)}
+ * at the start of playback. The library modules provide default implementations for regular media
+ * files ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource)
+ * and HLS (HlsMediaSource), an implementation for loading single media samples
+ * ({@link SingleSampleMediaSource}) that's most often used for side-loaded subtitle files, and
+ * implementations for building more complex MediaSources from simpler ones
+ * ({@link MergingMediaSource}, {@link ConcatenatingMediaSource},
+ * {@link DynamicConcatenatingMediaSource}, {@link LoopingMediaSource} and
+ * {@link ClippingMediaSource}).
* - {@link Renderer}s that render individual components of the media. The library
- * provides default implementations for common media types ({@link MediaCodecVideoRenderer},
- * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A
- * Renderer consumes media from the MediaSource being played. Renderers are injected when the
- * player is created.
+ * provides default implementations for common media types ({@link MediaCodecVideoRenderer},
+ * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A Renderer
+ * consumes media from the MediaSource being played. Renderers are injected when the player is
+ * created.
* - A {@link TrackSelector} that selects tracks provided by the MediaSource to be
- * consumed by each of the available Renderers. The library provides a default implementation
- * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected
- * when the player is created.
+ * consumed by each of the available Renderers. The library provides a default implementation
+ * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected when
+ * the player is created.
* - A {@link LoadControl} that controls when the MediaSource buffers more media, and how
- * much media is buffered. The library provides a default implementation ({@link
- * DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the player
- * is created.
+ * much media is buffered. The library provides a default implementation
+ * ({@link DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the
+ * player is created.
*
- *
* 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.
- *
- *
+ *
The figure below shows ExoPlayer's threading model.
+ *
+ *
+ *
*
*
- * - It is recommended that ExoPlayer instances are created and accessed from a single
- * application thread. The application's main thread is ideal. Accessing an instance from
- * multiple threads is discouraged, however if an application does wish to do this then it may
- * do so provided that it ensures accesses are synchronized.
- *
- Registered listeners are called on the thread that created the ExoPlayer instance, unless
- * the thread that created the ExoPlayer instance does not have a {@link Looper}. In that
- * case, registered listeners will be called on the application's main thread.
- *
- An internal playback thread is responsible for playback. Injected player components such as
- * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
- * thread.
- *
- When the application performs an operation on the player, for example a seek, a message is
- * delivered to the internal playback thread via a message queue. The internal playback thread
- * consumes messages from the queue and performs the corresponding operations. Similarly, when
- * a playback event occurs on the internal playback thread, a message is delivered to the
- * application thread via a second message queue. The application thread consumes messages
- * from the queue, updating the application visible state and calling corresponding listener
- * methods.
- *
- Injected player components may use additional background threads. For example a MediaSource
- * may use background threads to load data. These are implementation specific.
+ *
- It is recommended that ExoPlayer instances are created and accessed from a single application
+ * thread. The application's main thread is ideal. Accessing an instance from multiple threads is
+ * discouraged, however if an application does wish to do this then it may do so provided that it
+ * ensures accesses are synchronized.
+ * - Registered listeners are called on the thread that created the ExoPlayer instance, unless
+ * the thread that created the ExoPlayer instance does not have a {@link Looper}. In that case,
+ * registered listeners will be called on the application's main thread.
+ * - An internal playback thread is responsible for playback. Injected player components such as
+ * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
+ * thread.
+ * - When the application performs an operation on the player, for example a seek, a message is
+ * delivered to the internal playback thread via a message queue. The internal playback thread
+ * consumes messages from the queue and performs the corresponding operations. Similarly, when a
+ * playback event occurs on the internal playback thread, a message is delivered to the application
+ * thread via a second message queue. The application thread consumes messages from the queue,
+ * updating the application visible state and calling corresponding listener methods.
+ * - Injected player components may use additional background threads. For example a MediaSource
+ * may use background threads to load data. These are implementation specific.
*
*/
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.
- *
- *
+ *
+ *
+ *
*/
-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();