From ddbd9512da68a281a1cc552bfca36be6f949289a Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 8 Feb 2022 09:44:33 +0000 Subject: [PATCH] Rollback of https://github.com/google/ExoPlayer/commit/12be2bc35791bd00c3af8b403f5db7b14074c55c *** Original commit *** Rollback of https://github.com/google/ExoPlayer/commit/3bb0210d229ffd1455d546fa738dfebbac50f552 *** Original commit *** Move SimpleExoPlayer logic into ExoPlayerImpl This makes SimpleExoPlayer a simple forwarding wrapper which can be removed in the future. The changes are all purely mechanical with none of the potential further simplifications made yet... *** PiperOrigin-RevId: 427131338 --- .../android/exoplayer2/ExoPlayerImpl.java | 1656 +++++++++++++++-- .../android/exoplayer2/SimpleExoPlayer.java | 1378 ++------------ 2 files changed, 1666 insertions(+), 1368 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index dfc77d95e1..47ec445f1c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -15,20 +15,31 @@ */ package com.google.android.exoplayer2; +import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO; +import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION; +import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; +import static com.google.android.exoplayer2.Player.COMMAND_ADJUST_DEVICE_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_CHANGE_MEDIA_ITEMS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_AUDIO_ATTRIBUTES; import static com.google.android.exoplayer2.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_GET_DEVICE_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACK_INFOS; +import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; import static com.google.android.exoplayer2.Player.COMMAND_PREPARE; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_MEDIA_ITEM; +import static com.google.android.exoplayer2.Player.COMMAND_SET_DEVICE_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_SET_MEDIA_ITEMS_METADATA; import static com.google.android.exoplayer2.Player.COMMAND_SET_REPEAT_MODE; import static com.google.android.exoplayer2.Player.COMMAND_SET_SHUFFLE_MODE; import static com.google.android.exoplayer2.Player.COMMAND_SET_SPEED_AND_PITCH; import static com.google.android.exoplayer2.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VIDEO_SURFACE; +import static com.google.android.exoplayer2.Player.COMMAND_SET_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_STOP; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL; @@ -42,12 +53,23 @@ import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_ import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT; import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK; import static com.google.android.exoplayer2.Player.PLAYBACK_SUPPRESSION_REASON_NONE; +import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS; import static com.google.android.exoplayer2.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; import static com.google.android.exoplayer2.Player.STATE_BUFFERING; import static com.google.android.exoplayer2.Player.STATE_ENDED; import static com.google.android.exoplayer2.Player.STATE_IDLE; import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; import static com.google.android.exoplayer2.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; +import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; +import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; +import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; +import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; +import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; +import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; +import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; +import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.castNonNull; @@ -55,10 +77,20 @@ 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; @@ -75,14 +107,22 @@ import com.google.android.exoplayer2.Player.RepeatMode; import com.google.android.exoplayer2.Player.State; import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.PlayerMessage.Target; +import com.google.android.exoplayer2.Renderer.MessageType; import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.PlayerId; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; @@ -91,15 +131,24 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Clock; +import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.PriorityTaskManager; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; +import com.google.android.exoplayer2.video.VideoFrameMetadataListener; +import com.google.android.exoplayer2.video.VideoRendererEventListener; +import com.google.android.exoplayer2.video.VideoSize; +import com.google.android.exoplayer2.video.spherical.CameraMotionListener; +import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; import com.google.common.collect.ImmutableList; import 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 { @@ -120,13 +169,18 @@ import java.util.concurrent.CopyOnWriteArraySet; /* 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; - private final ListenerSet listeners; + + @SuppressWarnings("deprecation") // TODO(b/187152483): Merge with non-deprecated listeners. + private final ListenerSet eventListeners; + private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; private final Timeline.Window window; @@ -139,6 +193,15 @@ import java.util.concurrent.CopyOnWriteArraySet; 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; @@ -153,6 +216,35 @@ import java.util.concurrent.CopyOnWriteArraySet; 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. @@ -166,145 +258,178 @@ import java.util.concurrent.CopyOnWriteArraySet; 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( - 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)) - + " [" - + ExoPlayerLibraryInfo.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); + public ExoPlayerImpl(ExoPlayer.Builder builder, Player wrappingPlayer) { + constructorFinished = new ConditionVariable(); + try { + Log.i( + TAG, + "Init " + + Integer.toHexString(System.identityHashCode(this)) + + " [" + + ExoPlayerLibraryInfo.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(); + } } /** @@ -322,63 +447,72 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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) { - listeners.add(eventListener); + // Don't verify application thread. We allow calls to this method from any thread. + eventListeners.add(eventListener); } @SuppressWarnings("deprecation") // Deregister deprecated EventListener. public void removeEventListener(Player.EventListener eventListener) { - listeners.remove(eventListener); + // Don't verify application thread. We allow calls to this method from any thread. + eventListeners.remove(eventListener); } public void addAudioOffloadListener(AudioOffloadListener listener) { + // 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; } @@ -389,6 +523,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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; } @@ -418,6 +558,7 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @Deprecated public void prepare(MediaSource mediaSource) { + verifyApplicationThread(); setMediaSource(mediaSource); prepare(); } @@ -428,36 +569,44 @@ import java.util.concurrent.CopyOnWriteArraySet; */ @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, @@ -467,28 +616,34 @@ import java.util.concurrent.CopyOnWriteArraySet; 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++; @@ -512,6 +667,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } public void removeMediaItems(int fromIndex, int toIndex) { + verifyApplicationThread(); toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex); boolean positionDiscontinuity = @@ -528,6 +684,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { + verifyApplicationThread(); Assertions.checkArgument( fromIndex >= 0 && fromIndex <= toIndex @@ -556,6 +713,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } public void setShuffleOrder(ShuffleOrder shuffleOrder) { + verifyApplicationThread(); Timeline timeline = createMaskingTimeline(); PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( @@ -578,6 +736,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { + verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { return; } @@ -586,9 +745,18 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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, @@ -613,46 +781,54 @@ import java.util.concurrent.CopyOnWriteArraySet; } public boolean getPlayWhenReady() { + verifyApplicationThread(); return playbackInfo.playWhenReady; } public void setRepeatMode(@RepeatMode int repeatMode) { + verifyApplicationThread(); if (this.repeatMode != repeatMode) { this.repeatMode = repeatMode; internalPlayer.setRepeatMode(repeatMode); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_REPEAT_MODE_CHANGED, listener -> listener.onRepeatModeChanged(repeatMode)); updateAvailableCommands(); - listeners.flushEvents(); + eventListeners.flushEvents(); } } @RepeatMode public int getRepeatMode() { + verifyApplicationThread(); return repeatMode; } public void setShuffleModeEnabled(boolean shuffleModeEnabled) { + verifyApplicationThread(); if (this.shuffleModeEnabled != shuffleModeEnabled) { this.shuffleModeEnabled = shuffleModeEnabled; internalPlayer.setShuffleModeEnabled(shuffleModeEnabled); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)); updateAvailableCommands(); - listeners.flushEvents(); + eventListeners.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())) { @@ -693,18 +869,22 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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; } @@ -726,10 +906,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } public PlaybackParameters getPlaybackParameters() { + verifyApplicationThread(); return playbackInfo.playbackParameters; } public void setSeekParameters(@Nullable SeekParameters seekParameters) { + verifyApplicationThread(); if (seekParameters == null) { seekParameters = SeekParameters.DEFAULT; } @@ -740,10 +922,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } public SeekParameters getSeekParameters() { + verifyApplicationThread(); return seekParameters; } public void setForegroundMode(boolean foregroundMode) { + verifyApplicationThread(); if (this.foregroundMode != foregroundMode) { this.foregroundMode = foregroundMode; if (!internalPlayer.setForegroundMode(foregroundMode)) { @@ -757,8 +941,15 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + 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(); } /** @@ -811,9 +1002,19 @@ import java.util.concurrent.CopyOnWriteArraySet; + "] [" + ExoPlayerLibraryInfo.registeredModules() + "]"); + verifyApplicationThread(); + if (Util.SDK_INT < 21 && keepSessionIdAudioTrack != null) { + keepSessionIdAudioTrack.release(); + keepSessionIdAudioTrack = null; + } + audioBecomingNoisyManager.setEnabled(false); + streamVolumeManager.release(); + wakeLockManager.setStayAwake(false); + wifiLockManager.setStayAwake(false); + audioFocusManager.release(); if (!internalPlayer.release()) { // One of the renderers timed out releasing its resources. - listeners.sendEvent( + eventListeners.sendEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError( @@ -821,26 +1022,34 @@ import java.util.concurrent.CopyOnWriteArraySet; new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_RELEASE), PlaybackException.ERROR_CODE_TIMEOUT))); } - listeners.release(); + eventListeners.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) { - return new PlayerMessage( - internalPlayer, - target, - playbackInfo.timeline, - getCurrentMediaItemIndex(), - clock, - internalPlayer.getPlaybackLooper()); + verifyApplicationThread(); + return createMessageInternal(target); } public int getCurrentPeriodIndex() { + verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingPeriodIndex; } else { @@ -849,11 +1058,13 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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); @@ -871,10 +1082,12 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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) @@ -884,22 +1097,27 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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 @@ -914,6 +1132,7 @@ import java.util.concurrent.CopyOnWriteArraySet; } public long getContentBufferedPosition() { + verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { return maskingWindowPositionMs; } @@ -937,53 +1156,63 @@ import java.util.concurrent.CopyOnWriteArraySet; } 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); - listeners.queueEvent( + eventListeners.queueEvent( EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(parameters)); } public MediaMetadata getMediaMetadata() { + verifyApplicationThread(); return mediaMetadata; } - public void onMetadata(Metadata metadata) { + private void onMetadata(Metadata metadata) { staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(metadata).build(); @@ -993,29 +1222,452 @@ import java.util.concurrent.CopyOnWriteArraySet; return; } mediaMetadata = newMediaMetadata; - listeners.sendEvent( + eventListeners.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; - listeners.sendEvent( + eventListeners.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; @@ -1151,7 +1803,7 @@ import java.util.concurrent.CopyOnWriteArraySet; mediaMetadata = newMediaMetadata; if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); } @@ -1160,7 +1812,7 @@ import java.util.concurrent.CopyOnWriteArraySet; getPreviousPositionInfo( positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); @@ -1170,16 +1822,16 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (mediaItemTransitioned) { @Nullable final MediaItem finalMediaItem = mediaItem; - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason)); } if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError)); if (newPlaybackInfo.playbackError != null) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError)); } @@ -1188,21 +1840,21 @@ import java.util.concurrent.CopyOnWriteArraySet; trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); TrackSelectionArray newSelection = new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection)); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo)); } if (metadataChanged) { final MediaMetadata finalMediaMetadata = mediaMetadata; - listeners.queueEvent( + eventListeners.queueEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata)); } if (previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_IS_LOADING_CHANGED, listener -> { listener.onLoadingChanged(newPlaybackInfo.isLoading); @@ -1211,19 +1863,19 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState || previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { - listeners.queueEvent( + eventListeners.queueEvent( /* eventFlag= */ C.INDEX_UNSET, listener -> listener.onPlayerStateChanged( newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState)); } if (previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); } if (previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged( @@ -1231,27 +1883,27 @@ import java.util.concurrent.CopyOnWriteArraySet; } if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged( newPlaybackInfo.playbackSuppressionReason)); } if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo))); } if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters)); } if (seekProcessed) { - listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); + eventListeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } updateAvailableCommands(); - listeners.flushEvents(); + eventListeners.flushEvents(); if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) { for (AudioOffloadListener listener : audioOffloadListeners) { @@ -1408,7 +2060,7 @@ import java.util.concurrent.CopyOnWriteArraySet; Commands previousAvailableCommands = availableCommands; availableCommands = Util.getAvailableCommands(wrappingPlayer, permanentAvailableCommands); if (!availableCommands.equals(previousAvailableCommands)) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(availableCommands)); } @@ -1699,6 +2351,17 @@ import java.util.concurrent.CopyOnWriteArraySet; 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. * @@ -1716,6 +2379,235 @@ import java.util.concurrent.CopyOnWriteArraySet; 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 @@ -1744,6 +2636,410 @@ import java.util.concurrent.CopyOnWriteArraySet; } } + // TODO(b/204189802): Remove self-listening to deprecated EventListener. + @SuppressWarnings("deprecation") + private final class ComponentListener + implements VideoRendererEventListener, + AudioRendererEventListener, + TextOutput, + MetadataOutput, + SurfaceHolder.Callback, + TextureView.SurfaceTextureListener, + SphericalGLSurfaceView.VideoSurfaceListener, + AudioFocusManager.PlayerControl, + AudioBecomingNoisyManager.EventListener, + StreamVolumeManager.Listener, + Player.EventListener, + AudioOffloadListener { + + // VideoRendererEventListener implementation + + @Override + public void onVideoEnabled(DecoderCounters counters) { + videoDecoderCounters = counters; + analyticsCollector.onVideoEnabled(counters); + } + + @Override + public void onVideoDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) { + analyticsCollector.onVideoDecoderInitialized( + decoderName, initializedTimestampMs, initializationDurationMs); + } + + @Override + public void onVideoInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { + videoFormat = format; + analyticsCollector.onVideoInputFormatChanged(format, decoderReuseEvaluation); + } + + @Override + public void onDroppedFrames(int count, long elapsed) { + analyticsCollector.onDroppedFrames(count, elapsed); + } + + @Override + public void onVideoSizeChanged(VideoSize videoSize) { + ExoPlayerImpl.this.videoSize = videoSize; + analyticsCollector.onVideoSizeChanged(videoSize); + // TODO(internal b/187152483): Events should be dispatched via ListenerSet + for (Listener listener : listeners) { + listener.onVideoSizeChanged(videoSize); + } + } + + @Override + public void onRenderedFirstFrame(Object output, long renderTimeMs) { + analyticsCollector.onRenderedFirstFrame(output, renderTimeMs); + if (videoOutput == output) { + // TODO(internal b/187152483): Events should be dispatched via ListenerSet + for (Listener listener : listeners) { + listener.onRenderedFirstFrame(); + } + } + } + + @Override + public void onVideoDecoderReleased(String decoderName) { + analyticsCollector.onVideoDecoderReleased(decoderName); + } + + @Override + public void onVideoDisabled(DecoderCounters counters) { + analyticsCollector.onVideoDisabled(counters); + videoFormat = null; + videoDecoderCounters = null; + } + + @Override + public void onVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) { + analyticsCollector.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount); + } + + @Override + public void onVideoCodecError(Exception videoCodecError) { + analyticsCollector.onVideoCodecError(videoCodecError); + } + + // AudioRendererEventListener implementation + + @Override + public void onAudioEnabled(DecoderCounters counters) { + audioDecoderCounters = counters; + analyticsCollector.onAudioEnabled(counters); + } + + @Override + public void onAudioDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) { + analyticsCollector.onAudioDecoderInitialized( + decoderName, initializedTimestampMs, initializationDurationMs); + } + + @Override + public void onAudioInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { + audioFormat = format; + analyticsCollector.onAudioInputFormatChanged(format, decoderReuseEvaluation); + } + + @Override + public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) { + analyticsCollector.onAudioPositionAdvancing(playoutStartSystemTimeMs); + } + + @Override + public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + analyticsCollector.onAudioUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + + @Override + public void onAudioDecoderReleased(String decoderName) { + analyticsCollector.onAudioDecoderReleased(decoderName); + } + + @Override + public void onAudioDisabled(DecoderCounters counters) { + analyticsCollector.onAudioDisabled(counters); + audioFormat = null; + audioDecoderCounters = null; + } + + @Override + public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) { + if (ExoPlayerImpl.this.skipSilenceEnabled == skipSilenceEnabled) { + return; + } + ExoPlayerImpl.this.skipSilenceEnabled = skipSilenceEnabled; + notifySkipSilenceEnabledChanged(); + } + + @Override + public void onAudioSinkError(Exception audioSinkError) { + analyticsCollector.onAudioSinkError(audioSinkError); + } + + @Override + public void onAudioCodecError(Exception audioCodecError) { + analyticsCollector.onAudioCodecError(audioCodecError); + } + + // TextOutput implementation + + @Override + public void onCues(List 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/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index da9440bbab..14d5d8683d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -15,28 +15,7 @@ */ package com.google.android.exoplayer2; -import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO; -import static com.google.android.exoplayer2.C.TRACK_TYPE_CAMERA_MOTION; -import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO; -import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_ATTRIBUTES; -import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID; -import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; -import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; -import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; -import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; -import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; -import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; -import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_OUTPUT; -import static com.google.android.exoplayer2.Renderer.MSG_SET_VOLUME; -import static com.google.android.exoplayer2.util.Assertions.checkNotNull; - import android.content.Context; -import android.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; @@ -45,43 +24,28 @@ import android.view.TextureView; import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.google.android.exoplayer2.Renderer.MessageType; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; -import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; -import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.PriorityTaskManager; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoFrameMetadataListener; -import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.spherical.CameraMotionListener; -import com.google.android.exoplayer2.video.spherical.SphericalGLSurfaceView; -import 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. */ @Deprecated @@ -345,52 +309,8 @@ public class SimpleExoPlayer extends BasePlayer } } - private static final String TAG = "SimpleExoPlayer"; - - private final Renderer[] renderers; - private final ConditionVariable constructorFinished; - private final Context applicationContext; private final ExoPlayerImpl player; - private final 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; + private final ConditionVariable constructorFinished; /** @deprecated Use the {@link ExoPlayer.Builder}. */ @Deprecated @@ -428,103 +348,7 @@ public class SimpleExoPlayer extends BasePlayer /* package */ SimpleExoPlayer(ExoPlayer.Builder builder) { 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); + player = new ExoPlayerImpl(builder, /* wrappingPlayer= */ this); } finally { constructorFinished.open(); } @@ -532,13 +356,13 @@ public class SimpleExoPlayer extends BasePlayer @Override public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); } @Override public boolean experimentalIsSleepingForOffload() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.experimentalIsSleepingForOffload(); } @@ -568,493 +392,336 @@ public class SimpleExoPlayer extends BasePlayer @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { - verifyApplicationThread(); - this.videoScalingMode = videoScalingMode; - sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); + blockUntilConstructorFinished(); + player.setVideoScalingMode(videoScalingMode); } @Override @C.VideoScalingMode public int getVideoScalingMode() { - return videoScalingMode; + blockUntilConstructorFinished(); + return player.getVideoScalingMode(); } @Override 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); + blockUntilConstructorFinished(); + player.setVideoChangeFrameRateStrategy(videoChangeFrameRateStrategy); } @Override @C.VideoChangeFrameRateStrategy public int getVideoChangeFrameRateStrategy() { - return videoChangeFrameRateStrategy; + blockUntilConstructorFinished(); + return player.getVideoChangeFrameRateStrategy(); } @Override public VideoSize getVideoSize() { - return videoSize; + blockUntilConstructorFinished(); + return player.getVideoSize(); } @Override public void clearVideoSurface() { - verifyApplicationThread(); - removeSurfaceCallbacks(); - setVideoOutputInternal(/* videoOutput= */ null); - maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); + blockUntilConstructorFinished(); + player.clearVideoSurface(); } @Override public void clearVideoSurface(@Nullable Surface surface) { - verifyApplicationThread(); - if (surface != null && surface == videoOutput) { - clearVideoSurface(); - } + blockUntilConstructorFinished(); + player.clearVideoSurface(surface); } @Override public void setVideoSurface(@Nullable Surface surface) { - verifyApplicationThread(); - removeSurfaceCallbacks(); - setVideoOutputInternal(surface); - int newSurfaceSize = surface == null ? 0 : C.LENGTH_UNSET; - maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); + blockUntilConstructorFinished(); + player.setVideoSurface(surface); } @Override 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); - } - } + blockUntilConstructorFinished(); + player.setVideoSurfaceHolder(surfaceHolder); } @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { - verifyApplicationThread(); - if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { - clearVideoSurface(); - } + blockUntilConstructorFinished(); + player.clearVideoSurfaceHolder(surfaceHolder); } @Override 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; - 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()); - } + blockUntilConstructorFinished(); + player.setVideoSurfaceView(surfaceView); } @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { - verifyApplicationThread(); - clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); + blockUntilConstructorFinished(); + player.clearVideoSurfaceView(surfaceView); } @Override 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()); - } - } + blockUntilConstructorFinished(); + player.setVideoTextureView(textureView); } @Override public void clearVideoTextureView(@Nullable TextureView textureView) { - verifyApplicationThread(); - if (textureView != null && textureView == this.textureView) { - clearVideoSurface(); - } + blockUntilConstructorFinished(); + player.clearVideoTextureView(textureView); } @Override public void addAudioOffloadListener(AudioOffloadListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. + blockUntilConstructorFinished(); player.addAudioOffloadListener(listener); } @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. + blockUntilConstructorFinished(); player.removeAudioOffloadListener(listener); } @Override 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)); + blockUntilConstructorFinished(); + player.setAudioAttributes(audioAttributes, handleAudioFocus); } @Override public AudioAttributes getAudioAttributes() { - return audioAttributes; + blockUntilConstructorFinished(); + return player.getAudioAttributes(); } @Override 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); - } + blockUntilConstructorFinished(); + player.setAudioSessionId(audioSessionId); } @Override public int getAudioSessionId() { - return audioSessionId; + blockUntilConstructorFinished(); + return player.getAudioSessionId(); } @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { - verifyApplicationThread(); - sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); + blockUntilConstructorFinished(); + player.setAuxEffectInfo(auxEffectInfo); } @Override public void clearAuxEffectInfo() { - setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); + blockUntilConstructorFinished(); + player.clearAuxEffectInfo(); } @Override 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); - } + blockUntilConstructorFinished(); + player.setVolume(volume); } @Override public float getVolume() { - return volume; + blockUntilConstructorFinished(); + return player.getVolume(); } @Override public boolean getSkipSilenceEnabled() { - return skipSilenceEnabled; + blockUntilConstructorFinished(); + return player.getSkipSilenceEnabled(); } @Override 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(); + blockUntilConstructorFinished(); + player.setSkipSilenceEnabled(skipSilenceEnabled); } @Override public AnalyticsCollector getAnalyticsCollector() { - return analyticsCollector; + blockUntilConstructorFinished(); + return player.getAnalyticsCollector(); } @Override public void addAnalyticsListener(AnalyticsListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. - checkNotNull(listener); - analyticsCollector.addListener(listener); + blockUntilConstructorFinished(); + player.addAnalyticsListener(listener); } @Override public void removeAnalyticsListener(AnalyticsListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. - analyticsCollector.removeListener(listener); + blockUntilConstructorFinished(); + player.removeAnalyticsListener(listener); } @Override public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { - verifyApplicationThread(); - if (playerReleased) { - return; - } - audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); + blockUntilConstructorFinished(); + player.setHandleAudioBecomingNoisy(handleAudioBecomingNoisy); } @Override 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; + blockUntilConstructorFinished(); + player.setPriorityTaskManager(priorityTaskManager); } @Override @Nullable public Format getVideoFormat() { - return videoFormat; + blockUntilConstructorFinished(); + return player.getVideoFormat(); } @Override @Nullable public Format getAudioFormat() { - return audioFormat; + blockUntilConstructorFinished(); + return player.getAudioFormat(); } @Override @Nullable public DecoderCounters getVideoDecoderCounters() { - return videoDecoderCounters; + blockUntilConstructorFinished(); + return player.getVideoDecoderCounters(); } @Override @Nullable public DecoderCounters getAudioDecoderCounters() { - return audioDecoderCounters; + blockUntilConstructorFinished(); + return player.getAudioDecoderCounters(); } @Override public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { - verifyApplicationThread(); - videoFrameMetadataListener = listener; - player - .createMessage(frameMetadataListener) - .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) - .setPayload(listener) - .send(); + blockUntilConstructorFinished(); + player.setVideoFrameMetadataListener(listener); } @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { - verifyApplicationThread(); - if (videoFrameMetadataListener != listener) { - return; - } - player - .createMessage(frameMetadataListener) - .setType(FrameMetadataListener.MSG_SET_VIDEO_FRAME_METADATA_LISTENER) - .setPayload(null) - .send(); + blockUntilConstructorFinished(); + player.clearVideoFrameMetadataListener(listener); } @Override public void setCameraMotionListener(CameraMotionListener listener) { - verifyApplicationThread(); - cameraMotionListener = listener; - player - .createMessage(frameMetadataListener) - .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) - .setPayload(listener) - .send(); + blockUntilConstructorFinished(); + player.setCameraMotionListener(listener); } @Override public void clearCameraMotionListener(CameraMotionListener listener) { - verifyApplicationThread(); - if (cameraMotionListener != listener) { - return; - } - player - .createMessage(frameMetadataListener) - .setType(FrameMetadataListener.MSG_SET_CAMERA_MOTION_LISTENER) - .setPayload(null) - .send(); + blockUntilConstructorFinished(); + player.clearCameraMotionListener(listener); } @Override public List getCurrentCues() { - verifyApplicationThread(); - return currentCues; + blockUntilConstructorFinished(); + return player.getCurrentCues(); } // ExoPlayer implementation @Override public Looper getPlaybackLooper() { + blockUntilConstructorFinished(); return player.getPlaybackLooper(); } @Override public Looper getApplicationLooper() { + blockUntilConstructorFinished(); return player.getApplicationLooper(); } @Override public Clock getClock() { + blockUntilConstructorFinished(); return player.getClock(); } @Override public void addListener(Listener listener) { - checkNotNull(listener); - listeners.add(listener); - EventListener eventListener = listener; - addListener(eventListener); + blockUntilConstructorFinished(); + player.addListener(listener); } @Deprecated @Override public void addListener(Player.EventListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. - checkNotNull(listener); + blockUntilConstructorFinished(); player.addEventListener(listener); } @Override public void removeListener(Listener listener) { - checkNotNull(listener); - listeners.remove(listener); - EventListener eventListener = listener; - removeListener(eventListener); + blockUntilConstructorFinished(); + player.removeListener(listener); } @Deprecated @Override public void removeListener(Player.EventListener listener) { - // Don't verify application thread. We allow calls to this method from any thread. + blockUntilConstructorFinished(); player.removeEventListener(listener); } @Override @State public int getPlaybackState() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPlaybackState(); } @Override @PlaybackSuppressionReason public int getPlaybackSuppressionReason() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPlaybackSuppressionReason(); } @Override @Nullable public ExoPlaybackException getPlayerError() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPlayerError(); } /** @deprecated Use {@link #prepare()} instead. */ @Deprecated @Override + @SuppressWarnings("deprecation") // Calling deprecated method. public void retry() { - verifyApplicationThread(); - prepare(); + blockUntilConstructorFinished(); + player.retry(); } @Override public Commands getAvailableCommands() { - verifyApplicationThread(); + blockUntilConstructorFinished(); 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)); + blockUntilConstructorFinished(); player.prepare(); } @@ -1063,9 +730,10 @@ public class SimpleExoPlayer extends BasePlayer */ @Deprecated @Override - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") // Forwarding deprecated method. public void prepare(MediaSource mediaSource) { - prepare(mediaSource, /* resetPosition= */ true, /* resetState= */ true); + blockUntilConstructorFinished(); + player.prepare(mediaSource); } /** @@ -1074,1115 +742,449 @@ public class SimpleExoPlayer extends BasePlayer */ @Deprecated @Override + @SuppressWarnings("deprecation") // Forwarding deprecated method. public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { - verifyApplicationThread(); - setMediaSources(Collections.singletonList(mediaSource), resetPosition); - prepare(); + blockUntilConstructorFinished(); + player.prepare(mediaSource, resetPosition, resetState); } @Override public void setMediaItems(List mediaItems, boolean resetPosition) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaItems(mediaItems, resetPosition); } @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaItems(mediaItems, startIndex, startPositionMs); } @Override public void setMediaSources(List mediaSources) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSources(mediaSources); } @Override public void setMediaSources(List mediaSources, boolean resetPosition) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSources(mediaSources, resetPosition); } @Override public void setMediaSources( List mediaSources, int startMediaItemIndex, long startPositionMs) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSources(mediaSources, startMediaItemIndex, startPositionMs); } @Override public void setMediaSource(MediaSource mediaSource) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSource(mediaSource); } @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSource(mediaSource, resetPosition); } @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setMediaSource(mediaSource, startPositionMs); } @Override public void addMediaItems(int index, List mediaItems) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.addMediaItems(index, mediaItems); } @Override public void addMediaSource(MediaSource mediaSource) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.addMediaSource(mediaSource); } @Override public void addMediaSource(int index, MediaSource mediaSource) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.addMediaSource(index, mediaSource); } @Override public void addMediaSources(List mediaSources) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.addMediaSources(mediaSources); } @Override public void addMediaSources(int index, List mediaSources) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.addMediaSources(index, mediaSources); } @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.moveMediaItems(fromIndex, toIndex, newIndex); } @Override public void removeMediaItems(int fromIndex, int toIndex) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.removeMediaItems(fromIndex, toIndex); } @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setShuffleOrder(shuffleOrder); } @Override public void setPlayWhenReady(boolean playWhenReady) { - verifyApplicationThread(); - @AudioFocusManager.PlayerCommand - int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); - updatePlayWhenReady( - playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); + blockUntilConstructorFinished(); + player.setPlayWhenReady(playWhenReady); } @Override public boolean getPlayWhenReady() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPlayWhenReady(); } @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems); } @Override public boolean getPauseAtEndOfMediaItems() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPauseAtEndOfMediaItems(); } @Override public @RepeatMode int getRepeatMode() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getRepeatMode(); } @Override public void setRepeatMode(@RepeatMode int repeatMode) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setRepeatMode(repeatMode); } @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setShuffleModeEnabled(shuffleModeEnabled); } @Override public boolean getShuffleModeEnabled() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getShuffleModeEnabled(); } @Override public boolean isLoading() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.isLoading(); } @Override public void seekTo(int mediaItemIndex, long positionMs) { - verifyApplicationThread(); - analyticsCollector.notifySeekStarted(); + blockUntilConstructorFinished(); player.seekTo(mediaItemIndex, positionMs); } @Override public long getSeekBackIncrement() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getSeekBackIncrement(); } @Override public long getSeekForwardIncrement() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getSeekForwardIncrement(); } @Override public long getMaxSeekToPreviousPosition() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getMaxSeekToPreviousPosition(); } @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setPlaybackParameters(playbackParameters); } @Override public PlaybackParameters getPlaybackParameters() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getPlaybackParameters(); } @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setSeekParameters(seekParameters); } @Override public SeekParameters getSeekParameters() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getSeekParameters(); } @Override public void setForegroundMode(boolean foregroundMode) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setForegroundMode(foregroundMode); } @Override public void stop() { - stop(/* reset= */ false); + blockUntilConstructorFinished(); + player.stop(); } @Deprecated @Override public void stop(boolean reset) { - verifyApplicationThread(); - audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); + blockUntilConstructorFinished(); 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(); + blockUntilConstructorFinished(); 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(); + blockUntilConstructorFinished(); return player.createMessage(target); } @Override public int getRendererCount() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getRendererCount(); } @Override public @C.TrackType int getRendererType(int index) { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getRendererType(index); } @Override public Renderer getRenderer(int index) { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getRenderer(index); } @Override public TrackSelector getTrackSelector() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getTrackSelector(); } @Override public TrackGroupArray getCurrentTrackGroups() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentTrackGroups(); } @Override public TrackSelectionArray getCurrentTrackSelections() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentTrackSelections(); } @Override public TracksInfo getCurrentTracksInfo() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentTracksInfo(); } @Override public TrackSelectionParameters getTrackSelectionParameters() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getTrackSelectionParameters(); } @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { - verifyApplicationThread(); + blockUntilConstructorFinished(); player.setTrackSelectionParameters(parameters); } @Override public MediaMetadata getMediaMetadata() { + blockUntilConstructorFinished(); return player.getMediaMetadata(); } @Override public MediaMetadata getPlaylistMetadata() { + blockUntilConstructorFinished(); return player.getPlaylistMetadata(); } @Override public void setPlaylistMetadata(MediaMetadata mediaMetadata) { + blockUntilConstructorFinished(); player.setPlaylistMetadata(mediaMetadata); } @Override public Timeline getCurrentTimeline() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentTimeline(); } @Override public int getCurrentPeriodIndex() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentPeriodIndex(); } @Override public int getCurrentMediaItemIndex() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentMediaItemIndex(); } @Override public long getDuration() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getDuration(); } @Override public long getCurrentPosition() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentPosition(); } @Override public long getBufferedPosition() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getBufferedPosition(); } @Override public long getTotalBufferedDuration() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getTotalBufferedDuration(); } @Override public boolean isPlayingAd() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.isPlayingAd(); } @Override public int getCurrentAdGroupIndex() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentAdGroupIndex(); } @Override public int getCurrentAdIndexInAdGroup() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getCurrentAdIndexInAdGroup(); } @Override public long getContentPosition() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getContentPosition(); } @Override public long getContentBufferedPosition() { - verifyApplicationThread(); + blockUntilConstructorFinished(); return player.getContentBufferedPosition(); } @Deprecated @Override public void setHandleWakeLock(boolean handleWakeLock) { - setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE); + blockUntilConstructorFinished(); + player.setHandleWakeLock(handleWakeLock); } @Override 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; - } + blockUntilConstructorFinished(); + player.setWakeMode(wakeMode); } @Override public DeviceInfo getDeviceInfo() { - verifyApplicationThread(); - return deviceInfo; + blockUntilConstructorFinished(); + return player.getDeviceInfo(); } @Override public int getDeviceVolume() { - verifyApplicationThread(); - return streamVolumeManager.getVolume(); + blockUntilConstructorFinished(); + return player.getDeviceVolume(); } @Override public boolean isDeviceMuted() { - verifyApplicationThread(); - return streamVolumeManager.isMuted(); + blockUntilConstructorFinished(); + return player.isDeviceMuted(); } @Override public void setDeviceVolume(int volume) { - verifyApplicationThread(); - streamVolumeManager.setVolume(volume); + blockUntilConstructorFinished(); + player.setDeviceVolume(volume); } @Override public void increaseDeviceVolume() { - verifyApplicationThread(); - streamVolumeManager.increaseVolume(); + blockUntilConstructorFinished(); + player.increaseDeviceVolume(); } @Override public void decreaseDeviceVolume() { - verifyApplicationThread(); - streamVolumeManager.decreaseVolume(); + blockUntilConstructorFinished(); + player.decreaseDeviceVolume(); } @Override public void setDeviceMuted(boolean muted) { - verifyApplicationThread(); - streamVolumeManager.setMuted(muted); + blockUntilConstructorFinished(); + player.setDeviceMuted(muted); } /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { - this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; + blockUntilConstructorFinished(); + player.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread); } - // Internal methods. - - private void removeSurfaceCallbacks() { - if (sphericalGLSurfaceView != null) { - player - .createMessage(frameMetadataListener) - .setType(FrameMetadataListener.MSG_SET_SPHERICAL_SURFACE_VIEW) - .setPayload(null) - .send(); - sphericalGLSurfaceView.removeVideoSurfaceListener(componentListener); - sphericalGLSurfaceView = null; - } - if (textureView != null) { - if (textureView.getSurfaceTextureListener() != componentListener) { - Log.w(TAG, "SurfaceTextureListener already unset or replaced."); - } else { - textureView.setSurfaceTextureListener(null); - } - textureView = null; - } - if (surfaceHolder != null) { - surfaceHolder.removeCallback(componentListener); - surfaceHolder = null; - } - } - - private void setSurfaceTextureInternal(SurfaceTexture surfaceTexture) { - Surface surface = new Surface(surfaceTexture); - setVideoOutputInternal(surface); - ownedSurface = surface; - } - - private void setVideoOutputInternal(@Nullable Object videoOutput) { - // Note: We don't turn this method into a no-op if the output is being replaced with itself so - // as to ensure onRenderedFirstFrame callbacks are still called in this case. - List 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() { + private void blockUntilConstructorFinished() { // 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(); - } - } } }