Commit 4f2b5960 by tonihei Committed by Oliver Woodman

Allow to specify player looper at the time of ExoPlayer creation.

Currently, the looper of the thread the player is created on is used (or the
main looper if this thread doesn't have a looper). To allow more control over
the threading, this change lets users specificy the looper which must be used
to call player methods and which is used for event callbacks.

Issue:#4278

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=201331564
parent 01ce549e
......@@ -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 ###
......
......@@ -19,7 +19,6 @@ import android.graphics.Bitmap;
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.Player;
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 final class MediaSessionConnector {
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 final class MediaSessionConnector {
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...)}.
* <p>
* Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
* Creates an instance.
*
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
*/
......@@ -353,8 +351,7 @@ public final class 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.
*
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, playbackController, true, null)}.
*
......@@ -367,8 +364,7 @@ public final class 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 final class 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 final class 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.
*
* <p>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 final class MediaSessionConnector {
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);
}
......
......@@ -89,12 +89,13 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* model">
*
* <ul>
* <li>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.
* <li>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.
* <li>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.
* <li>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.
* <li>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 interface ExoPlayer extends Player {
@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
......
......@@ -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 final class ExoPlayerFactory {
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 final class ExoPlayerFactory {
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl, drmSessionManager);
return new SimpleExoPlayer(
renderersFactory, trackSelector, loadControl, drmSessionManager, Util.getLooper());
}
/**
......@@ -194,7 +201,62 @@ public final class ExoPlayerFactory {
@Nullable DrmSessionManager<FrameworkMediaCrypto> 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<FrameworkMediaCrypto> 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<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory,
Looper looper) {
return new SimpleExoPlayer(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
analyticsCollectorFactory,
looper);
}
/**
......@@ -216,7 +278,20 @@ public final class ExoPlayerFactory {
*/
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);
}
}
......@@ -81,10 +81,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
* @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 @@ import java.util.concurrent.CopyOnWriteArraySet;
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,
......@@ -147,6 +153,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@Override
public Looper getApplicationLooper() {
return eventHandler.getLooper();
}
@Override
public void addListener(Player.EventListener listener) {
listeners.add(listener);
}
......
......@@ -98,23 +98,40 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
private List<Cue> currentCues;
/**
* @deprecated Use {@link #SimpleExoPlayer(RenderersFactory, TrackSelector, LoadControl,
* DrmSessionManager, Looper)}.
*/
@Deprecated
protected SimpleExoPlayer(
RenderersFactory renderersFactory,
TrackSelector trackSelector,
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> 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<FrameworkMediaCrypto> drmSessionManager) {
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
Looper looper) {
this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
new AnalyticsCollector.Factory());
new AnalyticsCollector.Factory(),
looper);
}
/**
......@@ -125,20 +142,24 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* 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<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) {
AnalyticsCollector.Factory analyticsCollectorFactory,
Looper looper) {
this(
renderersFactory,
trackSelector,
loadControl,
drmSessionManager,
analyticsCollectorFactory,
Clock.DEFAULT);
Clock.DEFAULT,
looper);
}
/**
......@@ -151,6 +172,8 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* 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 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> 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 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
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);
......@@ -672,6 +695,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
}
@Override
public Looper getApplicationLooper() {
return player.getApplicationLooper();
}
@Override
public void addListener(Player.EventListener listener) {
player.addListener(listener);
}
......@@ -954,11 +982,17 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* @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() {
......
......@@ -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<MediaSourceHo
private final List<MediaSourceHolder> mediaSourceHolders;
private final MediaSourceHolder query;
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final List<EventDispatcher> pendingOnCompletionActions;
private final List<Runnable> 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 class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
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 class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
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 class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
super.releaseSourceInternal();
mediaSourceHolders.clear();
player = null;
playerApplicationHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0;
periodCount = 0;
......@@ -462,6 +460,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@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<MediaSourceHolder> addMessage = (MessageData<MediaSourceHolder>) message;
......@@ -493,15 +495,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
break;
case MSG_CLEAR:
clearInternal();
scheduleListenerNotification((EventDispatcher) message);
scheduleListenerNotification((Runnable) message);
break;
case MSG_NOTIFY_LISTENER:
notifyListener();
break;
case MSG_ON_COMPLETION:
List<EventDispatcher> actionsOnCompletion = ((List<EventDispatcher>) message);
List<Runnable> actionsOnCompletion = ((List<Runnable>) 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 class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
}
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 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private void notifyListener() {
listenerNotificationScheduled = false;
List<EventDispatcher> actionsOnCompletion =
List<Runnable> actionsOnCompletion =
pendingOnCompletionActions.isEmpty()
? Collections.<EventDispatcher>emptyList()
? Collections.<Runnable>emptyList()
: new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear();
refreshSourceInfo(
......@@ -698,34 +701,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
}
/** 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<T> {
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;
}
}
......
......@@ -252,11 +252,14 @@ public final class Util {
* assumption that the Handler won't be used to send messages until the callback is fully
* initialized.
*
* <p>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);
}
/**
......@@ -276,6 +279,15 @@ public final class Util {
}
/**
* 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.
*
* @param threadName The name of the thread.
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
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 final class ExoPlayerTestRunner extends Player.DefaultEventListener
loadControl,
/* drmSessionManager= */ null,
new AnalyticsCollector.Factory(),
clock);
clock,
Looper.myLooper());
}
}
}
......@@ -443,6 +443,11 @@ public class MediaSourceTestRunner {
}
@Override
public Looper getApplicationLooper() {
return handler.getLooper();
}
@Override
public PlayerMessage createMessage(PlayerMessage.Target target) {
return new PlayerMessage(
/* sender= */ this, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler);
......
......@@ -50,6 +50,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
}
@Override
public Looper getApplicationLooper() {
throw new UnsupportedOperationException();
}
@Override
public void addListener(Player.EventListener listener) {
throw new UnsupportedOperationException();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment