diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 3ae194c223c..87adaa88401 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -33,6 +33,8 @@
([#4385](https://github.com/google/ExoPlayer/issues/4385)).
* Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
CommentFrame to InternalFrame for frames with gapless metadata in MP4.
+* Allow setting the `Looper`, which is used to access the player, in
+ `ExoPlayerFactory` ([#4278](https://github.com/google/ExoPlayer/issues/4278)).
### 2.8.2 ###
diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
index 4bafaa43260..e8758cd05b1 100644
--- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
+++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java
@@ -19,7 +19,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.support.annotation.NonNull;
@@ -39,6 +38,7 @@
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.RepeatModeUtil;
+import com.google.android.exoplayer2.util.Util;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -323,7 +323,6 @@ public interface CustomActionProvider {
public final MediaSessionCompat mediaSession;
private final MediaControllerCompat mediaController;
- private final Handler handler;
private final boolean doMaintainMetadata;
private final ExoPlayerEventListener exoPlayerEventListener;
private final MediaSessionCallback mediaSessionCallback;
@@ -341,10 +340,9 @@ public interface CustomActionProvider {
private RatingCallback ratingCallback;
/**
- * Creates an instance. Must be called on the same thread that is used to construct the player
- * instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
- *
- * Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
+ * Creates an instance.
+ *
+ *
Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
*/
@@ -353,8 +351,7 @@ public MediaSessionConnector(MediaSessionCompat mediaSession) {
}
/**
- * Creates an instance. Must be called on the same thread that is used to construct the player
- * instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
+ * Creates an instance.
*
*
Equivalent to {@code MediaSessionConnector(mediaSession, playbackController, true, null)}.
*
@@ -367,8 +364,7 @@ public MediaSessionConnector(
}
/**
- * Creates an instance. Must be called on the same thread that is used to construct the player
- * instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
+ * Creates an instance.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param playbackController A {@link PlaybackController} for handling playback actions, or {@code
@@ -388,8 +384,6 @@ public MediaSessionConnector(
this.playbackController = playbackController != null ? playbackController
: new DefaultPlaybackController();
this.metadataExtrasPrefix = metadataExtrasPrefix != null ? metadataExtrasPrefix : "";
- this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
- : Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata;
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaController = mediaSession.getController();
@@ -401,7 +395,8 @@ public MediaSessionConnector(
}
/**
- * Sets the player to be connected to the media session.
+ * Sets the player to be connected to the media session. Must be called on the same thread that is
+ * used to access the player.
*
*
The order in which any {@link CustomActionProvider}s are passed determines the order of the
* actions published with the playback state of the session.
@@ -428,6 +423,7 @@ public void setPlayer(
this.customActionProviders = (player != null && customActionProviders != null)
? customActionProviders : new CustomActionProvider[0];
if (player != null) {
+ Handler handler = new Handler(Util.getLooper());
mediaSession.setCallback(mediaSessionCallback, handler);
player.addListener(exoPlayerEventListener);
}
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 b97790d5fb2..ce43772d8db 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
@@ -89,12 +89,13 @@
* model">
*
*
- * - ExoPlayer instances must be accessed from a single application thread. This must be the
- * thread the player is created on if that thread has a {@link Looper}, or the application's
- * main thread otherwise.
- *
- Registered listeners are called on the thread the player is created on if that thread has a
- * {@link Looper}, or the application's main thread otherwise. Note that this means registered
- * listeners are called on the same thread which must be used to access the player.
+ *
- ExoPlayer instances must be accessed from the thread associated with {@link
+ * #getApplicationLooper()}. This Looper can be specified when creating the player, or this is
+ * the Looper of the thread the player is created on, or the Looper of the application's main
+ * thread if the player is created on a thread without Looper.
+ *
- Registered listeners are called on the thread thread associated with {@link
+ * #getApplicationLooper()}. Note that this means registered listeners are called on the same
+ * thread which must be used to access the player.
*
- 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.
@@ -178,12 +179,14 @@ public ExoPlayerMessage(PlayerMessage.Target target, int messageType, Object mes
@Deprecated
@RepeatMode int REPEAT_MODE_ALL = Player.REPEAT_MODE_ALL;
+ /** Returns the {@link Looper} associated with the playback thread. */
+ Looper getPlaybackLooper();
+
/**
- * Gets the {@link Looper} associated with the playback thread.
- *
- * @return The {@link Looper} associated with the playback thread.
+ * Returns the {@link Looper} associated with the application thread that's used to access the
+ * player and on which player events are received.
*/
- Looper getPlaybackLooper();
+ Looper getApplicationLooper();
/**
* Prepares the player to play the provided {@link MediaSource}. Equivalent to
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
index 8095ed9c640..e8bd8b34f6d 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerFactory.java
@@ -16,12 +16,14 @@
package com.google.android.exoplayer2;
import android.content.Context;
+import android.os.Looper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock;
+import com.google.android.exoplayer2.util.Util;
/**
* A factory for {@link ExoPlayer} instances.
@@ -156,7 +158,11 @@ public static SimpleExoPlayer newSimpleInstance(
public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory,
TrackSelector trackSelector, LoadControl loadControl) {
return new SimpleExoPlayer(
- renderersFactory, trackSelector, loadControl, /* drmSessionManager= */ null);
+ renderersFactory,
+ trackSelector,
+ loadControl,
+ /* drmSessionManager= */ null,
+ Util.getLooper());
}
/**
@@ -173,7 +179,8 @@ public static SimpleExoPlayer newSimpleInstance(
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager drmSessionManager) {
- return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl, drmSessionManager);
+ return new SimpleExoPlayer(
+ renderersFactory, trackSelector, loadControl, drmSessionManager, Util.getLooper());
}
/**
@@ -194,7 +201,62 @@ public static SimpleExoPlayer newSimpleInstance(
@Nullable DrmSessionManager drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) {
return new SimpleExoPlayer(
- renderersFactory, trackSelector, loadControl, drmSessionManager, analyticsCollectorFactory);
+ renderersFactory,
+ trackSelector,
+ loadControl,
+ drmSessionManager,
+ analyticsCollectorFactory,
+ Util.getLooper());
+ }
+
+ /**
+ * Creates a {@link SimpleExoPlayer} instance.
+ *
+ * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
+ * @param trackSelector The {@link TrackSelector} that will be used by the instance.
+ * @param loadControl The {@link LoadControl} that will be used by the instance.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
+ * will not be used for DRM protected playbacks.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
+ */
+ public static SimpleExoPlayer newSimpleInstance(
+ RenderersFactory renderersFactory,
+ TrackSelector trackSelector,
+ LoadControl loadControl,
+ @Nullable DrmSessionManager drmSessionManager,
+ Looper looper) {
+ return new SimpleExoPlayer(
+ renderersFactory, trackSelector, loadControl, drmSessionManager, looper);
+ }
+
+ /**
+ * Creates a {@link SimpleExoPlayer} instance.
+ *
+ * @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
+ * @param trackSelector The {@link TrackSelector} that will be used by the instance.
+ * @param loadControl The {@link LoadControl} that will be used by the instance.
+ * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
+ * will not be used for DRM protected playbacks.
+ * @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
+ * will collect and forward all player events.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
+ */
+ public static SimpleExoPlayer newSimpleInstance(
+ RenderersFactory renderersFactory,
+ TrackSelector trackSelector,
+ LoadControl loadControl,
+ @Nullable DrmSessionManager drmSessionManager,
+ AnalyticsCollector.Factory analyticsCollectorFactory,
+ Looper looper) {
+ return new SimpleExoPlayer(
+ renderersFactory,
+ trackSelector,
+ loadControl,
+ drmSessionManager,
+ analyticsCollectorFactory,
+ looper);
}
/**
@@ -216,7 +278,20 @@ public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSel
*/
public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl) {
- return new ExoPlayerImpl(renderers, trackSelector, loadControl, Clock.DEFAULT);
+ return newInstance(renderers, trackSelector, loadControl, Util.getLooper());
}
+ /**
+ * Creates an {@link ExoPlayer} instance.
+ *
+ * @param renderers The {@link Renderer}s that will be used by the instance.
+ * @param trackSelector The {@link TrackSelector} that will be used by the instance.
+ * @param loadControl The {@link LoadControl} that will be used by the instance.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
+ */
+ public static ExoPlayer newInstance(
+ Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Looper looper) {
+ return new ExoPlayerImpl(renderers, trackSelector, loadControl, Clock.DEFAULT, looper);
+ }
}
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 390f8b59a59..e803fb30ee9 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
@@ -81,10 +81,16 @@
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} that will be used by the instance.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(
- Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
+ Renderer[] renderers,
+ TrackSelector trackSelector,
+ LoadControl loadControl,
+ Clock clock,
+ Looper looper) {
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0);
@@ -102,13 +108,13 @@ public ExoPlayerImpl(
window = new Timeline.Window();
period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT;
- Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
- eventHandler = new Handler(eventLooper) {
- @Override
- public void handleMessage(Message msg) {
- ExoPlayerImpl.this.handleEvent(msg);
- }
- };
+ eventHandler =
+ new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ ExoPlayerImpl.this.handleEvent(msg);
+ }
+ };
playbackInfo =
new PlaybackInfo(
Timeline.EMPTY,
@@ -146,6 +152,11 @@ public Looper getPlaybackLooper() {
return internalPlayer.getPlaybackLooper();
}
+ @Override
+ public Looper getApplicationLooper() {
+ return eventHandler.getLooper();
+ }
+
@Override
public void addListener(Player.EventListener listener) {
listeners.add(listener);
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 7fa13338add..0fa72790794 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
@@ -97,24 +97,41 @@ public interface VideoListener extends com.google.android.exoplayer2.video.Video
private MediaSource mediaSource;
private List currentCues;
+ /**
+ * @deprecated Use {@link #SimpleExoPlayer(RenderersFactory, TrackSelector, LoadControl,
+ * DrmSessionManager, Looper)}.
+ */
+ @Deprecated
+ protected SimpleExoPlayer(
+ RenderersFactory renderersFactory,
+ TrackSelector trackSelector,
+ LoadControl loadControl,
+ @Nullable DrmSessionManager drmSessionManager) {
+ this(renderersFactory, trackSelector, loadControl, drmSessionManager, Util.getLooper());
+ }
+
/**
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
- @Nullable DrmSessionManager drmSessionManager) {
+ @Nullable DrmSessionManager drmSessionManager,
+ Looper looper) {
this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
- new AnalyticsCollector.Factory());
+ new AnalyticsCollector.Factory(),
+ looper);
}
/**
@@ -125,20 +142,24 @@ protected SimpleExoPlayer(
* will not be used for DRM protected playbacks.
* @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
* will collect and forward all player events.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager drmSessionManager,
- AnalyticsCollector.Factory analyticsCollectorFactory) {
+ AnalyticsCollector.Factory analyticsCollectorFactory,
+ Looper looper) {
this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
analyticsCollectorFactory,
- Clock.DEFAULT);
+ Clock.DEFAULT,
+ looper);
}
/**
@@ -151,6 +172,8 @@ protected SimpleExoPlayer(
* will collect and forward all player events.
* @param clock The {@link Clock} that will be used by the instance. Should always be {@link
* Clock#DEFAULT}, unless the player is being used from a test.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
*/
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
@@ -158,15 +181,15 @@ protected SimpleExoPlayer(
LoadControl loadControl,
@Nullable DrmSessionManager drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory,
- Clock clock) {
+ Clock clock,
+ Looper looper) {
componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>();
metadataOutputs = new CopyOnWriteArraySet<>();
videoDebugListeners = new CopyOnWriteArraySet<>();
audioDebugListeners = new CopyOnWriteArraySet<>();
- Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
- eventHandler = new Handler(eventLooper);
+ eventHandler = new Handler(looper);
renderers =
renderersFactory.createRenderers(
eventHandler,
@@ -184,7 +207,7 @@ protected SimpleExoPlayer(
currentCues = Collections.emptyList();
// Build the player and associated objects.
- player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock);
+ player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock, looper);
analyticsCollector = analyticsCollectorFactory.createAnalyticsCollector(player, clock);
addListener(analyticsCollector);
videoDebugListeners.add(analyticsCollector);
@@ -671,6 +694,11 @@ public Looper getPlaybackLooper() {
return player.getPlaybackLooper();
}
+ @Override
+ public Looper getApplicationLooper() {
+ return player.getApplicationLooper();
+ }
+
@Override
public void addListener(Player.EventListener listener) {
player.addListener(listener);
@@ -954,11 +982,17 @@ public long getContentBufferedPosition() {
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} that will be used by this instance.
+ * @param looper The {@link Looper} which must be used for all calls to the player and which is
+ * used to call listeners on.
* @return A new {@link ExoPlayer} instance.
*/
protected ExoPlayer createExoPlayerImpl(
- Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) {
- return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock);
+ Renderer[] renderers,
+ TrackSelector trackSelector,
+ LoadControl loadControl,
+ Clock clock,
+ Looper looper) {
+ return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock, looper);
}
private void removeSurfaceCallbacks() {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
index 52e44c3a4b0..680eea6a3ec 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source;
import android.os.Handler;
-import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
@@ -61,13 +60,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource mediaSourceHolders;
private final MediaSourceHolder query;
private final Map mediaSourceByMediaPeriod;
- private final List pendingOnCompletionActions;
+ private final List pendingOnCompletionActions;
private final boolean isAtomic;
private final boolean useLazyPreparation;
private final Timeline.Window window;
private final Timeline.Period period;
private @Nullable ExoPlayer player;
+ private @Nullable Handler playerApplicationHandler;
private boolean listenerNotificationScheduled;
private ShuffleOrder shuffleOrder;
private int windowCount;
@@ -351,11 +351,7 @@ public final synchronized void clear() {
public final synchronized void clear(@Nullable Runnable actionOnCompletion) {
mediaSourcesPublic.clear();
if (player != null) {
- player
- .createMessage(this)
- .setType(MSG_CLEAR)
- .setPayload(actionOnCompletion != null ? new EventDispatcher(actionOnCompletion) : null)
- .send();
+ player.createMessage(this).setType(MSG_CLEAR).setPayload(actionOnCompletion).send();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
}
@@ -380,6 +376,7 @@ public final synchronized MediaSource getMediaSource(int index) {
public final synchronized void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
super.prepareSourceInternal(player, isTopLevelSource);
this.player = player;
+ playerApplicationHandler = new Handler(player.getApplicationLooper());
if (mediaSourcesPublic.isEmpty()) {
notifyListener();
} else {
@@ -424,6 +421,7 @@ public final void releaseSourceInternal() {
super.releaseSourceInternal();
mediaSourceHolders.clear();
player = null;
+ playerApplicationHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0;
periodCount = 0;
@@ -462,6 +460,10 @@ protected int getWindowIndexForChildWindowIndex(
@Override
@SuppressWarnings("unchecked")
public final void handleMessage(int messageType, Object message) throws ExoPlaybackException {
+ if (player == null) {
+ // Stale event.
+ return;
+ }
switch (messageType) {
case MSG_ADD:
MessageData addMessage = (MessageData) message;
@@ -493,15 +495,16 @@ public final void handleMessage(int messageType, Object message) throws ExoPlayb
break;
case MSG_CLEAR:
clearInternal();
- scheduleListenerNotification((EventDispatcher) message);
+ scheduleListenerNotification((Runnable) message);
break;
case MSG_NOTIFY_LISTENER:
notifyListener();
break;
case MSG_ON_COMPLETION:
- List actionsOnCompletion = ((List) message);
+ List actionsOnCompletion = ((List) message);
+ Handler handler = Assertions.checkNotNull(playerApplicationHandler);
for (int i = 0; i < actionsOnCompletion.size(); i++) {
- actionsOnCompletion.get(i).dispatchEvent();
+ handler.post(actionsOnCompletion.get(i));
}
break;
default:
@@ -509,7 +512,7 @@ public final void handleMessage(int messageType, Object message) throws ExoPlayb
}
}
- private void scheduleListenerNotification(@Nullable EventDispatcher actionOnCompletion) {
+ private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) {
if (!listenerNotificationScheduled) {
Assertions.checkNotNull(player).createMessage(this).setType(MSG_NOTIFY_LISTENER).send();
listenerNotificationScheduled = true;
@@ -521,9 +524,9 @@ private void scheduleListenerNotification(@Nullable EventDispatcher actionOnComp
private void notifyListener() {
listenerNotificationScheduled = false;
- List actionsOnCompletion =
+ List actionsOnCompletion =
pendingOnCompletionActions.isEmpty()
- ? Collections.emptyList()
+ ? Collections.emptyList()
: new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear();
refreshSourceInfo(
@@ -698,34 +701,16 @@ public int compareTo(@NonNull MediaSourceHolder other) {
}
}
- /** Can be used to dispatch a runnable on the thread the object was created on. */
- private static final class EventDispatcher {
-
- public final Handler eventHandler;
- public final Runnable runnable;
-
- public EventDispatcher(Runnable runnable) {
- this.runnable = runnable;
- this.eventHandler =
- new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
- }
-
- public void dispatchEvent() {
- eventHandler.post(runnable);
- }
- }
-
/** Message used to post actions from app thread to playback thread. */
private static final class MessageData {
public final int index;
public final T customData;
- public final @Nullable EventDispatcher actionOnCompletion;
+ public final @Nullable Runnable actionOnCompletion;
public MessageData(int index, T customData, @Nullable Runnable actionOnCompletion) {
this.index = index;
- this.actionOnCompletion =
- actionOnCompletion != null ? new EventDispatcher(actionOnCompletion) : null;
+ this.actionOnCompletion = actionOnCompletion;
this.customData = customData;
}
}
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 0e12964dfb3..7a38350563b 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
@@ -252,11 +252,14 @@ public static T[] nullSafeArrayCopy(T[] input, int length) {
* assumption that the Handler won't be used to send messages until the callback is fully
* initialized.
*
+ *
If the current thread doesn't have a {@link Looper}, the application's main thread {@link
+ * Looper} is used.
+ *
* @param callback A {@link Handler.Callback}. May be a partially initialized class.
* @return A {@link Handler} with the specified callback on the current {@link Looper} thread.
*/
public static Handler createHandler(Handler.@UnknownInitialization Callback callback) {
- return createHandler(Looper.myLooper(), callback);
+ return createHandler(getLooper(), callback);
}
/**
@@ -275,6 +278,15 @@ public static Handler createHandler(
return new Handler(looper, callback);
}
+ /**
+ * Returns the {@link Looper} associated with the current thread, or the {@link Looper} of the
+ * application's main thread if the current thread doesn't have a {@link Looper}.
+ */
+ public static Looper getLooper() {
+ Looper myLooper = Looper.myLooper();
+ return myLooper != null ? myLooper : Looper.getMainLooper();
+ }
+
/**
* Instantiates a new single threaded executor whose thread has the specified name.
*
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
index cf7470b80a1..b47ab16db6d 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
import android.os.HandlerThread;
+import android.os.Looper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -663,7 +664,8 @@ public TestSimpleExoPlayer(
loadControl,
/* drmSessionManager= */ null,
new AnalyticsCollector.Factory(),
- clock);
+ clock,
+ Looper.myLooper());
}
}
}
diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
index ee3a3a2d323..5062b5c0052 100644
--- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
+++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java
@@ -442,6 +442,11 @@ public EventHandlingExoPlayer(Looper looper) {
this.handler = new Handler(looper, this);
}
+ @Override
+ public Looper getApplicationLooper() {
+ return handler.getLooper();
+ }
+
@Override
public PlayerMessage createMessage(PlayerMessage.Target target) {
return new PlayerMessage(
diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
index b8ad949f4ae..214c2b6a5e1 100644
--- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
+++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java
@@ -49,6 +49,11 @@ public Looper getPlaybackLooper() {
throw new UnsupportedOperationException();
}
+ @Override
+ public Looper getApplicationLooper() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void addListener(Player.EventListener listener) {
throw new UnsupportedOperationException();