From fab5dfa156d96c7b6ad21e7b4801de188de58dc5 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 7 Feb 2022 21:15:51 +0000 Subject: [PATCH] Rollback of https://github.com/androidx/media/commit/d93b0093aeaa860011ad447710a15dc800df7c95 *** Original commit *** Move SimpleExoPlayer logic into ExoPlayerImpl This makes SimpleExoPlayer a simple forwarding wrapper which can be removed in the future. The changes are all purely mechanical with none of the potential further simplifications made yet. The only exceptions are name clashes where either EPI or SEP was calling a method in one of the classes and both classes had different implementations for the same method name. In these cases we needed to disambiguate between the two different implementations (e *** PiperOrigin-RevId: 426997821 --- .../media3/exoplayer/ExoPlayerImpl.java | 1657 ++--------------- .../media3/exoplayer/SimpleExoPlayer.java | 1258 ++++++++++++- 2 files changed, 1379 insertions(+), 1536 deletions(-) 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(); + } + } } }