diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java index 36d21c7ad8..488237fd82 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -1018,7 +1018,9 @@ public interface ExoPlayer extends Player { * @throws IllegalStateException If this method has already been called. */ public ExoPlayer build() { - return buildSimpleExoPlayer(); + checkState(!buildCalled); + buildCalled = true; + return new ExoPlayerImpl(/* builder= */ this, /* wrappingPlayer= */ null); } /* package */ SimpleExoPlayer buildSimpleExoPlayer() { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index b3343eec90..14a8b20162 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -18,59 +18,6 @@ package androidx.media3.exoplayer; import static androidx.media3.common.C.TRACK_TYPE_AUDIO; import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION; import static androidx.media3.common.C.TRACK_TYPE_VIDEO; -import static androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME; -import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS; -import static androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES; -import static androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM; -import static androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME; -import static androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; -import static androidx.media3.common.Player.COMMAND_GET_TEXT; -import static androidx.media3.common.Player.COMMAND_GET_TIMELINE; -import static androidx.media3.common.Player.COMMAND_GET_TRACK_INFOS; -import static androidx.media3.common.Player.COMMAND_GET_VOLUME; -import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE; -import static androidx.media3.common.Player.COMMAND_PREPARE; -import static androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION; -import static androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM; -import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME; -import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA; -import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE; -import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE; -import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH; -import static androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS; -import static androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE; -import static androidx.media3.common.Player.COMMAND_SET_VOLUME; -import static androidx.media3.common.Player.COMMAND_STOP; -import static androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION; -import static androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL; -import static androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE; -import static androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK; -import static androidx.media3.common.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED; -import static androidx.media3.common.Player.EVENT_AUDIO_SESSION_ID; -import static androidx.media3.common.Player.EVENT_CUES; -import static androidx.media3.common.Player.EVENT_DEVICE_INFO_CHANGED; -import static androidx.media3.common.Player.EVENT_DEVICE_VOLUME_CHANGED; -import static androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED; -import static androidx.media3.common.Player.EVENT_METADATA; -import static androidx.media3.common.Player.EVENT_PLAYLIST_METADATA_CHANGED; -import static androidx.media3.common.Player.EVENT_RENDERED_FIRST_FRAME; -import static androidx.media3.common.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED; -import static androidx.media3.common.Player.EVENT_SURFACE_SIZE_CHANGED; -import static androidx.media3.common.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED; -import static androidx.media3.common.Player.EVENT_VIDEO_SIZE_CHANGED; -import static androidx.media3.common.Player.EVENT_VOLUME_CHANGED; -import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; -import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; -import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT; -import static androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK; -import static androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE; -import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS; -import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST; -import static androidx.media3.common.Player.STATE_BUFFERING; -import static androidx.media3.common.Player.STATE_ENDED; -import static androidx.media3.common.Player.STATE_IDLE; -import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED; -import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.castNonNull; @@ -107,6 +54,7 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.media3.common.AudioAttributes; import androidx.media3.common.AuxEffectInfo; +import androidx.media3.common.BasePlayer; import androidx.media3.common.C; import androidx.media3.common.DeviceInfo; import androidx.media3.common.Format; @@ -118,16 +66,6 @@ import androidx.media3.common.Metadata; import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackParameters; import androidx.media3.common.Player; -import androidx.media3.common.Player.Commands; -import androidx.media3.common.Player.DiscontinuityReason; -import androidx.media3.common.Player.Events; -import androidx.media3.common.Player.Listener; -import androidx.media3.common.Player.PlayWhenReadyChangeReason; -import androidx.media3.common.Player.PlaybackSuppressionReason; -import androidx.media3.common.Player.PositionInfo; -import androidx.media3.common.Player.RepeatMode; -import androidx.media3.common.Player.State; -import androidx.media3.common.Player.TimelineChangeReason; import androidx.media3.common.PriorityTaskManager; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; @@ -144,7 +82,6 @@ import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; -import androidx.media3.exoplayer.ExoPlayer.AudioOffloadListener; import androidx.media3.exoplayer.PlayerMessage.Target; import androidx.media3.exoplayer.Renderer.MessageType; import androidx.media3.exoplayer.analytics.AnalyticsCollector; @@ -173,8 +110,13 @@ 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 { +/** The default implementation of {@link ExoPlayer}. */ +/* package */ final class ExoPlayerImpl extends BasePlayer + implements ExoPlayer, + ExoPlayer.AudioComponent, + ExoPlayer.VideoComponent, + ExoPlayer.TextComponent, + ExoPlayer.DeviceComponent { static { MediaLibraryInfo.registerModule("media3.exoplayer"); @@ -204,7 +146,6 @@ import java.util.concurrent.TimeoutException; private final ListenerSet listeners; private final CopyOnWriteArraySet audioOffloadListeners; private final Timeline.Period period; - private final Timeline.Window window; private final List mediaSourceHolderSnapshots; private final boolean useLazyPreparation; private final MediaSource.Factory mediaSourceFactory; @@ -279,7 +220,7 @@ import java.util.concurrent.TimeoutException; private long maskingWindowPositionMs; @SuppressLint("HandlerLeak") - public ExoPlayerImpl(ExoPlayer.Builder builder, Player wrappingPlayer) { + public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer) { constructorFinished = new ConditionVariable(); try { Log.i( @@ -323,12 +264,12 @@ import java.util.concurrent.TimeoutException; this.pauseAtEndOfMediaItems = builder.pauseAtEndOfMediaItems; this.applicationLooper = builder.looper; this.clock = builder.clock; - this.wrappingPlayer = wrappingPlayer; + this.wrappingPlayer = wrappingPlayer == null ? this : wrappingPlayer; listeners = new ListenerSet<>( applicationLooper, clock, - (listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags))); + (listener, flags) -> listener.onEvents(this.wrappingPlayer, new Events(flags))); audioOffloadListeners = new CopyOnWriteArraySet<>(); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); @@ -339,7 +280,6 @@ import java.util.concurrent.TimeoutException; TracksInfo.EMPTY, /* info= */ null); period = new Timeline.Period(); - window = new Timeline.Window(); permanentAvailableCommands = new Commands.Builder() .addAll( @@ -377,7 +317,7 @@ import java.util.concurrent.TimeoutException; playbackInfoUpdate -> playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult); - analyticsCollector.setPlayer(wrappingPlayer, applicationLooper); + analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper); PlayerId playerId = Util.SDK_INT < 31 ? new PlayerId() @@ -420,7 +360,7 @@ import java.util.concurrent.TimeoutException; bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); addAudioOffloadListener(componentListener); if (builder.foregroundModeTimeoutMs > 0) { - experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); + internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); } audioBecomingNoisyManager = @@ -454,82 +394,108 @@ import java.util.concurrent.TimeoutException; } } - /** - * Sets a limit on the time a call to {@link #setForegroundMode} can spend. If a call to {@link - * #setForegroundMode} takes more than {@code timeoutMs} milliseconds to complete, the player will - * raise an error via {@link Player.Listener#onPlayerError}. - * - *

This method is experimental, and will be renamed or removed in a future release. It should - * only be called before the player is used. - * - * @param timeoutMs The time limit in milliseconds. - */ - public void experimentalSetForegroundModeTimeoutMs(long timeoutMs) { - internalPlayer.experimentalSetForegroundModeTimeoutMs(timeoutMs); + @SuppressWarnings("deprecation") // Returning deprecated class. + @Override + @Deprecated + public AudioComponent getAudioComponent() { + return this; } + @SuppressWarnings("deprecation") // Returning deprecated class. + @Override + @Deprecated + public VideoComponent getVideoComponent() { + return this; + } + + @SuppressWarnings("deprecation") // Returning deprecated class. + @Override + @Deprecated + public TextComponent getTextComponent() { + return this; + } + + @SuppressWarnings("deprecation") // Returning deprecated class. + @Override + @Deprecated + public DeviceComponent getDeviceComponent() { + return this; + } + + @Override public void experimentalSetOffloadSchedulingEnabled(boolean offloadSchedulingEnabled) { verifyApplicationThread(); internalPlayer.experimentalSetOffloadSchedulingEnabled(offloadSchedulingEnabled); } + @Override public boolean experimentalIsSleepingForOffload() { verifyApplicationThread(); return playbackInfo.sleepingForOffload; } + @Override public Looper getPlaybackLooper() { // Don't verify application thread. We allow calls to this method from any thread. return internalPlayer.getPlaybackLooper(); } + @Override public Looper getApplicationLooper() { // Don't verify application thread. We allow calls to this method from any thread. return applicationLooper; } + @Override public Clock getClock() { // Don't verify application thread. We allow calls to this method from any thread. return clock; } + @Override public void addAudioOffloadListener(AudioOffloadListener listener) { // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.add(listener); } + @Override public void removeAudioOffloadListener(AudioOffloadListener listener) { // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.remove(listener); } + @Override public Commands getAvailableCommands() { verifyApplicationThread(); return availableCommands; } + @Override public @State int getPlaybackState() { verifyApplicationThread(); return playbackInfo.playbackState; } + @Override public @PlaybackSuppressionReason int getPlaybackSuppressionReason() { verifyApplicationThread(); return playbackInfo.playbackSuppressionReason; } + @Override @Nullable public ExoPlaybackException getPlayerError() { verifyApplicationThread(); return playbackInfo.playbackError; } - /** @deprecated Use {@link #prepare()} instead. */ + @Override @Deprecated public void retry() { prepare(); } + @Override public void prepare() { verifyApplicationThread(); boolean playWhenReady = getPlayWhenReady(); @@ -561,9 +527,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } - /** - * @deprecated Use {@link #setMediaSource(MediaSource)} and {@link ExoPlayer#prepare()} instead. - */ + @Override @Deprecated public void prepare(MediaSource mediaSource) { verifyApplicationThread(); @@ -571,10 +535,7 @@ import java.util.concurrent.TimeoutException; prepare(); } - /** - * @deprecated Use {@link #setMediaSource(MediaSource, boolean)} and {@link ExoPlayer#prepare()} - * instead. - */ + @Override @Deprecated public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { verifyApplicationThread(); @@ -582,37 +543,44 @@ import java.util.concurrent.TimeoutException; prepare(); } + @Override public void setMediaItems(List mediaItems, boolean resetPosition) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), resetPosition); } + @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { verifyApplicationThread(); setMediaSources(createMediaSources(mediaItems), startIndex, startPositionMs); } + @Override public void setMediaSource(MediaSource mediaSource) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource)); } + @Override public void setMediaSource(MediaSource mediaSource, long startPositionMs) { verifyApplicationThread(); setMediaSources( Collections.singletonList(mediaSource), /* startWindowIndex= */ 0, startPositionMs); } + @Override public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { verifyApplicationThread(); setMediaSources(Collections.singletonList(mediaSource), resetPosition); } + @Override public void setMediaSources(List mediaSources) { verifyApplicationThread(); setMediaSources(mediaSources, /* resetPosition= */ true); } + @Override public void setMediaSources(List mediaSources, boolean resetPosition) { verifyApplicationThread(); setMediaSourcesInternal( @@ -622,6 +590,7 @@ import java.util.concurrent.TimeoutException; /* resetToDefaultPosition= */ resetPosition); } + @Override public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); @@ -629,27 +598,32 @@ import java.util.concurrent.TimeoutException; mediaSources, startWindowIndex, startPositionMs, /* resetToDefaultPosition= */ false); } + @Override public void addMediaItems(int index, List mediaItems) { verifyApplicationThread(); index = min(index, mediaSourceHolderSnapshots.size()); addMediaSources(index, createMediaSources(mediaItems)); } + @Override public void addMediaSource(MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(Collections.singletonList(mediaSource)); } + @Override public void addMediaSource(int index, MediaSource mediaSource) { verifyApplicationThread(); addMediaSources(index, Collections.singletonList(mediaSource)); } + @Override public void addMediaSources(List mediaSources) { verifyApplicationThread(); addMediaSources(/* index= */ mediaSourceHolderSnapshots.size(), mediaSources); } + @Override public void addMediaSources(int index, List mediaSources) { verifyApplicationThread(); Assertions.checkArgument(index >= 0); @@ -674,6 +648,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); @@ -691,6 +666,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { verifyApplicationThread(); Assertions.checkArgument( @@ -720,6 +696,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { verifyApplicationThread(); Timeline timeline = createMaskingTimeline(); @@ -743,6 +720,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { @@ -752,11 +730,13 @@ import java.util.concurrent.TimeoutException; internalPlayer.setPauseAtEndOfWindow(pauseAtEndOfMediaItems); } + @Override public boolean getPauseAtEndOfMediaItems() { verifyApplicationThread(); return pauseAtEndOfMediaItems; } + @Override public void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThread(); @AudioFocusManager.PlayerCommand @@ -765,11 +745,13 @@ import java.util.concurrent.TimeoutException; playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); } + @Override public boolean getPlayWhenReady() { verifyApplicationThread(); return playbackInfo.playWhenReady; } + @Override public void setRepeatMode(@RepeatMode int repeatMode) { verifyApplicationThread(); if (this.repeatMode != repeatMode) { @@ -782,11 +764,13 @@ import java.util.concurrent.TimeoutException; } } + @Override public @RepeatMode int getRepeatMode() { verifyApplicationThread(); return repeatMode; } + @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { verifyApplicationThread(); if (this.shuffleModeEnabled != shuffleModeEnabled) { @@ -800,16 +784,19 @@ import java.util.concurrent.TimeoutException; } } + @Override public boolean getShuffleModeEnabled() { verifyApplicationThread(); return shuffleModeEnabled; } + @Override public boolean isLoading() { verifyApplicationThread(); return playbackInfo.isLoading; } + @Override public void seekTo(int mediaItemIndex, long positionMs) { verifyApplicationThread(); analyticsCollector.notifySeekStarted(); @@ -852,21 +839,25 @@ import java.util.concurrent.TimeoutException; oldMaskingMediaItemIndex); } + @Override public long getSeekBackIncrement() { verifyApplicationThread(); return seekBackIncrementMs; } + @Override public long getSeekForwardIncrement() { verifyApplicationThread(); return seekForwardIncrementMs; } + @Override public long getMaxSeekToPreviousPosition() { verifyApplicationThread(); return C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS; } + @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { verifyApplicationThread(); if (playbackParameters == null) { @@ -889,11 +880,13 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public PlaybackParameters getPlaybackParameters() { verifyApplicationThread(); return playbackInfo.playbackParameters; } + @Override public void setSeekParameters(@Nullable SeekParameters seekParameters) { verifyApplicationThread(); if (seekParameters == null) { @@ -905,18 +898,20 @@ import java.util.concurrent.TimeoutException; } } + @Override public SeekParameters getSeekParameters() { verifyApplicationThread(); return seekParameters; } + @Override public void setForegroundMode(boolean foregroundMode) { verifyApplicationThread(); if (this.foregroundMode != foregroundMode) { this.foregroundMode = foregroundMode; if (!internalPlayer.setForegroundMode(foregroundMode)) { // One of the renderers timed out releasing its resources. - stop( + stopInternal( /* reset= */ false, ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_SET_FOREGROUND_MODE), @@ -925,55 +920,20 @@ import java.util.concurrent.TimeoutException; } } + @Override public void stop() { stop(/* reset= */ false); } + @Override public void stop(boolean reset) { verifyApplicationThread(); audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); - stop(reset, /* error= */ null); + stopInternal(reset, /* error= */ null); currentCues = ImmutableList.of(); } - /** - * Stops the player. - * - * @param reset Whether the playlist should be cleared and whether the playback position and - * playback error should be reset. - * @param error An optional {@link ExoPlaybackException} to set. - */ - public void stop(boolean reset, @Nullable ExoPlaybackException error) { - PlaybackInfo playbackInfo; - if (reset) { - playbackInfo = - removeMediaItemsInternal( - /* fromIndex= */ 0, /* toIndex= */ mediaSourceHolderSnapshots.size()); - playbackInfo = playbackInfo.copyWithPlaybackError(null); - } else { - playbackInfo = this.playbackInfo.copyWithLoadingMediaPeriodId(this.playbackInfo.periodId); - playbackInfo.bufferedPositionUs = playbackInfo.positionUs; - playbackInfo.totalBufferedDurationUs = 0; - } - playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); - if (error != null) { - playbackInfo = playbackInfo.copyWithPlaybackError(error); - } - pendingOperationAcks++; - internalPlayer.stop(); - boolean positionDiscontinuity = - playbackInfo.timeline.isEmpty() && !this.playbackInfo.timeline.isEmpty(); - updatePlaybackInfo( - playbackInfo, - TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, - /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, - /* seekProcessed= */ false, - positionDiscontinuity, - DISCONTINUITY_REASON_REMOVE, - /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(playbackInfo), - /* ignored */ C.INDEX_UNSET); - } - + @Override public void release() { Log.i( TAG, @@ -1027,11 +987,13 @@ import java.util.concurrent.TimeoutException; playerReleased = true; } + @Override public PlayerMessage createMessage(Target target) { verifyApplicationThread(); return createMessageInternal(target); } + @Override public int getCurrentPeriodIndex() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { @@ -1041,12 +1003,14 @@ import java.util.concurrent.TimeoutException; } } + @Override public int getCurrentMediaItemIndex() { verifyApplicationThread(); int currentWindowIndex = getCurrentWindowIndexInternal(); return currentWindowIndex == C.INDEX_UNSET ? 0 : currentWindowIndex; } + @Override public long getDuration() { verifyApplicationThread(); if (isPlayingAd()) { @@ -1058,18 +1022,13 @@ import java.util.concurrent.TimeoutException; return getContentDuration(); } - private long getContentDuration() { - Timeline timeline = getCurrentTimeline(); - return timeline.isEmpty() - ? C.TIME_UNSET - : timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs(); - } - + @Override public long getCurrentPosition() { verifyApplicationThread(); return Util.usToMs(getCurrentPositionUsInternal(playbackInfo)); } + @Override public long getBufferedPosition() { verifyApplicationThread(); if (isPlayingAd()) { @@ -1080,26 +1039,31 @@ import java.util.concurrent.TimeoutException; return getContentBufferedPosition(); } + @Override public long getTotalBufferedDuration() { verifyApplicationThread(); return Util.usToMs(playbackInfo.totalBufferedDurationUs); } + @Override public boolean isPlayingAd() { verifyApplicationThread(); return playbackInfo.periodId.isAd(); } + @Override public int getCurrentAdGroupIndex() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adGroupIndex : C.INDEX_UNSET; } + @Override public int getCurrentAdIndexInAdGroup() { verifyApplicationThread(); return isPlayingAd() ? playbackInfo.periodId.adIndexInAdGroup : C.INDEX_UNSET; } + @Override public long getContentPosition() { verifyApplicationThread(); if (isPlayingAd()) { @@ -1115,6 +1079,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public long getContentBufferedPosition() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { @@ -1139,46 +1104,55 @@ import java.util.concurrent.TimeoutException; playbackInfo.timeline, playbackInfo.loadingMediaPeriodId, contentBufferedPositionUs)); } + @Override public int getRendererCount() { verifyApplicationThread(); return renderers.length; } + @Override public @C.TrackType int getRendererType(int index) { verifyApplicationThread(); return renderers[index].getTrackType(); } + @Override public Renderer getRenderer(int index) { verifyApplicationThread(); return renderers[index]; } + @Override public TrackSelector getTrackSelector() { verifyApplicationThread(); return trackSelector; } + @Override public TrackGroupArray getCurrentTrackGroups() { verifyApplicationThread(); return playbackInfo.trackGroups; } + @Override public TrackSelectionArray getCurrentTrackSelections() { verifyApplicationThread(); return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); } + @Override public TracksInfo getCurrentTracksInfo() { verifyApplicationThread(); return playbackInfo.trackSelectorResult.tracksInfo; } + @Override public TrackSelectionParameters getTrackSelectionParameters() { verifyApplicationThread(); return trackSelector.getParameters(); } + @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { verifyApplicationThread(); if (!trackSelector.isSetParametersSupported() @@ -1191,16 +1165,19 @@ import java.util.concurrent.TimeoutException; listener -> listener.onTrackSelectionParametersChanged(parameters)); } + @Override public MediaMetadata getMediaMetadata() { verifyApplicationThread(); return mediaMetadata; } + @Override public MediaMetadata getPlaylistMetadata() { verifyApplicationThread(); return playlistMetadata; } + @Override public void setPlaylistMetadata(MediaMetadata playlistMetadata) { verifyApplicationThread(); checkNotNull(playlistMetadata); @@ -1213,21 +1190,25 @@ import java.util.concurrent.TimeoutException; listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); } + @Override public Timeline getCurrentTimeline() { verifyApplicationThread(); return playbackInfo.timeline; } + @Override public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { verifyApplicationThread(); this.videoScalingMode = videoScalingMode; sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode); } + @Override public @C.VideoScalingMode int getVideoScalingMode() { return videoScalingMode; } + @Override public void setVideoChangeFrameRateStrategy( @C.VideoChangeFrameRateStrategy int videoChangeFrameRateStrategy) { verifyApplicationThread(); @@ -1239,14 +1220,17 @@ import java.util.concurrent.TimeoutException; TRACK_TYPE_VIDEO, MSG_SET_CHANGE_FRAME_RATE_STRATEGY, videoChangeFrameRateStrategy); } + @Override public @C.VideoChangeFrameRateStrategy int getVideoChangeFrameRateStrategy() { return videoChangeFrameRateStrategy; } + @Override public VideoSize getVideoSize() { return videoSize; } + @Override public void clearVideoSurface() { verifyApplicationThread(); removeSurfaceCallbacks(); @@ -1254,6 +1238,7 @@ import java.util.concurrent.TimeoutException; maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } + @Override public void clearVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); if (surface != null && surface == videoOutput) { @@ -1261,6 +1246,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); removeSurfaceCallbacks(); @@ -1269,6 +1255,7 @@ import java.util.concurrent.TimeoutException; maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } + @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder == null) { @@ -1290,6 +1277,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { @@ -1297,6 +1285,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { @@ -1318,11 +1307,13 @@ import java.util.concurrent.TimeoutException; } } + @Override public void clearVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder()); } + @Override public void setVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView == null) { @@ -1347,6 +1338,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void clearVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView != null && textureView == this.textureView) { @@ -1354,6 +1346,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setAudioAttributes(AudioAttributes newAudioAttributes, boolean handleAudioFocus) { verifyApplicationThread(); if (playerReleased) { @@ -1378,10 +1371,12 @@ import java.util.concurrent.TimeoutException; listeners.flushEvents(); } + @Override public AudioAttributes getAudioAttributes() { return audioAttributes; } + @Override public void setAudioSessionId(int audioSessionId) { verifyApplicationThread(); if (this.audioSessionId == audioSessionId) { @@ -1406,19 +1401,23 @@ import java.util.concurrent.TimeoutException; EVENT_AUDIO_SESSION_ID, listener -> listener.onAudioSessionIdChanged(finalAudioSessionId)); } + @Override public int getAudioSessionId() { return audioSessionId; } + @Override public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { verifyApplicationThread(); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUX_EFFECT_INFO, auxEffectInfo); } + @Override public void clearAuxEffectInfo() { setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); } + @Override public void setVolume(float volume) { verifyApplicationThread(); volume = Util.constrainValue(volume, /* min= */ 0, /* max= */ 1); @@ -1431,14 +1430,17 @@ import java.util.concurrent.TimeoutException; listeners.sendEvent(EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(finalVolume)); } + @Override public float getVolume() { return volume; } + @Override public boolean getSkipSilenceEnabled() { return skipSilenceEnabled; } + @Override public void setSkipSilenceEnabled(boolean newSkipSilenceEnabled) { verifyApplicationThread(); if (skipSilenceEnabled == newSkipSilenceEnabled) { @@ -1451,21 +1453,25 @@ import java.util.concurrent.TimeoutException; listener -> listener.onSkipSilenceEnabledChanged(newSkipSilenceEnabled)); } + @Override public AnalyticsCollector getAnalyticsCollector() { return analyticsCollector; } + @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); } + @Override public void removeAnalyticsListener(AnalyticsListener listener) { // Don't verify application thread. We allow calls to this method from any thread. analyticsCollector.removeListener(listener); } + @Override public void setHandleAudioBecomingNoisy(boolean handleAudioBecomingNoisy) { verifyApplicationThread(); if (playerReleased) { @@ -1474,6 +1480,7 @@ import java.util.concurrent.TimeoutException; audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); } + @Override public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { verifyApplicationThread(); if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) { @@ -1491,26 +1498,31 @@ import java.util.concurrent.TimeoutException; this.priorityTaskManager = priorityTaskManager; } + @Override @Nullable public Format getVideoFormat() { return videoFormat; } + @Override @Nullable public Format getAudioFormat() { return audioFormat; } + @Override @Nullable public DecoderCounters getVideoDecoderCounters() { return videoDecoderCounters; } + @Override @Nullable public DecoderCounters getAudioDecoderCounters() { return audioDecoderCounters; } + @Override public void setVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); videoFrameMetadataListener = listener; @@ -1520,6 +1532,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); if (videoFrameMetadataListener != listener) { @@ -1531,6 +1544,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void setCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); cameraMotionListener = listener; @@ -1540,6 +1554,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void clearCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); if (cameraMotionListener != listener) { @@ -1551,27 +1566,32 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public List getCurrentCues() { verifyApplicationThread(); return currentCues; } + @Override public void addListener(Listener listener) { // Don't verify application thread. We allow calls to this method from any thread. checkNotNull(listener); listeners.add(listener); } + @Override public void removeListener(Listener listener) { // Don't verify application thread. We allow calls to this method from any thread. checkNotNull(listener); listeners.remove(listener); } + @Override public void setHandleWakeLock(boolean handleWakeLock) { setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE); } + @Override public void setWakeMode(@C.WakeMode int wakeMode) { verifyApplicationThread(); switch (wakeMode) { @@ -1592,36 +1612,43 @@ import java.util.concurrent.TimeoutException; } } + @Override public DeviceInfo getDeviceInfo() { verifyApplicationThread(); return deviceInfo; } + @Override public int getDeviceVolume() { verifyApplicationThread(); return streamVolumeManager.getVolume(); } + @Override public boolean isDeviceMuted() { verifyApplicationThread(); return streamVolumeManager.isMuted(); } + @Override public void setDeviceVolume(int volume) { verifyApplicationThread(); streamVolumeManager.setVolume(volume); } + @Override public void increaseDeviceVolume() { verifyApplicationThread(); streamVolumeManager.increaseVolume(); } + @Override public void decreaseDeviceVolume() { verifyApplicationThread(); streamVolumeManager.decreaseVolume(); } + @Override public void setDeviceMuted(boolean muted) { verifyApplicationThread(); streamVolumeManager.setMuted(muted); @@ -1631,6 +1658,44 @@ import java.util.concurrent.TimeoutException; this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; } + /** + * Stops the player. + * + * @param reset Whether the playlist should be cleared and whether the playback position and + * playback error should be reset. + * @param error An optional {@link ExoPlaybackException} to set. + */ + private void stopInternal(boolean reset, @Nullable ExoPlaybackException error) { + PlaybackInfo playbackInfo; + if (reset) { + playbackInfo = + removeMediaItemsInternal( + /* fromIndex= */ 0, /* toIndex= */ mediaSourceHolderSnapshots.size()); + playbackInfo = playbackInfo.copyWithPlaybackError(null); + } else { + playbackInfo = this.playbackInfo.copyWithLoadingMediaPeriodId(this.playbackInfo.periodId); + playbackInfo.bufferedPositionUs = playbackInfo.positionUs; + playbackInfo.totalBufferedDurationUs = 0; + } + playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE); + if (error != null) { + playbackInfo = playbackInfo.copyWithPlaybackError(error); + } + pendingOperationAcks++; + internalPlayer.stop(); + boolean positionDiscontinuity = + playbackInfo.timeline.isEmpty() && !this.playbackInfo.timeline.isEmpty(); + updatePlaybackInfo( + playbackInfo, + TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, + /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, + /* seekProcessed= */ false, + positionDiscontinuity, + DISCONTINUITY_REASON_REMOVE, + /* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(playbackInfo), + /* ignored */ C.INDEX_UNSET); + } + private int getCurrentWindowIndexInternal() { if (playbackInfo.timeline.isEmpty()) { return maskingWindowIndex; @@ -2415,7 +2480,7 @@ import java.util.concurrent.TimeoutException; } this.videoOutput = videoOutput; if (messageDeliveryTimedOut) { - stop( + stopInternal( /* reset= */ false, ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE),