Commit 585806de by tonihei Committed by Oliver Woodman

Clarify doc of components which only allow a main thread player.

ExoPlayer can be run on a background thread, but some components (UI and IMA)
only support players on the main thread. This adds some documentation and
assertions for that.

To simplify assertions, this also moves the getApplicationLooper method from
ExoPlayer to Player.

Issue:#4439

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=213280359
parent c18ee3f9
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.ext.cast; package com.google.android.exoplayer2.ext.cast;
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 android.util.Log; import android.util.Log;
...@@ -299,6 +300,11 @@ public final class CastPlayer implements Player { ...@@ -299,6 +300,11 @@ public final class CastPlayer implements Player {
} }
@Override @Override
public Looper getApplicationLooper() {
return Looper.getMainLooper();
}
@Override
public void addListener(EventListener listener) { public void addListener(EventListener listener) {
listeners.add(listener); listeners.add(listener);
} }
......
...@@ -30,7 +30,9 @@ To play ads alongside a single-window content `MediaSource`, prepare the player ...@@ -30,7 +30,9 @@ To play ads alongside a single-window content `MediaSource`, prepare the player
with an `AdsMediaSource` constructed using an `ImaAdsLoader`, the content with an `AdsMediaSource` constructed using an `ImaAdsLoader`, the content
`MediaSource` and an overlay `ViewGroup` on top of the player. Pass an ad tag `MediaSource` and an overlay `ViewGroup` on top of the player. Pass an ad tag
URI from your ad campaign when creating the `ImaAdsLoader`. The IMA URI from your ad campaign when creating the `ImaAdsLoader`. The IMA
documentation includes some [sample ad tags][] for testing. documentation includes some [sample ad tags][] for testing. Note that the IMA
extension only supports players which are accessed on the application's main
thread.
Resuming the player after entering the background requires some special handling Resuming the player after entering the background requires some special handling
when playing ads. The player and its media source are released on entering the when playing ads. The player and its media source are released on entering the
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.ima; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.ima;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -478,6 +479,7 @@ public final class ImaAdsLoader ...@@ -478,6 +479,7 @@ public final class ImaAdsLoader
@Override @Override
public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) { public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) {
Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper());
this.player = player; this.player = player;
this.eventListener = eventListener; this.eventListener = eventListener;
lastVolumePercentage = 0; lastVolumePercentage = 0;
......
...@@ -19,6 +19,7 @@ import android.graphics.Bitmap; ...@@ -19,6 +19,7 @@ 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;
...@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; ...@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions;
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 com.google.android.exoplayer2.util.Util;
...@@ -367,8 +369,7 @@ public final class MediaSessionConnector { ...@@ -367,8 +369,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
...@@ -400,15 +401,17 @@ public final class MediaSessionConnector { ...@@ -400,15 +401,17 @@ public final class MediaSessionConnector {
* <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.
* *
* @param player The player to be connected to the {@code MediaSession}. * @param player The player to be connected to the {@code MediaSession}, or {@link null} to
* disconnect the current player.
* @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player. * @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player.
* @param customActionProviders Optional {@link CustomActionProvider}s to publish and handle * @param customActionProviders Optional {@link CustomActionProvider}s to publish and handle
* custom actions. * custom actions.
*/ */
public void setPlayer( public void setPlayer(
Player player, @Nullable Player player,
@Nullable PlaybackPreparer playbackPreparer, @Nullable PlaybackPreparer playbackPreparer,
CustomActionProvider... customActionProviders) { CustomActionProvider... customActionProviders) {
Assertions.checkArgument(player == null || player.getApplicationLooper() == Looper.myLooper());
if (this.player != null) { if (this.player != null) {
this.player.removeListener(exoPlayerEventListener); this.player.removeListener(exoPlayerEventListener);
mediaSession.setCallback(null); mediaSession.setCallback(null);
......
...@@ -89,10 +89,15 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; ...@@ -89,10 +89,15 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* model"> * model">
* *
* <ul> * <ul>
* <li>ExoPlayer instances must be accessed from the thread associated with {@link * <li>ExoPlayer instances must be accessed from a single application thread. For the vast
* #getApplicationLooper()}. This Looper can be specified when creating the player, or this is * majority of cases this should be the application's main thread. Using the application's
* the Looper of the thread the player is created on, or the Looper of the application's main * main thread is also a requirement when using ExoPlayer's UI components or the IMA
* thread if the player is created on a thread without Looper. * extension. The thread on which an ExoPlayer instance must be accessed can be explicitly
* specified by passing a `Looper` when creating the player. If no `Looper` is specified, then
* the `Looper` of the thread that the player is created on is used, or if that thread does
* not have a `Looper`, the `Looper` of the application's main thread is used. In all cases
* the `Looper` of the thread from which the player must be accessed can be queried using
* {@link #getApplicationLooper()}.
* <li>Registered listeners are called on the thread associated with {@link * <li>Registered listeners are called on the thread associated with {@link
* #getApplicationLooper()}. Note that this means registered listeners are called on the same * #getApplicationLooper()}. Note that this means registered listeners are called on the same
* thread which must be used to access the player. * thread which must be used to access the player.
...@@ -183,12 +188,6 @@ public interface ExoPlayer extends Player { ...@@ -183,12 +188,6 @@ public interface ExoPlayer extends Player {
Looper getPlaybackLooper(); Looper getPlaybackLooper();
/** /**
* Returns the {@link Looper} associated with the application thread that's used to access the
* player and on which player events are received.
*/
Looper getApplicationLooper();
/**
* Retries a failed or stopped playback. Does nothing if the player has been reset, or if playback * Retries a failed or stopped playback. Does nothing if the player has been reset, or if playback
* has not failed or been stopped. * has not failed or been stopped.
*/ */
......
...@@ -530,6 +530,12 @@ public interface Player { ...@@ -530,6 +530,12 @@ public interface Player {
TextComponent getTextComponent(); TextComponent getTextComponent();
/** /**
* Returns the {@link Looper} associated with the application thread that's used to access the
* player and on which player events are received.
*/
Looper getApplicationLooper();
/**
* Register a listener to receive events from the player. The listener's methods will be called on * Register a listener to receive events from the player. The listener's methods will be called on
* the thread that was used to construct the player. However, if the thread used to construct the * the thread that was used to construct the player. However, if the thread used to construct the
* player does not have a {@link Looper}, then the listener will be called on the main thread. * player does not have a {@link Looper}, then the listener will be called on the main thread.
......
...@@ -88,7 +88,9 @@ public interface AdsLoader { ...@@ -88,7 +88,9 @@ public interface AdsLoader {
* Attaches a player that will play ads loaded using this instance. Called on the main thread by * Attaches a player that will play ads loaded using this instance. Called on the main thread by
* {@link AdsMediaSource}. * {@link AdsMediaSource}.
* *
* @param player The player instance that will play the loaded ads. * @param player The player instance that will play the loaded ads. Only players which are
* accessed on the main thread are supported ({@code player.getApplicationLooper() ==
* Looper.getMainLooper()}).
* @param eventListener Listener for ads loader events. * @param eventListener Listener for ads loader events.
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI. * @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
*/ */
......
...@@ -16,11 +16,13 @@ ...@@ -16,11 +16,13 @@
package com.google.android.exoplayer2.ui; package com.google.android.exoplayer2.ui;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Looper;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions;
import java.util.Locale; import java.util.Locale;
/** /**
...@@ -37,10 +39,13 @@ public class DebugTextViewHelper implements Player.EventListener, Runnable { ...@@ -37,10 +39,13 @@ public class DebugTextViewHelper implements Player.EventListener, Runnable {
private boolean started; private boolean started;
/** /**
* @param player The {@link SimpleExoPlayer} from which debug information should be obtained. * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. Only
* players which are accessed on the main thread are supported ({@code
* player.getApplicationLooper() == Looper.getMainLooper()}).
* @param textView The {@link TextView} that should be updated to display the information. * @param textView The {@link TextView} that should be updated to display the information.
*/ */
public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) {
Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper());
this.player = player; this.player = player;
this.textView = textView; this.textView = textView;
} }
......
...@@ -20,6 +20,7 @@ import android.content.Context; ...@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
...@@ -362,9 +363,14 @@ public class PlayerControlView extends FrameLayout { ...@@ -362,9 +363,14 @@ public class PlayerControlView extends FrameLayout {
/** /**
* Sets the {@link Player} to control. * Sets the {@link Player} to control.
* *
* @param player The {@link Player} to control. * @param player The {@link Player} to control, or {@code null} to detach the current player. Only
* players which are accessed on the main thread are supported ({@code
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/ */
public void setPlayer(Player player) { public void setPlayer(@Nullable Player player) {
Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
Assertions.checkArgument(
player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) { if (this.player == player) {
return; return;
} }
......
...@@ -433,8 +433,15 @@ public class PlayerNotificationManager { ...@@ -433,8 +433,15 @@ public class PlayerNotificationManager {
* *
* <p>If the player is released it must be removed from the manager by calling {@code * <p>If the player is released it must be removed from the manager by calling {@code
* setPlayer(null)}. This will cancel the notification. * setPlayer(null)}. This will cancel the notification.
*
* @param player The {@link Player} to use, or {@code null} to remove the current player. Only
* players which are accessed on the main thread are supported ({@code
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/ */
public final void setPlayer(@Nullable Player player) { public final void setPlayer(@Nullable Player player) {
Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
Assertions.checkArgument(
player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) { if (this.player == player) {
return; return;
} }
......
...@@ -27,6 +27,7 @@ import android.graphics.Matrix; ...@@ -27,6 +27,7 @@ import android.graphics.Matrix;
import android.graphics.RectF; import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -499,9 +500,14 @@ public class PlayerView extends FrameLayout { ...@@ -499,9 +500,14 @@ public class PlayerView extends FrameLayout {
* calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly
* more efficient and may allow for more seamless transitions. * more efficient and may allow for more seamless transitions.
* *
* @param player The {@link Player} to use. * @param player The {@link Player} to use, or {@code null} to detach the current player. Only
* players which are accessed on the main thread are supported ({@code
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/ */
public void setPlayer(Player player) { public void setPlayer(@Nullable Player player) {
Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
Assertions.checkArgument(
player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) { if (this.player == player) {
return; return;
} }
......
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