diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 6cfdc10e35..823f3ffd1a 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -15,31 +15,20 @@ */ package androidx.media3.exoplayer; -import static androidx.media3.common.C.TRACK_TYPE_AUDIO; -import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION; -import static androidx.media3.common.C.TRACK_TYPE_VIDEO; -import static androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME; import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS; -import static androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES; import static androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; -import static androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME; import static androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; -import static androidx.media3.common.Player.COMMAND_GET_TEXT; import static androidx.media3.common.Player.COMMAND_GET_TIMELINE; import static androidx.media3.common.Player.COMMAND_GET_TRACK_INFOS; -import static androidx.media3.common.Player.COMMAND_GET_VOLUME; import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE; import static androidx.media3.common.Player.COMMAND_PREPARE; import static androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM; -import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME; import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA; import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE; import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE; import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH; import static androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; -import static androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE; -import static androidx.media3.common.Player.COMMAND_SET_VOLUME; import static androidx.media3.common.Player.COMMAND_STOP; import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; import static androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL; @@ -53,7 +42,6 @@ import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIS import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT; import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK; import static androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE; -import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS; import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; import static androidx.media3.common.Player.STATE_BUFFERING; import static androidx.media3.common.Player.STATE_ENDED; @@ -63,41 +51,18 @@ import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.castNonNull; -import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_ATTRIBUTES; -import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_SESSION_ID; -import static androidx.media3.exoplayer.Renderer.MSG_SET_AUX_EFFECT_INFO; -import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; -import static androidx.media3.exoplayer.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; -import static androidx.media3.exoplayer.Renderer.MSG_SET_SCALING_MODE; -import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; -import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; -import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_OUTPUT; -import static androidx.media3.exoplayer.Renderer.MSG_SET_VOLUME; import static java.lang.Math.max; import static java.lang.Math.min; 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.os.Handler; import android.os.Looper; 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.Nullable; import androidx.annotation.RequiresApi; -import androidx.media3.common.AudioAttributes; -import androidx.media3.common.AuxEffectInfo; import androidx.media3.common.C; -import androidx.media3.common.DeviceInfo; import androidx.media3.common.Format; import androidx.media3.common.IllegalSeekPositionException; import androidx.media3.common.MediaItem; @@ -118,49 +83,34 @@ import androidx.media3.common.Player.PositionInfo; import androidx.media3.common.Player.RepeatMode; import androidx.media3.common.Player.State; import androidx.media3.common.Player.TimelineChangeReason; -import androidx.media3.common.PriorityTaskManager; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroupArray; import androidx.media3.common.TrackSelectionArray; import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TracksInfo; -import androidx.media3.common.VideoSize; -import androidx.media3.common.text.Cue; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Clock; -import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.ExoPlayer.AudioOffloadListener; import androidx.media3.exoplayer.PlayerMessage.Target; -import androidx.media3.exoplayer.Renderer.MessageType; import androidx.media3.exoplayer.analytics.AnalyticsCollector; -import androidx.media3.exoplayer.analytics.AnalyticsListener; import androidx.media3.exoplayer.analytics.PlayerId; -import androidx.media3.exoplayer.audio.AudioRendererEventListener; -import androidx.media3.exoplayer.metadata.MetadataOutput; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.source.ShuffleOrder; -import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.trackselection.TrackSelector; import androidx.media3.exoplayer.trackselection.TrackSelectorResult; import androidx.media3.exoplayer.upstream.BandwidthMeter; -import androidx.media3.exoplayer.video.VideoDecoderOutputBufferRenderer; -import androidx.media3.exoplayer.video.VideoFrameMetadataListener; -import androidx.media3.exoplayer.video.VideoRendererEventListener; -import androidx.media3.exoplayer.video.spherical.CameraMotionListener; -import androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.TimeoutException; /** A helper class for the {@link SimpleExoPlayer} implementation of {@link ExoPlayer}. */ /* package */ final class ExoPlayerImpl { @@ -181,18 +131,13 @@ import java.util.concurrent.TimeoutException; /* package */ final TrackSelectorResult emptyTrackSelectorResult; /* package */ final Commands permanentAvailableCommands; - private final ConditionVariable constructorFinished; - private final Context applicationContext; private final Player wrappingPlayer; private final Renderer[] renderers; private final TrackSelector trackSelector; private final HandlerWrapper playbackInfoUpdateHandler; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; - - @SuppressWarnings("deprecation") // TODO(b/187152483): Merge with non-deprecated listeners. - private final ListenerSet eventListeners; - + private final ListenerSet listeners; private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; private final Timeline.Window window; @@ -205,15 +150,6 @@ import java.util.concurrent.TimeoutException; private final long seekBackIncrementMs; private final long seekForwardIncrementMs; private final Clock clock; - private final ComponentListener componentListener; - private final FrameMetadataListener frameMetadataListener; - private final CopyOnWriteArraySet 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; private boolean shuffleModeEnabled; @@ -228,35 +164,6 @@ import java.util.concurrent.TimeoutException; private Commands availableCommands; private MediaMetadata mediaMetadata; 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 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 // sources. @@ -270,178 +177,145 @@ import java.util.concurrent.TimeoutException; private int maskingPeriodIndex; 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") - public ExoPlayerImpl(ExoPlayer.Builder builder, Player wrappingPlayer) { - constructorFinished = new ConditionVariable(); - try { - Log.i( - TAG, - "Init " - + Integer.toHexString(System.identityHashCode(this)) - + " [" - + MediaLibraryInfo.VERSION_SLASHY - + "] [" - + Util.DEVICE_DEBUG_INFO - + "]"); - applicationContext = builder.context.getApplicationContext(); - analyticsCollector = builder.analyticsCollectorSupplier.get(); - priorityTaskManager = builder.priorityTaskManager; - audioAttributes = builder.audioAttributes; - videoScalingMode = builder.videoScalingMode; - videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy; - skipSilenceEnabled = builder.skipSilenceEnabled; - detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; - componentListener = new ComponentListener(); - frameMetadataListener = new FrameMetadataListener(); - listeners = new CopyOnWriteArraySet<>(); - Handler eventHandler = new Handler(builder.looper); - renderers = - builder - .renderersFactorySupplier - .get() - .createRenderers( - eventHandler, - componentListener, - componentListener, - componentListener, - componentListener); - checkState(renderers.length > 0); - this.trackSelector = builder.trackSelectorSupplier.get(); - this.mediaSourceFactory = builder.mediaSourceFactorySupplier.get(); - this.bandwidthMeter = builder.bandwidthMeterSupplier.get(); - this.useLazyPreparation = builder.useLazyPreparation; - this.seekParameters = builder.seekParameters; - this.seekBackIncrementMs = builder.seekBackIncrementMs; - this.seekForwardIncrementMs = builder.seekForwardIncrementMs; - this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems; - this.applicationLooper = builder.looper; - this.clock = builder.clock; - this.wrappingPlayer = wrappingPlayer; - eventListeners = - new ListenerSet<>( - applicationLooper, - clock, - (listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags))); - audioOffloadListeners = new CopyOnWriteArraySet<>(); - mediaSourceHolderSnapshots = new ArrayList<>(); - shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); - emptyTrackSelectorResult = - new TrackSelectorResult( - new RendererConfiguration[renderers.length], - new ExoTrackSelection[renderers.length], - TracksInfo.EMPTY, - /* info= */ null); - period = new Timeline.Period(); - window = new Timeline.Window(); - permanentAvailableCommands = - new Commands.Builder() - .addAll( - COMMAND_PLAY_PAUSE, - COMMAND_PREPARE, - COMMAND_STOP, - COMMAND_SET_SPEED_AND_PITCH, - COMMAND_SET_SHUFFLE_MODE, - COMMAND_SET_REPEAT_MODE, - COMMAND_GET_CURRENT_MEDIA_ITEM, - COMMAND_GET_TIMELINE, - COMMAND_GET_MEDIA_ITEMS_METADATA, - COMMAND_SET_MEDIA_ITEMS_METADATA, - COMMAND_CHANGE_MEDIA_ITEMS, - COMMAND_GET_TRACK_INFOS, - 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) - .addIf( - COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) - .build(); - availableCommands = - new Commands.Builder() - .addAll(permanentAvailableCommands) - .add(COMMAND_SEEK_TO_DEFAULT_POSITION) - .add(COMMAND_SEEK_TO_MEDIA_ITEM) - .build(); - playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); - playbackInfoUpdateListener = - playbackInfoUpdate -> - playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); - playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); - analyticsCollector.setPlayer(wrappingPlayer, applicationLooper); - PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.createPlayerId(); - internalPlayer = - new ExoPlayerImplInternal( - renderers, - trackSelector, - emptyTrackSelectorResult, - builder.loadControlSupplier.get(), - bandwidthMeter, - repeatMode, - shuffleModeEnabled, - analyticsCollector, - seekParameters, - 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(); - } + public ExoPlayerImpl( + Renderer[] renderers, + TrackSelector trackSelector, + MediaSource.Factory mediaSourceFactory, + LoadControl loadControl, + BandwidthMeter bandwidthMeter, + AnalyticsCollector analyticsCollector, + boolean useLazyPreparation, + SeekParameters seekParameters, + long seekBackIncrementMs, + long seekForwardIncrementMs, + LivePlaybackSpeedControl livePlaybackSpeedControl, + long releaseTimeoutMs, + boolean pauseAtEndOfMediaItems, + Clock clock, + Looper applicationLooper, + Player wrappingPlayer, + Commands additionalPermanentAvailableCommands) { + Log.i( + TAG, + "Init " + + Integer.toHexString(System.identityHashCode(this)) + + " [" + + MediaLibraryInfo.VERSION_SLASHY + + "] [" + + Util.DEVICE_DEBUG_INFO + + "]"); + checkState(renderers.length > 0); + this.renderers = checkNotNull(renderers); + this.trackSelector = checkNotNull(trackSelector); + this.mediaSourceFactory = mediaSourceFactory; + this.bandwidthMeter = bandwidthMeter; + this.analyticsCollector = analyticsCollector; + this.useLazyPreparation = useLazyPreparation; + this.seekParameters = seekParameters; + this.seekBackIncrementMs = seekBackIncrementMs; + this.seekForwardIncrementMs = seekForwardIncrementMs; + this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; + this.applicationLooper = applicationLooper; + this.clock = clock; + this.wrappingPlayer = wrappingPlayer; + repeatMode = Player.REPEAT_MODE_OFF; + listeners = + new ListenerSet<>( + applicationLooper, + clock, + (listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags))); + audioOffloadListeners = new CopyOnWriteArraySet<>(); + mediaSourceHolderSnapshots = new ArrayList<>(); + shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); + emptyTrackSelectorResult = + new TrackSelectorResult( + new RendererConfiguration[renderers.length], + new ExoTrackSelection[renderers.length], + TracksInfo.EMPTY, + /* info= */ null); + period = new Timeline.Period(); + window = new Timeline.Window(); + permanentAvailableCommands = + new Commands.Builder() + .addAll( + COMMAND_PLAY_PAUSE, + COMMAND_PREPARE, + COMMAND_STOP, + COMMAND_SET_SPEED_AND_PITCH, + COMMAND_SET_SHUFFLE_MODE, + COMMAND_SET_REPEAT_MODE, + COMMAND_GET_CURRENT_MEDIA_ITEM, + COMMAND_GET_TIMELINE, + COMMAND_GET_MEDIA_ITEMS_METADATA, + COMMAND_SET_MEDIA_ITEMS_METADATA, + COMMAND_CHANGE_MEDIA_ITEMS, + COMMAND_GET_TRACK_INFOS) + .addIf(COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) + .addAll(additionalPermanentAvailableCommands) + .build(); + availableCommands = + new Commands.Builder() + .addAll(permanentAvailableCommands) + .add(COMMAND_SEEK_TO_DEFAULT_POSITION) + .add(COMMAND_SEEK_TO_MEDIA_ITEM) + .build(); + mediaMetadata = MediaMetadata.EMPTY; + playlistMetadata = MediaMetadata.EMPTY; + staticAndDynamicMediaMetadata = MediaMetadata.EMPTY; + maskingWindowIndex = C.INDEX_UNSET; + playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null); + playbackInfoUpdateListener = + playbackInfoUpdate -> + playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); + playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); + analyticsCollector.setPlayer(wrappingPlayer, applicationLooper); + addListener(analyticsCollector); + bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); + PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() : Api31.createPlayerId(); + internalPlayer = + new ExoPlayerImplInternal( + renderers, + trackSelector, + emptyTrackSelectorResult, + loadControl, + bandwidthMeter, + repeatMode, + shuffleModeEnabled, + analyticsCollector, + seekParameters, + livePlaybackSpeedControl, + releaseTimeoutMs, + pauseAtEndOfMediaItems, + applicationLooper, + clock, + playbackInfoUpdateListener, + playerId); } /** @@ -459,72 +333,63 @@ import java.util.concurrent.TimeoutException; } public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { - verifyApplicationThread(); internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); } public boolean experimentalIsSleepingForOffload() { - verifyApplicationThread(); return playbackInfo.sleepingForOffload; } public Looper getPlaybackLooper() { - // Don't verify application thread. We allow calls to this method from any thread. return internalPlayer.getPlaybackLooper(); } public Looper getApplicationLooper() { - // Don't verify application thread. We allow calls to this method from any thread. return applicationLooper; } public Clock getClock() { - // Don't verify application thread. We allow calls to this method from any thread. return clock; } + public void addListener(Listener listener) { + addEventListener(listener); + } + @SuppressWarnings("deprecation") // Register deprecated EventListener. public void addEventListener(Player.EventListener eventListener) { - // Don't verify application thread. We allow calls to this method from any thread. - eventListeners.add(eventListener); + listeners.add(eventListener); } @SuppressWarnings("deprecation") // Deregister deprecated EventListener. public void removeEventListener(Player.EventListener eventListener) { - // Don't verify application thread. We allow calls to this method from any thread. - eventListeners.remove(eventListener); + listeners.remove(eventListener); } public void addAudioOffloadListener(AudioOffloadListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.add(listener); } public void removeAudioOffloadListener(AudioOffloadListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.remove(listener); } public Commands getAvailableCommands() { - verifyApplicationThread(); return availableCommands; } @State public int getPlaybackState() { - verifyApplicationThread(); return playbackInfo.playbackState; } @PlaybackSuppressionReason public int getPlaybackSuppressionReason() { - verifyApplicationThread(); return playbackInfo.playbackSuppressionReason; } @Nullable public ExoPlaybackException getPlayerError() { - verifyApplicationThread(); return playbackInfo.playbackError; } @@ -535,12 +400,6 @@ import java.util.concurrent.TimeoutException; } 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) { return; } @@ -570,7 +429,6 @@ import java.util.concurrent.TimeoutException; */ @Deprecated public void prepare(MediaSource mediaSource) { - verifyApplicationThread(); setMediaSource(mediaSource); prepare(); } @@ -581,44 +439,36 @@ import java.util.concurrent.TimeoutException; */ @Deprecated public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { - verifyApplicationThread(); setMediaSource(mediaSource, resetPosition); prepare(); } public void setMediaItems(List mediaItems, boolean resetPosition) { - verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), resetPosition); } public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { - verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); } public void setMediaSource(MediaSource mediaSource) { - verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource)); } public void setMediaSource(MediaSource mediaSource, long startPositionMs) { - verifyApplicationThread(); setMediaSources( Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs); } public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { - verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource), resetPosition); } public void setMediaSources(List mediaSources) { - verifyApplicationThread(); setMediaSources(mediaSources, /* resetPosition= */ true); } public void setMediaSources(List mediaSources, boolean resetPosition) { - verifyApplicationThread(); setMediaSourcesInternal( mediaSources, /* startWindowIndex= */ C.INDEX_UNSET, @@ -628,34 +478,28 @@ import java.util.concurrent.TimeoutException; public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { - verifyApplicationThread(); setMediaSourcesInternal( mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); } public void addMediaItems(int index, List mediaItems) { - verifyApplicationThread(); index = min(index, mediaSourceHolderSnapshots.size()); addMediaSources(index, createMediaSources(mediaItems)); } public void addMediaSource(MediaSource mediaSource) { - verifyApplicationThread(); addMediaSources(Collections.singletonList(mediaSource)); } public void addMediaSource(int index, MediaSource mediaSource) { - verifyApplicationThread(); addMediaSources(index, Collections.singletonList(mediaSource)); } public void addMediaSources(List mediaSources) { - verifyApplicationThread(); addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources); } public void addMediaSources(int index, List mediaSources) { - verifyApplicationThread(); Assertions.checkArgument(index >= 0); Timeline oldTimeline = getCurrentTimeline(); pendingOperationAcks++; @@ -679,7 +523,6 @@ import java.util.concurrent.TimeoutException; } public void removeMediaItems(int fromIndex, int toIndex) { - verifyApplicationThread(); toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex); boolean positionDiscontinuity = @@ -696,7 +539,6 @@ import java.util.concurrent.TimeoutException; } public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { - verifyApplicationThread(); Assertions.checkArgument( fromIndex >= 0 && fromIndex <= toIndex @@ -725,7 +567,6 @@ import java.util.concurrent.TimeoutException; } public void setShuffleOrder(ShuffleOrder shuffleOrder) { - verifyApplicationThread(); Timeline timeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( @@ -748,7 +589,6 @@ import java.util.concurrent.TimeoutException; } public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { - verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { return; } @@ -757,18 +597,9 @@ import java.util.concurrent.TimeoutException; } public boolean getPauseAtEndOfMediaItems() { - verifyApplicationThread(); 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( boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason, @@ -793,54 +624,46 @@ import java.util.concurrent.TimeoutException; } public boolean getPlayWhenReady() { - verifyApplicationThread(); return playbackInfo.playWhenReady; } public void setRepeatMode(@RepeatMode int repeatMode) { - verifyApplicationThread(); if (this.repeatMode != repeatMode) { this.repeatMode = repeatMode; internalPlayer.setRepeatMode(repeatMode); - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); updateAvailableCommands(); - eventListeners.flushEvents(); + listeners.flushEvents(); } } @RepeatMode public int getRepeatMode() { - verifyApplicationThread(); return repeatMode; } public void setShuffleModeEnabled(boolean shuffleModeEnabled) { - verifyApplicationThread(); if (this.shuffleModeEnabled != shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); updateAvailableCommands(); - eventListeners.flushEvents(); + listeners.flushEvents(); } } public boolean getShuffleModeEnabled() { - verifyApplicationThread(); return shuffleModeEnabled; } public boolean isLoading() { - verifyApplicationThread(); return playbackInfo.isLoading; } public void seekTo(int mediaItemIndex, long positionMs) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); Timeline timeline = playbackInfo.timeline; if (mediaItemIndex < 0 || (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) { @@ -881,22 +704,18 @@ import java.util.concurrent.TimeoutException; } public long getSeekBackIncrement() { - verifyApplicationThread(); return seekBackIncrementMs; } public long getSeekForwardIncrement() { - verifyApplicationThread(); return seekForwardIncrementMs; } public long getMaxSeekToPreviousPosition() { - verifyApplicationThread(); return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; } public void setPlaybackParameters(PlaybackParameters playbackParameters) { - verifyApplicationThread(); if (playbackParameters == null) { playbackParameters = PlaybackParameters.DEFAULT; } @@ -918,12 +737,10 @@ import java.util.concurrent.TimeoutException; } public PlaybackParameters getPlaybackParameters() { - verifyApplicationThread(); return playbackInfo.playbackParameters; } public void setSeekParameters(@Nullable SeekParameters seekParameters) { - verifyApplicationThread(); if (seekParameters == null) { seekParameters = SeekParameters.DEFAULT; } @@ -934,12 +751,10 @@ import java.util.concurrent.TimeoutException; } public SeekParameters getSeekParameters() { - verifyApplicationThread(); return seekParameters; } public void setForegroundMode(boolean foregroundMode) { - verifyApplicationThread(); if (this.foregroundMode != foregroundMode) { this.foregroundMode = foregroundMode; if (!internalPlayer.setForegroundMode(foregroundMode)) { @@ -953,15 +768,8 @@ import java.util.concurrent.TimeoutException; } } - public void stop() { - stop(/* reset= */ false); - } - public void stop(boolean reset) { - verifyApplicationThread(); - audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); stop(reset, /* error= */ null); - currentCues = ImmutableList.of(); } /** @@ -1014,19 +822,9 @@ import java.util.concurrent.TimeoutException; + "] [" + MediaLibraryInfo.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()) { // One of the renderers timed out releasing its resources. - eventListeners.sendEvent( + listeners.sendEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError( @@ -1034,34 +832,26 @@ import java.util.concurrent.TimeoutException; new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE), PlaybackException.ERROR_CODE_TIMEOUT))); } - eventListeners.release(); + listeners.release(); playbackInfoUpdateHandler.removeCallbacksAndMessages(null); bandwidthMeter.removeEventListener(analyticsCollector); playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId); playbackInfo.bufferedPositionUs = playbackInfo.positionUs; 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) { - verifyApplicationThread(); - return createMessageInternal(target); + return new PlayerMessage( + internalPlayer, + target, + playbackInfo.timeline, + getCurrentMediaItemIndex(), + clock, + internalPlayer.getPlaybackLooper()); } public int getCurrentPeriodIndex() { - verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingPeriodIndex; } else { @@ -1070,13 +860,11 @@ import java.util.concurrent.TimeoutException; } public int getCurrentMediaItemIndex() { - verifyApplicationThread(); int currentWindowIndex = getCurrentWindowIndexInternal(); return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex; } public long getDuration() { - verifyApplicationThread(); if (isPlayingAd()) { MediaPeriodId periodId = playbackInfo.periodId; playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period); @@ -1094,12 +882,10 @@ import java.util.concurrent.TimeoutException; } public long getCurrentPosition() { - verifyApplicationThread(); return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } public long getBufferedPosition() { - verifyApplicationThread(); if (isPlayingAd()) { return playbackInfo.loadingMediaPeriodId.equals(playbackInfo.periodId) ? Util.usToMs(playbackInfo.bufferedPositionUs) @@ -1109,27 +895,22 @@ import java.util.concurrent.TimeoutException; } public long getTotalBufferedDuration() { - verifyApplicationThread(); return Util.usToMs(playbackInfo.totalBufferedDurationUs); } public boolean isPlayingAd() { - verifyApplicationThread(); return playbackInfo.periodId.isAd(); } public int getCurrentAdGroupIndex() { - verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; } public int getCurrentAdIndexInAdGroup() { - verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } public long getContentPosition() { - verifyApplicationThread(); if (isPlayingAd()) { playbackInfo.timeline.getPeriodByUid(playbackInfo.periodId.periodUid, period); return playbackInfo.requestedContentPositionUs == C.TIME_UNSET @@ -1144,7 +925,6 @@ import java.util.concurrent.TimeoutException; } public long getContentBufferedPosition() { - verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingWindowPositionMs; } @@ -1168,63 +948,53 @@ import java.util.concurrent.TimeoutException; } public int getRendererCount() { - verifyApplicationThread(); return renderers.length; } public @C.TrackType int getRendererType(int index) { - verifyApplicationThread(); return renderers[index].getTrackType(); } public Renderer getRenderer(int index) { - verifyApplicationThread(); return renderers[index]; } public TrackSelector getTrackSelector() { - verifyApplicationThread(); return trackSelector; } public TrackGroupArray getCurrentTrackGroups() { - verifyApplicationThread(); return playbackInfo.trackGroups; } public TrackSelectionArray getCurrentTrackSelections() { - verifyApplicationThread(); return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); } public TracksInfo getCurrentTracksInfo() { - verifyApplicationThread(); return playbackInfo.trackSelectorResult.tracksInfo; } public TrackSelectionParameters getTrackSelectionParameters() { - verifyApplicationThread(); return trackSelector.getParameters(); } public void setTrackSelectionParameters(TrackSelectionParameters parameters) { - verifyApplicationThread(); if (!trackSelector.isSetParametersSupported() || parameters.equals(trackSelector.getParameters())) { return; } trackSelector.setParameters(parameters); - eventListeners.queueEvent( + listeners.queueEvent( EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(parameters)); } public MediaMetadata getMediaMetadata() { - verifyApplicationThread(); return mediaMetadata; } - private void onMetadata(Metadata metadata) { + public void onMetadata(Metadata metadata) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); @@ -1234,452 +1004,29 @@ import java.util.concurrent.TimeoutException; return; } mediaMetadata = newMediaMetadata; - eventListeners.sendEvent( + listeners.sendEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); } public MediaMetadata getPlaylistMetadata() { - verifyApplicationThread(); return playlistMetadata; } public void setPlaylistMetadata(MediaMetadata playlistMetadata) { - verifyApplicationThread(); checkNotNull(playlistMetadata); if (playlistMetadata.equals(this.playlistMetadata)) { return; } this.playlistMetadata = playlistMetadata; - eventListeners.sendEvent( + listeners.sendEvent( EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); } public Timeline getCurrentTimeline() { - verifyApplicationThread(); 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 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() { if (playbackInfo.timeline.isEmpty()) { return maskingWindowIndex; @@ -1815,7 +1162,7 @@ import java.util.concurrent.TimeoutException; mediaMetadata = newMediaMetadata; if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); } @@ -1824,7 +1171,7 @@ import java.util.concurrent.TimeoutException; getPreviousPositionInfo( positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); @@ -1834,16 +1181,16 @@ import java.util.concurrent.TimeoutException; } if (mediaItemTransitioned) { @Nullable final MediaItem finalMediaItem = mediaItem; - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); } if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError)); if (newPlaybackInfo.playbackError != null) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError)); } @@ -1852,21 +1199,21 @@ import java.util.concurrent.TimeoutException; trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); TrackSelectionArray newSelection = new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections); - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection)); - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo)); } if (metadataChanged) { final MediaMetadata finalMediaMetadata = mediaMetadata; - eventListeners.queueEvent( + listeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); } if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, listener -> { listener.onLoadingChanged(newPlaybackInfo.isLoading); @@ -1875,19 +1222,19 @@ import java.util.concurrent.TimeoutException; } if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState || previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { - eventListeners.queueEvent( + listeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onPlayerStateChanged( newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState)); } if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); } if (previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged( @@ -1895,27 +1242,27 @@ import java.util.concurrent.TimeoutException; } if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged( newPlaybackInfo.playbackSuppressionReason)); } if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo))); } if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters)); } if (seekProcessed) { - eventListeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); + listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } updateAvailableCommands(); - eventListeners.flushEvents(); + listeners.flushEvents(); if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) { for (AudioOffloadListener listener : audioOffloadListeners) { @@ -2072,7 +1419,7 @@ import java.util.concurrent.TimeoutException; Commands previousAvailableCommands = availableCommands; availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands); if (!availableCommands.equals(previousAvailableCommands)) { - eventListeners.queueEvent( + listeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(availableCommands)); } @@ -2363,17 +1710,6 @@ import java.util.concurrent.TimeoutException; 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. * @@ -2391,235 +1727,6 @@ import java.util.concurrent.TimeoutException; 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 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 - * not 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. - * - *

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) { return playbackInfo.playbackState == Player.STATE_READY && playbackInfo.playWhenReady @@ -2648,410 +1755,6 @@ import java.util.concurrent.TimeoutException; } } - // 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 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) private static final class Api31 { private Api31() {} diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java index bffbe5e453..bc9f029e28 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java @@ -15,7 +15,28 @@ */ package androidx.media3.exoplayer; +import static androidx.media3.common.C.TRACK_TYPE_AUDIO; +import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION; +import static androidx.media3.common.C.TRACK_TYPE_VIDEO; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_ATTRIBUTES; +import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_SESSION_ID; +import static androidx.media3.exoplayer.Renderer.MSG_SET_AUX_EFFECT_INFO; +import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; +import static androidx.media3.exoplayer.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; +import static androidx.media3.exoplayer.Renderer.MSG_SET_SCALING_MODE; +import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; +import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; +import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_OUTPUT; +import static androidx.media3.exoplayer.Renderer.MSG_SET_VOLUME; + 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.view.Surface; import android.view.SurfaceHolder; @@ -32,6 +53,8 @@ import androidx.media3.common.DeviceInfo; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaMetadata; +import androidx.media3.common.Metadata; +import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackParameters; import androidx.media3.common.Player; import androidx.media3.common.PriorityTaskManager; @@ -43,18 +66,32 @@ import androidx.media3.common.TracksInfo; import androidx.media3.common.VideoSize; import androidx.media3.common.text.Cue; import androidx.media3.common.util.Clock; +import androidx.media3.common.util.ConditionVariable; +import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import androidx.media3.exoplayer.Renderer.MessageType; import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.AnalyticsListener; +import androidx.media3.exoplayer.audio.AudioRendererEventListener; +import androidx.media3.exoplayer.metadata.MetadataOutput; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.ShuffleOrder; +import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.trackselection.TrackSelector; import androidx.media3.exoplayer.upstream.BandwidthMeter; +import androidx.media3.exoplayer.video.VideoDecoderOutputBufferRenderer; import androidx.media3.exoplayer.video.VideoFrameMetadataListener; +import androidx.media3.exoplayer.video.VideoRendererEventListener; import androidx.media3.exoplayer.video.spherical.CameraMotionListener; +import androidx.media3.exoplayer.video.spherical.SphericalGLSurfaceView; import androidx.media3.extractor.ExtractorsFactory; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeoutException; /** @deprecated Use {@link ExoPlayer} instead. */ @UnstableApi @@ -319,7 +356,52 @@ 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 ComponentListener componentListener; + private final FrameMetadataListener frameMetadataListener; + private final CopyOnWriteArraySet 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 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 @@ -355,16 +437,119 @@ public class SimpleExoPlayer extends BasePlayer /** @param builder The {@link ExoPlayer.Builder} to obtain all construction parameters. */ /* package */ SimpleExoPlayer(ExoPlayer.Builder builder) { - player = new ExoPlayerImpl(builder, /* wrappingPlayer= */ this); + constructorFinished = new ConditionVariable(); + try { + applicationContext = builder.context.getApplicationContext(); + analyticsCollector = builder.analyticsCollectorSupplier.get(); + priorityTaskManager = builder.priorityTaskManager; + audioAttributes = builder.audioAttributes; + videoScalingMode = builder.videoScalingMode; + videoChangeFrameRateStrategy = builder.videoChangeFrameRateStrategy; + skipSilenceEnabled = builder.skipSilenceEnabled; + detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; + componentListener = new ComponentListener(); + frameMetadataListener = new FrameMetadataListener(); + listeners = new CopyOnWriteArraySet<>(); + Handler eventHandler = new Handler(builder.looper); + renderers = + builder + .renderersFactorySupplier + .get() + .createRenderers( + eventHandler, + componentListener, + componentListener, + componentListener, + componentListener); + + // Set initial values. + volume = 1; + if (Util.SDK_INT < 21) { + audioSessionId = initializeKeepSessionIdAudioTrack(C.AUDIO_SESSION_ID_UNSET); + } else { + audioSessionId = Util.generateAudioSessionIdV21(applicationContext); + } + currentCues = Collections.emptyList(); + throwsWhenUsingWrongThread = true; + + // Build the player and associated objects. + Commands additionalPermanentAvailableCommands = + new Commands.Builder() + .addAll( + COMMAND_GET_AUDIO_ATTRIBUTES, + COMMAND_GET_VOLUME, + COMMAND_GET_DEVICE_VOLUME, + COMMAND_SET_VOLUME, + COMMAND_SET_DEVICE_VOLUME, + COMMAND_ADJUST_DEVICE_VOLUME, + COMMAND_SET_VIDEO_SURFACE, + COMMAND_GET_TEXT) + .build(); + player = + new ExoPlayerImpl( + renderers, + builder.trackSelectorSupplier.get(), + builder.mediaSourceFactorySupplier.get(), + builder.loadControlSupplier.get(), + builder.bandwidthMeterSupplier.get(), + analyticsCollector, + builder.useLazyPreparation, + builder.seekParameters, + builder.seekBackIncrementMs, + builder.seekForwardIncrementMs, + builder.livePlaybackSpeedControl, + builder.releaseTimeoutMs, + builder.pauseAtEndOfMediaItems, + builder.clock, + builder.looper, + /* wrappingPlayer= */ this, + additionalPermanentAvailableCommands); + player.addEventListener(componentListener); + player.addAudioOffloadListener(componentListener); + if (builder.foregroundModeTimeoutMs > 0) { + player.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); + } + + audioBecomingNoisyManager = + new AudioBecomingNoisyManager(builder.context, eventHandler, componentListener); + audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy); + audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener); + audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null); + streamVolumeManager = + new StreamVolumeManager(builder.context, eventHandler, componentListener); + streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); + wakeLockManager = new WakeLockManager(builder.context); + wakeLockManager.setEnabled(builder.wakeMode != C.WAKE_MODE_NONE); + wifiLockManager = new WifiLockManager(builder.context); + wifiLockManager.setEnabled(builder.wakeMode == C.WAKE_MODE_NETWORK); + deviceInfo = createDeviceInfo(streamVolumeManager); + videoSize = VideoSize.UNKNOWN; + + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); + sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); + sendRendererMessage( + TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); + sendRendererMessage( + TRACK_TYPE_VIDEO, MSG_SET_VIDEO_FRAME_METADATA_LISTENER, frameMetadataListener); + sendRendererMessage( + TRACK_TYPE_CAMERA_MOTION, MSG_SET_CAMERA_MOTION_LISTENER, frameMetadataListener); + } finally { + constructorFinished.open(); + } } @Override public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { + verifyApplicationThread(); player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); } @Override public boolean experimentalIsSleepingForOffload() { + verifyApplicationThread(); return player.experimentalIsSleepingForOffload(); } @@ -394,209 +579,400 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { - player.setVideoScalingMode(videoScalingMode); + verifyApplicationThread(); + this.videoScalingMode = videoScalingMode; + sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); } @Override @C.VideoScalingMode public int getVideoScalingMode() { - return player.getVideoScalingMode(); + return videoScalingMode; } @Override public void setVideoChangeFrameRateStrategy( @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { - player.setVideoChangeFrameRateStrategy(videoChangeFrameRateStrategy); + verifyApplicationThread(); + if (this.videoChangeFrameRateStrategy == videoChangeFrameRateStrategy) { + return; + } + this.videoChangeFrameRateStrategy = videoChangeFrameRateStrategy; + sendRendererMessage( + TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); } @Override @C.VideoChangeFrameRateStrategy public int getVideoChangeFrameRateStrategy() { - return player.getVideoChangeFrameRateStrategy(); + return videoChangeFrameRateStrategy; } @Override public VideoSize getVideoSize() { - return player.getVideoSize(); + return videoSize; } @Override public void clearVideoSurface() { - player.clearVideoSurface(); + verifyApplicationThread(); + removeSurfaceCallbacks(); + setVideoOutputInternal(/* videoOutput= */ null); + maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } @Override public void clearVideoSurface(@Nullable Surface surface) { - player.clearVideoSurface(surface); + verifyApplicationThread(); + if (surface != null && surface == videoOutput) { + clearVideoSurface(); + } } @Override public void setVideoSurface(@Nullable Surface surface) { - player.setVideoSurface(surface); + verifyApplicationThread(); + removeSurfaceCallbacks(); + setVideoOutputInternal(surface); + int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; + maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - player.setVideoSurfaceHolder(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); + } + } } @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - player.clearVideoSurfaceHolder(surfaceHolder); + verifyApplicationThread(); + if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { + clearVideoSurface(); + } } @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { - player.setVideoSurfaceView(surfaceView); + verifyApplicationThread(); + if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { + removeSurfaceCallbacks(); + setVideoOutputInternal(surfaceView); + setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); + } else if (surfaceView instanceof SphericalGLSurfaceView) { + removeSurfaceCallbacks(); + sphericalGLSurfaceView = (SphericalGLSurfaceView) surfaceView; + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) + .setPayload(sphericalGLSurfaceView) + .send(); + sphericalGLSurfaceView.addVideoSurfaceListener(componentListener); + setVideoOutputInternal(sphericalGLSurfaceView.getVideoSurface()); + setNonVideoOutputSurfaceHolderInternal(surfaceView.getHolder()); + } else { + setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); + } } @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { - player.clearVideoSurfaceView(surfaceView); + verifyApplicationThread(); + clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } @Override public void setVideoTextureView(@Nullable TextureView textureView) { - player.setVideoTextureView(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()); + } + } } @Override public void clearVideoTextureView(@Nullable TextureView textureView) { - player.clearVideoTextureView(textureView); + verifyApplicationThread(); + if (textureView != null && textureView == this.textureView) { + clearVideoSurface(); + } } @Override public void addAudioOffloadListener(AudioOffloadListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. player.addAudioOffloadListener(listener); } @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. player.removeAudioOffloadListener(listener); } @Override public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) { - player.setAudioAttributes(audioAttributes, 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)); } @Override public AudioAttributes getAudioAttributes() { - return player.getAudioAttributes(); + return audioAttributes; } @Override public void setAudioSessionId(int audioSessionId) { - player.setAudioSessionId(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); + } } @Override public int getAudioSessionId() { - return player.getAudioSessionId(); + return audioSessionId; } @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { - player.setAuxEffectInfo(auxEffectInfo); + verifyApplicationThread(); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); } @Override public void clearAuxEffectInfo() { - player.clearAuxEffectInfo(); + setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); } @Override public void setVolume(float volume) { - player.setVolume(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); + } } @Override public float getVolume() { - return player.getVolume(); + return volume; } @Override public boolean getSkipSilenceEnabled() { - return player.getSkipSilenceEnabled(); + return skipSilenceEnabled; } @Override public void setSkipSilenceEnabled(boolean skipSilenceEnabled) { - player.setSkipSilenceEnabled(skipSilenceEnabled); + verifyApplicationThread(); + if (this.skipSilenceEnabled == skipSilenceEnabled) { + return; + } + this.skipSilenceEnabled = skipSilenceEnabled; + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_SKIP_SILENCE_ENABLED, skipSilenceEnabled); + notifySkipSilenceEnabledChanged(); } @Override public AnalyticsCollector getAnalyticsCollector() { - return player.getAnalyticsCollector(); + return analyticsCollector; } @Override public void addAnalyticsListener(AnalyticsListener listener) { - player.addAnalyticsListener(listener); + // Don't verify application thread. We allow calls to this method from any thread. + checkNotNull(listener); + analyticsCollector.addListener(listener); } @Override public void removeAnalyticsListener(AnalyticsListener listener) { - player.removeAnalyticsListener(listener); + // Don't verify application thread. We allow calls to this method from any thread. + analyticsCollector.removeListener(listener); } @Override public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { - player.setHandleAudioBecomingNoisy(handleAudioBecomingNoisy); + verifyApplicationThread(); + if (playerReleased) { + return; + } + audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); } @Override public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { - player.setPriorityTaskManager(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; } @Override @Nullable public Format getVideoFormat() { - return player.getVideoFormat(); + return videoFormat; } @Override @Nullable public Format getAudioFormat() { - return player.getAudioFormat(); + return audioFormat; } @Override @Nullable public DecoderCounters getVideoDecoderCounters() { - return player.getVideoDecoderCounters(); + return videoDecoderCounters; } @Override @Nullable public DecoderCounters getAudioDecoderCounters() { - return player.getAudioDecoderCounters(); + return audioDecoderCounters; } @Override public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { - player.setVideoFrameMetadataListener(listener); + verifyApplicationThread(); + videoFrameMetadataListener = listener; + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(listener) + .send(); } @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { - player.clearVideoFrameMetadataListener(listener); + verifyApplicationThread(); + if (videoFrameMetadataListener != listener) { + return; + } + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) + .setPayload(null) + .send(); } @Override public void setCameraMotionListener(CameraMotionListener listener) { - player.setCameraMotionListener(listener); + verifyApplicationThread(); + cameraMotionListener = listener; + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(listener) + .send(); } @Override public void clearCameraMotionListener(CameraMotionListener listener) { - player.clearCameraMotionListener(listener); + verifyApplicationThread(); + if (cameraMotionListener != listener) { + return; + } + player + .createMessage(frameMetadataListener) + .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) + .setPayload(null) + .send(); } @Override public List getCurrentCues() { - return player.getCurrentCues(); + verifyApplicationThread(); + return currentCues; } // ExoPlayer implementation @@ -618,59 +994,78 @@ public class SimpleExoPlayer extends BasePlayer @Override public void addListener(Listener listener) { - player.addListener(listener); + checkNotNull(listener); + listeners.add(listener); + EventListener eventListener = listener; + addListener(eventListener); } @Deprecated @Override public void addListener(Player.EventListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. + checkNotNull(listener); player.addEventListener(listener); } @Override public void removeListener(Listener listener) { - player.removeListener(listener); + checkNotNull(listener); + listeners.remove(listener); + EventListener eventListener = listener; + removeListener(eventListener); } @Deprecated @Override public void removeListener(Player.EventListener listener) { + // Don't verify application thread. We allow calls to this method from any thread. player.removeEventListener(listener); } @Override @State public int getPlaybackState() { + verifyApplicationThread(); return player.getPlaybackState(); } @Override @PlaybackSuppressionReason public int getPlaybackSuppressionReason() { + verifyApplicationThread(); return player.getPlaybackSuppressionReason(); } @Override @Nullable public ExoPlaybackException getPlayerError() { + verifyApplicationThread(); return player.getPlayerError(); } /** @deprecated Use {@link #prepare()} instead. */ @Deprecated @Override - @SuppressWarnings("deprecation") // Calling deprecated method. public void retry() { - player.retry(); + verifyApplicationThread(); + prepare(); } @Override public Commands getAvailableCommands() { + verifyApplicationThread(); return player.getAvailableCommands(); } @Override public void prepare() { + verifyApplicationThread(); + boolean playWhenReady = getPlayWhenReady(); + @AudioFocusManager.PlayerCommand + int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING); + updatePlayWhenReady( + playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); player.prepare(); } @@ -679,9 +1074,9 @@ public class SimpleExoPlayer extends BasePlayer */ @Deprecated @Override - @SuppressWarnings("deprecation") // Forwarding deprecated method. + @SuppressWarnings("deprecation") public void prepare(MediaSource mediaSource) { - player.prepare(mediaSource); + prepare(mediaSource, /* resetPosition= */ true, /* resetState= */ true); } /** @@ -690,245 +1085,319 @@ public class SimpleExoPlayer extends BasePlayer */ @Deprecated @Override - @SuppressWarnings("deprecation") // Forwarding deprecated method. public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { - player.prepare(mediaSource, resetPosition, resetState); + verifyApplicationThread(); + setMediaSources(Collections.singletonList(mediaSource), resetPosition); + prepare(); } @Override public void setMediaItems(List mediaItems, boolean resetPosition) { + verifyApplicationThread(); player.setMediaItems(mediaItems, resetPosition); } @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { + verifyApplicationThread(); player.setMediaItems(mediaItems, startIndex, startPositionMs); } @Override public void setMediaSources(List mediaSources) { + verifyApplicationThread(); player.setMediaSources(mediaSources); } @Override public void setMediaSources(List mediaSources, boolean resetPosition) { + verifyApplicationThread(); player.setMediaSources(mediaSources, resetPosition); } @Override public void setMediaSources( List mediaSources, int startMediaItemIndex, long startPositionMs) { + verifyApplicationThread(); player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource) { + verifyApplicationThread(); player.setMediaSource(mediaSource); } @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { + verifyApplicationThread(); player.setMediaSource(mediaSource, resetPosition); } @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { + verifyApplicationThread(); player.setMediaSource(mediaSource, startPositionMs); } @Override public void addMediaItems(int index, List mediaItems) { + verifyApplicationThread(); player.addMediaItems(index, mediaItems); } @Override public void addMediaSource(MediaSource mediaSource) { + verifyApplicationThread(); player.addMediaSource(mediaSource); } @Override public void addMediaSource(int index, MediaSource mediaSource) { + verifyApplicationThread(); player.addMediaSource(index, mediaSource); } @Override public void addMediaSources(List mediaSources) { + verifyApplicationThread(); player.addMediaSources(mediaSources); } @Override public void addMediaSources(int index, List mediaSources) { + verifyApplicationThread(); player.addMediaSources(index, mediaSources); } @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { + verifyApplicationThread(); player.moveMediaItems(fromIndex, toIndex, newIndex); } @Override public void removeMediaItems(int fromIndex, int toIndex) { + verifyApplicationThread(); player.removeMediaItems(fromIndex, toIndex); } @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { + verifyApplicationThread(); player.setShuffleOrder(shuffleOrder); } @Override public void setPlayWhenReady(boolean playWhenReady) { - player.setPlayWhenReady(playWhenReady); + verifyApplicationThread(); + @AudioFocusManager.PlayerCommand + int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); + updatePlayWhenReady( + playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } @Override public boolean getPlayWhenReady() { + verifyApplicationThread(); return player.getPlayWhenReady(); } @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { + verifyApplicationThread(); player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems); } @Override public boolean getPauseAtEndOfMediaItems() { + verifyApplicationThread(); return player.getPauseAtEndOfMediaItems(); } @Override public @RepeatMode int getRepeatMode() { + verifyApplicationThread(); return player.getRepeatMode(); } @Override public void setRepeatMode(@RepeatMode int repeatMode) { + verifyApplicationThread(); player.setRepeatMode(repeatMode); } @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { + verifyApplicationThread(); player.setShuffleModeEnabled(shuffleModeEnabled); } @Override public boolean getShuffleModeEnabled() { + verifyApplicationThread(); return player.getShuffleModeEnabled(); } @Override public boolean isLoading() { + verifyApplicationThread(); return player.isLoading(); } @Override public void seekTo(int mediaItemIndex, long positionMs) { + verifyApplicationThread(); + analyticsCollector.notifySeekStarted(); player.seekTo(mediaItemIndex, positionMs); } @Override public long getSeekBackIncrement() { + verifyApplicationThread(); return player.getSeekBackIncrement(); } @Override public long getSeekForwardIncrement() { + verifyApplicationThread(); return player.getSeekForwardIncrement(); } @Override public long getMaxSeekToPreviousPosition() { + verifyApplicationThread(); return player.getMaxSeekToPreviousPosition(); } @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { + verifyApplicationThread(); player.setPlaybackParameters(playbackParameters); } @Override public PlaybackParameters getPlaybackParameters() { + verifyApplicationThread(); return player.getPlaybackParameters(); } @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { + verifyApplicationThread(); player.setSeekParameters(seekParameters); } @Override public SeekParameters getSeekParameters() { + verifyApplicationThread(); return player.getSeekParameters(); } @Override public void setForegroundMode(boolean foregroundMode) { + verifyApplicationThread(); player.setForegroundMode(foregroundMode); } @Override public void stop() { - player.stop(); + stop(/* reset= */ false); } @Deprecated @Override public void stop(boolean reset) { + verifyApplicationThread(); + audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); player.stop(reset); + currentCues = Collections.emptyList(); } @Override public void release() { + verifyApplicationThread(); + if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) { + keepSessionIdAudioTrack.release(); + keepSessionIdAudioTrack = null; + } + audioBecomingNoisyManager.setEnabled(false); + streamVolumeManager.release(); + wakeLockManager.setStayAwake(false); + wifiLockManager.setStayAwake(false); + audioFocusManager.release(); player.release(); + 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 public PlayerMessage createMessage(PlayerMessage.Target target) { + verifyApplicationThread(); return player.createMessage(target); } @Override public int getRendererCount() { + verifyApplicationThread(); return player.getRendererCount(); } @Override public @C.TrackType int getRendererType(int index) { + verifyApplicationThread(); return player.getRendererType(index); } @Override public Renderer getRenderer(int index) { + verifyApplicationThread(); return player.getRenderer(index); } @Override public TrackSelector getTrackSelector() { + verifyApplicationThread(); return player.getTrackSelector(); } @Override public TrackGroupArray getCurrentTrackGroups() { + verifyApplicationThread(); return player.getCurrentTrackGroups(); } @Override public TrackSelectionArray getCurrentTrackSelections() { + verifyApplicationThread(); return player.getCurrentTrackSelections(); } @Override public TracksInfo getCurrentTracksInfo() { + verifyApplicationThread(); return player.getCurrentTracksInfo(); } @Override public TrackSelectionParameters getTrackSelectionParameters() { + verifyApplicationThread(); return player.getTrackSelectionParameters(); } @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { + verifyApplicationThread(); player.setTrackSelectionParameters(parameters); } @@ -949,111 +1418,782 @@ public class SimpleExoPlayer extends BasePlayer @Override public Timeline getCurrentTimeline() { + verifyApplicationThread(); return player.getCurrentTimeline(); } @Override public int getCurrentPeriodIndex() { + verifyApplicationThread(); return player.getCurrentPeriodIndex(); } @Override public int getCurrentMediaItemIndex() { + verifyApplicationThread(); return player.getCurrentMediaItemIndex(); } @Override public long getDuration() { + verifyApplicationThread(); return player.getDuration(); } @Override public long getCurrentPosition() { + verifyApplicationThread(); return player.getCurrentPosition(); } @Override public long getBufferedPosition() { + verifyApplicationThread(); return player.getBufferedPosition(); } @Override public long getTotalBufferedDuration() { + verifyApplicationThread(); return player.getTotalBufferedDuration(); } @Override public boolean isPlayingAd() { + verifyApplicationThread(); return player.isPlayingAd(); } @Override public int getCurrentAdGroupIndex() { + verifyApplicationThread(); return player.getCurrentAdGroupIndex(); } @Override public int getCurrentAdIndexInAdGroup() { + verifyApplicationThread(); return player.getCurrentAdIndexInAdGroup(); } @Override public long getContentPosition() { + verifyApplicationThread(); return player.getContentPosition(); } @Override public long getContentBufferedPosition() { + verifyApplicationThread(); return player.getContentBufferedPosition(); } @Deprecated @Override public void setHandleWakeLock(boolean handleWakeLock) { - player.setHandleWakeLock(handleWakeLock); + setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE); } @Override public void setWakeMode(@C.WakeMode int wakeMode) { - player.setWakeMode(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; + } } @Override public DeviceInfo getDeviceInfo() { - return player.getDeviceInfo(); + verifyApplicationThread(); + return deviceInfo; } @Override public int getDeviceVolume() { - return player.getDeviceVolume(); + verifyApplicationThread(); + return streamVolumeManager.getVolume(); } @Override public boolean isDeviceMuted() { - return player.isDeviceMuted(); + verifyApplicationThread(); + return streamVolumeManager.isMuted(); } @Override public void setDeviceVolume(int volume) { - player.setDeviceVolume(volume); + verifyApplicationThread(); + streamVolumeManager.setVolume(volume); } @Override public void increaseDeviceVolume() { - player.increaseDeviceVolume(); + verifyApplicationThread(); + streamVolumeManager.increaseVolume(); } @Override public void decreaseDeviceVolume() { - player.decreaseDeviceVolume(); + verifyApplicationThread(); + streamVolumeManager.decreaseVolume(); } @Override public void setDeviceMuted(boolean muted) { - player.setDeviceMuted(muted); + verifyApplicationThread(); + streamVolumeManager.setMuted(muted); } /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { - player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); + this.throwsWhenUsingWrongThread = 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 messages = new ArrayList<>(); + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == TRACK_TYPE_VIDEO) { + messages.add( + player + .createMessage(renderer) + .setType(MSG_SET_VIDEO_OUTPUT) + .setPayload(videoOutput) + .send()); + } + } + boolean messageDeliveryTimedOut = false; + if (this.videoOutput != null && this.videoOutput != videoOutput) { + // We're replacing an output. Block to ensure that this output will not be accessed by the + // renderers after this method returns. + try { + for (PlayerMessage message : messages) { + message.blockUntilDelivered(detachSurfaceTimeoutMs); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + messageDeliveryTimedOut = true; + } + if (this.videoOutput == ownedSurface) { + // We're replacing a surface that we are responsible for releasing. + ownedSurface.release(); + ownedSurface = null; + } + } + this.videoOutput = videoOutput; + if (messageDeliveryTimedOut) { + player.stop( + /* reset= */ false, + ExoPlaybackException.createForUnexpected( + new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE), + PlaybackException.ERROR_CODE_TIMEOUT)); + } + } + + /** + * Sets the holder of the surface that will be displayed to the user, but which should + * not be the output for video renderers. This case occurs when video frames need to be + * rendered to an intermediate surface (which is not the one held by the provided holder). + * + * @param nonVideoOutputSurfaceHolder The holder of the surface that will eventually be displayed + * to the user. + */ + private void setNonVideoOutputSurfaceHolderInternal(SurfaceHolder nonVideoOutputSurfaceHolder) { + // Although we won't use the view's surface directly as the video output, still use the holder + // to query the surface size, to be informed in changes to the size via componentListener, and + // for equality checking in clearVideoSurfaceHolder. + surfaceHolderSurfaceIsVideoOutput = false; + surfaceHolder = nonVideoOutputSurfaceHolder; + surfaceHolder.addCallback(componentListener); + Surface surface = surfaceHolder.getSurface(); + if (surface != null && surface.isValid()) { + Rect surfaceSize = surfaceHolder.getSurfaceFrame(); + maybeNotifySurfaceSizeChanged(surfaceSize.width(), surfaceSize.height()); + } else { + maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + } + } + + private void maybeNotifySurfaceSizeChanged(int width, int height) { + if (width != surfaceWidth || height != surfaceHeight) { + surfaceWidth = width; + surfaceHeight = height; + analyticsCollector.onSurfaceSizeChanged(width, height); + // TODO(internal b/187152483): Events should be dispatched via ListenerSet + for (Listener listener : listeners) { + listener.onSurfaceSizeChanged(width, height); + } + } + } + + private void sendVolumeToRenderers() { + float scaledVolume = volume * audioFocusManager.getVolumeMultiplier(); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_VOLUME, scaledVolume); + } + + @SuppressWarnings("SuspiciousMethodCalls") + private void notifySkipSilenceEnabledChanged() { + analyticsCollector.onSkipSilenceEnabledChanged(skipSilenceEnabled); + // TODO(internal b/187152483): Events should be dispatched via ListenerSet + for (Listener listener : listeners) { + listener.onSkipSilenceEnabledChanged(skipSilenceEnabled); + } + } + + private void updatePlayWhenReady( + boolean playWhenReady, + @AudioFocusManager.PlayerCommand int playerCommand, + @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) { + playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY; + @PlaybackSuppressionReason + int playbackSuppressionReason = + playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY + ? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS + : Player.PLAYBACK_SUPPRESSION_REASON_NONE; + player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason); + } + + private void updateWakeAndWifiLock() { + @State int playbackState = getPlaybackState(); + switch (playbackState) { + case Player.STATE_READY: + case Player.STATE_BUFFERING: + boolean isSleeping = experimentalIsSleepingForOffload(); + wakeLockManager.setStayAwake(getPlayWhenReady() && !isSleeping); + // The wifi lock is not released while sleeping to avoid interrupting downloads. + wifiLockManager.setStayAwake(getPlayWhenReady()); + break; + case Player.STATE_ENDED: + case Player.STATE_IDLE: + wakeLockManager.setStayAwake(false); + wifiLockManager.setStayAwake(false); + break; + default: + throw new IllegalStateException(); + } + } + + private void verifyApplicationThread() { + // The constructor may be executed on a background thread. Wait with accessing the player from + // the app thread until the constructor finished executing. + constructorFinished.blockUninterruptible(); + if (Thread.currentThread() != getApplicationLooper().getThread()) { + String message = + Util.formatInvariant( + "Player is accessed on the wrong thread.\n" + + "Current thread: '%s'\n" + + "Expected thread: '%s'\n" + + "See https://exoplayer.dev/issues/player-accessed-on-wrong-thread", + Thread.currentThread().getName(), getApplicationLooper().getThread().getName()); + if (throwsWhenUsingWrongThread) { + throw new IllegalStateException(message); + } + Log.w(TAG, message, hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException()); + hasNotifiedFullWrongThreadWarning = true; + } + } + + private void sendRendererMessage( + @C.TrackType int trackType, int messageType, @Nullable Object payload) { + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == trackType) { + player.createMessage(renderer).setType(messageType).setPayload(payload).send(); + } + } + } + + /** + * Initializes {@link #keepSessionIdAudioTrack} to keep an audio session ID alive. If the audio + * session ID is {@link C#AUDIO_SESSION_ID_UNSET} then a new audio session ID is generated. + * + *

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 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(); + } + } } }