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 @@ ...@@ -33,6 +33,8 @@
([#4385](https://github.com/google/ExoPlayer/issues/4385)). ([#4385](https://github.com/google/ExoPlayer/issues/4385)).
* Expose all internal ID3 data stored in MP4 udta boxes, and switch from using * Expose all internal ID3 data stored in MP4 udta boxes, and switch from using
CommentFrame to InternalFrame for frames with gapless metadata in MP4. 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 ### ### 2.8.2 ###
......
...@@ -19,7 +19,6 @@ import android.graphics.Bitmap; ...@@ -19,7 +19,6 @@ import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
...@@ -39,6 +38,7 @@ import com.google.android.exoplayer2.Player; ...@@ -39,6 +38,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -323,7 +323,6 @@ public final class MediaSessionConnector { ...@@ -323,7 +323,6 @@ public final class MediaSessionConnector {
public final MediaSessionCompat mediaSession; public final MediaSessionCompat mediaSession;
private final MediaControllerCompat mediaController; private final MediaControllerCompat mediaController;
private final Handler handler;
private final boolean doMaintainMetadata; private final boolean doMaintainMetadata;
private final ExoPlayerEventListener exoPlayerEventListener; private final ExoPlayerEventListener exoPlayerEventListener;
private final MediaSessionCallback mediaSessionCallback; private final MediaSessionCallback mediaSessionCallback;
...@@ -341,10 +340,9 @@ public final class MediaSessionConnector { ...@@ -341,10 +340,9 @@ public final class MediaSessionConnector {
private RatingCallback ratingCallback; private RatingCallback ratingCallback;
/** /**
* Creates an instance. Must be called on the same thread that is used to construct the player * Creates an instance.
* instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}. *
* <p> * <p>Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
* Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
* *
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @param mediaSession The {@link MediaSessionCompat} to connect to.
*/ */
...@@ -353,8 +351,7 @@ public final class MediaSessionConnector { ...@@ -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 * Creates an instance.
* instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
* *
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, playbackController, true, null)}. * <p>Equivalent to {@code MediaSessionConnector(mediaSession, playbackController, true, null)}.
* *
...@@ -367,8 +364,7 @@ public final class MediaSessionConnector { ...@@ -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 * Creates an instance.
* instances passed to {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
* *
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param playbackController A {@link PlaybackController} for handling playback actions, or {@code * @param playbackController A {@link PlaybackController} for handling playback actions, or {@code
...@@ -388,8 +384,6 @@ public final class MediaSessionConnector { ...@@ -388,8 +384,6 @@ public final class MediaSessionConnector {
this.playbackController = playbackController != null ? playbackController this.playbackController = playbackController != null ? playbackController
: new DefaultPlaybackController(); : new DefaultPlaybackController();
this.metadataExtrasPrefix = metadataExtrasPrefix != null ? metadataExtrasPrefix : ""; this.metadataExtrasPrefix = metadataExtrasPrefix != null ? metadataExtrasPrefix : "";
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
: Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata; this.doMaintainMetadata = doMaintainMetadata;
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS); mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaController = mediaSession.getController(); mediaController = mediaSession.getController();
...@@ -401,7 +395,8 @@ public final class MediaSessionConnector { ...@@ -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 * <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. * actions published with the playback state of the session.
...@@ -428,6 +423,7 @@ public final class MediaSessionConnector { ...@@ -428,6 +423,7 @@ public final class MediaSessionConnector {
this.customActionProviders = (player != null && customActionProviders != null) this.customActionProviders = (player != null && customActionProviders != null)
? customActionProviders : new CustomActionProvider[0]; ? customActionProviders : new CustomActionProvider[0];
if (player != null) { if (player != null) {
Handler handler = new Handler(Util.getLooper());
mediaSession.setCallback(mediaSessionCallback, handler); mediaSession.setCallback(mediaSessionCallback, handler);
player.addListener(exoPlayerEventListener); player.addListener(exoPlayerEventListener);
} }
......
...@@ -89,12 +89,13 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; ...@@ -89,12 +89,13 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* model"> * model">
* *
* <ul> * <ul>
* <li>ExoPlayer instances must be accessed from a single application thread. This must be the * <li>ExoPlayer instances must be accessed from the thread associated with {@link
* thread the player is created on if that thread has a {@link Looper}, or the application's * #getApplicationLooper()}. This Looper can be specified when creating the player, or this is
* main thread otherwise. * the Looper of the thread the player is created on, or the Looper of the application's main
* <li>Registered listeners are called on the thread the player is created on if that thread has a * thread if the player is created on a thread without Looper.
* {@link Looper}, or the application's main thread otherwise. Note that this means registered * <li>Registered listeners are called on the thread thread associated with {@link
* listeners are called on the same thread which must be used to access the player. * #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 * <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 * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
* thread. * thread.
...@@ -178,12 +179,14 @@ public interface ExoPlayer extends Player { ...@@ -178,12 +179,14 @@ public interface ExoPlayer extends Player {
@Deprecated @Deprecated
@RepeatMode int REPEAT_MODE_ALL = Player.REPEAT_MODE_ALL; @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. * Returns the {@link Looper} associated with the application thread that's used to access the
* * player and on which player events are received.
* @return The {@link Looper} associated with the playback thread.
*/ */
Looper getPlaybackLooper(); Looper getApplicationLooper();
/** /**
* Prepares the player to play the provided {@link MediaSource}. Equivalent to * Prepares the player to play the provided {@link MediaSource}. Equivalent to
......
...@@ -16,12 +16,14 @@ ...@@ -16,12 +16,14 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.content.Context; import android.content.Context;
import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
/** /**
* A factory for {@link ExoPlayer} instances. * A factory for {@link ExoPlayer} instances.
...@@ -156,7 +158,11 @@ public final class ExoPlayerFactory { ...@@ -156,7 +158,11 @@ public final class ExoPlayerFactory {
public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory, public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory,
TrackSelector trackSelector, LoadControl loadControl) { TrackSelector trackSelector, LoadControl loadControl) {
return new SimpleExoPlayer( return new SimpleExoPlayer(
renderersFactory, trackSelector, loadControl, /* drmSessionManager= */ null); renderersFactory,
trackSelector,
loadControl,
/* drmSessionManager= */ null,
Util.getLooper());
} }
/** /**
...@@ -173,7 +179,8 @@ public final class ExoPlayerFactory { ...@@ -173,7 +179,8 @@ public final class ExoPlayerFactory {
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { @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 { ...@@ -194,7 +201,62 @@ public final class ExoPlayerFactory {
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) { AnalyticsCollector.Factory analyticsCollectorFactory) {
return new SimpleExoPlayer( 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 { ...@@ -216,7 +278,20 @@ public final class ExoPlayerFactory {
*/ */
public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl) { 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; ...@@ -81,10 +81,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
* @param trackSelector The {@link TrackSelector} 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 loadControl The {@link LoadControl} that will be used by the instance.
* @param clock The {@link Clock} 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") @SuppressLint("HandlerLeak")
public ExoPlayerImpl( 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)) + " [" Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]"); + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0); Assertions.checkState(renderers.length > 0);
...@@ -102,8 +108,8 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -102,8 +108,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
window = new Timeline.Window(); window = new Timeline.Window();
period = new Timeline.Period(); period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT; playbackParameters = PlaybackParameters.DEFAULT;
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); eventHandler =
eventHandler = new Handler(eventLooper) { new Handler(looper) {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg); ExoPlayerImpl.this.handleEvent(msg);
...@@ -147,6 +153,11 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -147,6 +153,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public Looper getApplicationLooper() {
return eventHandler.getLooper();
}
@Override
public void addListener(Player.EventListener listener) { public void addListener(Player.EventListener listener) {
listeners.add(listener); listeners.add(listener);
} }
......
...@@ -98,23 +98,40 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -98,23 +98,40 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
private List<Cue> currentCues; 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 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 trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} 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 * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks. * 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( protected SimpleExoPlayer(
RenderersFactory renderersFactory, RenderersFactory renderersFactory,
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) { @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
Looper looper) {
this( this(
renderersFactory, renderersFactory,
trackSelector, trackSelector,
loadControl, loadControl,
drmSessionManager, drmSessionManager,
new AnalyticsCollector.Factory()); new AnalyticsCollector.Factory(),
looper);
} }
/** /**
...@@ -125,20 +142,24 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -125,20 +142,24 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* will not be used for DRM protected playbacks. * will not be used for DRM protected playbacks.
* @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that * @param analyticsCollectorFactory A factory for creating the {@link AnalyticsCollector} that
* will collect and forward all player events. * 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( protected SimpleExoPlayer(
RenderersFactory renderersFactory, RenderersFactory renderersFactory,
TrackSelector trackSelector, TrackSelector trackSelector,
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory) { AnalyticsCollector.Factory analyticsCollectorFactory,
Looper looper) {
this( this(
renderersFactory, renderersFactory,
trackSelector, trackSelector,
loadControl, loadControl,
drmSessionManager, drmSessionManager,
analyticsCollectorFactory, analyticsCollectorFactory,
Clock.DEFAULT); Clock.DEFAULT,
looper);
} }
/** /**
...@@ -151,6 +172,8 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -151,6 +172,8 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
* will collect and forward all player events. * will collect and forward all player events.
* @param clock The {@link Clock} that will be used by the instance. Should always be {@link * @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. * 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( protected SimpleExoPlayer(
RenderersFactory renderersFactory, RenderersFactory renderersFactory,
...@@ -158,15 +181,15 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -158,15 +181,15 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AnalyticsCollector.Factory analyticsCollectorFactory, AnalyticsCollector.Factory analyticsCollectorFactory,
Clock clock) { Clock clock,
Looper looper) {
componentListener = new ComponentListener(); componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>(); videoListeners = new CopyOnWriteArraySet<>();
textOutputs = new CopyOnWriteArraySet<>(); textOutputs = new CopyOnWriteArraySet<>();
metadataOutputs = new CopyOnWriteArraySet<>(); metadataOutputs = new CopyOnWriteArraySet<>();
videoDebugListeners = new CopyOnWriteArraySet<>(); videoDebugListeners = new CopyOnWriteArraySet<>();
audioDebugListeners = new CopyOnWriteArraySet<>(); audioDebugListeners = new CopyOnWriteArraySet<>();
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper(); eventHandler = new Handler(looper);
eventHandler = new Handler(eventLooper);
renderers = renderers =
renderersFactory.createRenderers( renderersFactory.createRenderers(
eventHandler, eventHandler,
...@@ -184,7 +207,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -184,7 +207,7 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
currentCues = Collections.emptyList(); currentCues = Collections.emptyList();
// Build the player and associated objects. // Build the player and associated objects.
player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock); player = createExoPlayerImpl(renderers, trackSelector, loadControl, clock, looper);
analyticsCollector = analyticsCollectorFactory.createAnalyticsCollector(player, clock); analyticsCollector = analyticsCollectorFactory.createAnalyticsCollector(player, clock);
addListener(analyticsCollector); addListener(analyticsCollector);
videoDebugListeners.add(analyticsCollector); videoDebugListeners.add(analyticsCollector);
...@@ -672,6 +695,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -672,6 +695,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
} }
@Override @Override
public Looper getApplicationLooper() {
return player.getApplicationLooper();
}
@Override
public void addListener(Player.EventListener listener) { public void addListener(Player.EventListener listener) {
player.addListener(listener); player.addListener(listener);
} }
...@@ -954,11 +982,17 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -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 trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} 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 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. * @return A new {@link ExoPlayer} instance.
*/ */
protected ExoPlayer createExoPlayerImpl( protected ExoPlayer createExoPlayerImpl(
Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl, Clock clock) { Renderer[] renderers,
return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock); TrackSelector trackSelector,
LoadControl loadControl,
Clock clock,
Looper looper) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl, clock, looper);
} }
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -61,13 +60,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -61,13 +60,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private final List<MediaSourceHolder> mediaSourceHolders; private final List<MediaSourceHolder> mediaSourceHolders;
private final MediaSourceHolder query; private final MediaSourceHolder query;
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod; private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final List<EventDispatcher> pendingOnCompletionActions; private final List<Runnable> pendingOnCompletionActions;
private final boolean isAtomic; private final boolean isAtomic;
private final boolean useLazyPreparation; private final boolean useLazyPreparation;
private final Timeline.Window window; private final Timeline.Window window;
private final Timeline.Period period; private final Timeline.Period period;
private @Nullable ExoPlayer player; private @Nullable ExoPlayer player;
private @Nullable Handler playerApplicationHandler;
private boolean listenerNotificationScheduled; private boolean listenerNotificationScheduled;
private ShuffleOrder shuffleOrder; private ShuffleOrder shuffleOrder;
private int windowCount; private int windowCount;
...@@ -351,11 +351,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -351,11 +351,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public final synchronized void clear(@Nullable Runnable actionOnCompletion) { public final synchronized void clear(@Nullable Runnable actionOnCompletion) {
mediaSourcesPublic.clear(); mediaSourcesPublic.clear();
if (player != null) { if (player != null) {
player player.createMessage(this).setType(MSG_CLEAR).setPayload(actionOnCompletion).send();
.createMessage(this)
.setType(MSG_CLEAR)
.setPayload(actionOnCompletion != null ? new EventDispatcher(actionOnCompletion) : null)
.send();
} else if (actionOnCompletion != null) { } else if (actionOnCompletion != null) {
actionOnCompletion.run(); actionOnCompletion.run();
} }
...@@ -380,6 +376,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -380,6 +376,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public final synchronized void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) { public final synchronized void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
super.prepareSourceInternal(player, isTopLevelSource); super.prepareSourceInternal(player, isTopLevelSource);
this.player = player; this.player = player;
playerApplicationHandler = new Handler(player.getApplicationLooper());
if (mediaSourcesPublic.isEmpty()) { if (mediaSourcesPublic.isEmpty()) {
notifyListener(); notifyListener();
} else { } else {
...@@ -424,6 +421,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -424,6 +421,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
super.releaseSourceInternal(); super.releaseSourceInternal();
mediaSourceHolders.clear(); mediaSourceHolders.clear();
player = null; player = null;
playerApplicationHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear(); shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0; windowCount = 0;
periodCount = 0; periodCount = 0;
...@@ -462,6 +460,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -462,6 +460,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final void handleMessage(int messageType, Object message) throws ExoPlaybackException { public final void handleMessage(int messageType, Object message) throws ExoPlaybackException {
if (player == null) {
// Stale event.
return;
}
switch (messageType) { switch (messageType) {
case MSG_ADD: case MSG_ADD:
MessageData<MediaSourceHolder> addMessage = (MessageData<MediaSourceHolder>) message; MessageData<MediaSourceHolder> addMessage = (MessageData<MediaSourceHolder>) message;
...@@ -493,15 +495,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -493,15 +495,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
break; break;
case MSG_CLEAR: case MSG_CLEAR:
clearInternal(); clearInternal();
scheduleListenerNotification((EventDispatcher) message); scheduleListenerNotification((Runnable) message);
break; break;
case MSG_NOTIFY_LISTENER: case MSG_NOTIFY_LISTENER:
notifyListener(); notifyListener();
break; break;
case MSG_ON_COMPLETION: 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++) { for (int i = 0; i < actionsOnCompletion.size(); i++) {
actionsOnCompletion.get(i).dispatchEvent(); handler.post(actionsOnCompletion.get(i));
} }
break; break;
default: default:
...@@ -509,7 +512,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -509,7 +512,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
} }
private void scheduleListenerNotification(@Nullable EventDispatcher actionOnCompletion) { private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) {
if (!listenerNotificationScheduled) { if (!listenerNotificationScheduled) {
Assertions.checkNotNull(player).createMessage(this).setType(MSG_NOTIFY_LISTENER).send(); Assertions.checkNotNull(player).createMessage(this).setType(MSG_NOTIFY_LISTENER).send();
listenerNotificationScheduled = true; listenerNotificationScheduled = true;
...@@ -521,9 +524,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -521,9 +524,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private void notifyListener() { private void notifyListener() {
listenerNotificationScheduled = false; listenerNotificationScheduled = false;
List<EventDispatcher> actionsOnCompletion = List<Runnable> actionsOnCompletion =
pendingOnCompletionActions.isEmpty() pendingOnCompletionActions.isEmpty()
? Collections.<EventDispatcher>emptyList() ? Collections.<Runnable>emptyList()
: new ArrayList<>(pendingOnCompletionActions); : new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear(); pendingOnCompletionActions.clear();
refreshSourceInfo( refreshSourceInfo(
...@@ -698,34 +701,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -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. */ /** Message used to post actions from app thread to playback thread. */
private static final class MessageData<T> { private static final class MessageData<T> {
public final int index; public final int index;
public final T customData; public final T customData;
public final @Nullable EventDispatcher actionOnCompletion; public final @Nullable Runnable actionOnCompletion;
public MessageData(int index, T customData, @Nullable Runnable actionOnCompletion) { public MessageData(int index, T customData, @Nullable Runnable actionOnCompletion) {
this.index = index; this.index = index;
this.actionOnCompletion = this.actionOnCompletion = actionOnCompletion;
actionOnCompletion != null ? new EventDispatcher(actionOnCompletion) : null;
this.customData = customData; this.customData = customData;
} }
} }
......
...@@ -252,11 +252,14 @@ public final class Util { ...@@ -252,11 +252,14 @@ public final class Util {
* assumption that the Handler won't be used to send messages until the callback is fully * assumption that the Handler won't be used to send messages until the callback is fully
* initialized. * 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. * @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. * @return A {@link Handler} with the specified callback on the current {@link Looper} thread.
*/ */
public static Handler createHandler(Handler.@UnknownInitialization Callback callback) { 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 { ...@@ -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. * Instantiates a new single threaded executor whose thread has the specified name.
* *
* @param threadName The name of the thread. * @param threadName The name of the thread.
......
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -663,7 +664,8 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener ...@@ -663,7 +664,8 @@ public final class ExoPlayerTestRunner extends Player.DefaultEventListener
loadControl, loadControl,
/* drmSessionManager= */ null, /* drmSessionManager= */ null,
new AnalyticsCollector.Factory(), new AnalyticsCollector.Factory(),
clock); clock,
Looper.myLooper());
} }
} }
} }
...@@ -443,6 +443,11 @@ public class MediaSourceTestRunner { ...@@ -443,6 +443,11 @@ public class MediaSourceTestRunner {
} }
@Override @Override
public Looper getApplicationLooper() {
return handler.getLooper();
}
@Override
public PlayerMessage createMessage(PlayerMessage.Target target) { public PlayerMessage createMessage(PlayerMessage.Target target) {
return new PlayerMessage( return new PlayerMessage(
/* sender= */ this, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler); /* sender= */ this, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler);
......
...@@ -50,6 +50,11 @@ public abstract class StubExoPlayer implements ExoPlayer { ...@@ -50,6 +50,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
} }
@Override @Override
public Looper getApplicationLooper() {
throw new UnsupportedOperationException();
}
@Override
public void addListener(Player.EventListener listener) { public void addListener(Player.EventListener listener) {
throw new UnsupportedOperationException(); 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