From 631b107bda9351a79f111b01e9855b7257bb2bed Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 7 Feb 2022 21:04:58 +0000 Subject: [PATCH] Revert of 9c8c0a59823e2a49451fbdf7040107c6cea43cc5 PiperOrigin-RevId: 426994820 --- .../java/androidx/media3/cast/CastPlayer.java | 36 +- .../media3/common/ForwardingPlayer.java | 136 ++-- .../java/androidx/media3/common/Player.java | 654 ++++++++++-------- .../media3/common/ForwardingPlayerTest.java | 15 + .../androidx/media3/exoplayer/ExoPlayer.java | 26 + .../media3/exoplayer/ExoPlayerImpl.java | 116 ++-- .../media3/exoplayer/SimpleExoPlayer.java | 13 + .../media3/test/utils/StubExoPlayer.java | 11 + 8 files changed, 602 insertions(+), 405 deletions(-) diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java index 347d27904e..cc026ba69a 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java @@ -137,7 +137,7 @@ public final class CastPlayer extends BasePlayer { private final SeekResultCallback seekResultCallback; // Listeners and notification. - private final ListenerSet listeners; + private final ListenerSet listeners; @Nullable private SessionAvailabilityListener sessionAvailabilityListener; // Internal state. @@ -280,11 +280,41 @@ public final class CastPlayer extends BasePlayer { @Override public void addListener(Listener listener) { + EventListener eventListener = listener; + addListener(eventListener); + } + + /** + * Registers a listener to receive events from the player. + * + *

The listener's methods will be called on the thread associated with {@link + * #getApplicationLooper()}. + * + * @param listener The listener to register. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. + */ + @Deprecated + @SuppressWarnings("deprecation") + public void addListener(EventListener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { + EventListener eventListener = listener; + removeListener(eventListener); + } + + /** + * Unregister a listener registered through {@link #addListener(EventListener)}. The listener will + * no longer receive events from the player. + * + * @param listener The listener to unregister. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. + */ + @Deprecated + @SuppressWarnings("deprecation") + public void removeListener(EventListener listener) { listeners.remove(listener); } @@ -443,7 +473,7 @@ public final class CastPlayer extends BasePlayer { } updateAvailableCommandsAndNotifyIfChanged(); } else if (pendingSeekCount == 0) { - listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed); + listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } listeners.flushEvents(); } @@ -1447,7 +1477,7 @@ public final class CastPlayer extends BasePlayer { currentWindowIndex = pendingSeekWindowIndex; pendingSeekWindowIndex = C.INDEX_UNSET; pendingSeekPositionMs = C.TIME_UNSET; - listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed); + listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } } } diff --git a/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java b/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java index 5e00fa0663..c0ea8befcc 100644 --- a/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java +++ b/libraries/common/src/main/java/androidx/media3/common/ForwardingPlayer.java @@ -819,159 +819,191 @@ public class ForwardingPlayer implements Player { return player; } - private static final class ForwardingListener implements Listener { + @SuppressWarnings("deprecation") // Use of deprecated type for backwards compatibility. + private static class ForwardingEventListener implements EventListener { private final ForwardingPlayer forwardingPlayer; - private final Listener listener; + private final EventListener eventListener; - public ForwardingListener(ForwardingPlayer forwardingPlayer, Listener listener) { + private ForwardingEventListener( + ForwardingPlayer forwardingPlayer, EventListener eventListener) { this.forwardingPlayer = forwardingPlayer; - this.listener = listener; - } - - @Override - public void onEvents(Player player, Events events) { - // Replace player with forwarding player. - listener.onEvents(forwardingPlayer, events); + this.eventListener = eventListener; } @Override public void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { - listener.onTimelineChanged(timeline, reason); + eventListener.onTimelineChanged(timeline, reason); } @Override public void onMediaItemTransition( @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) { - listener.onMediaItemTransition(mediaItem, reason); + eventListener.onMediaItemTransition(mediaItem, reason); } @Override - @SuppressWarnings("deprecation") public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - listener.onTracksChanged(trackGroups, trackSelections); + eventListener.onTracksChanged(trackGroups, trackSelections); } @Override public void onTracksInfoChanged(TracksInfo tracksInfo) { - listener.onTracksInfoChanged(tracksInfo); + eventListener.onTracksInfoChanged(tracksInfo); } @Override public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { - listener.onMediaMetadataChanged(mediaMetadata); + eventListener.onMediaMetadataChanged(mediaMetadata); } @Override public void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) { - listener.onPlaylistMetadataChanged(mediaMetadata); + eventListener.onPlaylistMetadataChanged(mediaMetadata); } @Override public void onIsLoadingChanged(boolean isLoading) { - listener.onIsLoadingChanged(isLoading); + eventListener.onIsLoadingChanged(isLoading); } @Override - @SuppressWarnings("deprecation") public void onLoadingChanged(boolean isLoading) { - listener.onIsLoadingChanged(isLoading); + eventListener.onIsLoadingChanged(isLoading); } @Override public void onAvailableCommandsChanged(Commands availableCommands) { - listener.onAvailableCommandsChanged(availableCommands); + eventListener.onAvailableCommandsChanged(availableCommands); } @Override public void onTrackSelectionParametersChanged(TrackSelectionParameters parameters) { - listener.onTrackSelectionParametersChanged(parameters); + eventListener.onTrackSelectionParametersChanged(parameters); } @Override - @SuppressWarnings("deprecation") public void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) { - listener.onPlayerStateChanged(playWhenReady, playbackState); + eventListener.onPlayerStateChanged(playWhenReady, playbackState); } @Override public void onPlaybackStateChanged(@State int playbackState) { - listener.onPlaybackStateChanged(playbackState); + eventListener.onPlaybackStateChanged(playbackState); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) { - listener.onPlayWhenReadyChanged(playWhenReady, reason); + eventListener.onPlayWhenReadyChanged(playWhenReady, reason); } @Override public void onPlaybackSuppressionReasonChanged( @PlayWhenReadyChangeReason int playbackSuppressionReason) { - listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason); + eventListener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason); } @Override public void onIsPlayingChanged(boolean isPlaying) { - listener.onIsPlayingChanged(isPlaying); + eventListener.onIsPlayingChanged(isPlaying); } @Override public void onRepeatModeChanged(@RepeatMode int repeatMode) { - listener.onRepeatModeChanged(repeatMode); + eventListener.onRepeatModeChanged(repeatMode); } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { - listener.onShuffleModeEnabledChanged(shuffleModeEnabled); + eventListener.onShuffleModeEnabledChanged(shuffleModeEnabled); } @Override public void onPlayerError(PlaybackException error) { - listener.onPlayerError(error); + eventListener.onPlayerError(error); } @Override public void onPlayerErrorChanged(@Nullable PlaybackException error) { - listener.onPlayerErrorChanged(error); + eventListener.onPlayerErrorChanged(error); } @Override - @SuppressWarnings("deprecation") public void onPositionDiscontinuity(@DiscontinuityReason int reason) { - listener.onPositionDiscontinuity(reason); + eventListener.onPositionDiscontinuity(reason); } @Override public void onPositionDiscontinuity( PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) { - listener.onPositionDiscontinuity(oldPosition, newPosition, reason); + eventListener.onPositionDiscontinuity(oldPosition, newPosition, reason); } @Override public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { - listener.onPlaybackParametersChanged(playbackParameters); + eventListener.onPlaybackParametersChanged(playbackParameters); } @Override public void onSeekBackIncrementChanged(long seekBackIncrementMs) { - listener.onSeekBackIncrementChanged(seekBackIncrementMs); + eventListener.onSeekBackIncrementChanged(seekBackIncrementMs); } @Override public void onSeekForwardIncrementChanged(long seekForwardIncrementMs) { - listener.onSeekForwardIncrementChanged(seekForwardIncrementMs); + eventListener.onSeekForwardIncrementChanged(seekForwardIncrementMs); } @Override public void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) { - listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs); + eventListener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs); } @Override - @SuppressWarnings("deprecation") public void onSeekProcessed() { - listener.onSeekProcessed(); + eventListener.onSeekProcessed(); + } + + @Override + public void onEvents(Player player, Events events) { + // Replace player with forwarding player. + eventListener.onEvents(forwardingPlayer, events); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ForwardingEventListener)) { + return false; + } + + ForwardingEventListener that = (ForwardingEventListener) o; + + if (!forwardingPlayer.equals(that.forwardingPlayer)) { + return false; + } + return eventListener.equals(that.eventListener); + } + + @Override + public int hashCode() { + int result = forwardingPlayer.hashCode(); + result = 31 * result + eventListener.hashCode(); + return result; + } + } + + private static final class ForwardingListener extends ForwardingEventListener + implements Listener { + + private final Listener listener; + + public ForwardingListener(ForwardingPlayer forwardingPlayer, Listener listener) { + super(forwardingPlayer, listener); + this.listener = listener; } @Override @@ -1028,27 +1060,5 @@ public class ForwardingPlayer implements Player { public void onDeviceVolumeChanged(int volume, boolean muted) { listener.onDeviceVolumeChanged(volume, muted); } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ForwardingListener)) { - return false; - } - ForwardingListener that = (ForwardingListener) o; - if (!forwardingPlayer.equals(that.forwardingPlayer)) { - return false; - } - return listener.equals(that.listener); - } - - @Override - public int hashCode() { - int result = forwardingPlayer.hashCode(); - result = 31 * result + listener.hashCode(); - return result; - } } } diff --git a/libraries/common/src/main/java/androidx/media3/common/Player.java b/libraries/common/src/main/java/androidx/media3/common/Player.java index 7487910ebb..1e7ed137e3 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -63,6 +63,343 @@ import java.util.List; */ public interface Player { + /** + * Listener of changes in player state. + * + *

All methods have no-op default implementations to allow selective overrides. + * + *

Listeners can choose to implement individual events (e.g. {@link + * #onIsPlayingChanged(boolean)}) or {@link #onEvents(Player, Events)}, which is called after one + * or more events occurred together. + * + * @deprecated Use {@link Player.Listener}. + */ + @UnstableApi + @Deprecated + interface EventListener { + + /** + * Called when the timeline has been refreshed. + * + *

Note that the current {@link MediaItem} or playback position may change as a result of a + * timeline change. If playback can't continue smoothly because of this timeline change, a + * separate {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} callback will be + * triggered. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param timeline The latest timeline. Never null, but may be empty. + * @param reason The {@link TimelineChangeReason} responsible for this timeline change. + */ + default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {} + + /** + * Called when playback transitions to a media item or starts repeating a media item according + * to the current {@link #getRepeatMode() repeat mode}. + * + *

Note that this callback is also called when the playlist becomes non-empty or empty as a + * consequence of a playlist change. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param mediaItem The {@link MediaItem}. May be null if the playlist becomes empty. + * @param reason The reason for the transition. + */ + default void onMediaItemTransition( + @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {} + + /** + * Called when the available or selected tracks change. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param trackGroups The available tracks. Never null, but may be of length zero. + * @param trackSelections The selected tracks. Never null, but may contain null elements. A + * concrete implementation may include null elements if it has a fixed number of renderer + * components, wishes to report a TrackSelection for each of them, and has one or more + * renderer components that is not assigned any selected tracks. + * @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead. + */ + @UnstableApi + @Deprecated + default void onTracksChanged( + TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + + /** + * Called when the available or selected tracks change. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param tracksInfo The available tracks information. Never null, but may be of length zero. + */ + default void onTracksInfoChanged(TracksInfo tracksInfo) {} + + /** + * Called when the combined {@link MediaMetadata} changes. + * + *

The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} + * and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track + * selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in + * the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from + * static or dynamic metadata. + * + *

This method may be called multiple times in quick succession. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param mediaMetadata The combined {@link MediaMetadata}. + */ + default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {} + + /** Called when the playlist {@link MediaMetadata} changes. */ + default void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) {} + + /** + * Called when the player starts or stops loading the source. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param isLoading Whether the source is currently being loaded. + */ + default void onIsLoadingChanged(boolean isLoading) {} + + /** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */ + @Deprecated + default void onLoadingChanged(boolean isLoading) {} + + /** + * Called when the value returned from {@link #isCommandAvailable(int)} changes for at least one + * {@link Command}. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param availableCommands The available {@link Commands}. + */ + default void onAvailableCommandsChanged(Commands availableCommands) {} + + /** + * Called when the value returned from {@link #getTrackSelectionParameters()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param parameters The new {@link TrackSelectionParameters}. + */ + default void onTrackSelectionParametersChanged(TrackSelectionParameters parameters) {} + + /** + * @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link + * #onPlayWhenReadyChanged(boolean, int)} instead. + */ + @Deprecated + default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {} + + /** + * Called when the value returned from {@link #getPlaybackState()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param playbackState The new playback {@link State state}. + */ + default void onPlaybackStateChanged(@State int playbackState) {} + + /** + * Called when the value returned from {@link #getPlayWhenReady()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param playWhenReady Whether playback will proceed when ready. + * @param reason The {@link PlayWhenReadyChangeReason reason} for the change. + */ + default void onPlayWhenReadyChanged( + boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {} + + /** + * Called when the value returned from {@link #getPlaybackSuppressionReason()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param playbackSuppressionReason The current {@link PlaybackSuppressionReason}. + */ + default void onPlaybackSuppressionReasonChanged( + @PlaybackSuppressionReason int playbackSuppressionReason) {} + + /** + * Called when the value of {@link #isPlaying()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param isPlaying Whether the player is playing. + */ + default void onIsPlayingChanged(boolean isPlaying) {} + + /** + * Called when the value of {@link #getRepeatMode()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param repeatMode The {@link RepeatMode} used for playback. + */ + default void onRepeatModeChanged(@RepeatMode int repeatMode) {} + + /** + * Called when the value of {@link #getShuffleModeEnabled()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param shuffleModeEnabled Whether shuffling of {@link MediaItem media items} is enabled. + */ + default void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {} + + /** + * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} + * immediately after this method is called. The player instance can still be used, and {@link + * #release()} must still be called on the player should it no longer be required. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + *

Implementations of Player may pass an instance of a subclass of {@link PlaybackException} + * to this method in order to include more information about the error. + * + * @param error The error. + */ + default void onPlayerError(PlaybackException error) {} + + /** + * Called when the {@link PlaybackException} returned by {@link #getPlayerError()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + *

Implementations of Player may pass an instance of a subclass of {@link PlaybackException} + * to this method in order to include more information about the error. + * + * @param error The new error, or null if the error is being cleared. + */ + default void onPlayerErrorChanged(@Nullable PlaybackException error) {} + + /** + * @deprecated Use {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} instead. + */ + @Deprecated + default void onPositionDiscontinuity(@DiscontinuityReason int reason) {} + + /** + * Called when a position discontinuity occurs. + * + *

A position discontinuity occurs when the playing period changes, the playback position + * jumps within the period currently being played, or when the playing period has been skipped + * or removed. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param oldPosition The position before the discontinuity. + * @param newPosition The position after the discontinuity. + * @param reason The {@link DiscontinuityReason} responsible for the discontinuity. + */ + default void onPositionDiscontinuity( + PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {} + + /** + * Called when the current playback parameters change. The playback parameters may change due to + * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change + * them (for example, if audio playback switches to passthrough or offload mode, where speed + * adjustment is no longer possible). + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param playbackParameters The playback parameters. + */ + default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {} + + /** + * Called when the value of {@link #getSeekBackIncrement()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds. + */ + default void onSeekBackIncrementChanged(long seekBackIncrementMs) {} + + /** + * Called when the value of {@link #getSeekForwardIncrement()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds. + */ + default void onSeekForwardIncrementChanged(long seekForwardIncrementMs) {} + + /** + * Called when the value of {@link #getMaxSeekToPreviousPosition()} changes. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} + * seeks to the previous position, in milliseconds. + */ + default void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) {} + + /** + * @deprecated Seeks are processed without delay. Listen to {@link + * #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} with reason {@link + * #DISCONTINUITY_REASON_SEEK} instead. + */ + @Deprecated + default void onSeekProcessed() {} + + /** + * Called when one or more player states changed. + * + *

State changes and events that happen within one {@link Looper} message queue iteration are + * reported together and only after all individual callbacks were triggered. + * + *

Only state changes represented by {@link Event events} are reported through this method. + * + *

Listeners should prefer this method over individual callbacks in the following cases: + * + *

    + *
  • They intend to trigger the same logic for multiple events (e.g. when updating a UI for + * both {@link #onPlaybackStateChanged(int)} and {@link #onPlayWhenReadyChanged(boolean, + * int)}). + *
  • They need access to the {@link Player} object to trigger further events (e.g. to call + * {@link Player#seekTo(long)} after a {@link #onMediaItemTransition(MediaItem, int)}). + *
  • They intend to use multiple state values together or in combination with {@link Player} + * getter methods. For example using {@link #getCurrentMediaItemIndex()} with the {@code + * timeline} provided in {@link #onTimelineChanged(Timeline, int)} is only safe from + * within this method. + *
  • They are interested in events that logically happened together (e.g {@link + * #onPlaybackStateChanged(int)} to {@link #STATE_BUFFERING} because of {@link + * #onMediaItemTransition(MediaItem, int)}). + *
+ * + * @param player The {@link Player} whose state changed. Use the getters to obtain the latest + * states. + * @param events The {@link Events} that happened in this iteration, indicating which player + * states changed. + */ + default void onEvents(Player player, Events events) {} + } + /** A set of {@link Event events}. */ final class Events { @@ -602,332 +939,62 @@ public interface Player { * *

All methods have no-op default implementations to allow selective overrides. */ - interface Listener { + interface Listener extends EventListener { - /** - * Called when one or more player states changed. - * - *

State changes and events that happen within one {@link Looper} message queue iteration are - * reported together and only after all individual callbacks were triggered. - * - *

Only state changes represented by {@link Event events} are reported through this method. - * - *

Listeners should prefer this method over individual callbacks in the following cases: - * - *

    - *
  • They intend to trigger the same logic for multiple events (e.g. when updating a UI for - * both {@link #onPlaybackStateChanged(int)} and {@link #onPlayWhenReadyChanged(boolean, - * int)}). - *
  • They need access to the {@link Player} object to trigger further events (e.g. to call - * {@link Player#seekTo(long)} after a {@link #onMediaItemTransition(MediaItem, int)}). - *
  • They intend to use multiple state values together or in combination with {@link Player} - * getter methods. For example using {@link #getCurrentMediaItemIndex()} with the {@code - * timeline} provided in {@link #onTimelineChanged(Timeline, int)} is only safe from - * within this method. - *
  • They are interested in events that logically happened together (e.g {@link - * #onPlaybackStateChanged(int)} to {@link #STATE_BUFFERING} because of {@link - * #onMediaItemTransition(MediaItem, int)}). - *
- * - * @param player The {@link Player} whose state changed. Use the getters to obtain the latest - * states. - * @param events The {@link Events} that happened in this iteration, indicating which player - * states changed. - */ - default void onEvents(Player player, Events events) {} - - /** - * Called when the timeline has been refreshed. - * - *

Note that the current {@link MediaItem} or playback position may change as a result of a - * timeline change. If playback can't continue smoothly because of this timeline change, a - * separate {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} callback will be - * triggered. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param timeline The latest timeline. Never null, but may be empty. - * @param reason The {@link TimelineChangeReason} responsible for this timeline change. - */ + @Override default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) {} - /** - * Called when playback transitions to a media item or starts repeating a media item according - * to the current {@link #getRepeatMode() repeat mode}. - * - *

Note that this callback is also called when the playlist becomes non-empty or empty as a - * consequence of a playlist change. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param mediaItem The {@link MediaItem}. May be null if the playlist becomes empty. - * @param reason The reason for the transition. - */ + @Override default void onMediaItemTransition( @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {} - /** - * Called when the available or selected tracks change. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param trackGroups The available tracks. Never null, but may be of length zero. - * @param trackSelections The selected tracks. Never null, but may contain null elements. A - * concrete implementation may include null elements if it has a fixed number of renderer - * components, wishes to report a TrackSelection for each of them, and has one or more - * renderer components that is not assigned any selected tracks. - * @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead. - */ - @UnstableApi - @Deprecated - default void onTracksChanged( - TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} - - /** - * Called when the available or selected tracks change. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param tracksInfo The available tracks information. Never null, but may be of length zero. - */ + @Override default void onTracksInfoChanged(TracksInfo tracksInfo) {} - /** - * Called when the combined {@link MediaMetadata} changes. - * - *

The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} - * and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track - * selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in - * the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from - * static or dynamic metadata. - * - *

This method may be called multiple times in quick succession. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param mediaMetadata The combined {@link MediaMetadata}. - */ - default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {} - - /** Called when the playlist {@link MediaMetadata} changes. */ - default void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) {} - - /** - * Called when the player starts or stops loading the source. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param isLoading Whether the source is currently being loaded. - */ + @Override default void onIsLoadingChanged(boolean isLoading) {} - /** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */ - @Deprecated - @UnstableApi - default void onLoadingChanged(boolean isLoading) {} - - /** - * Called when the value returned from {@link #isCommandAvailable(int)} changes for at least one - * {@link Command}. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param availableCommands The available {@link Commands}. - */ + @Override default void onAvailableCommandsChanged(Commands availableCommands) {} - /** - * Called when the value returned from {@link #getTrackSelectionParameters()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param parameters The new {@link TrackSelectionParameters}. - */ - default void onTrackSelectionParametersChanged(TrackSelectionParameters parameters) {} - - /** - * @deprecated Use {@link #onPlaybackStateChanged(int)} and {@link - * #onPlayWhenReadyChanged(boolean, int)} instead. - */ - @Deprecated - @UnstableApi - default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {} - - /** - * Called when the value returned from {@link #getPlaybackState()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param playbackState The new playback {@link State state}. - */ + @Override default void onPlaybackStateChanged(@State int playbackState) {} - /** - * Called when the value returned from {@link #getPlayWhenReady()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param playWhenReady Whether playback will proceed when ready. - * @param reason The {@link PlayWhenReadyChangeReason reason} for the change. - */ + @Override default void onPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {} - /** - * Called when the value returned from {@link #getPlaybackSuppressionReason()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param playbackSuppressionReason The current {@link PlaybackSuppressionReason}. - */ + @Override default void onPlaybackSuppressionReasonChanged( @PlaybackSuppressionReason int playbackSuppressionReason) {} - /** - * Called when the value of {@link #isPlaying()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param isPlaying Whether the player is playing. - */ + @Override default void onIsPlayingChanged(boolean isPlaying) {} - /** - * Called when the value of {@link #getRepeatMode()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param repeatMode The {@link RepeatMode} used for playback. - */ + @Override default void onRepeatModeChanged(@RepeatMode int repeatMode) {} - /** - * Called when the value of {@link #getShuffleModeEnabled()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param shuffleModeEnabled Whether shuffling of {@link MediaItem media items} is enabled. - */ + @Override default void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {} - /** - * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} - * immediately after this method is called. The player instance can still be used, and {@link - * #release()} must still be called on the player should it no longer be required. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - *

Implementations of Player may pass an instance of a subclass of {@link PlaybackException} - * to this method in order to include more information about the error. - * - * @param error The error. - */ + @Override default void onPlayerError(PlaybackException error) {} - /** - * Called when the {@link PlaybackException} returned by {@link #getPlayerError()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - *

Implementations of Player may pass an instance of a subclass of {@link PlaybackException} - * to this method in order to include more information about the error. - * - * @param error The new error, or null if the error is being cleared. - */ + @Override default void onPlayerErrorChanged(@Nullable PlaybackException error) {} - /** - * @deprecated Use {@link #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} instead. - */ - @Deprecated - @UnstableApi - default void onPositionDiscontinuity(@DiscontinuityReason int reason) {} - - /** - * Called when a position discontinuity occurs. - * - *

A position discontinuity occurs when the playing period changes, the playback position - * jumps within the period currently being played, or when the playing period has been skipped - * or removed. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param oldPosition The position before the discontinuity. - * @param newPosition The position after the discontinuity. - * @param reason The {@link DiscontinuityReason} responsible for the discontinuity. - */ + @Override default void onPositionDiscontinuity( PositionInfo oldPosition, PositionInfo newPosition, @DiscontinuityReason int reason) {} - /** - * Called when the current playback parameters change. The playback parameters may change due to - * a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change - * them (for example, if audio playback switches to passthrough or offload mode, where speed - * adjustment is no longer possible). - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param playbackParameters The playback parameters. - */ + @Override default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {} - /** - * Called when the value of {@link #getSeekBackIncrement()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds. - */ - default void onSeekBackIncrementChanged(long seekBackIncrementMs) {} - - /** - * Called when the value of {@link #getSeekForwardIncrement()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param seekForwardIncrementMs The {@link #seekForward()} increment, in milliseconds. - */ + @Override default void onSeekForwardIncrementChanged(long seekForwardIncrementMs) {} - /** - * Called when the value of {@link #getMaxSeekToPreviousPosition()} changes. - * - *

{@link #onEvents(Player, Events)} will also be called to report this event along with - * other events that happen in the same {@link Looper} message queue iteration. - * - * @param maxSeekToPreviousPositionMs The maximum position for which {@link #seekToPrevious()} - * seeks to the previous position, in milliseconds. - */ - default void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) {} - - /** - * @deprecated Seeks are processed without delay. Listen to {@link - * #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} with reason {@link - * #DISCONTINUITY_REASON_SEEK} instead. - */ - @Deprecated - @UnstableApi - default void onSeekProcessed() {} + @Override + default void onSeekBackIncrementChanged(long seekBackIncrementMs) {} /** * Called when the audio session ID changes. @@ -964,6 +1031,9 @@ public interface Player { /** Called when the device volume or mute state changes. */ default void onDeviceVolumeChanged(int volume, boolean muted) {} + @Override + default void onEvents(Player player, Events events) {} + /** * Called each time there's a change in the size of the video being rendered. * @@ -1005,6 +1075,12 @@ public interface Player { */ @UnstableApi default void onMetadata(Metadata metadata) {} + + @Override + default void onMediaMetadataChanged(MediaMetadata mediaMetadata) {} + + @Override + default void onPlaylistMetadataChanged(MediaMetadata mediaMetadata) {} } /** diff --git a/libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java b/libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java index 22b30a8b33..2973bbb2c2 100644 --- a/libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/ForwardingPlayerTest.java @@ -115,6 +115,21 @@ public class ForwardingPlayerTest { } } + @Test + @SuppressWarnings("deprecation") // Testing backwards compatibility with deprecated type. + public void forwardingEventListener_overridesAllEventListenerMethods() throws Exception { + // Check with reflection that ForwardingListener overrides all Listener methods. + Class forwardingListenerClass = getInnerClass("ForwardingEventListener"); + List methods = getPublicMethods(Player.EventListener.class); + for (int i = 0; i < methods.size(); i++) { + Method method = methods.get(i); + assertThat( + forwardingListenerClass.getDeclaredMethod( + method.getName(), method.getParameterTypes())) + .isNotNull(); + } + } + @Test public void forwardingListener_overridesAllListenerMethods() throws Exception { // Check with reflection that ForwardingListener overrides all Listener methods. 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..ab755cbc26 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java @@ -1081,6 +1081,32 @@ public interface ExoPlayer extends Player { @Deprecated DeviceComponent getDeviceComponent(); + /** + * Registers a listener to receive events from the player. + * + *

The listener's methods will be called on the thread associated with {@link + * #getApplicationLooper()}. + * + * @param listener The listener to register. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. + */ + @UnstableApi + @Deprecated + @SuppressWarnings("deprecation") + void addListener(EventListener listener); + + /** + * Unregister a listener registered through {@link #addListener(EventListener)}. The listener will + * no longer receive events from the player. + * + * @param listener The listener to unregister. + * @deprecated Use {@link #addListener(Listener)} and {@link #removeListener(Listener)} instead. + */ + @UnstableApi + @Deprecated + @SuppressWarnings("deprecation") + void removeListener(EventListener listener); + /** * Adds a listener to receive audio offload events. * 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 621ad87af9..ab7c22607d 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -109,6 +109,7 @@ 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.EventListener; import androidx.media3.common.Player.Events; import androidx.media3.common.Player.Listener; import androidx.media3.common.Player.PlayWhenReadyChangeReason; @@ -190,9 +191,9 @@ import java.util.concurrent.TimeoutException; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal internalPlayer; - private final ListenerSet listeners; - // TODO(b/187152483): Remove this once all events are dispatched via ListenerSet. - private final CopyOnWriteArraySet listenerArraySet; + @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; @@ -207,6 +208,7 @@ import java.util.concurrent.TimeoutException; 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; @@ -292,6 +294,7 @@ import java.util.concurrent.TimeoutException; detachSurfaceTimeoutMs = builder.detachSurfaceTimeoutMs; componentListener = new ComponentListener(); frameMetadataListener = new FrameMetadataListener(); + listeners = new CopyOnWriteArraySet<>(); Handler eventHandler = new Handler(builder.looper); renderers = builder @@ -315,12 +318,11 @@ import java.util.concurrent.TimeoutException; this.applicationLooper = builder.looper; this.clock = builder.clock; this.wrappingPlayer = wrappingPlayer; - listeners = + eventListeners = new ListenerSet<>( applicationLooper, clock, (listener, flags) -> listener.onEvents(wrappingPlayer, new Events(flags))); - listenerArraySet = new CopyOnWriteArraySet<>(); audioOffloadListeners = new CopyOnWriteArraySet<>(); mediaSourceHolderSnapshots = new ArrayList<>(); shuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0); @@ -408,9 +410,9 @@ import java.util.concurrent.TimeoutException; currentCues = ImmutableList.of(); throwsWhenUsingWrongThread = true; - listeners.add(analyticsCollector); + addEventListener(analyticsCollector); bandwidthMeter.addEventListener(new Handler(applicationLooper), analyticsCollector); - listeners.add(componentListener); + addEventListener(componentListener); addAudioOffloadListener(componentListener); if (builder.foregroundModeTimeoutMs > 0) { experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs); @@ -486,6 +488,18 @@ import java.util.concurrent.TimeoutException; return clock; } + @SuppressWarnings("deprecation") // Register deprecated EventListener. + public void addEventListener(Player.EventListener eventListener) { + // Don't verify application thread. We allow calls to this method from any thread. + eventListeners.add(eventListener); + } + + @SuppressWarnings("deprecation") // Deregister deprecated EventListener. + public void removeEventListener(Player.EventListener eventListener) { + // Don't verify application thread. We allow calls to this method from any thread. + eventListeners.remove(eventListener); + } + public void addAudioOffloadListener(AudioOffloadListener listener) { // Don't verify application thread. We allow calls to this method from any thread. audioOffloadListeners.add(listener); @@ -791,10 +805,10 @@ import java.util.concurrent.TimeoutException; 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(); } } @@ -808,11 +822,11 @@ import java.util.concurrent.TimeoutException; 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(); } } @@ -1014,7 +1028,7 @@ import java.util.concurrent.TimeoutException; 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( @@ -1022,7 +1036,7 @@ import java.util.concurrent.TimeoutException; 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); @@ -1202,7 +1216,7 @@ import java.util.concurrent.TimeoutException; return; } trackSelector.setParameters(parameters); - listeners.queueEvent( + eventListeners.queueEvent( EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, listener -> listener.onTrackSelectionParametersChanged(parameters)); } @@ -1222,7 +1236,7 @@ import java.util.concurrent.TimeoutException; return; } mediaMetadata = newMediaMetadata; - listeners.sendEvent( + eventListeners.sendEvent( EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(mediaMetadata)); } @@ -1238,7 +1252,7 @@ import java.util.concurrent.TimeoutException; return; } this.playlistMetadata = playlistMetadata; - listeners.sendEvent( + eventListeners.sendEvent( EVENT_PLAYLIST_METADATA_CHANGED, listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata)); } @@ -1395,7 +1409,7 @@ import java.util.concurrent.TimeoutException; streamVolumeManager.setStreamType(Util.getStreamTypeForAudioUsage(audioAttributes.usage)); analyticsCollector.onAudioAttributesChanged(audioAttributes); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onAudioAttributesChanged(audioAttributes); } } @@ -1433,7 +1447,7 @@ import java.util.concurrent.TimeoutException; 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 : listenerArraySet) { + for (Listener listener : listeners) { listener.onAudioSessionIdChanged(audioSessionId); } } @@ -1461,7 +1475,7 @@ import java.util.concurrent.TimeoutException; sendVolumeToRenderers(); analyticsCollector.onVolumeChanged(volume); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onVolumeChanged(volume); } } @@ -1593,14 +1607,14 @@ import java.util.concurrent.TimeoutException; // Don't verify application thread. We allow calls to this method from any thread. checkNotNull(listener); listeners.add(listener); - listenerArraySet.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); - listenerArraySet.remove(listener); + removeEventListener(listener); } public void setHandleWakeLock(boolean handleWakeLock) { @@ -1801,7 +1815,7 @@ import java.util.concurrent.TimeoutException; mediaMetadata = newMediaMetadata; if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) { - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason)); } @@ -1810,7 +1824,7 @@ import java.util.concurrent.TimeoutException; getPreviousPositionInfo( positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex); PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs); - listeners.queueEvent( + eventListeners.queueEvent( Player.EVENT_POSITION_DISCONTINUITY, listener -> { listener.onPositionDiscontinuity(positionDiscontinuityReason); @@ -1820,16 +1834,16 @@ import java.util.concurrent.TimeoutException; } 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)); } @@ -1838,21 +1852,21 @@ import java.util.concurrent.TimeoutException; 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); @@ -1861,19 +1875,19 @@ import java.util.concurrent.TimeoutException; } 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( @@ -1881,27 +1895,27 @@ import java.util.concurrent.TimeoutException; } 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, Listener::onSeekProcessed); + eventListeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, EventListener::onSeekProcessed); } updateAvailableCommands(); - listeners.flushEvents(); + eventListeners.flushEvents(); if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) { for (AudioOffloadListener listener : audioOffloadListeners) { @@ -2058,7 +2072,7 @@ import java.util.concurrent.TimeoutException; 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)); } @@ -2478,7 +2492,7 @@ import java.util.concurrent.TimeoutException; surfaceHeight = height; analyticsCollector.onSurfaceSizeChanged(width, height); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onSurfaceSizeChanged(width, height); } } @@ -2492,7 +2506,7 @@ import java.util.concurrent.TimeoutException; private void notifySkipSilenceEnabledChanged() { analyticsCollector.onSkipSilenceEnabledChanged(skipSilenceEnabled); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onSkipSilenceEnabledChanged(skipSilenceEnabled); } } @@ -2634,9 +2648,10 @@ import java.util.concurrent.TimeoutException; } } + // TODO(b/204189802): Remove self-listening to deprecated EventListener. + @SuppressWarnings("deprecation") private final class ComponentListener - implements Player.Listener, - VideoRendererEventListener, + implements VideoRendererEventListener, AudioRendererEventListener, TextOutput, MetadataOutput, @@ -2646,6 +2661,7 @@ import java.util.concurrent.TimeoutException; AudioFocusManager.PlayerControl, AudioBecomingNoisyManager.EventListener, StreamVolumeManager.Listener, + Player.EventListener, AudioOffloadListener { // VideoRendererEventListener implementation @@ -2680,7 +2696,7 @@ import java.util.concurrent.TimeoutException; ExoPlayerImpl.this.videoSize = videoSize; analyticsCollector.onVideoSizeChanged(videoSize); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onVideoSizeChanged(videoSize); } } @@ -2690,7 +2706,7 @@ import java.util.concurrent.TimeoutException; analyticsCollector.onRenderedFirstFrame(output, renderTimeMs); if (videoOutput == output) { // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onRenderedFirstFrame(); } } @@ -2787,7 +2803,7 @@ import java.util.concurrent.TimeoutException; public void onCues(List cues) { currentCues = cues; // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listeners : listenerArraySet) { + for (Listener listeners : listeners) { listeners.onCues(cues); } } @@ -2799,7 +2815,7 @@ import java.util.concurrent.TimeoutException; analyticsCollector.onMetadata(metadata); ExoPlayerImpl.this.onMetadata(metadata); // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onMetadata(metadata); } } @@ -2895,7 +2911,7 @@ import java.util.concurrent.TimeoutException; if (!deviceInfo.equals(ExoPlayerImpl.this.deviceInfo)) { ExoPlayerImpl.this.deviceInfo = deviceInfo; // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onDeviceInfoChanged(deviceInfo); } } @@ -2904,12 +2920,12 @@ import java.util.concurrent.TimeoutException; @Override public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) { // TODO(internal b/187152483): Events should be dispatched via ListenerSet - for (Listener listener : listenerArraySet) { + for (Listener listener : listeners) { listener.onDeviceVolumeChanged(streamVolume, streamMuted); } } - // Player.Listener implementation. + // Player.EventListener implementation. @Override public void onIsLoadingChanged(boolean isLoading) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java index 53b308104f..14540012df 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/SimpleExoPlayer.java @@ -33,6 +33,7 @@ import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaMetadata; import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Player; import androidx.media3.common.PriorityTaskManager; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroupArray; @@ -618,11 +619,23 @@ public class SimpleExoPlayer extends BasePlayer player.addListener(listener); } + @Deprecated + @Override + public void addListener(Player.EventListener listener) { + player.addEventListener(listener); + } + @Override public void removeListener(Listener listener) { player.removeListener(listener); } + @Deprecated + @Override + public void removeListener(Player.EventListener listener) { + player.removeEventListener(listener); + } + @Override public @State int getPlaybackState() { return player.getPlaybackState(); diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java index baa9047330..0f4cd5bdae 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/StubExoPlayer.java @@ -20,6 +20,7 @@ import androidx.annotation.Nullable; import androidx.media3.common.AudioAttributes; import androidx.media3.common.AuxEffectInfo; import androidx.media3.common.Format; +import androidx.media3.common.Player; import androidx.media3.common.PriorityTaskManager; import androidx.media3.common.util.Clock; import androidx.media3.common.util.UnstableApi; @@ -79,6 +80,16 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public void addListener(Player.EventListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeListener(Player.EventListener listener) { + throw new UnsupportedOperationException(); + } + @Override public void addAudioOffloadListener(AudioOffloadListener listener) { throw new UnsupportedOperationException();