diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 943a86582b..059428a850 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -958,7 +958,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/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 442a82794c..243da73f15 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 @@ -18,59 +18,6 @@ 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; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_REMOVE; -import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; -import static com.google.android.exoplayer2.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_AUDIO_SESSION_ID; -import static com.google.android.exoplayer2.Player.EVENT_CUES; -import static com.google.android.exoplayer2.Player.EVENT_DEVICE_INFO_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_DEVICE_VOLUME_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_MEDIA_METADATA_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_METADATA; -import static com.google.android.exoplayer2.Player.EVENT_PLAYLIST_METADATA_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_RENDERED_FIRST_FRAME; -import static com.google.android.exoplayer2.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_SURFACE_SIZE_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_VIDEO_SIZE_CHANGED; -import static com.google.android.exoplayer2.Player.EVENT_VOLUME_CHANGED; -import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; -import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED; -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; @@ -105,17 +52,6 @@ import android.view.TextureView; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import com.google.android.exoplayer2.ExoPlayer.AudioOffloadListener; -import com.google.android.exoplayer2.Player.Commands; -import com.google.android.exoplayer2.Player.DiscontinuityReason; -import com.google.android.exoplayer2.Player.Events; -import com.google.android.exoplayer2.Player.Listener; -import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason; -import com.google.android.exoplayer2.Player.PlaybackSuppressionReason; -import com.google.android.exoplayer2.Player.PositionInfo; -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; @@ -162,8 +98,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 { ExoPlayerLibraryInfo.registerModule("goog.exo.exoplayer"); @@ -193,7 +134,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; @@ -268,7 +208,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( @@ -312,12 +252,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); @@ -328,7 +268,6 @@ import java.util.concurrent.TimeoutException; TracksInfo.EMPTY, /* info= */ null); period = new Timeline.Period(); - window = new Timeline.Window(); permanentAvailableCommands = new Commands.Builder() .addAll( @@ -366,7 +305,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() : Api31.createPlayerId(); internalPlayer = new ExoPlayerImplInternal( @@ -405,7 +344,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 = @@ -439,82 +378,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(); @@ -546,9 +511,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(); @@ -556,10 +519,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(); @@ -567,37 +527,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( @@ -607,6 +574,7 @@ import java.util.concurrent.TimeoutException; /* resetToDefaultPosition= */ resetPosition); } + @Override public void setMediaSources( List mediaSources, int startWindowIndex, long startPositionMs) { verifyApplicationThread(); @@ -614,27 +582,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); @@ -659,6 +632,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void removeMediaItems(int fromIndex, int toIndex) { verifyApplicationThread(); toIndex = min(toIndex, mediaSourceHolderSnapshots.size()); @@ -676,6 +650,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) { verifyApplicationThread(); Assertions.checkArgument( @@ -705,6 +680,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void setShuffleOrder(ShuffleOrder shuffleOrder) { verifyApplicationThread(); Timeline timeline = createMaskingTimeline(); @@ -728,6 +704,7 @@ import java.util.concurrent.TimeoutException; /* ignored */ C.INDEX_UNSET); } + @Override public void setPauseAtEndOfMediaItems(boolean pauseAtEndOfMediaItems) { verifyApplicationThread(); if (this.pauseAtEndOfMediaItems == pauseAtEndOfMediaItems) { @@ -737,11 +714,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 @@ -750,11 +729,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) { @@ -767,11 +748,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) { @@ -785,16 +768,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(); @@ -837,21 +823,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) { @@ -874,11 +864,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) { @@ -890,18 +882,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), @@ -910,55 +904,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, @@ -1012,11 +971,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()) { @@ -1026,12 +987,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()) { @@ -1043,18 +1006,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()) { @@ -1065,26 +1023,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()) { @@ -1100,6 +1063,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public long getContentBufferedPosition() { verifyApplicationThread(); if (playbackInfo.timeline.isEmpty()) { @@ -1124,46 +1088,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() @@ -1176,16 +1149,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); @@ -1198,21 +1174,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(); @@ -1224,14 +1204,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(); @@ -1239,6 +1222,7 @@ import java.util.concurrent.TimeoutException; maybeNotifySurfaceSizeChanged(/* width= */ 0, /* height= */ 0); } + @Override public void clearVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); if (surface != null && surface == videoOutput) { @@ -1246,6 +1230,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setVideoSurface(@Nullable Surface surface) { verifyApplicationThread(); removeSurfaceCallbacks(); @@ -1254,6 +1239,7 @@ import java.util.concurrent.TimeoutException; maybeNotifySurfaceSizeChanged(/* width= */ newSurfaceSize, /* height= */ newSurfaceSize); } + @Override public void setVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder == null) { @@ -1275,6 +1261,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void clearVideoSurfaceHolder(@Nullable SurfaceHolder surfaceHolder) { verifyApplicationThread(); if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) { @@ -1282,6 +1269,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setVideoSurfaceView(@Nullable SurfaceView surfaceView) { verifyApplicationThread(); if (surfaceView instanceof VideoDecoderOutputBufferRenderer) { @@ -1303,11 +1291,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) { @@ -1332,6 +1322,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void clearVideoTextureView(@Nullable TextureView textureView) { verifyApplicationThread(); if (textureView != null && textureView == this.textureView) { @@ -1339,6 +1330,7 @@ import java.util.concurrent.TimeoutException; } } + @Override public void setAudioAttributes(AudioAttributes newAudioAttributes, boolean handleAudioFocus) { verifyApplicationThread(); if (playerReleased) { @@ -1363,10 +1355,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) { @@ -1391,19 +1385,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); @@ -1416,14 +1414,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) { @@ -1436,21 +1437,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) { @@ -1459,6 +1464,7 @@ import java.util.concurrent.TimeoutException; audioBecomingNoisyManager.setEnabled(handleAudioBecomingNoisy); } + @Override public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) { verifyApplicationThread(); if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) { @@ -1476,26 +1482,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; @@ -1505,6 +1516,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void clearVideoFrameMetadataListener(VideoFrameMetadataListener listener) { verifyApplicationThread(); if (videoFrameMetadataListener != listener) { @@ -1516,6 +1528,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void setCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); cameraMotionListener = listener; @@ -1525,6 +1538,7 @@ import java.util.concurrent.TimeoutException; .send(); } + @Override public void clearCameraMotionListener(CameraMotionListener listener) { verifyApplicationThread(); if (cameraMotionListener != listener) { @@ -1536,27 +1550,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) { @@ -1577,36 +1596,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); @@ -1616,6 +1642,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; @@ -2400,7 +2464,7 @@ import java.util.concurrent.TimeoutException; } this.videoOutput = videoOutput; if (messageDeliveryTimedOut) { - stop( + stopInternal( /* reset= */ false, ExoPlaybackException.createForUnexpected( new ExoTimeoutException(ExoTimeoutException.TIMEOUT_OPERATION_DETACH_SURFACE),