Commit ddbd9512 by tonihei Committed by Ian Baker

Rollback of https://github.com/google/ExoPlayer/commit/12be2bc35791bd00c3af8b403f5db7b14074c55c

*** Original commit ***

Rollback of https://github.com/google/ExoPlayer/commit/3bb0210d229ffd1455d546fa738dfebbac50f552

*** Original commit ***

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...

***

PiperOrigin-RevId: 427131338
parent 7f6accd0
...@@ -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,28 @@ import android.view.TextureView; ...@@ -45,43 +24,28 @@ 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.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 +309,8 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -345,52 +309,8 @@ 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 ConditionVariable constructorFinished;
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
...@@ -428,103 +348,7 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -428,103 +348,7 @@ public class SimpleExoPlayer extends BasePlayer
/* package */ SimpleExoPlayer(ExoPlayer.Builder builder) { /* package */ SimpleExoPlayer(ExoPlayer.Builder builder) {
constructorFinished = new ConditionVariable(); constructorFinished = new ConditionVariable();
try { try {
applicationContext = builder.context.getApplicationContext(); player = new ExoPlayerImpl(builder, /* wrappingPlayer= */ this);
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 { } finally {
constructorFinished.open(); constructorFinished.open();
} }
...@@ -532,13 +356,13 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -532,13 +356,13 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled);
} }
@Override @Override
public boolean experimentalIsSleepingForOffload() { public boolean experimentalIsSleepingForOffload() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.experimentalIsSleepingForOffload(); return player.experimentalIsSleepingForOffload();
} }
...@@ -568,493 +392,336 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -568,493 +392,336 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) {
verifyApplicationThread(); blockUntilConstructorFinished();
this.videoScalingMode = videoScalingMode; player.setVideoScalingMode(videoScalingMode);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
} }
@Override @Override
@C.VideoScalingMode @C.VideoScalingMode
public int getVideoScalingMode() { public int getVideoScalingMode() {
return videoScalingMode; blockUntilConstructorFinished();
return player.getVideoScalingMode();
} }
@Override @Override
public void setVideoChangeFrameRateStrategy( public void setVideoChangeFrameRateStrategy(
@C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) { player.setVideoChangeFrameRateStrategy(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; blockUntilConstructorFinished();
return player.getVideoChangeFrameRateStrategy();
} }
@Override @Override
public VideoSize getVideoSize() { public VideoSize getVideoSize() {
return videoSize; blockUntilConstructorFinished();
return player.getVideoSize();
} }
@Override @Override
public void clearVideoSurface() { public void clearVideoSurface() {
verifyApplicationThread(); blockUntilConstructorFinished();
removeSurfaceCallbacks(); player.clearVideoSurface();
setVideoOutputInternal(/* videoOutput= */ null);
maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0);
} }
@Override @Override
public void clearVideoSurface(@Nullable Surface surface) { public void clearVideoSurface(@Nullable Surface surface) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (surface != null && surface == videoOutput) { player.clearVideoSurface(surface);
clearVideoSurface();
}
} }
@Override @Override
public void setVideoSurface(@Nullable Surface surface) { public void setVideoSurface(@Nullable Surface surface) {
verifyApplicationThread(); blockUntilConstructorFinished();
removeSurfaceCallbacks(); player.setVideoSurface(surface);
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(); blockUntilConstructorFinished();
if (surfaceHolder == null) { player.setVideoSurfaceHolder(surfaceHolder);
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(); blockUntilConstructorFinished();
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { player.clearVideoSurfaceHolder(surfaceHolder);
clearVideoSurface();
}
} }
@Override @Override
public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { player.setVideoSurfaceView(surfaceView);
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(); blockUntilConstructorFinished();
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); player.clearVideoSurfaceView(surfaceView);
} }
@Override @Override
public void setVideoTextureView(@Nullable TextureView textureView) { public void setVideoTextureView(@Nullable TextureView textureView) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (textureView == null) { player.setVideoTextureView(textureView);
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(); blockUntilConstructorFinished();
if (textureView != null && textureView == this.textureView) { player.clearVideoTextureView(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. blockUntilConstructorFinished();
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. blockUntilConstructorFinished();
player.removeAudioOffloadListener(listener); player.removeAudioOffloadListener(listener);
} }
@Override @Override
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) { public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (playerReleased) { player.setAudioAttributes(audioAttributes, handleAudioFocus);
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; blockUntilConstructorFinished();
return player.getAudioAttributes();
} }
@Override @Override
public void setAudioSessionId(int audioSessionId) { public void setAudioSessionId(int audioSessionId) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (this.audioSessionId == audioSessionId) { player.setAudioSessionId(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; blockUntilConstructorFinished();
return player.getAudioSessionId();
} }
@Override @Override
public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) {
verifyApplicationThread(); blockUntilConstructorFinished();
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); player.setAuxEffectInfo(auxEffectInfo);
} }
@Override @Override
public void clearAuxEffectInfo() { public void clearAuxEffectInfo() {
setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); blockUntilConstructorFinished();
player.clearAuxEffectInfo();
} }
@Override @Override
public void setVolume(float volume) { public void setVolume(float volume) {
verifyApplicationThread(); blockUntilConstructorFinished();
volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1); player.setVolume(volume);
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; blockUntilConstructorFinished();
return player.getVolume();
} }
@Override @Override
public boolean getSkipSilenceEnabled() { public boolean getSkipSilenceEnabled() {
return skipSilenceEnabled; blockUntilConstructorFinished();
return player.getSkipSilenceEnabled();
} }
@Override @Override
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) { public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (this.skipSilenceEnabled == skipSilenceEnabled) { player.setSkipSilenceEnabled(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; blockUntilConstructorFinished();
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. blockUntilConstructorFinished();
checkNotNull(listener); player.addAnalyticsListener(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. blockUntilConstructorFinished();
analyticsCollector.removeListener(listener); player.removeAnalyticsListener(listener);
} }
@Override @Override
public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (playerReleased) { player.setHandleAudioBecomingNoisy(handleAudioBecomingNoisy);
return;
}
audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy);
} }
@Override @Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread(); blockUntilConstructorFinished();
if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) { player.setPriorityTaskManager(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; blockUntilConstructorFinished();
return player.getVideoFormat();
} }
@Override @Override
@Nullable @Nullable
public Format getAudioFormat() { public Format getAudioFormat() {
return audioFormat; blockUntilConstructorFinished();
return player.getAudioFormat();
} }
@Override @Override
@Nullable @Nullable
public DecoderCounters getVideoDecoderCounters() { public DecoderCounters getVideoDecoderCounters() {
return videoDecoderCounters; blockUntilConstructorFinished();
return player.getVideoDecoderCounters();
} }
@Override @Override
@Nullable @Nullable
public DecoderCounters getAudioDecoderCounters() { public DecoderCounters getAudioDecoderCounters() {
return audioDecoderCounters; blockUntilConstructorFinished();
return player.getAudioDecoderCounters();
} }
@Override @Override
public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) {
verifyApplicationThread(); blockUntilConstructorFinished();
videoFrameMetadataListener = listener; player.setVideoFrameMetadataListener(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(); blockUntilConstructorFinished();
if (videoFrameMetadataListener != listener) { player.clearVideoFrameMetadataListener(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(); blockUntilConstructorFinished();
cameraMotionListener = listener; player.setCameraMotionListener(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(); blockUntilConstructorFinished();
if (cameraMotionListener != listener) { player.clearCameraMotionListener(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(); blockUntilConstructorFinished();
return currentCues; return player.getCurrentCues();
} }
// ExoPlayer implementation // ExoPlayer implementation
@Override @Override
public Looper getPlaybackLooper() { public Looper getPlaybackLooper() {
blockUntilConstructorFinished();
return player.getPlaybackLooper(); return player.getPlaybackLooper();
} }
@Override @Override
public Looper getApplicationLooper() { public Looper getApplicationLooper() {
blockUntilConstructorFinished();
return player.getApplicationLooper(); return player.getApplicationLooper();
} }
@Override @Override
public Clock getClock() { public Clock getClock() {
blockUntilConstructorFinished();
return player.getClock(); return player.getClock();
} }
@Override @Override
public void addListener(Listener listener) { public void addListener(Listener listener) {
checkNotNull(listener); blockUntilConstructorFinished();
listeners.add(listener); player.addListener(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. blockUntilConstructorFinished();
checkNotNull(listener);
player.addEventListener(listener); player.addEventListener(listener);
} }
@Override @Override
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
checkNotNull(listener); blockUntilConstructorFinished();
listeners.remove(listener); player.removeListener(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. blockUntilConstructorFinished();
player.removeEventListener(listener); player.removeEventListener(listener);
} }
@Override @Override
@State @State
public int getPlaybackState() { public int getPlaybackState() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getPlaybackState(); return player.getPlaybackState();
} }
@Override @Override
@PlaybackSuppressionReason @PlaybackSuppressionReason
public int getPlaybackSuppressionReason() { public int getPlaybackSuppressionReason() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getPlaybackSuppressionReason(); return player.getPlaybackSuppressionReason();
} }
@Override @Override
@Nullable @Nullable
public ExoPlaybackException getPlayerError() { public ExoPlaybackException getPlayerError() {
verifyApplicationThread(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
prepare(); player.retry();
} }
@Override @Override
public Commands getAvailableCommands() { public Commands getAvailableCommands() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getAvailableCommands(); return player.getAvailableCommands();
} }
@Override @Override
public void prepare() { public void prepare() {
verifyApplicationThread(); blockUntilConstructorFinished();
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 +730,10 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1063,9 +730,10 @@ 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); blockUntilConstructorFinished();
player.prepare(mediaSource);
} }
/** /**
...@@ -1074,1115 +742,449 @@ public class SimpleExoPlayer extends BasePlayer ...@@ -1074,1115 +742,449 @@ 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(); blockUntilConstructorFinished();
setMediaSources(Collections.singletonList(mediaSource), resetPosition); player.prepare(mediaSource, resetPosition, resetState);
prepare();
} }
@Override @Override
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
verifyApplicationThread(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs);
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource) { public void setMediaSource(MediaSource mediaSource) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setMediaSource(mediaSource); player.setMediaSource(mediaSource);
} }
@Override @Override
public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { public void setMediaSource(MediaSource mediaSource, boolean resetPosition) {
verifyApplicationThread(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
player.addMediaItems(index, mediaItems); player.addMediaItems(index, mediaItems);
} }
@Override @Override
public void addMediaSource(MediaSource mediaSource) { public void addMediaSource(MediaSource mediaSource) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.addMediaSource(mediaSource); player.addMediaSource(mediaSource);
} }
@Override @Override
public void addMediaSource(int index, MediaSource mediaSource) { public void addMediaSource(int index, MediaSource mediaSource) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.addMediaSource(index, mediaSource); player.addMediaSource(index, mediaSource);
} }
@Override @Override
public void addMediaSources(List<MediaSource> mediaSources) { public void addMediaSources(List<MediaSource> mediaSources) {
verifyApplicationThread(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
player.removeMediaItems(fromIndex, toIndex); player.removeMediaItems(fromIndex, toIndex);
} }
@Override @Override
public void setShuffleOrder(ShuffleOrder shuffleOrder) { public void setShuffleOrder(ShuffleOrder shuffleOrder) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setShuffleOrder(shuffleOrder); player.setShuffleOrder(shuffleOrder);
} }
@Override @Override
public void setPlayWhenReady(boolean playWhenReady) { public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread(); blockUntilConstructorFinished();
@AudioFocusManager.PlayerCommand player.setPlayWhenReady(playWhenReady);
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
public boolean getPlayWhenReady() { public boolean getPlayWhenReady() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getPlayWhenReady(); return player.getPlayWhenReady();
} }
@Override @Override
public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems); player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems);
} }
@Override @Override
public boolean getPauseAtEndOfMediaItems() { public boolean getPauseAtEndOfMediaItems() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getPauseAtEndOfMediaItems(); return player.getPauseAtEndOfMediaItems();
} }
@Override @Override
public @RepeatMode int getRepeatMode() { public @RepeatMode int getRepeatMode() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getRepeatMode(); return player.getRepeatMode();
} }
@Override @Override
public void setRepeatMode(@RepeatMode int repeatMode) { public void setRepeatMode(@RepeatMode int repeatMode) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setRepeatMode(repeatMode); player.setRepeatMode(repeatMode);
} }
@Override @Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) { public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setShuffleModeEnabled(shuffleModeEnabled); player.setShuffleModeEnabled(shuffleModeEnabled);
} }
@Override @Override
public boolean getShuffleModeEnabled() { public boolean getShuffleModeEnabled() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getShuffleModeEnabled(); return player.getShuffleModeEnabled();
} }
@Override @Override
public boolean isLoading() { public boolean isLoading() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.isLoading(); return player.isLoading();
} }
@Override @Override
public void seekTo(int mediaItemIndex, long positionMs) { public void seekTo(int mediaItemIndex, long positionMs) {
verifyApplicationThread(); blockUntilConstructorFinished();
analyticsCollector.notifySeekStarted();
player.seekTo(mediaItemIndex, positionMs); player.seekTo(mediaItemIndex, positionMs);
} }
@Override @Override
public long getSeekBackIncrement() { public long getSeekBackIncrement() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getSeekBackIncrement(); return player.getSeekBackIncrement();
} }
@Override @Override
public long getSeekForwardIncrement() { public long getSeekForwardIncrement() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getSeekForwardIncrement(); return player.getSeekForwardIncrement();
} }
@Override @Override
public long getMaxSeekToPreviousPosition() { public long getMaxSeekToPreviousPosition() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getMaxSeekToPreviousPosition(); return player.getMaxSeekToPreviousPosition();
} }
@Override @Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) { public void setPlaybackParameters(PlaybackParameters playbackParameters) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setPlaybackParameters(playbackParameters); player.setPlaybackParameters(playbackParameters);
} }
@Override @Override
public PlaybackParameters getPlaybackParameters() { public PlaybackParameters getPlaybackParameters() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getPlaybackParameters(); return player.getPlaybackParameters();
} }
@Override @Override
public void setSeekParameters(@Nullable SeekParameters seekParameters) { public void setSeekParameters(@Nullable SeekParameters seekParameters) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setSeekParameters(seekParameters); player.setSeekParameters(seekParameters);
} }
@Override @Override
public SeekParameters getSeekParameters() { public SeekParameters getSeekParameters() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getSeekParameters(); return player.getSeekParameters();
} }
@Override @Override
public void setForegroundMode(boolean foregroundMode) { public void setForegroundMode(boolean foregroundMode) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setForegroundMode(foregroundMode); player.setForegroundMode(foregroundMode);
} }
@Override @Override
public void stop() { public void stop() {
stop(/* reset= */ false); blockUntilConstructorFinished();
player.stop();
} }
@Deprecated @Deprecated
@Override @Override
public void stop(boolean reset) { public void stop(boolean reset) {
verifyApplicationThread(); blockUntilConstructorFinished();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
player.stop(reset); player.stop(reset);
currentCues = Collections.emptyList();
} }
@Override @Override
public void release() { public void release() {
verifyApplicationThread(); blockUntilConstructorFinished();
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(); blockUntilConstructorFinished();
return player.createMessage(target); return player.createMessage(target);
} }
@Override @Override
public int getRendererCount() { public int getRendererCount() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getRendererCount(); return player.getRendererCount();
} }
@Override @Override
public @C.TrackType int getRendererType(int index) { public @C.TrackType int getRendererType(int index) {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getRendererType(index); return player.getRendererType(index);
} }
@Override @Override
public Renderer getRenderer(int index) { public Renderer getRenderer(int index) {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getRenderer(index); return player.getRenderer(index);
} }
@Override @Override
public TrackSelector getTrackSelector() { public TrackSelector getTrackSelector() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getTrackSelector(); return player.getTrackSelector();
} }
@Override @Override
public TrackGroupArray getCurrentTrackGroups() { public TrackGroupArray getCurrentTrackGroups() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentTrackGroups(); return player.getCurrentTrackGroups();
} }
@Override @Override
public TrackSelectionArray getCurrentTrackSelections() { public TrackSelectionArray getCurrentTrackSelections() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentTrackSelections(); return player.getCurrentTrackSelections();
} }
@Override @Override
public TracksInfo getCurrentTracksInfo() { public TracksInfo getCurrentTracksInfo() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentTracksInfo(); return player.getCurrentTracksInfo();
} }
@Override @Override
public TrackSelectionParameters getTrackSelectionParameters() { public TrackSelectionParameters getTrackSelectionParameters() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getTrackSelectionParameters(); return player.getTrackSelectionParameters();
} }
@Override @Override
public void setTrackSelectionParameters(TrackSelectionParameters parameters) { public void setTrackSelectionParameters(TrackSelectionParameters parameters) {
verifyApplicationThread(); blockUntilConstructorFinished();
player.setTrackSelectionParameters(parameters); player.setTrackSelectionParameters(parameters);
} }
@Override @Override
public MediaMetadata getMediaMetadata() { public MediaMetadata getMediaMetadata() {
blockUntilConstructorFinished();
return player.getMediaMetadata(); return player.getMediaMetadata();
} }
@Override @Override
public MediaMetadata getPlaylistMetadata() { public MediaMetadata getPlaylistMetadata() {
blockUntilConstructorFinished();
return player.getPlaylistMetadata(); return player.getPlaylistMetadata();
} }
@Override @Override
public void setPlaylistMetadata(MediaMetadata mediaMetadata) { public void setPlaylistMetadata(MediaMetadata mediaMetadata) {
blockUntilConstructorFinished();
player.setPlaylistMetadata(mediaMetadata); player.setPlaylistMetadata(mediaMetadata);
} }
@Override @Override
public Timeline getCurrentTimeline() { public Timeline getCurrentTimeline() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentTimeline(); return player.getCurrentTimeline();
} }
@Override @Override
public int getCurrentPeriodIndex() { public int getCurrentPeriodIndex() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentPeriodIndex(); return player.getCurrentPeriodIndex();
} }
@Override @Override
public int getCurrentMediaItemIndex() { public int getCurrentMediaItemIndex() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentMediaItemIndex(); return player.getCurrentMediaItemIndex();
} }
@Override @Override
public long getDuration() { public long getDuration() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getDuration(); return player.getDuration();
} }
@Override @Override
public long getCurrentPosition() { public long getCurrentPosition() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentPosition(); return player.getCurrentPosition();
} }
@Override @Override
public long getBufferedPosition() { public long getBufferedPosition() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getBufferedPosition(); return player.getBufferedPosition();
} }
@Override @Override
public long getTotalBufferedDuration() { public long getTotalBufferedDuration() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getTotalBufferedDuration(); return player.getTotalBufferedDuration();
} }
@Override @Override
public boolean isPlayingAd() { public boolean isPlayingAd() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.isPlayingAd(); return player.isPlayingAd();
} }
@Override @Override
public int getCurrentAdGroupIndex() { public int getCurrentAdGroupIndex() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentAdGroupIndex(); return player.getCurrentAdGroupIndex();
} }
@Override @Override
public int getCurrentAdIndexInAdGroup() { public int getCurrentAdIndexInAdGroup() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getCurrentAdIndexInAdGroup(); return player.getCurrentAdIndexInAdGroup();
} }
@Override @Override
public long getContentPosition() { public long getContentPosition() {
verifyApplicationThread(); blockUntilConstructorFinished();
return player.getContentPosition(); return player.getContentPosition();
} }
@Override @Override
public long getContentBufferedPosition() { public long getContentBufferedPosition() {
verifyApplicationThread(); blockUntilConstructorFinished();
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); blockUntilConstructorFinished();
player.setHandleWakeLock(handleWakeLock);
} }
@Override @Override
public void setWakeMode(@C.WakeMode int wakeMode) { public void setWakeMode(@C.WakeMode int wakeMode) {
verifyApplicationThread(); blockUntilConstructorFinished();
switch (wakeMode) { player.setWakeMode(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(); blockUntilConstructorFinished();
return deviceInfo; return player.getDeviceInfo();
} }
@Override @Override
public int getDeviceVolume() { public int getDeviceVolume() {
verifyApplicationThread(); blockUntilConstructorFinished();
return streamVolumeManager.getVolume(); return player.getDeviceVolume();
} }
@Override @Override
public boolean isDeviceMuted() { public boolean isDeviceMuted() {
verifyApplicationThread(); blockUntilConstructorFinished();
return streamVolumeManager.isMuted(); return player.isDeviceMuted();
} }
@Override @Override
public void setDeviceVolume(int volume) { public void setDeviceVolume(int volume) {
verifyApplicationThread(); blockUntilConstructorFinished();
streamVolumeManager.setVolume(volume); player.setDeviceVolume(volume);
} }
@Override @Override
public void increaseDeviceVolume() { public void increaseDeviceVolume() {
verifyApplicationThread(); blockUntilConstructorFinished();
streamVolumeManager.increaseVolume(); player.increaseDeviceVolume();
} }
@Override @Override
public void decreaseDeviceVolume() { public void decreaseDeviceVolume() {
verifyApplicationThread(); blockUntilConstructorFinished();
streamVolumeManager.decreaseVolume(); player.decreaseDeviceVolume();
} }
@Override @Override
public void setDeviceMuted(boolean muted) { public void setDeviceMuted(boolean muted) {
verifyApplicationThread(); blockUntilConstructorFinished();
streamVolumeManager.setMuted(muted); player.setDeviceMuted(muted);
} }
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; blockUntilConstructorFinished();
} 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));
}
} }
/** private void blockUntilConstructorFinished() {
* 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 constructor may be executed on a background thread. Wait with accessing the player from
// the app thread until the constructor finished executing. // the app thread until the constructor finished executing.
constructorFinished.blockUninterruptible(); 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