Commit 8416423e by tonihei Committed by Ian Baker

Move SimpleExoPlayer logic into ExoPlayerImpl

This makes SimpleExoPlayer a simple forwarding wrapper which can be
removed in the future.

The changes are all purely mechanical with none of the potential further
simplifications made yet. The only exceptions are name clashes where
either EPI or SEP was calling a method in one of the classes and both
classes had different implementations for the same method name. In these
cases we needed to disambiguate between the two different
implementations (example: ExoPlayerImpl.setListener was renamed to
setEventListener).

#minor-release

PiperOrigin-RevId: 425823095
parent ec8303c2
...@@ -15,20 +15,31 @@ ...@@ -15,20 +15,31 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO;
import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION;
import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO;
import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS; import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS;
import static com.google.android.exoplayer2.Player.COMMAND_GET_AUDIO_ATTRIBUTES;
import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_GET_DEVICE_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA;
import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT;
import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE;
import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACK_INFOS; import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACK_INFOS;
import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE;
import static com.google.android.exoplayer2.Player.COMMAND_PREPARE; import static com.google.android.exoplayer2.Player.COMMAND_PREPARE;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEMS_METADATA; import static com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE; import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE; import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH; import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH;
import static com.google.android.exoplayer2.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; import static com.google.android.exoplayer2.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS;
import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE;
import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME;
import static com.google.android.exoplayer2.Player.COMMAND_STOP; import static com.google.android.exoplayer2.Player.COMMAND_STOP;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION;
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
...@@ -42,12 +53,23 @@ import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_ ...@@ -42,12 +53,23 @@ import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_
import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT; import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT;
import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK; import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK;
import static com.google.android.exoplayer2.Player.PLAYBACK_SUPPRESSION_REASON_NONE; import static com.google.android.exoplayer2.Player.PLAYBACK_SUPPRESSION_REASON_NONE;
import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS;
import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
import static com.google.android.exoplayer2.Player.STATE_BUFFERING; import static com.google.android.exoplayer2.Player.STATE_BUFFERING;
import static com.google.android.exoplayer2.Player.STATE_ENDED; import static com.google.android.exoplayer2.Player.STATE_ENDED;
import static com.google.android.exoplayer2.Player.STATE_IDLE; import static com.google.android.exoplayer2.Player.STATE_IDLE;
import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE; import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO;
import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY;
import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE;
import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
...@@ -55,10 +77,20 @@ import static java.lang.Math.max; ...@@ -55,10 +77,20 @@ import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.media.MediaFormat;
import android.media.metrics.LogSessionId; import android.media.metrics.LogSessionId;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Pair; import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.DoNotInline; import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
...@@ -75,14 +107,22 @@ import com.google.android.exoplayer2.Player.RepeatMode; ...@@ -75,14 +107,22 @@ import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.Player.State; import com.google.android.exoplayer2.Player.State;
import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.Player.TimelineChangeReason;
import com.google.android.exoplayer2.PlayerMessage.Target; import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.Renderer.MessageType;
import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.analytics.PlayerId;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
...@@ -91,15 +131,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; ...@@ -91,15 +131,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.PriorityTaskManager;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoSize;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeoutException;
/** A helper class for the {@link SimpleExoPlayer} implementation of {@link ExoPlayer}. */ /** A helper class for the {@link SimpleExoPlayer} implementation of {@link ExoPlayer}. */
/* package */ final class ExoPlayerImpl { /* package */ final class ExoPlayerImpl {
...@@ -120,13 +169,18 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -120,13 +169,18 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* package */ final TrackSelectorResult emptyTrackSelectorResult; /* package */ final TrackSelectorResult emptyTrackSelectorResult;
/* package */ final Commands permanentAvailableCommands; /* package */ final Commands permanentAvailableCommands;
private final ConditionVariable constructorFinished;
private final Context applicationContext;
private final Player wrappingPlayer; private final Player wrappingPlayer;
private final Renderer[] renderers; private final Renderer[] renderers;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final HandlerWrapper playbackInfoUpdateHandler; private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final ExoPlayerImplInternal internalPlayer; private final ExoPlayerImplInternal internalPlayer;
private final ListenerSet<Player.EventListener> listeners;
@SuppressWarnings("deprecation") // TODO(b/187152483): Merge with non-deprecated listeners.
private final ListenerSet<Player.EventListener> eventListeners;
private final CopyOnWriteArraySet<AudioOffloadListener> audioOffloadListeners; private final CopyOnWriteArraySet<AudioOffloadListener> audioOffloadListeners;
private final Timeline.Period period; private final Timeline.Period period;
private final Timeline.Window window; private final Timeline.Window window;
...@@ -139,6 +193,15 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -139,6 +193,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final long seekBackIncrementMs; private final long seekBackIncrementMs;
private final long seekForwardIncrementMs; private final long seekForwardIncrementMs;
private final Clock clock; private final Clock clock;
private final ComponentListener componentListener;
private final FrameMetadataListener frameMetadataListener;
private final CopyOnWriteArraySet<Listener> listeners;
private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager;
private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
private final long detachSurfaceTimeoutMs;
@RepeatMode private int repeatMode; @RepeatMode private int repeatMode;
private boolean shuffleModeEnabled; private boolean shuffleModeEnabled;
...@@ -153,6 +216,35 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -153,6 +216,35 @@ import java.util.concurrent.CopyOnWriteArraySet;
private Commands availableCommands; private Commands availableCommands;
private MediaMetadata mediaMetadata; private MediaMetadata mediaMetadata;
private MediaMetadata playlistMetadata; private MediaMetadata playlistMetadata;
@Nullable private Format videoFormat;
@Nullable private Format audioFormat;
@Nullable private AudioTrack keepSessionIdAudioTrack;
@Nullable private Object videoOutput;
@Nullable private Surface ownedSurface;
@Nullable private SurfaceHolder surfaceHolder;
@Nullable private SphericalGLSurfaceView sphericalGLSurfaceView;
private boolean surfaceHolderSurfaceIsVideoOutput;
@Nullable private TextureView textureView;
@C.VideoScalingMode private int videoScalingMode;
@C.VideoChangeFrameRateStrategy private int videoChangeFrameRateStrategy;
private int surfaceWidth;
private int surfaceHeight;
@Nullable private DecoderCounters videoDecoderCounters;
@Nullable private DecoderCounters audioDecoderCounters;
private int audioSessionId;
private AudioAttributes audioAttributes;
private float volume;
private boolean skipSilenceEnabled;
private List<Cue> currentCues;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread;
private boolean hasNotifiedFullWrongThreadWarning;
@Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased;
private DeviceInfo deviceInfo;
private VideoSize videoSize;
// MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata // MediaMetadata built from static (TrackGroup Format) and dynamic (onMetadata(Metadata)) metadata
// sources. // sources.
...@@ -166,145 +258,178 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -166,145 +258,178 @@ import java.util.concurrent.CopyOnWriteArraySet;
private int maskingPeriodIndex; private int maskingPeriodIndex;
private long maskingWindowPositionMs; private long maskingWindowPositionMs;
/**
* Constructs an instance. Must be called from a thread that has an associated {@link Looper}.
*
* @param renderers The {@link Renderer}s.
* @param trackSelector The {@link TrackSelector}.
* @param mediaSourceFactory The {@link MediaSource.Factory}.
* @param loadControl The {@link LoadControl}.
* @param bandwidthMeter The {@link BandwidthMeter}.
* @param analyticsCollector The {@link AnalyticsCollector}.
* @param useLazyPreparation Whether playlist items are prepared lazily. If false, all manifest
* loads and other initial preparation steps happen immediately. If true, these initial
* preparations are triggered only when the player starts buffering the media.
* @param seekParameters The {@link SeekParameters}.
* @param seekBackIncrementMs The seek back increment in milliseconds.
* @param seekForwardIncrementMs The seek forward increment in milliseconds.
* @param livePlaybackSpeedControl The {@link LivePlaybackSpeedControl}.
* @param releaseTimeoutMs The timeout for calls to {@link #release()} in milliseconds.
* @param pauseAtEndOfMediaItems Whether to pause playback at the end of each media item.
* @param clock The {@link Clock}.
* @param applicationLooper The {@link Looper} that must be used for all calls to the player and
* which is used to call listeners on.
* @param wrappingPlayer The {@link Player} using this class.
* @param additionalPermanentAvailableCommands The {@link Commands} that are permanently available
* in the wrapping player but that are not in this player.
*/
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
public ExoPlayerImpl( public ExoPlayerImpl(ExoPlayer.Builder builder, Player wrappingPlayer) {
Renderer[] renderers, constructorFinished = new ConditionVariable();
TrackSelector trackSelector, try {
MediaSource.Factory mediaSourceFactory, Log.i(
LoadControl loadControl, TAG,
BandwidthMeter bandwidthMeter, "Init "
AnalyticsCollector analyticsCollector, + Integer.toHexString(System.identityHashCode(this))
boolean useLazyPreparation, + " ["
SeekParameters seekParameters, + ExoPlayerLibraryInfo.VERSION_SLASHY
long seekBackIncrementMs, + "] ["
long seekForwardIncrementMs, + Util.DEVICE_DEBUG_INFO
LivePlaybackSpeedControl livePlaybackSpeedControl, + "]");
long releaseTimeoutMs, applicationContext = builder.context.getApplicationContext();
boolean pauseAtEndOfMediaItems, analyticsCollector = builder.analyticsCollectorSupplier.get();
Clock clock, priorityTaskManager = builder.priorityTaskManager;
Looper applicationLooper, audioAttributes = builder.audioAttributes;
Player wrappingPlayer, videoScalingMode = builder.videoScalingMode;
Commands additionalPermanentAvailableCommands) { videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy;
Log.i( skipSilenceEnabled = builder.skipSilenceEnabled;
TAG, detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs;
"Init " componentListener = new ComponentListener();
+ Integer.toHexString(System.identityHashCode(this)) frameMetadataListener = new FrameMetadataListener();
+ " [" listeners = new CopyOnWriteArraySet<>();
+ ExoPlayerLibraryInfo.VERSION_SLASHY Handler eventHandler = new Handler(builder.looper);
+ "] [" renderers =
+ Util.DEVICE_DEBUG_INFO builder
+ "]"); .renderersFactorySupplier
checkState(renderers.length > 0); .get()
this.renderers = checkNotNull(renderers); .createRenderers(
this.trackSelector = checkNotNull(trackSelector); eventHandler,
this.mediaSourceFactory = mediaSourceFactory; componentListener,
this.bandwidthMeter = bandwidthMeter; componentListener,
this.analyticsCollector = analyticsCollector; componentListener,
this.useLazyPreparation = useLazyPreparation; componentListener);
this.seekParameters = seekParameters; checkState(renderers.length > 0);
this.seekBackIncrementMs = seekBackIncrementMs; this.trackSelector = builder.trackSelectorSupplier.get();
this.seekForwardIncrementMs = seekForwardIncrementMs; this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get();
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; this.bandwidthMeter = builder.bandwidthMeterSupplier.get();
this.applicationLooper = applicationLooper; this.useLazyPreparation = builder.useLazyPreparation;
this.clock = clock; this.seekParameters = builder.seekParameters;
this.wrappingPlayer = wrappingPlayer; this.seekBackIncrementMs = builder.seekBackIncrementMs;
repeatMode = Player.REPEAT_MODE_OFF; this.seekForwardIncrementMs = builder.seekForwardIncrementMs;
listeners = this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems;
new ListenerSet<>( this.applicationLooper = builder.looper;
applicationLooper, this.clock = builder.clock;
clock, this.wrappingPlayer = wrappingPlayer;
(listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags))); eventListeners =
audioOffloadListeners = new CopyOnWriteArraySet<>(); new ListenerSet<>(
mediaSourceHolderSnapshots = new ArrayList<>(); applicationLooper,
shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); clock,
emptyTrackSelectorResult = (listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags)));
new TrackSelectorResult( audioOffloadListeners = new CopyOnWriteArraySet<>();
new RendererConfiguration[renderers.length], mediaSourceHolderSnapshots = new ArrayList<>();
new ExoTrackSelection[renderers.length], shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
TracksInfo.EMPTY, emptyTrackSelectorResult =
/* info= */ null); new TrackSelectorResult(
period = new Timeline.Period(); new RendererConfiguration[renderers.length],
window = new Timeline.Window(); new ExoTrackSelection[renderers.length],
permanentAvailableCommands = TracksInfo.EMPTY,
new Commands.Builder() /* info= */ null);
.addAll( period = new Timeline.Period();
COMMAND_PLAY_PAUSE, window = new Timeline.Window();
COMMAND_PREPARE, permanentAvailableCommands =
COMMAND_STOP, new Commands.Builder()
COMMAND_SET_SPEED_AND_PITCH, .addAll(
COMMAND_SET_SHUFFLE_MODE, COMMAND_PLAY_PAUSE,
COMMAND_SET_REPEAT_MODE, COMMAND_PREPARE,
COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_STOP,
COMMAND_GET_TIMELINE, COMMAND_SET_SPEED_AND_PITCH,
COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_SET_SHUFFLE_MODE,
COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_SET_REPEAT_MODE,
COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_CURRENT_MEDIA_ITEM,
COMMAND_GET_TRACK_INFOS) COMMAND_GET_TIMELINE,
.addIf(COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) COMMAND_GET_MEDIA_ITEMS_METADATA,
.addAll(additionalPermanentAvailableCommands) COMMAND_SET_MEDIA_ITEMS_METADATA,
.build(); COMMAND_CHANGE_MEDIA_ITEMS,
availableCommands = COMMAND_GET_TRACK_INFOS,
new Commands.Builder() COMMAND_GET_AUDIO_ATTRIBUTES,
.addAll(permanentAvailableCommands) COMMAND_GET_VOLUME,
.add(COMMAND_SEEK_TO_DEFAULT_POSITION) COMMAND_GET_DEVICE_VOLUME,
.add(COMMAND_SEEK_TO_MEDIA_ITEM) COMMAND_SET_VOLUME,
.build(); COMMAND_SET_DEVICE_VOLUME,
mediaMetadata = MediaMetadata.EMPTY; COMMAND_ADJUST_DEVICE_VOLUME,
playlistMetadata = MediaMetadata.EMPTY; COMMAND_SET_VIDEO_SURFACE,
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; COMMAND_GET_TEXT)
maskingWindowIndex = C.INDEX_UNSET; .addIf(
playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported())
playbackInfoUpdateListener = .build();
playbackInfoUpdate -> availableCommands =
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); new Commands.Builder()
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); .addAll(permanentAvailableCommands)
analyticsCollector.setPlayer(wrappingPlayer, applicationLooper); .add(COMMAND_SEEK_TO_DEFAULT_POSITION)
addListener(analyticsCollector); .add(COMMAND_SEEK_TO_MEDIA_ITEM)
bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); .build();
PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.createPlayerId(); playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
internalPlayer = playbackInfoUpdateListener =
new ExoPlayerImplInternal( playbackInfoUpdate ->
renderers, playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
trackSelector, playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
emptyTrackSelectorResult, analyticsCollector.setPlayer(wrappingPlayer, applicationLooper);
loadControl, PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.createPlayerId();
bandwidthMeter, internalPlayer =
repeatMode, new ExoPlayerImplInternal(
shuffleModeEnabled, renderers,
analyticsCollector, trackSelector,
seekParameters, emptyTrackSelectorResult,
livePlaybackSpeedControl, builder.loadControlSupplier.get(),
releaseTimeoutMs, bandwidthMeter,
pauseAtEndOfMediaItems, repeatMode,
applicationLooper, shuffleModeEnabled,
clock, analyticsCollector,
playbackInfoUpdateListener, seekParameters,
playerId); builder.livePlaybackSpeedControl,
builder.releaseTimeoutMs,
pauseAtEndOfMediaItems,
applicationLooper,
clock,
playbackInfoUpdateListener,
playerId);
volume = 1;
repeatMode = Player.REPEAT_MODE_OFF;
mediaMetadata = MediaMetadata.EMPTY;
playlistMetadata = MediaMetadata.EMPTY;
staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
maskingWindowIndex = C.INDEX_UNSET;
if (Util.SDK_INT < 21) {
audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET);
} else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
currentCues = ImmutableList.of();
throwsWhenUsingWrongThread = true;
addEventListener(analyticsCollector);
bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector);
addEventListener(componentListener);
addAudioOffloadListener(componentListener);
if (builder.foregroundModeTimeoutMs > 0) {
experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
}
audioBecomingNoisyManager =
new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener);
audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy);
audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener);
audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null);
streamVolumeManager =
new StreamVolumeManager(builder.context, eventHandler, componentListener);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
wakeLockManager = new WakeLockManager(builder.context);
wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE);
wifiLockManager = new WifiLockManager(builder.context);
wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK);
deviceInfo = createDeviceInfo(streamVolumeManager);
videoSize = VideoSize.UNKNOWN;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled);
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener);
sendRendererMessage(
TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener);
} finally {
constructorFinished.open();
}
} }
/** /**
...@@ -322,63 +447,72 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -322,63 +447,72 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) {
verifyApplicationThread();
internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled);
} }
public boolean experimentalIsSleepingForOffload() { public boolean experimentalIsSleepingForOffload() {
verifyApplicationThread();
return playbackInfo.sleepingForOffload; return playbackInfo.sleepingForOffload;
} }
public Looper getPlaybackLooper() { public Looper getPlaybackLooper() {
// Don't verify application thread. We allow calls to this method from any thread.
return internalPlayer.getPlaybackLooper(); return internalPlayer.getPlaybackLooper();
} }
public Looper getApplicationLooper() { public Looper getApplicationLooper() {
// Don't verify application thread. We allow calls to this method from any thread.
return applicationLooper; return applicationLooper;
} }
public Clock getClock() { public Clock getClock() {
// Don't verify application thread. We allow calls to this method from any thread.
return clock; return clock;
} }
public void addListener(Listener listener) {
addEventListener(listener);
}
@SuppressWarnings("deprecation") // Register deprecated EventListener. @SuppressWarnings("deprecation") // Register deprecated EventListener.
public void addEventListener(Player.EventListener eventListener) { public void addEventListener(Player.EventListener eventListener) {
listeners.add(eventListener); // Don't verify application thread. We allow calls to this method from any thread.
eventListeners.add(eventListener);
} }
@SuppressWarnings("deprecation") // Deregister deprecated EventListener. @SuppressWarnings("deprecation") // Deregister deprecated EventListener.
public void removeEventListener(Player.EventListener eventListener) { public void removeEventListener(Player.EventListener eventListener) {
listeners.remove(eventListener); // Don't verify application thread. We allow calls to this method from any thread.
eventListeners.remove(eventListener);
} }
public void addAudioOffloadListener(AudioOffloadListener listener) { public void addAudioOffloadListener(AudioOffloadListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
audioOffloadListeners.add(listener); audioOffloadListeners.add(listener);
} }
public void removeAudioOffloadListener(AudioOffloadListener listener) { public void removeAudioOffloadListener(AudioOffloadListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
audioOffloadListeners.remove(listener); audioOffloadListeners.remove(listener);
} }
public Commands getAvailableCommands() { public Commands getAvailableCommands() {
verifyApplicationThread();
return availableCommands; return availableCommands;
} }
@State @State
public int getPlaybackState() { public int getPlaybackState() {
verifyApplicationThread();
return playbackInfo.playbackState; return playbackInfo.playbackState;
} }
@PlaybackSuppressionReason @PlaybackSuppressionReason
public int getPlaybackSuppressionReason() { public int getPlaybackSuppressionReason() {
verifyApplicationThread();
return playbackInfo.playbackSuppressionReason; return playbackInfo.playbackSuppressionReason;
} }
@Nullable @Nullable
public ExoPlaybackException getPlayerError() { public ExoPlaybackException getPlayerError() {
verifyApplicationThread();
return playbackInfo.playbackError; return playbackInfo.playbackError;
} }
...@@ -389,6 +523,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -389,6 +523,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void prepare() { public void prepare() {
verifyApplicationThread();
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
if (playbackInfo.playbackState != Player.STATE_IDLE) { if (playbackInfo.playbackState != Player.STATE_IDLE) {
return; return;
} }
...@@ -418,6 +558,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -418,6 +558,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/ */
@Deprecated @Deprecated
public void prepare(MediaSource mediaSource) { public void prepare(MediaSource mediaSource) {
verifyApplicationThread();
setMediaSource(mediaSource); setMediaSource(mediaSource);
prepare(); prepare();
} }
...@@ -428,36 +569,44 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -428,36 +569,44 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/ */
@Deprecated @Deprecated
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
verifyApplicationThread();
setMediaSource(mediaSource, resetPosition); setMediaSource(mediaSource, resetPosition);
prepare(); prepare();
} }
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
verifyApplicationThread();
setMediaSources(createMediaSources(mediaItems), resetPosition); setMediaSources(createMediaSources(mediaItems), resetPosition);
} }
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) { public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
verifyApplicationThread();
setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs);
} }
public void setMediaSource(MediaSource mediaSource) { public void setMediaSource(MediaSource mediaSource) {
verifyApplicationThread();
setMediaSources(Collections.singletonList(mediaSource)); setMediaSources(Collections.singletonList(mediaSource));
} }
public void setMediaSource(MediaSource mediaSource, long startPositionMs) { public void setMediaSource(MediaSource mediaSource, long startPositionMs) {
verifyApplicationThread();
setMediaSources( setMediaSources(
Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs); Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs);
} }
public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { public void setMediaSource(MediaSource mediaSource, boolean resetPosition) {
verifyApplicationThread();
setMediaSources(Collections.singletonList(mediaSource), resetPosition); setMediaSources(Collections.singletonList(mediaSource), resetPosition);
} }
public void setMediaSources(List<MediaSource> mediaSources) { public void setMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread();
setMediaSources(mediaSources, /* resetPosition= */ true); setMediaSources(mediaSources, /* resetPosition= */ true);
} }
public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) { public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) {
verifyApplicationThread();
setMediaSourcesInternal( setMediaSourcesInternal(
mediaSources, mediaSources,
/* startWindowIndex= */ C.INDEX_UNSET, /* startWindowIndex= */ C.INDEX_UNSET,
...@@ -467,28 +616,34 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -467,28 +616,34 @@ import java.util.concurrent.CopyOnWriteArraySet;
public void setMediaSources( public void setMediaSources(
List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs) { List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs) {
verifyApplicationThread();
setMediaSourcesInternal( setMediaSourcesInternal(
mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false);
} }
public void addMediaItems(int index, List<MediaItem> mediaItems) { public void addMediaItems(int index, List<MediaItem> mediaItems) {
verifyApplicationThread();
index = min(index, mediaSourceHolderSnapshots.size()); index = min(index, mediaSourceHolderSnapshots.size());
addMediaSources(index, createMediaSources(mediaItems)); addMediaSources(index, createMediaSources(mediaItems));
} }
public void addMediaSource(MediaSource mediaSource) { public void addMediaSource(MediaSource mediaSource) {
verifyApplicationThread();
addMediaSources(Collections.singletonList(mediaSource)); addMediaSources(Collections.singletonList(mediaSource));
} }
public void addMediaSource(int index, MediaSource mediaSource) { public void addMediaSource(int index, MediaSource mediaSource) {
verifyApplicationThread();
addMediaSources(index, Collections.singletonList(mediaSource)); addMediaSources(index, Collections.singletonList(mediaSource));
} }
public void addMediaSources(List<MediaSource> mediaSources) { public void addMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread();
addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources); addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources);
} }
public void addMediaSources(int index, List<MediaSource> mediaSources) { public void addMediaSources(int index, List<MediaSource> mediaSources) {
verifyApplicationThread();
Assertions.checkArgument(index >= 0); Assertions.checkArgument(index >= 0);
Timeline oldTimeline = getCurrentTimeline(); Timeline oldTimeline = getCurrentTimeline();
pendingOperationAcks++; pendingOperationAcks++;
...@@ -512,6 +667,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -512,6 +667,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void removeMediaItems(int fromIndex, int toIndex) { public void removeMediaItems(int fromIndex, int toIndex) {
verifyApplicationThread();
toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); toIndex = min(toIndex, mediaSourceHolderSnapshots.size());
PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex); PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex);
boolean positionDiscontinuity = boolean positionDiscontinuity =
...@@ -528,6 +684,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -528,6 +684,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) {
verifyApplicationThread();
Assertions.checkArgument( Assertions.checkArgument(
fromIndex >= 0 fromIndex >= 0
&& fromIndex <= toIndex && fromIndex <= toIndex
...@@ -556,6 +713,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -556,6 +713,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
verifyApplicationThread();
Timeline timeline = createMaskingTimeline(); Timeline timeline = createMaskingTimeline();
PlaybackInfo newPlaybackInfo = PlaybackInfo newPlaybackInfo =
maskTimelineAndPosition( maskTimelineAndPosition(
...@@ -578,6 +736,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -578,6 +736,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
verifyApplicationThread();
if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) {
return; return;
} }
...@@ -586,9 +745,18 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -586,9 +745,18 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public boolean getPauseAtEndOfMediaItems() { public boolean getPauseAtEndOfMediaItems() {
verifyApplicationThread();
return pauseAtEndOfMediaItems; return pauseAtEndOfMediaItems;
} }
public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
public void setPlayWhenReady( public void setPlayWhenReady(
boolean playWhenReady, boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason, @PlaybackSuppressionReason int playbackSuppressionReason,
...@@ -613,46 +781,54 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -613,46 +781,54 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public boolean getPlayWhenReady() { public boolean getPlayWhenReady() {
verifyApplicationThread();
return playbackInfo.playWhenReady; return playbackInfo.playWhenReady;
} }
public void setRepeatMode(@RepeatMode int repeatMode) { public void setRepeatMode(@RepeatMode int repeatMode) {
verifyApplicationThread();
if (this.repeatMode != repeatMode) { if (this.repeatMode != repeatMode) {
this.repeatMode = repeatMode; this.repeatMode = repeatMode;
internalPlayer.setRepeatMode(repeatMode); internalPlayer.setRepeatMode(repeatMode);
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode));
updateAvailableCommands(); updateAvailableCommands();
listeners.flushEvents(); eventListeners.flushEvents();
} }
} }
@RepeatMode @RepeatMode
public int getRepeatMode() { public int getRepeatMode() {
verifyApplicationThread();
return repeatMode; return repeatMode;
} }
public void setShuffleModeEnabled(boolean shuffleModeEnabled) { public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
verifyApplicationThread();
if (this.shuffleModeEnabled != shuffleModeEnabled) { if (this.shuffleModeEnabled != shuffleModeEnabled) {
this.shuffleModeEnabled = shuffleModeEnabled; this.shuffleModeEnabled = shuffleModeEnabled;
internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); internalPlayer.setShuffleModeEnabled(shuffleModeEnabled);
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED,
listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled));
updateAvailableCommands(); updateAvailableCommands();
listeners.flushEvents(); eventListeners.flushEvents();
} }
} }
public boolean getShuffleModeEnabled() { public boolean getShuffleModeEnabled() {
verifyApplicationThread();
return shuffleModeEnabled; return shuffleModeEnabled;
} }
public boolean isLoading() { public boolean isLoading() {
verifyApplicationThread();
return playbackInfo.isLoading; return playbackInfo.isLoading;
} }
public void seekTo(int mediaItemIndex, long positionMs) { public void seekTo(int mediaItemIndex, long positionMs) {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
Timeline timeline = playbackInfo.timeline; Timeline timeline = playbackInfo.timeline;
if (mediaItemIndex < 0 if (mediaItemIndex < 0
|| (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) {
...@@ -693,18 +869,22 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -693,18 +869,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public long getSeekBackIncrement() { public long getSeekBackIncrement() {
verifyApplicationThread();
return seekBackIncrementMs; return seekBackIncrementMs;
} }
public long getSeekForwardIncrement() { public long getSeekForwardIncrement() {
verifyApplicationThread();
return seekForwardIncrementMs; return seekForwardIncrementMs;
} }
public long getMaxSeekToPreviousPosition() { public long getMaxSeekToPreviousPosition() {
verifyApplicationThread();
return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
} }
public void setPlaybackParameters(PlaybackParameters playbackParameters) { public void setPlaybackParameters(PlaybackParameters playbackParameters) {
verifyApplicationThread();
if (playbackParameters == null) { if (playbackParameters == null) {
playbackParameters = PlaybackParameters.DEFAULT; playbackParameters = PlaybackParameters.DEFAULT;
} }
...@@ -726,10 +906,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -726,10 +906,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public PlaybackParameters getPlaybackParameters() { public PlaybackParameters getPlaybackParameters() {
verifyApplicationThread();
return playbackInfo.playbackParameters; return playbackInfo.playbackParameters;
} }
public void setSeekParameters(@Nullable SeekParameters seekParameters) { public void setSeekParameters(@Nullable SeekParameters seekParameters) {
verifyApplicationThread();
if (seekParameters == null) { if (seekParameters == null) {
seekParameters = SeekParameters.DEFAULT; seekParameters = SeekParameters.DEFAULT;
} }
...@@ -740,10 +922,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -740,10 +922,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public SeekParameters getSeekParameters() { public SeekParameters getSeekParameters() {
verifyApplicationThread();
return seekParameters; return seekParameters;
} }
public void setForegroundMode(boolean foregroundMode) { public void setForegroundMode(boolean foregroundMode) {
verifyApplicationThread();
if (this.foregroundMode != foregroundMode) { if (this.foregroundMode != foregroundMode) {
this.foregroundMode = foregroundMode; this.foregroundMode = foregroundMode;
if (!internalPlayer.setForegroundMode(foregroundMode)) { if (!internalPlayer.setForegroundMode(foregroundMode)) {
...@@ -757,8 +941,15 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -757,8 +941,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
} }
public void stop() {
stop(/* reset= */ false);
}
public void stop(boolean reset) { public void stop(boolean reset) {
verifyApplicationThread();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
stop(reset, /* error= */ null); stop(reset, /* error= */ null);
currentCues = ImmutableList.of();
} }
/** /**
...@@ -811,9 +1002,19 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -811,9 +1002,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
+ "] [" + "] ["
+ ExoPlayerLibraryInfo.registeredModules() + ExoPlayerLibraryInfo.registeredModules()
+ "]"); + "]");
verifyApplicationThread();
if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) {
keepSessionIdAudioTrack.release();
keepSessionIdAudioTrack = null;
}
audioBecomingNoisyManager.setEnabled(false);
streamVolumeManager.release();
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
audioFocusManager.release();
if (!internalPlayer.release()) { if (!internalPlayer.release()) {
// One of the renderers timed out releasing its resources. // One of the renderers timed out releasing its resources.
listeners.sendEvent( eventListeners.sendEvent(
Player.EVENT_PLAYER_ERROR, Player.EVENT_PLAYER_ERROR,
listener -> listener ->
listener.onPlayerError( listener.onPlayerError(
...@@ -821,26 +1022,34 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -821,26 +1022,34 @@ import java.util.concurrent.CopyOnWriteArraySet;
new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE), new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE),
PlaybackException.ERROR_CODE_TIMEOUT))); PlaybackException.ERROR_CODE_TIMEOUT)));
} }
listeners.release(); eventListeners.release();
playbackInfoUpdateHandler.removeCallbacksAndMessages(null); playbackInfoUpdateHandler.removeCallbacksAndMessages(null);
bandwidthMeter.removeEventListener(analyticsCollector); bandwidthMeter.removeEventListener(analyticsCollector);
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId);
playbackInfo.bufferedPositionUs = playbackInfo.positionUs; playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
playbackInfo.totalBufferedDurationUs = 0; playbackInfo.totalBufferedDurationUs = 0;
analyticsCollector.release();
removeSurfaceCallbacks();
if (ownedSurface != null) {
ownedSurface.release();
ownedSurface = null;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false;
}
currentCues = ImmutableList.of();
playerReleased = true;
} }
public PlayerMessage createMessage(Target target) { public PlayerMessage createMessage(Target target) {
return new PlayerMessage( verifyApplicationThread();
internalPlayer, return createMessageInternal(target);
target,
playbackInfo.timeline,
getCurrentMediaItemIndex(),
clock,
internalPlayer.getPlaybackLooper());
} }
public int getCurrentPeriodIndex() { public int getCurrentPeriodIndex() {
verifyApplicationThread();
if (playbackInfo.timeline.isEmpty()) { if (playbackInfo.timeline.isEmpty()) {
return maskingPeriodIndex; return maskingPeriodIndex;
} else { } else {
...@@ -849,11 +1058,13 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -849,11 +1058,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public int getCurrentMediaItemIndex() { public int getCurrentMediaItemIndex() {
verifyApplicationThread();
int currentWindowIndex = getCurrentWindowIndexInternal(); int currentWindowIndex = getCurrentWindowIndexInternal();
return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex; return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex;
} }
public long getDuration() { public long getDuration() {
verifyApplicationThread();
if (isPlayingAd()) { if (isPlayingAd()) {
MediaPeriodId periodId = playbackInfo.periodId; MediaPeriodId periodId = playbackInfo.periodId;
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
...@@ -871,10 +1082,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -871,10 +1082,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public long getCurrentPosition() { public long getCurrentPosition() {
verifyApplicationThread();
return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); return Util.usToMs(getCurrentPositionUsInternal(playbackInfo));
} }
public long getBufferedPosition() { public long getBufferedPosition() {
verifyApplicationThread();
if (isPlayingAd()) { if (isPlayingAd()) {
return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId) return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId)
? Util.usToMs(playbackInfo.bufferedPositionUs) ? Util.usToMs(playbackInfo.bufferedPositionUs)
...@@ -884,22 +1097,27 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -884,22 +1097,27 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public long getTotalBufferedDuration() { public long getTotalBufferedDuration() {
verifyApplicationThread();
return Util.usToMs(playbackInfo.totalBufferedDurationUs); return Util.usToMs(playbackInfo.totalBufferedDurationUs);
} }
public boolean isPlayingAd() { public boolean isPlayingAd() {
verifyApplicationThread();
return playbackInfo.periodId.isAd(); return playbackInfo.periodId.isAd();
} }
public int getCurrentAdGroupIndex() { public int getCurrentAdGroupIndex() {
verifyApplicationThread();
return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET;
} }
public int getCurrentAdIndexInAdGroup() { public int getCurrentAdIndexInAdGroup() {
verifyApplicationThread();
return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET;
} }
public long getContentPosition() { public long getContentPosition() {
verifyApplicationThread();
if (isPlayingAd()) { if (isPlayingAd()) {
playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period);
return playbackInfo.requestedContentPositionUs == C.TIME_UNSET return playbackInfo.requestedContentPositionUs == C.TIME_UNSET
...@@ -914,6 +1132,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -914,6 +1132,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public long getContentBufferedPosition() { public long getContentBufferedPosition() {
verifyApplicationThread();
if (playbackInfo.timeline.isEmpty()) { if (playbackInfo.timeline.isEmpty()) {
return maskingWindowPositionMs; return maskingWindowPositionMs;
} }
...@@ -937,53 +1156,63 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -937,53 +1156,63 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
public int getRendererCount() { public int getRendererCount() {
verifyApplicationThread();
return renderers.length; return renderers.length;
} }
public @C.TrackType int getRendererType(int index) { public @C.TrackType int getRendererType(int index) {
verifyApplicationThread();
return renderers[index].getTrackType(); return renderers[index].getTrackType();
} }
public Renderer getRenderer(int index) { public Renderer getRenderer(int index) {
verifyApplicationThread();
return renderers[index]; return renderers[index];
} }
public TrackSelector getTrackSelector() { public TrackSelector getTrackSelector() {
verifyApplicationThread();
return trackSelector; return trackSelector;
} }
public TrackGroupArray getCurrentTrackGroups() { public TrackGroupArray getCurrentTrackGroups() {
verifyApplicationThread();
return playbackInfo.trackGroups; return playbackInfo.trackGroups;
} }
public TrackSelectionArray getCurrentTrackSelections() { public TrackSelectionArray getCurrentTrackSelections() {
verifyApplicationThread();
return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections);
} }
public TracksInfo getCurrentTracksInfo() { public TracksInfo getCurrentTracksInfo() {
verifyApplicationThread();
return playbackInfo.trackSelectorResult.tracksInfo; return playbackInfo.trackSelectorResult.tracksInfo;
} }
public TrackSelectionParameters getTrackSelectionParameters() { public TrackSelectionParameters getTrackSelectionParameters() {
verifyApplicationThread();
return trackSelector.getParameters(); return trackSelector.getParameters();
} }
public void setTrackSelectionParameters(TrackSelectionParameters parameters) { public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
verifyApplicationThread();
if (!trackSelector.isSetParametersSupported() if (!trackSelector.isSetParametersSupported()
|| parameters.equals(trackSelector.getParameters())) { || parameters.equals(trackSelector.getParameters())) {
return; return;
} }
trackSelector.setParameters(parameters); trackSelector.setParameters(parameters);
listeners.queueEvent( eventListeners.queueEvent(
EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, EVENT_TRACK_SELECTION_PARAMETERS_CHANGED,
listener -> listener.onTrackSelectionParametersChanged(parameters)); listener -> listener.onTrackSelectionParametersChanged(parameters));
} }
public MediaMetadata getMediaMetadata() { public MediaMetadata getMediaMetadata() {
verifyApplicationThread();
return mediaMetadata; return mediaMetadata;
} }
public void onMetadata(Metadata metadata) { private void onMetadata(Metadata metadata) {
staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata =
staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build();
...@@ -993,29 +1222,452 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -993,29 +1222,452 @@ import java.util.concurrent.CopyOnWriteArraySet;
return; return;
} }
mediaMetadata = newMediaMetadata; mediaMetadata = newMediaMetadata;
listeners.sendEvent( eventListeners.sendEvent(
EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata));
} }
public MediaMetadata getPlaylistMetadata() { public MediaMetadata getPlaylistMetadata() {
verifyApplicationThread();
return playlistMetadata; return playlistMetadata;
} }
public void setPlaylistMetadata(MediaMetadata playlistMetadata) { public void setPlaylistMetadata(MediaMetadata playlistMetadata) {
verifyApplicationThread();
checkNotNull(playlistMetadata); checkNotNull(playlistMetadata);
if (playlistMetadata.equals(this.playlistMetadata)) { if (playlistMetadata.equals(this.playlistMetadata)) {
return; return;
} }
this.playlistMetadata = playlistMetadata; this.playlistMetadata = playlistMetadata;
listeners.sendEvent( eventListeners.sendEvent(
EVENT_PLAYLIST_METADATA_CHANGED, EVENT_PLAYLIST_METADATA_CHANGED,
listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata));
} }
public Timeline getCurrentTimeline() { public Timeline getCurrentTimeline() {
verifyApplicationThread();
return playbackInfo.timeline; return playbackInfo.timeline;
} }
public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) {
verifyApplicationThread();
this.videoScalingMode = videoScalingMode;
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
}
@C.VideoScalingMode
public int getVideoScalingMode() {
return videoScalingMode;
}
public void setVideoChangeFrameRateStrategy(
@C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) {
verifyApplicationThread();
if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) {
return;
}
this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy;
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy);
}
@C.VideoChangeFrameRateStrategy
public int getVideoChangeFrameRateStrategy() {
return videoChangeFrameRateStrategy;
}
public VideoSize getVideoSize() {
return videoSize;
}
public void clearVideoSurface() {
verifyApplicationThread();
removeSurfaceCallbacks();
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
public void clearVideoSurface(@Nullable Surface surface) {
verifyApplicationThread();
if (surface != null && surface == videoOutput) {
clearVideoSurface();
}
}
public void setVideoSurface(@Nullable Surface surface) {
verifyApplicationThread();
removeSurfaceCallbacks();
setVideoOutputInternal(surface);
int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET;
maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize);
}
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
verifyApplicationThread();
if (surfaceHolder == null) {
clearVideoSurface();
} else {
removeSurfaceCallbacks();
this.surfaceHolderSurfaceIsVideoOutput = true;
this.surfaceHolder = surfaceHolder;
surfaceHolder.addCallback(componentListener);
Surface surface = surfaceHolder.getSurface();
if (surface != null && surface.isValid()) {
setVideoOutputInternal(surface);
Rect surfaceSize = surfaceHolder.getSurfaceFrame();
maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height());
} else {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
}
}
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
verifyApplicationThread();
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) {
clearVideoSurface();
}
}
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {
verifyApplicationThread();
if (surfaceView instanceof VideoDecoderOutputBufferRenderer) {
removeSurfaceCallbacks();
setVideoOutputInternal(surfaceView);
setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder());
} else if (surfaceView instanceof SphericalGLSurfaceView) {
removeSurfaceCallbacks();
sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView;
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW)
.setPayload(sphericalGLSurfaceView)
.send();
sphericalGLSurfaceView.addVideoSurfaceListener(componentListener);
setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface());
setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder());
} else {
setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
}
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {
verifyApplicationThread();
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
public void setVideoTextureView(@Nullable TextureView textureView) {
verifyApplicationThread();
if (textureView == null) {
clearVideoSurface();
} else {
removeSurfaceCallbacks();
this.textureView = textureView;
if (textureView.getSurfaceTextureListener() != null) {
Log.w(TAG, "Replacing existing SurfaceTextureListener.");
}
textureView.setSurfaceTextureListener(componentListener);
@Nullable
SurfaceTexture surfaceTexture =
textureView.isAvailable() ? textureView.getSurfaceTexture() : null;
if (surfaceTexture == null) {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
} else {
setSurfaceTextureInternal(surfaceTexture);
maybeNotifySurfaceSizeChanged(textureView.getWidth(), textureView.getHeight());
}
}
}
public void clearVideoTextureView(@Nullable TextureView textureView) {
verifyApplicationThread();
if (textureView != null && textureView == this.textureView) {
clearVideoSurface();
}
}
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
verifyApplicationThread();
if (playerReleased) {
return;
}
if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
this.audioAttributes = audioAttributes;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
analyticsCollector.onAudioAttributesChanged(audioAttributes);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onAudioAttributesChanged(audioAttributes);
}
}
audioFocusManager.setAudioAttributes(handleAudioFocus ? audioAttributes : null);
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
public AudioAttributes getAudioAttributes() {
return audioAttributes;
}
public void setAudioSessionId(int audioSessionId) {
verifyApplicationThread();
if (this.audioSessionId == audioSessionId) {
return;
}
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
if (Util.SDK_INT < 21) {
audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET);
} else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
} else if (Util.SDK_INT < 21) {
// We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for
// as long as the player is using it.
initializeKeepSessionIdAudioTrack(audioSessionId);
}
this.audioSessionId = audioSessionId;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
analyticsCollector.onAudioSessionIdChanged(audioSessionId);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onAudioSessionIdChanged(audioSessionId);
}
}
public int getAudioSessionId() {
return audioSessionId;
}
public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) {
verifyApplicationThread();
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo);
}
public void clearAuxEffectInfo() {
setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f));
}
public void setVolume(float volume) {
verifyApplicationThread();
volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1);
if (this.volume == volume) {
return;
}
this.volume = volume;
sendVolumeToRenderers();
analyticsCollector.onVolumeChanged(volume);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onVolumeChanged(volume);
}
}
public float getVolume() {
return volume;
}
public boolean getSkipSilenceEnabled() {
return skipSilenceEnabled;
}
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
verifyApplicationThread();
if (this.skipSilenceEnabled == skipSilenceEnabled) {
return;
}
this.skipSilenceEnabled = skipSilenceEnabled;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled);
notifySkipSilenceEnabledChanged();
}
public AnalyticsCollector getAnalyticsCollector() {
return analyticsCollector;
}
public void addAnalyticsListener(AnalyticsListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
checkNotNull(listener);
analyticsCollector.addListener(listener);
}
public void removeAnalyticsListener(AnalyticsListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
analyticsCollector.removeListener(listener);
}
public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) {
verifyApplicationThread();
if (playerReleased) {
return;
}
audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy);
}
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread();
if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) {
return;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
}
if (priorityTaskManager != null && isLoading()) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = true;
} else {
isPriorityTaskManagerRegistered = false;
}
this.priorityTaskManager = priorityTaskManager;
}
@Nullable
public Format getVideoFormat() {
return videoFormat;
}
@Nullable
public Format getAudioFormat() {
return audioFormat;
}
@Nullable
public DecoderCounters getVideoDecoderCounters() {
return videoDecoderCounters;
}
@Nullable
public DecoderCounters getAudioDecoderCounters() {
return audioDecoderCounters;
}
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
verifyApplicationThread();
videoFrameMetadataListener = listener;
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(listener)
.send();
}
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
verifyApplicationThread();
if (videoFrameMetadataListener != listener) {
return;
}
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(null)
.send();
}
public void setCameraMotionListener(CameraMotionListener listener) {
verifyApplicationThread();
cameraMotionListener = listener;
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER)
.setPayload(listener)
.send();
}
public void clearCameraMotionListener(CameraMotionListener listener) {
verifyApplicationThread();
if (cameraMotionListener != listener) {
return;
}
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER)
.setPayload(null)
.send();
}
public List<Cue> getCurrentCues() {
verifyApplicationThread();
return currentCues;
}
public void addListener(Listener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
checkNotNull(listener);
listeners.add(listener);
addEventListener(listener);
}
public void removeListener(Listener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
checkNotNull(listener);
listeners.remove(listener);
removeEventListener(listener);
}
public void setHandleWakeLock(boolean handleWakeLock) {
setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE);
}
public void setWakeMode(@C.WakeMode int wakeMode) {
verifyApplicationThread();
switch (wakeMode) {
case C.WAKE_MODE_NONE:
wakeLockManager.setEnabled(false);
wifiLockManager.setEnabled(false);
break;
case C.WAKE_MODE_LOCAL:
wakeLockManager.setEnabled(true);
wifiLockManager.setEnabled(false);
break;
case C.WAKE_MODE_NETWORK:
wakeLockManager.setEnabled(true);
wifiLockManager.setEnabled(true);
break;
default:
break;
}
}
public DeviceInfo getDeviceInfo() {
verifyApplicationThread();
return deviceInfo;
}
public int getDeviceVolume() {
verifyApplicationThread();
return streamVolumeManager.getVolume();
}
public boolean isDeviceMuted() {
verifyApplicationThread();
return streamVolumeManager.isMuted();
}
public void setDeviceVolume(int volume) {
verifyApplicationThread();
streamVolumeManager.setVolume(volume);
}
public void increaseDeviceVolume() {
verifyApplicationThread();
streamVolumeManager.increaseVolume();
}
public void decreaseDeviceVolume() {
verifyApplicationThread();
streamVolumeManager.decreaseVolume();
}
public void setDeviceMuted(boolean muted) {
verifyApplicationThread();
streamVolumeManager.setMuted(muted);
}
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
}
private int getCurrentWindowIndexInternal() { private int getCurrentWindowIndexInternal() {
if (playbackInfo.timeline.isEmpty()) { if (playbackInfo.timeline.isEmpty()) {
return maskingWindowIndex; return maskingWindowIndex;
...@@ -1151,7 +1803,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1151,7 +1803,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
mediaMetadata = newMediaMetadata; mediaMetadata = newMediaMetadata;
if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_TIMELINE_CHANGED, Player.EVENT_TIMELINE_CHANGED,
listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason));
} }
...@@ -1160,7 +1812,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1160,7 +1812,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
getPreviousPositionInfo( getPreviousPositionInfo(
positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex);
PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs);
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_POSITION_DISCONTINUITY, Player.EVENT_POSITION_DISCONTINUITY,
listener -> { listener -> {
listener.onPositionDiscontinuity(positionDiscontinuityReason); listener.onPositionDiscontinuity(positionDiscontinuityReason);
...@@ -1170,16 +1822,16 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1170,16 +1822,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
if (mediaItemTransitioned) { if (mediaItemTransitioned) {
@Nullable final MediaItem finalMediaItem = mediaItem; @Nullable final MediaItem finalMediaItem = mediaItem;
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION, Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason));
} }
if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) { if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAYER_ERROR, Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError)); listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError));
if (newPlaybackInfo.playbackError != null) { if (newPlaybackInfo.playbackError != null) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAYER_ERROR, Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerError(newPlaybackInfo.playbackError)); listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
} }
...@@ -1188,21 +1840,21 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1188,21 +1840,21 @@ import java.util.concurrent.CopyOnWriteArraySet;
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection = TrackSelectionArray newSelection =
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections); new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection)); listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo)); listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo));
} }
if (metadataChanged) { if (metadataChanged) {
final MediaMetadata finalMediaMetadata = mediaMetadata; final MediaMetadata finalMediaMetadata = mediaMetadata;
listeners.queueEvent( eventListeners.queueEvent(
EVENT_MEDIA_METADATA_CHANGED, EVENT_MEDIA_METADATA_CHANGED,
listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); listener -> listener.onMediaMetadataChanged(finalMediaMetadata));
} }
if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) { if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_IS_LOADING_CHANGED, Player.EVENT_IS_LOADING_CHANGED,
listener -> { listener -> {
listener.onLoadingChanged(newPlaybackInfo.isLoading); listener.onLoadingChanged(newPlaybackInfo.isLoading);
...@@ -1211,19 +1863,19 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1211,19 +1863,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState
|| previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { || previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) {
listeners.queueEvent( eventListeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET, /* eventFlag= */ C.INDEX_UNSET,
listener -> listener ->
listener.onPlayerStateChanged( listener.onPlayerStateChanged(
newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState)); newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState));
} }
if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState) { if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED,
listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState));
} }
if (previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { if (previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAY_WHEN_READY_CHANGED,
listener -> listener ->
listener.onPlayWhenReadyChanged( listener.onPlayWhenReadyChanged(
...@@ -1231,27 +1883,27 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1231,27 +1883,27 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
if (previousPlaybackInfo.playbackSuppressionReason if (previousPlaybackInfo.playbackSuppressionReason
!= newPlaybackInfo.playbackSuppressionReason) { != newPlaybackInfo.playbackSuppressionReason) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED,
listener -> listener ->
listener.onPlaybackSuppressionReasonChanged( listener.onPlaybackSuppressionReasonChanged(
newPlaybackInfo.playbackSuppressionReason)); newPlaybackInfo.playbackSuppressionReason));
} }
if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) { if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_IS_PLAYING_CHANGED, Player.EVENT_IS_PLAYING_CHANGED,
listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo))); listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
} }
if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) { if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, Player.EVENT_PLAYBACK_PARAMETERS_CHANGED,
listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters)); listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters));
} }
if (seekProcessed) { if (seekProcessed) {
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); eventListeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed);
} }
updateAvailableCommands(); updateAvailableCommands();
listeners.flushEvents(); eventListeners.flushEvents();
if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) { if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) {
for (AudioOffloadListener listener : audioOffloadListeners) { for (AudioOffloadListener listener : audioOffloadListeners) {
...@@ -1408,7 +2060,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1408,7 +2060,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
Commands previousAvailableCommands = availableCommands; Commands previousAvailableCommands = availableCommands;
availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands); availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands);
if (!availableCommands.equals(previousAvailableCommands)) { if (!availableCommands.equals(previousAvailableCommands)) {
listeners.queueEvent( eventListeners.queueEvent(
Player.EVENT_AVAILABLE_COMMANDS_CHANGED, Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
listener -> listener.onAvailableCommandsChanged(availableCommands)); listener -> listener.onAvailableCommandsChanged(availableCommands));
} }
...@@ -1699,6 +2351,17 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1699,6 +2351,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
return positionUs; return positionUs;
} }
private PlayerMessage createMessageInternal(Target target) {
int currentWindowIndex = getCurrentWindowIndexInternal();
return new PlayerMessage(
internalPlayer,
target,
playbackInfo.timeline,
currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex,
clock,
internalPlayer.getPlaybackLooper());
}
/** /**
* Builds a {@link MediaMetadata} from the main sources. * Builds a {@link MediaMetadata} from the main sources.
* *
...@@ -1716,6 +2379,235 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1716,6 +2379,235 @@ import java.util.concurrent.CopyOnWriteArraySet;
return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build(); return staticAndDynamicMediaMetadata.buildUpon().populate(mediaItem.mediaMetadata).build();
} }
private void removeSurfaceCallbacks() {
if (sphericalGLSurfaceView != null) {
createMessageInternal(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW)
.setPayload(null)
.send();
sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener);
sphericalGLSurfaceView = null;
}
if (textureView != null) {
if (textureView.getSurfaceTextureListener() != componentListener) {
Log.w(TAG, "SurfaceTextureListener already unset or replaced.");
} else {
textureView.setSurfaceTextureListener(null);
}
textureView = null;
}
if (surfaceHolder != null) {
surfaceHolder.removeCallback(componentListener);
surfaceHolder = null;
}
}
private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) {
Surface surface = new Surface(surfaceTexture);
setVideoOutputInternal(surface);
ownedSurface = surface;
}
private void setVideoOutputInternal(@Nullable Object videoOutput) {
// Note: We don't turn this method into a no-op if the output is being replaced with itself so
// as to ensure onRenderedFirstFrame callbacks are still called in this case.
List<PlayerMessage> messages = new ArrayList<>();
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == TRACK_TYPE_VIDEO) {
messages.add(
createMessageInternal(renderer)
.setType(MSG_SET_VIDEO_OUTPUT)
.setPayload(videoOutput)
.send());
}
}
boolean messageDeliveryTimedOut = false;
if (this.videoOutput != null && this.videoOutput != videoOutput) {
// We're replacing an output. Block to ensure that this output will not be accessed by the
// renderers after this method returns.
try {
for (PlayerMessage message : messages) {
message.blockUntilDelivered(detachSurfaceTimeoutMs);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
messageDeliveryTimedOut = true;
}
if (this.videoOutput == ownedSurface) {
// We're replacing a surface that we are responsible for releasing.
ownedSurface.release();
ownedSurface = null;
}
}
this.videoOutput = videoOutput;
if (messageDeliveryTimedOut) {
stop(
/* reset= */ false,
ExoPlaybackException.createForUnexpected(
new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE),
PlaybackException.ERROR_CODE_TIMEOUT));
}
}
/**
* Sets the holder of the surface that will be displayed to the user, but which should
* <em>not</em> be the output for video renderers. This case occurs when video frames need to be
* rendered to an intermediate surface (which is not the one held by the provided holder).
*
* @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed
* to the user.
*/
private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) {
// Although we won't use the view's surface directly as the video output, still use the holder
// to query the surface size, to be informed in changes to the size via componentListener, and
// for equality checking in clearVideoSurfaceHolder.
surfaceHolderSurfaceIsVideoOutput = false;
surfaceHolder = nonVideoOutputSurfaceHolder;
surfaceHolder.addCallback(componentListener);
Surface surface = surfaceHolder.getSurface();
if (surface != null && surface.isValid()) {
Rect surfaceSize = surfaceHolder.getSurfaceFrame();
maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height());
} else {
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
}
private void maybeNotifySurfaceSizeChanged(int width, int height) {
if (width != surfaceWidth || height != surfaceHeight) {
surfaceWidth = width;
surfaceHeight = height;
analyticsCollector.onSurfaceSizeChanged(width, height);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onSurfaceSizeChanged(width, height);
}
}
}
private void sendVolumeToRenderers() {
float scaledVolume = volume * audioFocusManager.getVolumeMultiplier();
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume);
}
private void notifySkipSilenceEnabledChanged() {
analyticsCollector.onSkipSilenceEnabledChanged(skipSilenceEnabled);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onSkipSilenceEnabledChanged(skipSilenceEnabled);
}
}
private void updatePlayWhenReady(
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE;
setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
}
private void updateWakeAndWifiLock() {
@State int playbackState = getPlaybackState();
switch (playbackState) {
case Player.STATE_READY:
case Player.STATE_BUFFERING:
boolean isSleeping = experimentalIsSleepingForOffload();
wakeLockManager.setStayAwake(getPlayWhenReady() && !isSleeping);
// The wifi lock is not released while sleeping to avoid interrupting downloads.
wifiLockManager.setStayAwake(getPlayWhenReady());
break;
case Player.STATE_ENDED:
case Player.STATE_IDLE:
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
break;
default:
throw new IllegalStateException();
}
}
private void verifyApplicationThread() {
// The constructor may be executed on a background thread. Wait with accessing the player from
// the app thread until the constructor finished executing.
constructorFinished.blockUninterruptible();
if (Thread.currentThread() != getApplicationLooper().getThread()) {
String message =
Util.formatInvariant(
"Player is accessed on the wrong thread.\n"
+ "Current thread: '%s'\n"
+ "Expected thread: '%s'\n"
+ "See https://exoplayer.dev/issues/player-accessed-on-wrong-thread",
Thread.currentThread().getName(), getApplicationLooper().getThread().getName());
if (throwsWhenUsingWrongThread) {
throw new IllegalStateException(message);
}
Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException());
hasNotifiedFullWrongThreadWarning = true;
}
}
private void sendRendererMessage(
@C.TrackType int trackType, int messageType, @Nullable Object payload) {
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == trackType) {
createMessageInternal(renderer).setType(messageType).setPayload(payload).send();
}
}
}
/**
* Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio
* session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated.
*
* <p>Use of this method is only required on API level 21 and earlier.
*
* @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} to generate a
* new one.
* @return The audio session ID.
*/
private int initializeKeepSessionIdAudioTrack(int audioSessionId) {
if (keepSessionIdAudioTrack != null
&& keepSessionIdAudioTrack.getAudioSessionId() != audioSessionId) {
keepSessionIdAudioTrack.release();
keepSessionIdAudioTrack = null;
}
if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Minimum sample rate supported by the platform.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack =
new AudioTrack(
C.STREAM_TYPE_DEFAULT,
sampleRate,
channelConfig,
encoding,
bufferSize,
AudioTrack.MODE_STATIC,
audioSessionId);
}
return keepSessionIdAudioTrack.getAudioSessionId();
}
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
return new DeviceInfo(
DeviceInfo.PLAYBACK_TYPE_LOCAL,
streamVolumeManager.getMinVolume(),
streamVolumeManager.getMaxVolume());
}
private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) {
return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
}
private static boolean isPlaying(PlaybackInfo playbackInfo) { private static boolean isPlaying(PlaybackInfo playbackInfo) {
return playbackInfo.playbackState == Player.STATE_READY return playbackInfo.playbackState == Player.STATE_READY
&& playbackInfo.playWhenReady && playbackInfo.playWhenReady
...@@ -1744,6 +2636,410 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -1744,6 +2636,410 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
} }
// TODO(b/204189802): Remove self-listening to deprecated EventListener.
@SuppressWarnings("deprecation")
private final class ComponentListener
implements VideoRendererEventListener,
AudioRendererEventListener,
TextOutput,
MetadataOutput,
SurfaceHolder.Callback,
TextureView.SurfaceTextureListener,
SphericalGLSurfaceView.VideoSurfaceListener,
AudioFocusManager.PlayerControl,
AudioBecomingNoisyManager.EventListener,
StreamVolumeManager.Listener,
Player.EventListener,
AudioOffloadListener {
// VideoRendererEventListener implementation
@Override
public void onVideoEnabled(DecoderCounters counters) {
videoDecoderCounters = counters;
analyticsCollector.onVideoEnabled(counters);
}
@Override
public void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
analyticsCollector.onVideoDecoderInitialized(
decoderName, initializedTimestampMs, initializationDurationMs);
}
@Override
public void onVideoInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
videoFormat = format;
analyticsCollector.onVideoInputFormatChanged(format, decoderReuseEvaluation);
}
@Override
public void onDroppedFrames(int count, long elapsed) {
analyticsCollector.onDroppedFrames(count, elapsed);
}
@Override
public void onVideoSizeChanged(VideoSize videoSize) {
ExoPlayerImpl.this.videoSize = videoSize;
analyticsCollector.onVideoSizeChanged(videoSize);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onVideoSizeChanged(videoSize);
}
}
@Override
public void onRenderedFirstFrame(Object output, long renderTimeMs) {
analyticsCollector.onRenderedFirstFrame(output, renderTimeMs);
if (videoOutput == output) {
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onRenderedFirstFrame();
}
}
}
@Override
public void onVideoDecoderReleased(String decoderName) {
analyticsCollector.onVideoDecoderReleased(decoderName);
}
@Override
public void onVideoDisabled(DecoderCounters counters) {
analyticsCollector.onVideoDisabled(counters);
videoFormat = null;
videoDecoderCounters = null;
}
@Override
public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
}
@Override
public void onVideoCodecError(Exception videoCodecError) {
analyticsCollector.onVideoCodecError(videoCodecError);
}
// AudioRendererEventListener implementation
@Override
public void onAudioEnabled(DecoderCounters counters) {
audioDecoderCounters = counters;
analyticsCollector.onAudioEnabled(counters);
}
@Override
public void onAudioDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
analyticsCollector.onAudioDecoderInitialized(
decoderName, initializedTimestampMs, initializationDurationMs);
}
@Override
public void onAudioInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
audioFormat = format;
analyticsCollector.onAudioInputFormatChanged(format, decoderReuseEvaluation);
}
@Override
public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
analyticsCollector.onAudioPositionAdvancing(playoutStartSystemTimeMs);
}
@Override
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
analyticsCollector.onAudioUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
@Override
public void onAudioDecoderReleased(String decoderName) {
analyticsCollector.onAudioDecoderReleased(decoderName);
}
@Override
public void onAudioDisabled(DecoderCounters counters) {
analyticsCollector.onAudioDisabled(counters);
audioFormat = null;
audioDecoderCounters = null;
}
@Override
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
if (ExoPlayerImpl.this.skipSilenceEnabled == skipSilenceEnabled) {
return;
}
ExoPlayerImpl.this.skipSilenceEnabled = skipSilenceEnabled;
notifySkipSilenceEnabledChanged();
}
@Override
public void onAudioSinkError(Exception audioSinkError) {
analyticsCollector.onAudioSinkError(audioSinkError);
}
@Override
public void onAudioCodecError(Exception audioCodecError) {
analyticsCollector.onAudioCodecError(audioCodecError);
}
// TextOutput implementation
@Override
public void onCues(List<Cue> cues) {
currentCues = cues;
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listeners : listeners) {
listeners.onCues(cues);
}
}
// MetadataOutput implementation
@Override
public void onMetadata(Metadata metadata) {
analyticsCollector.onMetadata(metadata);
ExoPlayerImpl.this.onMetadata(metadata);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onMetadata(metadata);
}
}
// SurfaceHolder.Callback implementation
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (surfaceHolderSurfaceIsVideoOutput) {
setVideoOutputInternal(holder.getSurface());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (surfaceHolderSurfaceIsVideoOutput) {
setVideoOutputInternal(/* videoOutput= */ null);
}
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
// TextureView.SurfaceTextureListener implementation
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
setSurfaceTextureInternal(surfaceTexture);
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
// Do nothing.
}
// SphericalGLSurfaceView.VideoSurfaceListener
@Override
public void onVideoSurfaceCreated(Surface surface) {
setVideoOutputInternal(surface);
}
@Override
public void onVideoSurfaceDestroyed(Surface surface) {
setVideoOutputInternal(/* videoOutput= */ null);
}
// AudioFocusManager.PlayerControl implementation
@Override
public void setVolumeMultiplier(float volumeMultiplier) {
sendVolumeToRenderers();
}
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
// AudioBecomingNoisyManager.EventListener implementation.
@Override
public void onAudioBecomingNoisy() {
updatePlayWhenReady(
/* playWhenReady= */ false,
AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
}
// StreamVolumeManager.Listener implementation.
@Override
public void onStreamTypeChanged(@C.StreamType int streamType) {
DeviceInfo deviceInfo = createDeviceInfo(streamVolumeManager);
if (!deviceInfo.equals(ExoPlayerImpl.this.deviceInfo)) {
ExoPlayerImpl.this.deviceInfo = deviceInfo;
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onDeviceInfoChanged(deviceInfo);
}
}
}
@Override
public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) {
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onDeviceVolumeChanged(streamVolume, streamMuted);
}
}
// Player.EventListener implementation.
@Override
public void onIsLoadingChanged(boolean isLoading) {
if (priorityTaskManager != null) {
if (isLoading && !isPriorityTaskManagerRegistered) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = true;
} else if (!isLoading && isPriorityTaskManagerRegistered) {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false;
}
}
}
@Override
public void onPlaybackStateChanged(@State int playbackState) {
updateWakeAndWifiLock();
}
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
updateWakeAndWifiLock();
}
// Player.AudioOffloadListener implementation.
@Override
public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {
updateWakeAndWifiLock();
}
}
/** Listeners that are called on the playback thread. */
private static final class FrameMetadataListener
implements VideoFrameMetadataListener, CameraMotionListener, PlayerMessage.Target {
@MessageType
public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER =
Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
@MessageType
public static final int MSG_SET_CAMERA_MOTION_LISTENER =
Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
@MessageType public static final int MSG_SET_SPHERICAL_SURFACE_VIEW = Renderer.MSG_CUSTOM_BASE;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
@Nullable private VideoFrameMetadataListener internalVideoFrameMetadataListener;
@Nullable private CameraMotionListener internalCameraMotionListener;
@Override
public void handleMessage(@MessageType int messageType, @Nullable Object message) {
switch (messageType) {
case MSG_SET_VIDEO_FRAME_METADATA_LISTENER:
videoFrameMetadataListener = (VideoFrameMetadataListener) message;
break;
case MSG_SET_CAMERA_MOTION_LISTENER:
cameraMotionListener = (CameraMotionListener) message;
break;
case MSG_SET_SPHERICAL_SURFACE_VIEW:
@Nullable SphericalGLSurfaceView surfaceView = (SphericalGLSurfaceView) message;
if (surfaceView == null) {
internalVideoFrameMetadataListener = null;
internalCameraMotionListener = null;
} else {
internalVideoFrameMetadataListener = surfaceView.getVideoFrameMetadataListener();
internalCameraMotionListener = surfaceView.getCameraMotionListener();
}
break;
case Renderer.MSG_SET_AUDIO_ATTRIBUTES:
case Renderer.MSG_SET_AUDIO_SESSION_ID:
case Renderer.MSG_SET_AUX_EFFECT_INFO:
case Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY:
case Renderer.MSG_SET_SCALING_MODE:
case Renderer.MSG_SET_SKIP_SILENCE_ENABLED:
case Renderer.MSG_SET_VIDEO_OUTPUT:
case Renderer.MSG_SET_VOLUME:
case Renderer.MSG_SET_WAKEUP_LISTENER:
default:
break;
}
}
// VideoFrameMetadataListener
@Override
public void onVideoFrameAboutToBeRendered(
long presentationTimeUs,
long releaseTimeNs,
Format format,
@Nullable MediaFormat mediaFormat) {
if (internalVideoFrameMetadataListener != null) {
internalVideoFrameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, releaseTimeNs, format, mediaFormat);
}
if (videoFrameMetadataListener != null) {
videoFrameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, releaseTimeNs, format, mediaFormat);
}
}
// CameraMotionListener
@Override
public void onCameraMotion(long timeUs, float[] rotation) {
if (internalCameraMotionListener != null) {
internalCameraMotionListener.onCameraMotion(timeUs, rotation);
}
if (cameraMotionListener != null) {
cameraMotionListener.onCameraMotion(timeUs, rotation);
}
}
@Override
public void onCameraMotionReset() {
if (internalCameraMotionListener != null) {
internalCameraMotionListener.onCameraMotionReset();
}
if (cameraMotionListener != null) {
cameraMotionListener.onCameraMotionReset();
}
}
}
@RequiresApi(31) @RequiresApi(31)
private static final class Api31 { private static final class Api31 {
private Api31() {} private Api31() {}
......
...@@ -15,28 +15,7 @@ ...@@ -15,28 +15,7 @@
*/ */
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO;
import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION;
import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO;
import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY;
import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE;
import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT;
import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.content.Context; import android.content.Context;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
...@@ -45,43 +24,27 @@ import android.view.TextureView; ...@@ -45,43 +24,27 @@ import android.view.TextureView;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.Renderer.MessageType;
import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.PriorityTaskManager;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeoutException;
/** @deprecated Use {@link ExoPlayer} instead. */ /** @deprecated Use {@link ExoPlayer} instead. */
@Deprecated @Deprecated
...@@ -345,52 +308,7 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -345,52 +308,7 @@ public class SimpleExoPlayer extends BasePlayer
} }
} }
private static final String TAG = "SimpleExoPlayer";
private final Renderer[] renderers;
private final ConditionVariable constructorFinished;
private final Context applicationContext;
private final ExoPlayerImpl player; private final ExoPlayerImpl player;
private final ComponentListener componentListener;
private final FrameMetadataListener frameMetadataListener;
private final CopyOnWriteArraySet<Listener> listeners;
private final AnalyticsCollector analyticsCollector;
private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager;
private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
private final long detachSurfaceTimeoutMs;
@Nullable private Format videoFormat;
@Nullable private Format audioFormat;
@Nullable private AudioTrack keepSessionIdAudioTrack;
@Nullable private Object videoOutput;
@Nullable private Surface ownedSurface;
@Nullable private SurfaceHolder surfaceHolder;
@Nullable private SphericalGLSurfaceView sphericalGLSurfaceView;
private boolean surfaceHolderSurfaceIsVideoOutput;
@Nullable private TextureView textureView;
@C.VideoScalingMode private int videoScalingMode;
@C.VideoChangeFrameRateStrategy private int videoChangeFrameRateStrategy;
private int surfaceWidth;
private int surfaceHeight;
@Nullable private DecoderCounters videoDecoderCounters;
@Nullable private DecoderCounters audioDecoderCounters;
private int audioSessionId;
private AudioAttributes audioAttributes;
private float volume;
private boolean skipSilenceEnabled;
private List<Cue> currentCues;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
private boolean throwsWhenUsingWrongThread;
private boolean hasNotifiedFullWrongThreadWarning;
@Nullable private PriorityTaskManager priorityTaskManager;
private boolean isPriorityTaskManagerRegistered;
private boolean playerReleased;
private DeviceInfo deviceInfo;
private VideoSize videoSize;
/** @deprecated Use the {@link ExoPlayer.Builder}. */ /** @deprecated Use the {@link ExoPlayer.Builder}. */
@Deprecated @Deprecated
...@@ -426,119 +344,16 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -426,119 +344,16 @@ public class SimpleExoPlayer extends BasePlayer
/** @param builder The {@link ExoPlayer.Builder} to obtain all construction parameters. */ /** @param builder The {@link ExoPlayer.Builder} to obtain all construction parameters. */
/* package */ SimpleExoPlayer(ExoPlayer.Builder builder) { /* package */ SimpleExoPlayer(ExoPlayer.Builder builder) {
constructorFinished = new ConditionVariable(); player = new ExoPlayerImpl(builder, /* wrappingPlayer= */ this);
try {
applicationContext = builder.context.getApplicationContext();
analyticsCollector = builder.analyticsCollectorSupplier.get();
priorityTaskManager = builder.priorityTaskManager;
audioAttributes = builder.audioAttributes;
videoScalingMode = builder.videoScalingMode;
videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy;
skipSilenceEnabled = builder.skipSilenceEnabled;
detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs;
componentListener = new ComponentListener();
frameMetadataListener = new FrameMetadataListener();
listeners = new CopyOnWriteArraySet<>();
Handler eventHandler = new Handler(builder.looper);
renderers =
builder
.renderersFactorySupplier
.get()
.createRenderers(
eventHandler,
componentListener,
componentListener,
componentListener,
componentListener);
// Set initial values.
volume = 1;
if (Util.SDK_INT < 21) {
audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET);
} else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
currentCues = Collections.emptyList();
throwsWhenUsingWrongThread = true;
// Build the player and associated objects.
Commands additionalPermanentAvailableCommands =
new Commands.Builder()
.addAll(
COMMAND_GET_AUDIO_ATTRIBUTES,
COMMAND_GET_VOLUME,
COMMAND_GET_DEVICE_VOLUME,
COMMAND_SET_VOLUME,
COMMAND_SET_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_SET_VIDEO_SURFACE,
COMMAND_GET_TEXT)
.build();
player =
new ExoPlayerImpl(
renderers,
builder.trackSelectorSupplier.get(),
builder.mediaSourceFactorySupplier.get(),
builder.loadControlSupplier.get(),
builder.bandwidthMeterSupplier.get(),
analyticsCollector,
builder.useLazyPreparation,
builder.seekParameters,
builder.seekBackIncrementMs,
builder.seekForwardIncrementMs,
builder.livePlaybackSpeedControl,
builder.releaseTimeoutMs,
builder.pauseAtEndOfMediaItems,
builder.clock,
builder.looper,
/* wrappingPlayer= */ this,
additionalPermanentAvailableCommands);
player.addEventListener(componentListener);
player.addAudioOffloadListener(componentListener);
if (builder.foregroundModeTimeoutMs > 0) {
player.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
}
audioBecomingNoisyManager =
new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener);
audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy);
audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener);
audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null);
streamVolumeManager =
new StreamVolumeManager(builder.context, eventHandler, componentListener);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
wakeLockManager = new WakeLockManager(builder.context);
wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE);
wifiLockManager = new WifiLockManager(builder.context);
wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK);
deviceInfo = createDeviceInfo(streamVolumeManager);
videoSize = VideoSize.UNKNOWN;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled);
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener);
sendRendererMessage(
TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener);
} finally {
constructorFinished.open();
}
} }
@Override @Override
public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) {
verifyApplicationThread();
player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled);
} }
@Override @Override
public boolean experimentalIsSleepingForOffload() { public boolean experimentalIsSleepingForOffload() {
verifyApplicationThread();
return player.experimentalIsSleepingForOffload(); return player.experimentalIsSleepingForOffload();
} }
...@@ -568,400 +383,209 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -568,400 +383,209 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) {
verifyApplicationThread(); player.setVideoScalingMode(videoScalingMode);
this.videoScalingMode = videoScalingMode;
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
} }
@Override @Override
@C.VideoScalingMode @C.VideoScalingMode
public int getVideoScalingMode() { public int getVideoScalingMode() {
return videoScalingMode; return player.getVideoScalingMode();
} }
@Override @Override
public void setVideoChangeFrameRateStrategy( public void setVideoChangeFrameRateStrategy(
@C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) {
verifyApplicationThread(); player.setVideoChangeFrameRateStrategy(videoChangeFrameRateStrategy);
if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) {
return;
}
this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy;
sendRendererMessage(
TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy);
} }
@Override @Override
@C.VideoChangeFrameRateStrategy @C.VideoChangeFrameRateStrategy
public int getVideoChangeFrameRateStrategy() { public int getVideoChangeFrameRateStrategy() {
return videoChangeFrameRateStrategy; return player.getVideoChangeFrameRateStrategy();
} }
@Override @Override
public VideoSize getVideoSize() { public VideoSize getVideoSize() {
return videoSize; return player.getVideoSize();
} }
@Override @Override
public void clearVideoSurface() { public void clearVideoSurface() {
verifyApplicationThread(); player.clearVideoSurface();
removeSurfaceCallbacks();
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
} }
@Override @Override
public void clearVideoSurface(@Nullable Surface surface) { public void clearVideoSurface(@Nullable Surface surface) {
verifyApplicationThread(); player.clearVideoSurface(surface);
if (surface != null && surface == videoOutput) {
clearVideoSurface();
}
} }
@Override @Override
public void setVideoSurface(@Nullable Surface surface) { public void setVideoSurface(@Nullable Surface surface) {
verifyApplicationThread(); player.setVideoSurface(surface);
removeSurfaceCallbacks();
setVideoOutputInternal(surface);
int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET;
maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize);
} }
@Override @Override
public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
verifyApplicationThread(); player.setVideoSurfaceHolder(surfaceHolder);
if (surfaceHolder == null) {
clearVideoSurface();
} else {
removeSurfaceCallbacks();
this.surfaceHolderSurfaceIsVideoOutput = true;
this.surfaceHolder = surfaceHolder;
surfaceHolder.addCallback(componentListener);
Surface surface = surfaceHolder.getSurface();
if (surface != null && surface.isValid()) {
setVideoOutputInternal(surface);
Rect surfaceSize = surfaceHolder.getSurfaceFrame();
maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height());
} else {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
}
} }
@Override @Override
public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) {
verifyApplicationThread(); player.clearVideoSurfaceHolder(surfaceHolder);
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) {
clearVideoSurface();
}
} }
@Override @Override
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {
verifyApplicationThread(); player.setVideoSurfaceView(surfaceView);
if (surfaceView instanceof VideoDecoderOutputBufferRenderer) {
removeSurfaceCallbacks();
setVideoOutputInternal(surfaceView);
setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder());
} else if (surfaceView instanceof SphericalGLSurfaceView) {
removeSurfaceCallbacks();
sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView;
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW)
.setPayload(sphericalGLSurfaceView)
.send();
sphericalGLSurfaceView.addVideoSurfaceListener(componentListener);
setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface());
setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder());
} else {
setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
} }
@Override @Override
public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) {
verifyApplicationThread(); player.clearVideoSurfaceView(surfaceView);
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
} }
@Override @Override
public void setVideoTextureView(@Nullable TextureView textureView) { public void setVideoTextureView(@Nullable TextureView textureView) {
verifyApplicationThread(); player.setVideoTextureView(textureView);
if (textureView == null) {
clearVideoSurface();
} else {
removeSurfaceCallbacks();
this.textureView = textureView;
if (textureView.getSurfaceTextureListener() != null) {
Log.w(TAG, "Replacing existing SurfaceTextureListener.");
}
textureView.setSurfaceTextureListener(componentListener);
@Nullable
SurfaceTexture surfaceTexture =
textureView.isAvailable() ? textureView.getSurfaceTexture() : null;
if (surfaceTexture == null) {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
} else {
setSurfaceTextureInternal(surfaceTexture);
maybeNotifySurfaceSizeChanged(textureView.getWidth(), textureView.getHeight());
}
}
} }
@Override @Override
public void clearVideoTextureView(@Nullable TextureView textureView) { public void clearVideoTextureView(@Nullable TextureView textureView) {
verifyApplicationThread(); player.clearVideoTextureView(textureView);
if (textureView != null && textureView == this.textureView) {
clearVideoSurface();
}
} }
@Override @Override
public void addAudioOffloadListener(AudioOffloadListener listener) { public void addAudioOffloadListener(AudioOffloadListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
player.addAudioOffloadListener(listener); player.addAudioOffloadListener(listener);
} }
@Override @Override
public void removeAudioOffloadListener(AudioOffloadListener listener) { public void removeAudioOffloadListener(AudioOffloadListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
player.removeAudioOffloadListener(listener); player.removeAudioOffloadListener(listener);
} }
@Override @Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) { public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
verifyApplicationThread(); player.setAudioAttributes(audioAttributes, handleAudioFocus);
if (playerReleased) {
return;
}
if (!Util.areEqual(this.audioAttributes, audioAttributes)) {
this.audioAttributes = audioAttributes;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage));
analyticsCollector.onAudioAttributesChanged(audioAttributes);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onAudioAttributesChanged(audioAttributes);
}
}
audioFocusManager.setAudioAttributes(handleAudioFocus ? audioAttributes : null);
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
public AudioAttributes getAudioAttributes() { public AudioAttributes getAudioAttributes() {
return audioAttributes; return player.getAudioAttributes();
} }
@Override @Override
public void setAudioSessionId(int audioSessionId) { public void setAudioSessionId(int audioSessionId) {
verifyApplicationThread(); player.setAudioSessionId(audioSessionId);
if (this.audioSessionId == audioSessionId) {
return;
}
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
if (Util.SDK_INT < 21) {
audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET);
} else {
audioSessionId = Util.generateAudioSessionIdV21(applicationContext);
}
} else if (Util.SDK_INT < 21) {
// We need to re-initialize keepSessionIdAudioTrack to make sure the session is kept alive for
// as long as the player is using it.
initializeKeepSessionIdAudioTrack(audioSessionId);
}
this.audioSessionId = audioSessionId;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
analyticsCollector.onAudioSessionIdChanged(audioSessionId);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onAudioSessionIdChanged(audioSessionId);
}
} }
@Override @Override
public int getAudioSessionId() { public int getAudioSessionId() {
return audioSessionId; return player.getAudioSessionId();
} }
@Override @Override
public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) {
verifyApplicationThread(); player.setAuxEffectInfo(auxEffectInfo);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo);
} }
@Override @Override
public void clearAuxEffectInfo() { public void clearAuxEffectInfo() {
setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); player.clearAuxEffectInfo();
} }
@Override @Override
public void setVolume(float volume) { public void setVolume(float volume) {
verifyApplicationThread(); player.setVolume(volume);
volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1);
if (this.volume == volume) {
return;
}
this.volume = volume;
sendVolumeToRenderers();
analyticsCollector.onVolumeChanged(volume);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onVolumeChanged(volume);
}
} }
@Override @Override
public float getVolume() { public float getVolume() {
return volume; return player.getVolume();
} }
@Override @Override
public boolean getSkipSilenceEnabled() { public boolean getSkipSilenceEnabled() {
return skipSilenceEnabled; return player.getSkipSilenceEnabled();
} }
@Override @Override
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) { public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
verifyApplicationThread(); player.setSkipSilenceEnabled(skipSilenceEnabled);
if (this.skipSilenceEnabled == skipSilenceEnabled) {
return;
}
this.skipSilenceEnabled = skipSilenceEnabled;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled);
notifySkipSilenceEnabledChanged();
} }
@Override @Override
public AnalyticsCollector getAnalyticsCollector() { public AnalyticsCollector getAnalyticsCollector() {
return analyticsCollector; return player.getAnalyticsCollector();
} }
@Override @Override
public void addAnalyticsListener(AnalyticsListener listener) { public void addAnalyticsListener(AnalyticsListener listener) {
// Don't verify application thread. We allow calls to this method from any thread. player.addAnalyticsListener(listener);
checkNotNull(listener);
analyticsCollector.addListener(listener);
} }
@Override @Override
public void removeAnalyticsListener(AnalyticsListener listener) { public void removeAnalyticsListener(AnalyticsListener listener) {
// Don't verify application thread. We allow calls to this method from any thread. player.removeAnalyticsListener(listener);
analyticsCollector.removeListener(listener);
} }
@Override @Override
public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) {
verifyApplicationThread(); player.setHandleAudioBecomingNoisy(handleAudioBecomingNoisy);
if (playerReleased) {
return;
}
audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy);
} }
@Override @Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread(); player.setPriorityTaskManager(priorityTaskManager);
if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) {
return;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(this.priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
}
if (priorityTaskManager != null && isLoading()) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = true;
} else {
isPriorityTaskManagerRegistered = false;
}
this.priorityTaskManager = priorityTaskManager;
} }
@Override @Override
@Nullable @Nullable
public Format getVideoFormat() { public Format getVideoFormat() {
return videoFormat; return player.getVideoFormat();
} }
@Override @Override
@Nullable @Nullable
public Format getAudioFormat() { public Format getAudioFormat() {
return audioFormat; return player.getAudioFormat();
} }
@Override @Override
@Nullable @Nullable
public DecoderCounters getVideoDecoderCounters() { public DecoderCounters getVideoDecoderCounters() {
return videoDecoderCounters; return player.getVideoDecoderCounters();
} }
@Override @Override
@Nullable @Nullable
public DecoderCounters getAudioDecoderCounters() { public DecoderCounters getAudioDecoderCounters() {
return audioDecoderCounters; return player.getAudioDecoderCounters();
} }
@Override @Override
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
verifyApplicationThread(); player.setVideoFrameMetadataListener(listener);
videoFrameMetadataListener = listener;
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(listener)
.send();
} }
@Override @Override
public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
verifyApplicationThread(); player.clearVideoFrameMetadataListener(listener);
if (videoFrameMetadataListener != listener) {
return;
}
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER)
.setPayload(null)
.send();
} }
@Override @Override
public void setCameraMotionListener(CameraMotionListener listener) { public void setCameraMotionListener(CameraMotionListener listener) {
verifyApplicationThread(); player.setCameraMotionListener(listener);
cameraMotionListener = listener;
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER)
.setPayload(listener)
.send();
} }
@Override @Override
public void clearCameraMotionListener(CameraMotionListener listener) { public void clearCameraMotionListener(CameraMotionListener listener) {
verifyApplicationThread(); player.clearCameraMotionListener(listener);
if (cameraMotionListener != listener) {
return;
}
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER)
.setPayload(null)
.send();
} }
@Override @Override
public List<Cue> getCurrentCues() { public List<Cue> getCurrentCues() {
verifyApplicationThread(); return player.getCurrentCues();
return currentCues;
} }
// ExoPlayer implementation // ExoPlayer implementation
...@@ -983,78 +607,59 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -983,78 +607,59 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void addListener(Listener listener) { public void addListener(Listener listener) {
checkNotNull(listener); player.addListener(listener);
listeners.add(listener);
EventListener eventListener = listener;
addListener(eventListener);
} }
@Deprecated @Deprecated
@Override @Override
public void addListener(Player.EventListener listener) { public void addListener(Player.EventListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
checkNotNull(listener);
player.addEventListener(listener); player.addEventListener(listener);
} }
@Override @Override
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
checkNotNull(listener); player.removeListener(listener);
listeners.remove(listener);
EventListener eventListener = listener;
removeListener(eventListener);
} }
@Deprecated @Deprecated
@Override @Override
public void removeListener(Player.EventListener listener) { public void removeListener(Player.EventListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.
player.removeEventListener(listener); player.removeEventListener(listener);
} }
@Override @Override
@State @State
public int getPlaybackState() { public int getPlaybackState() {
verifyApplicationThread();
return player.getPlaybackState(); return player.getPlaybackState();
} }
@Override @Override
@PlaybackSuppressionReason @PlaybackSuppressionReason
public int getPlaybackSuppressionReason() { public int getPlaybackSuppressionReason() {
verifyApplicationThread();
return player.getPlaybackSuppressionReason(); return player.getPlaybackSuppressionReason();
} }
@Override @Override
@Nullable @Nullable
public ExoPlaybackException getPlayerError() { public ExoPlaybackException getPlayerError() {
verifyApplicationThread();
return player.getPlayerError(); return player.getPlayerError();
} }
/** @deprecated Use {@link #prepare()} instead. */ /** @deprecated Use {@link #prepare()} instead. */
@Deprecated @Deprecated
@Override @Override
@SuppressWarnings("deprecation") // Calling deprecated method.
public void retry() { public void retry() {
verifyApplicationThread(); player.retry();
prepare();
} }
@Override @Override
public Commands getAvailableCommands() { public Commands getAvailableCommands() {
verifyApplicationThread();
return player.getAvailableCommands(); return player.getAvailableCommands();
} }
@Override @Override
public void prepare() { public void prepare() {
verifyApplicationThread();
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
player.prepare(); player.prepare();
} }
...@@ -1063,9 +668,9 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1063,9 +668,9 @@ public class SimpleExoPlayer extends BasePlayer
*/ */
@Deprecated @Deprecated
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation") // Forwarding deprecated method.
public void prepare(MediaSource mediaSource) { public void prepare(MediaSource mediaSource) {
prepare(mediaSource, /* resetPosition= */ true, /* resetState= */ true); player.prepare(mediaSource);
} }
/** /**
...@@ -1074,319 +679,245 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1074,319 +679,245 @@ public class SimpleExoPlayer extends BasePlayer
*/ */
@Deprecated @Deprecated
@Override @Override
@SuppressWarnings("deprecation") // Forwarding deprecated method.
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
verifyApplicationThread(); player.prepare(mediaSource, resetPosition, resetState);
setMediaSources(Collections.singletonList(mediaSource), resetPosition);
prepare();
} }
@Override @Override
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
verifyApplicationThread();
player.setMediaItems(mediaItems, resetPosition); player.setMediaItems(mediaItems, resetPosition);
} }
@Override @Override
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) { public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
verifyApplicationThread();
player.setMediaItems(mediaItems, startIndex, startPositionMs); player.setMediaItems(mediaItems, startIndex, startPositionMs);
} }
@Override @Override
public void setMediaSources(List<MediaSource> mediaSources) { public void setMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread();
player.setMediaSources(mediaSources); player.setMediaSources(mediaSources);
} }
@Override @Override
public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) { public void setMediaSources(List<MediaSource> mediaSources, boolean resetPosition) {
verifyApplicationThread();
player.setMediaSources(mediaSources, resetPosition); player.setMediaSources(mediaSources, resetPosition);
} }
@Override @Override
public void setMediaSources( public void setMediaSources(
List<MediaSource> mediaSources, int startMediaItemIndex, long startPositionMs) { List<MediaSource> mediaSources, int startMediaItemIndex, long startPositionMs) {
verifyApplicationThread();
player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs);
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource) { public void setMediaSource(MediaSource mediaSource) {
verifyApplicationThread();
player.setMediaSource(mediaSource); player.setMediaSource(mediaSource);
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { public void setMediaSource(MediaSource mediaSource, boolean resetPosition) {
verifyApplicationThread();
player.setMediaSource(mediaSource, resetPosition); player.setMediaSource(mediaSource, resetPosition);
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource, long startPositionMs) { public void setMediaSource(MediaSource mediaSource, long startPositionMs) {
verifyApplicationThread();
player.setMediaSource(mediaSource, startPositionMs); player.setMediaSource(mediaSource, startPositionMs);
} }
@Override @Override
public void addMediaItems(int index, List<MediaItem> mediaItems) { public void addMediaItems(int index, List<MediaItem> mediaItems) {
verifyApplicationThread();
player.addMediaItems(index, mediaItems); player.addMediaItems(index, mediaItems);
} }
@Override @Override
public void addMediaSource(MediaSource mediaSource) { public void addMediaSource(MediaSource mediaSource) {
verifyApplicationThread();
player.addMediaSource(mediaSource); player.addMediaSource(mediaSource);
} }
@Override @Override
public void addMediaSource(int index, MediaSource mediaSource) { public void addMediaSource(int index, MediaSource mediaSource) {
verifyApplicationThread();
player.addMediaSource(index, mediaSource); player.addMediaSource(index, mediaSource);
} }
@Override @Override
public void addMediaSources(List<MediaSource> mediaSources) { public void addMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread();
player.addMediaSources(mediaSources); player.addMediaSources(mediaSources);
} }
@Override @Override
public void addMediaSources(int index, List<MediaSource> mediaSources) { public void addMediaSources(int index, List<MediaSource> mediaSources) {
verifyApplicationThread();
player.addMediaSources(index, mediaSources); player.addMediaSources(index, mediaSources);
} }
@Override @Override
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
verifyApplicationThread();
player.moveMediaItems(fromIndex, toIndex, newIndex); player.moveMediaItems(fromIndex, toIndex, newIndex);
} }
@Override @Override
public void removeMediaItems(int fromIndex, int toIndex) { public void removeMediaItems(int fromIndex, int toIndex) {
verifyApplicationThread();
player.removeMediaItems(fromIndex, toIndex); player.removeMediaItems(fromIndex, toIndex);
} }
@Override @Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
verifyApplicationThread();
player.setShuffleOrder(shuffleOrder); player.setShuffleOrder(shuffleOrder);
} }
@Override @Override
public void setPlayWhenReady(boolean playWhenReady) { public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread(); player.setPlayWhenReady(playWhenReady);
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
public boolean getPlayWhenReady() { public boolean getPlayWhenReady() {
verifyApplicationThread();
return player.getPlayWhenReady(); return player.getPlayWhenReady();
} }
@Override @Override
public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
verifyApplicationThread();
player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems); player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems);
} }
@Override @Override
public boolean getPauseAtEndOfMediaItems() { public boolean getPauseAtEndOfMediaItems() {
verifyApplicationThread();
return player.getPauseAtEndOfMediaItems(); return player.getPauseAtEndOfMediaItems();
} }
@Override @Override
public @RepeatMode int getRepeatMode() { public @RepeatMode int getRepeatMode() {
verifyApplicationThread();
return player.getRepeatMode(); return player.getRepeatMode();
} }
@Override @Override
public void setRepeatMode(@RepeatMode int repeatMode) { public void setRepeatMode(@RepeatMode int repeatMode) {
verifyApplicationThread();
player.setRepeatMode(repeatMode); player.setRepeatMode(repeatMode);
} }
@Override @Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) { public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
verifyApplicationThread();
player.setShuffleModeEnabled(shuffleModeEnabled); player.setShuffleModeEnabled(shuffleModeEnabled);
} }
@Override @Override
public boolean getShuffleModeEnabled() { public boolean getShuffleModeEnabled() {
verifyApplicationThread();
return player.getShuffleModeEnabled(); return player.getShuffleModeEnabled();
} }
@Override @Override
public boolean isLoading() { public boolean isLoading() {
verifyApplicationThread();
return player.isLoading(); return player.isLoading();
} }
@Override @Override
public void seekTo(int mediaItemIndex, long positionMs) { public void seekTo(int mediaItemIndex, long positionMs) {
verifyApplicationThread();
analyticsCollector.notifySeekStarted();
player.seekTo(mediaItemIndex, positionMs); player.seekTo(mediaItemIndex, positionMs);
} }
@Override @Override
public long getSeekBackIncrement() { public long getSeekBackIncrement() {
verifyApplicationThread();
return player.getSeekBackIncrement(); return player.getSeekBackIncrement();
} }
@Override @Override
public long getSeekForwardIncrement() { public long getSeekForwardIncrement() {
verifyApplicationThread();
return player.getSeekForwardIncrement(); return player.getSeekForwardIncrement();
} }
@Override @Override
public long getMaxSeekToPreviousPosition() { public long getMaxSeekToPreviousPosition() {
verifyApplicationThread();
return player.getMaxSeekToPreviousPosition(); return player.getMaxSeekToPreviousPosition();
} }
@Override @Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) { public void setPlaybackParameters(PlaybackParameters playbackParameters) {
verifyApplicationThread();
player.setPlaybackParameters(playbackParameters); player.setPlaybackParameters(playbackParameters);
} }
@Override @Override
public PlaybackParameters getPlaybackParameters() { public PlaybackParameters getPlaybackParameters() {
verifyApplicationThread();
return player.getPlaybackParameters(); return player.getPlaybackParameters();
} }
@Override @Override
public void setSeekParameters(@Nullable SeekParameters seekParameters) { public void setSeekParameters(@Nullable SeekParameters seekParameters) {
verifyApplicationThread();
player.setSeekParameters(seekParameters); player.setSeekParameters(seekParameters);
} }
@Override @Override
public SeekParameters getSeekParameters() { public SeekParameters getSeekParameters() {
verifyApplicationThread();
return player.getSeekParameters(); return player.getSeekParameters();
} }
@Override @Override
public void setForegroundMode(boolean foregroundMode) { public void setForegroundMode(boolean foregroundMode) {
verifyApplicationThread();
player.setForegroundMode(foregroundMode); player.setForegroundMode(foregroundMode);
} }
@Override @Override
public void stop() { public void stop() {
stop(/* reset= */ false); player.stop();
} }
@Deprecated @Deprecated
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
verifyApplicationThread();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
player.stop(reset); player.stop(reset);
currentCues = Collections.emptyList();
} }
@Override @Override
public void release() { public void release() {
verifyApplicationThread();
if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) {
keepSessionIdAudioTrack.release();
keepSessionIdAudioTrack = null;
}
audioBecomingNoisyManager.setEnabled(false);
streamVolumeManager.release();
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
audioFocusManager.release();
player.release(); player.release();
analyticsCollector.release();
removeSurfaceCallbacks();
if (ownedSurface != null) {
ownedSurface.release();
ownedSurface = null;
}
if (isPriorityTaskManagerRegistered) {
checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false;
}
currentCues = Collections.emptyList();
playerReleased = true;
} }
@Override @Override
public PlayerMessage createMessage(PlayerMessage.Target target) { public PlayerMessage createMessage(PlayerMessage.Target target) {
verifyApplicationThread();
return player.createMessage(target); return player.createMessage(target);
} }
@Override @Override
public int getRendererCount() { public int getRendererCount() {
verifyApplicationThread();
return player.getRendererCount(); return player.getRendererCount();
} }
@Override @Override
public @C.TrackType int getRendererType(int index) { public @C.TrackType int getRendererType(int index) {
verifyApplicationThread();
return player.getRendererType(index); return player.getRendererType(index);
} }
@Override @Override
public Renderer getRenderer(int index) { public Renderer getRenderer(int index) {
verifyApplicationThread();
return player.getRenderer(index); return player.getRenderer(index);
} }
@Override @Override
public TrackSelector getTrackSelector() { public TrackSelector getTrackSelector() {
verifyApplicationThread();
return player.getTrackSelector(); return player.getTrackSelector();
} }
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public TrackGroupArray getCurrentTrackGroups() {
verifyApplicationThread();
return player.getCurrentTrackGroups(); return player.getCurrentTrackGroups();
} }
@Override @Override
public TrackSelectionArray getCurrentTrackSelections() { public TrackSelectionArray getCurrentTrackSelections() {
verifyApplicationThread();
return player.getCurrentTrackSelections(); return player.getCurrentTrackSelections();
} }
@Override @Override
public TracksInfo getCurrentTracksInfo() { public TracksInfo getCurrentTracksInfo() {
verifyApplicationThread();
return player.getCurrentTracksInfo(); return player.getCurrentTracksInfo();
} }
@Override @Override
public TrackSelectionParameters getTrackSelectionParameters() { public TrackSelectionParameters getTrackSelectionParameters() {
verifyApplicationThread();
return player.getTrackSelectionParameters(); return player.getTrackSelectionParameters();
} }
@Override @Override
public void setTrackSelectionParameters(TrackSelectionParameters parameters) { public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
verifyApplicationThread();
player.setTrackSelectionParameters(parameters); player.setTrackSelectionParameters(parameters);
} }
...@@ -1407,782 +938,111 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1407,782 +938,111 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public Timeline getCurrentTimeline() { public Timeline getCurrentTimeline() {
verifyApplicationThread();
return player.getCurrentTimeline(); return player.getCurrentTimeline();
} }
@Override @Override
public int getCurrentPeriodIndex() { public int getCurrentPeriodIndex() {
verifyApplicationThread();
return player.getCurrentPeriodIndex(); return player.getCurrentPeriodIndex();
} }
@Override @Override
public int getCurrentMediaItemIndex() { public int getCurrentMediaItemIndex() {
verifyApplicationThread();
return player.getCurrentMediaItemIndex(); return player.getCurrentMediaItemIndex();
} }
@Override @Override
public long getDuration() { public long getDuration() {
verifyApplicationThread();
return player.getDuration(); return player.getDuration();
} }
@Override @Override
public long getCurrentPosition() { public long getCurrentPosition() {
verifyApplicationThread();
return player.getCurrentPosition(); return player.getCurrentPosition();
} }
@Override @Override
public long getBufferedPosition() { public long getBufferedPosition() {
verifyApplicationThread();
return player.getBufferedPosition(); return player.getBufferedPosition();
} }
@Override @Override
public long getTotalBufferedDuration() { public long getTotalBufferedDuration() {
verifyApplicationThread();
return player.getTotalBufferedDuration(); return player.getTotalBufferedDuration();
} }
@Override @Override
public boolean isPlayingAd() { public boolean isPlayingAd() {
verifyApplicationThread();
return player.isPlayingAd(); return player.isPlayingAd();
} }
@Override @Override
public int getCurrentAdGroupIndex() { public int getCurrentAdGroupIndex() {
verifyApplicationThread();
return player.getCurrentAdGroupIndex(); return player.getCurrentAdGroupIndex();
} }
@Override @Override
public int getCurrentAdIndexInAdGroup() { public int getCurrentAdIndexInAdGroup() {
verifyApplicationThread();
return player.getCurrentAdIndexInAdGroup(); return player.getCurrentAdIndexInAdGroup();
} }
@Override @Override
public long getContentPosition() { public long getContentPosition() {
verifyApplicationThread();
return player.getContentPosition(); return player.getContentPosition();
} }
@Override @Override
public long getContentBufferedPosition() { public long getContentBufferedPosition() {
verifyApplicationThread();
return player.getContentBufferedPosition(); return player.getContentBufferedPosition();
} }
@Deprecated @Deprecated
@Override @Override
public void setHandleWakeLock(boolean handleWakeLock) { public void setHandleWakeLock(boolean handleWakeLock) {
setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE); player.setHandleWakeLock(handleWakeLock);
} }
@Override @Override
public void setWakeMode(@C.WakeMode int wakeMode) { public void setWakeMode(@C.WakeMode int wakeMode) {
verifyApplicationThread(); player.setWakeMode(wakeMode);
switch (wakeMode) {
case C.WAKE_MODE_NONE:
wakeLockManager.setEnabled(false);
wifiLockManager.setEnabled(false);
break;
case C.WAKE_MODE_LOCAL:
wakeLockManager.setEnabled(true);
wifiLockManager.setEnabled(false);
break;
case C.WAKE_MODE_NETWORK:
wakeLockManager.setEnabled(true);
wifiLockManager.setEnabled(true);
break;
default:
break;
}
} }
@Override @Override
public DeviceInfo getDeviceInfo() { public DeviceInfo getDeviceInfo() {
verifyApplicationThread(); return player.getDeviceInfo();
return deviceInfo;
} }
@Override @Override
public int getDeviceVolume() { public int getDeviceVolume() {
verifyApplicationThread(); return player.getDeviceVolume();
return streamVolumeManager.getVolume();
} }
@Override @Override
public boolean isDeviceMuted() { public boolean isDeviceMuted() {
verifyApplicationThread(); return player.isDeviceMuted();
return streamVolumeManager.isMuted();
} }
@Override @Override
public void setDeviceVolume(int volume) { public void setDeviceVolume(int volume) {
verifyApplicationThread(); player.setDeviceVolume(volume);
streamVolumeManager.setVolume(volume);
} }
@Override @Override
public void increaseDeviceVolume() { public void increaseDeviceVolume() {
verifyApplicationThread(); player.increaseDeviceVolume();
streamVolumeManager.increaseVolume();
} }
@Override @Override
public void decreaseDeviceVolume() { public void decreaseDeviceVolume() {
verifyApplicationThread(); player.decreaseDeviceVolume();
streamVolumeManager.decreaseVolume();
} }
@Override @Override
public void setDeviceMuted(boolean muted) { public void setDeviceMuted(boolean muted) {
verifyApplicationThread(); player.setDeviceMuted(muted);
streamVolumeManager.setMuted(muted);
} }
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
}
// Internal methods.
private void removeSurfaceCallbacks() {
if (sphericalGLSurfaceView != null) {
player
.createMessage(frameMetadataListener)
.setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW)
.setPayload(null)
.send();
sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener);
sphericalGLSurfaceView = null;
}
if (textureView != null) {
if (textureView.getSurfaceTextureListener() != componentListener) {
Log.w(TAG, "SurfaceTextureListener already unset or replaced.");
} else {
textureView.setSurfaceTextureListener(null);
}
textureView = null;
}
if (surfaceHolder != null) {
surfaceHolder.removeCallback(componentListener);
surfaceHolder = null;
}
}
private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) {
Surface surface = new Surface(surfaceTexture);
setVideoOutputInternal(surface);
ownedSurface = surface;
}
private void setVideoOutputInternal(@Nullable Object videoOutput) {
// Note: We don't turn this method into a no-op if the output is being replaced with itself so
// as to ensure onRenderedFirstFrame callbacks are still called in this case.
List<PlayerMessage> messages = new ArrayList<>();
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == TRACK_TYPE_VIDEO) {
messages.add(
player
.createMessage(renderer)
.setType(MSG_SET_VIDEO_OUTPUT)
.setPayload(videoOutput)
.send());
}
}
boolean messageDeliveryTimedOut = false;
if (this.videoOutput != null && this.videoOutput != videoOutput) {
// We're replacing an output. Block to ensure that this output will not be accessed by the
// renderers after this method returns.
try {
for (PlayerMessage message : messages) {
message.blockUntilDelivered(detachSurfaceTimeoutMs);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
messageDeliveryTimedOut = true;
}
if (this.videoOutput == ownedSurface) {
// We're replacing a surface that we are responsible for releasing.
ownedSurface.release();
ownedSurface = null;
}
}
this.videoOutput = videoOutput;
if (messageDeliveryTimedOut) {
player.stop(
/* reset= */ false,
ExoPlaybackException.createForUnexpected(
new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE),
PlaybackException.ERROR_CODE_TIMEOUT));
}
}
/**
* Sets the holder of the surface that will be displayed to the user, but which should
* <em>not</em> be the output for video renderers. This case occurs when video frames need to be
* rendered to an intermediate surface (which is not the one held by the provided holder).
*
* @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed
* to the user.
*/
private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) {
// Although we won't use the view's surface directly as the video output, still use the holder
// to query the surface size, to be informed in changes to the size via componentListener, and
// for equality checking in clearVideoSurfaceHolder.
surfaceHolderSurfaceIsVideoOutput = false;
surfaceHolder = nonVideoOutputSurfaceHolder;
surfaceHolder.addCallback(componentListener);
Surface surface = surfaceHolder.getSurface();
if (surface != null && surface.isValid()) {
Rect surfaceSize = surfaceHolder.getSurfaceFrame();
maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height());
} else {
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
}
private void maybeNotifySurfaceSizeChanged(int width, int height) {
if (width != surfaceWidth || height != surfaceHeight) {
surfaceWidth = width;
surfaceHeight = height;
analyticsCollector.onSurfaceSizeChanged(width, height);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onSurfaceSizeChanged(width, height);
}
}
}
private void sendVolumeToRenderers() {
float scaledVolume = volume * audioFocusManager.getVolumeMultiplier();
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume);
}
@SuppressWarnings("SuspiciousMethodCalls")
private void notifySkipSilenceEnabledChanged() {
analyticsCollector.onSkipSilenceEnabledChanged(skipSilenceEnabled);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onSkipSilenceEnabledChanged(skipSilenceEnabled);
}
}
private void updatePlayWhenReady(
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE;
player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
}
private void updateWakeAndWifiLock() {
@State int playbackState = getPlaybackState();
switch (playbackState) {
case Player.STATE_READY:
case Player.STATE_BUFFERING:
boolean isSleeping = experimentalIsSleepingForOffload();
wakeLockManager.setStayAwake(getPlayWhenReady() && !isSleeping);
// The wifi lock is not released while sleeping to avoid interrupting downloads.
wifiLockManager.setStayAwake(getPlayWhenReady());
break;
case Player.STATE_ENDED:
case Player.STATE_IDLE:
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
break;
default:
throw new IllegalStateException();
}
}
private void verifyApplicationThread() {
// The constructor may be executed on a background thread. Wait with accessing the player from
// the app thread until the constructor finished executing.
constructorFinished.blockUninterruptible();
if (Thread.currentThread() != getApplicationLooper().getThread()) {
String message =
Util.formatInvariant(
"Player is accessed on the wrong thread.\n"
+ "Current thread: '%s'\n"
+ "Expected thread: '%s'\n"
+ "See https://exoplayer.dev/issues/player-accessed-on-wrong-thread",
Thread.currentThread().getName(), getApplicationLooper().getThread().getName());
if (throwsWhenUsingWrongThread) {
throw new IllegalStateException(message);
}
Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException());
hasNotifiedFullWrongThreadWarning = true;
}
}
private void sendRendererMessage(
@C.TrackType int trackType, int messageType, @Nullable Object payload) {
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == trackType) {
player.createMessage(renderer).setType(messageType).setPayload(payload).send();
}
}
}
/**
* Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio
* session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated.
*
* <p>Use of this method is only required on API level 21 and earlier.
*
* @param audioSessionId The audio session ID, or {@link C#AUDIO_SESSION_ID_UNSET} to generate a
* new one.
* @return The audio session ID.
*/
private int initializeKeepSessionIdAudioTrack(int audioSessionId) {
if (keepSessionIdAudioTrack != null
&& keepSessionIdAudioTrack.getAudioSessionId() != audioSessionId) {
keepSessionIdAudioTrack.release();
keepSessionIdAudioTrack = null;
}
if (keepSessionIdAudioTrack == null) {
int sampleRate = 4000; // Minimum sample rate supported by the platform.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback.
keepSessionIdAudioTrack =
new AudioTrack(
C.STREAM_TYPE_DEFAULT,
sampleRate,
channelConfig,
encoding,
bufferSize,
AudioTrack.MODE_STATIC,
audioSessionId);
}
return keepSessionIdAudioTrack.getAudioSessionId();
}
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
return new DeviceInfo(
DeviceInfo.PLAYBACK_TYPE_LOCAL,
streamVolumeManager.getMinVolume(),
streamVolumeManager.getMaxVolume());
}
private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) {
return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
}
private final class ComponentListener
implements VideoRendererEventListener,
AudioRendererEventListener,
TextOutput,
MetadataOutput,
SurfaceHolder.Callback,
TextureView.SurfaceTextureListener,
SphericalGLSurfaceView.VideoSurfaceListener,
AudioFocusManager.PlayerControl,
AudioBecomingNoisyManager.EventListener,
StreamVolumeManager.Listener,
Player.EventListener,
AudioOffloadListener {
// VideoRendererEventListener implementation
@Override
public void onVideoEnabled(DecoderCounters counters) {
videoDecoderCounters = counters;
analyticsCollector.onVideoEnabled(counters);
}
@Override
public void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
analyticsCollector.onVideoDecoderInitialized(
decoderName, initializedTimestampMs, initializationDurationMs);
}
@Override
public void onVideoInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
videoFormat = format;
analyticsCollector.onVideoInputFormatChanged(format, decoderReuseEvaluation);
}
@Override
public void onDroppedFrames(int count, long elapsed) {
analyticsCollector.onDroppedFrames(count, elapsed);
}
@Override
public void onVideoSizeChanged(VideoSize videoSize) {
SimpleExoPlayer.this.videoSize = videoSize;
analyticsCollector.onVideoSizeChanged(videoSize);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onVideoSizeChanged(videoSize);
}
}
@Override
public void onRenderedFirstFrame(Object output, long renderTimeMs) {
analyticsCollector.onRenderedFirstFrame(output, renderTimeMs);
if (videoOutput == output) {
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onRenderedFirstFrame();
}
}
}
@Override
public void onVideoDecoderReleased(String decoderName) {
analyticsCollector.onVideoDecoderReleased(decoderName);
}
@Override
public void onVideoDisabled(DecoderCounters counters) {
analyticsCollector.onVideoDisabled(counters);
videoFormat = null;
videoDecoderCounters = null;
}
@Override
public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount);
}
@Override
public void onVideoCodecError(Exception videoCodecError) {
analyticsCollector.onVideoCodecError(videoCodecError);
}
// AudioRendererEventListener implementation
@Override
public void onAudioEnabled(DecoderCounters counters) {
audioDecoderCounters = counters;
analyticsCollector.onAudioEnabled(counters);
}
@Override
public void onAudioDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
analyticsCollector.onAudioDecoderInitialized(
decoderName, initializedTimestampMs, initializationDurationMs);
}
@Override
public void onAudioInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
audioFormat = format;
analyticsCollector.onAudioInputFormatChanged(format, decoderReuseEvaluation);
}
@Override
public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
analyticsCollector.onAudioPositionAdvancing(playoutStartSystemTimeMs);
}
@Override
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
analyticsCollector.onAudioUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
@Override
public void onAudioDecoderReleased(String decoderName) {
analyticsCollector.onAudioDecoderReleased(decoderName);
}
@Override
public void onAudioDisabled(DecoderCounters counters) {
analyticsCollector.onAudioDisabled(counters);
audioFormat = null;
audioDecoderCounters = null;
}
@Override
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
if (SimpleExoPlayer.this.skipSilenceEnabled == skipSilenceEnabled) {
return;
}
SimpleExoPlayer.this.skipSilenceEnabled = skipSilenceEnabled;
notifySkipSilenceEnabledChanged();
}
@Override
public void onAudioSinkError(Exception audioSinkError) {
analyticsCollector.onAudioSinkError(audioSinkError);
}
@Override
public void onAudioCodecError(Exception audioCodecError) {
analyticsCollector.onAudioCodecError(audioCodecError);
}
// TextOutput implementation
@Override
public void onCues(List<Cue> cues) {
currentCues = cues;
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listeners : listeners) {
listeners.onCues(cues);
}
}
// MetadataOutput implementation
@Override
public void onMetadata(Metadata metadata) {
analyticsCollector.onMetadata(metadata);
player.onMetadata(metadata);
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onMetadata(metadata);
}
}
// SurfaceHolder.Callback implementation
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (surfaceHolderSurfaceIsVideoOutput) {
setVideoOutputInternal(holder.getSurface());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (surfaceHolderSurfaceIsVideoOutput) {
setVideoOutputInternal(/* videoOutput= */ null);
}
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
}
// TextureView.SurfaceTextureListener implementation
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
setSurfaceTextureInternal(surfaceTexture);
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
maybeNotifySurfaceSizeChanged(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
// Do nothing.
}
// SphericalGLSurfaceView.VideoSurfaceListener
@Override
public void onVideoSurfaceCreated(Surface surface) {
setVideoOutputInternal(surface);
}
@Override
public void onVideoSurfaceDestroyed(Surface surface) {
setVideoOutputInternal(/* videoOutput= */ null);
}
// AudioFocusManager.PlayerControl implementation
@Override
public void setVolumeMultiplier(float volumeMultiplier) {
sendVolumeToRenderers();
}
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
// AudioBecomingNoisyManager.EventListener implementation.
@Override
public void onAudioBecomingNoisy() {
updatePlayWhenReady(
/* playWhenReady= */ false,
AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
}
// StreamVolumeManager.Listener implementation.
@Override
public void onStreamTypeChanged(@C.StreamType int streamType) {
DeviceInfo deviceInfo = createDeviceInfo(streamVolumeManager);
if (!deviceInfo.equals(SimpleExoPlayer.this.deviceInfo)) {
SimpleExoPlayer.this.deviceInfo = deviceInfo;
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onDeviceInfoChanged(deviceInfo);
}
}
}
@Override
public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) {
// TODO(internal b/187152483): Events should be dispatched via ListenerSet
for (Listener listener : listeners) {
listener.onDeviceVolumeChanged(streamVolume, streamMuted);
}
}
// Player.EventListener implementation.
@Override
public void onIsLoadingChanged(boolean isLoading) {
if (priorityTaskManager != null) {
if (isLoading && !isPriorityTaskManagerRegistered) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = true;
} else if (!isLoading && isPriorityTaskManagerRegistered) {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
isPriorityTaskManagerRegistered = false;
}
}
}
@Override
public void onPlaybackStateChanged(@State int playbackState) {
updateWakeAndWifiLock();
}
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {
updateWakeAndWifiLock();
}
// Player.AudioOffloadListener implementation.
@Override
public void onExperimentalSleepingForOffloadChanged(boolean sleepingForOffload) {
updateWakeAndWifiLock();
}
}
/** Listeners that are called on the playback thread. */
private static final class FrameMetadataListener
implements VideoFrameMetadataListener, CameraMotionListener, PlayerMessage.Target {
@MessageType
public static final int MSG_SET_VIDEO_FRAME_METADATA_LISTENER =
Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
@MessageType
public static final int MSG_SET_CAMERA_MOTION_LISTENER =
Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
@MessageType public static final int MSG_SET_SPHERICAL_SURFACE_VIEW = Renderer.MSG_CUSTOM_BASE;
@Nullable private VideoFrameMetadataListener videoFrameMetadataListener;
@Nullable private CameraMotionListener cameraMotionListener;
@Nullable private VideoFrameMetadataListener internalVideoFrameMetadataListener;
@Nullable private CameraMotionListener internalCameraMotionListener;
@Override
public void handleMessage(@MessageType int messageType, @Nullable Object message) {
switch (messageType) {
case MSG_SET_VIDEO_FRAME_METADATA_LISTENER:
videoFrameMetadataListener = (VideoFrameMetadataListener) message;
break;
case MSG_SET_CAMERA_MOTION_LISTENER:
cameraMotionListener = (CameraMotionListener) message;
break;
case MSG_SET_SPHERICAL_SURFACE_VIEW:
@Nullable SphericalGLSurfaceView surfaceView = (SphericalGLSurfaceView) message;
if (surfaceView == null) {
internalVideoFrameMetadataListener = null;
internalCameraMotionListener = null;
} else {
internalVideoFrameMetadataListener = surfaceView.getVideoFrameMetadataListener();
internalCameraMotionListener = surfaceView.getCameraMotionListener();
}
break;
case Renderer.MSG_SET_AUDIO_ATTRIBUTES:
case Renderer.MSG_SET_AUDIO_SESSION_ID:
case Renderer.MSG_SET_AUX_EFFECT_INFO:
case Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY:
case Renderer.MSG_SET_SCALING_MODE:
case Renderer.MSG_SET_SKIP_SILENCE_ENABLED:
case Renderer.MSG_SET_VIDEO_OUTPUT:
case Renderer.MSG_SET_VOLUME:
case Renderer.MSG_SET_WAKEUP_LISTENER:
default:
break;
}
}
// VideoFrameMetadataListener
@Override
public void onVideoFrameAboutToBeRendered(
long presentationTimeUs,
long releaseTimeNs,
Format format,
@Nullable MediaFormat mediaFormat) {
if (internalVideoFrameMetadataListener != null) {
internalVideoFrameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, releaseTimeNs, format, mediaFormat);
}
if (videoFrameMetadataListener != null) {
videoFrameMetadataListener.onVideoFrameAboutToBeRendered(
presentationTimeUs, releaseTimeNs, format, mediaFormat);
}
}
// CameraMotionListener
@Override
public void onCameraMotion(long timeUs, float[] rotation) {
if (internalCameraMotionListener != null) {
internalCameraMotionListener.onCameraMotion(timeUs, rotation);
}
if (cameraMotionListener != null) {
cameraMotionListener.onCameraMotion(timeUs, rotation);
}
}
@Override
public void onCameraMotionReset() {
if (internalCameraMotionListener != null) {
internalCameraMotionListener.onCameraMotionReset();
}
if (cameraMotionListener != null) {
cameraMotionListener.onCameraMotionReset();
}
}
} }
} }
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