/* * Copyright (c) 2018 Nam Nguyen, nam@ene.im * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ml.docilealligator.infinityforreddit.videoautoplay; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media3.common.Metadata; import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackParameters; import androidx.media3.common.Player; import androidx.media3.common.Timeline; import androidx.media3.common.Tracks; import androidx.media3.common.VideoSize; import androidx.media3.common.text.Cue; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.metadata.MetadataOutput; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.ui.PlayerView; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; import ml.docilealligator.infinityforreddit.videoautoplay.annotations.RemoveIn; import ml.docilealligator.infinityforreddit.videoautoplay.media.PlaybackInfo; import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo; /** * Define an interface to control a playback, specific for {@link ExoPlayer} and {@link PlayerView}. *

* This interface is designed to be reused across Config change. Implementation must not hold any * strong reference to Activity, and if it supports any kind of that, make sure to implicitly clean * it up. * * @author eneim * @since 3.4.0 */ @SuppressWarnings("unused") // public interface Playable { /** * Prepare the resource for a {@link ExoPlayer}. This method should: * - Request for new {@link ExoPlayer} instance if there is not a usable one. * - Configure {@link EventListener} for it. * - If there is non-trivial PlaybackInfo, update it to the ExoPlayer. * - If client request to prepare MediaSource, then prepare it. *

* This method must be called before {@link #setPlayerView(PlayerView)}. * * @param prepareSource if {@code true}, also prepare the MediaSource when preparing the Player, * if {@code false} just do nothing for the MediaSource. */ void prepare(boolean prepareSource); /** * Set the {@link PlayerView} for this Playable. It is expected that a playback doesn't require a * UI, so this setup is optional. But it must be called after the ExoPlayer is prepared, * that is after {@link #prepare(boolean)} and before {@link #release()}. *

* Changing the PlayerView during playback is expected, though not always recommended, especially * on old Devices with low Android API. * * @param playerView the PlayerView to set to the ExoPlayer. */ void setPlayerView(@Nullable PlayerView playerView); /** * Get current {@link PlayerView} of this Playable. * * @return current PlayerView instance of this Playable. */ @Nullable PlayerView getPlayerView(); /** * Start the playback. If the {@link MediaSource} is not prepared, then also prepare it. */ void play(); /** * Pause the playback. */ void pause(); /** * Reset all resource, so that the playback can start all over again. This is to cleanup the * playback for reuse. The ExoPlayer instance must be still usable without calling * {@link #prepare(boolean)}. */ void reset(); /** * Release all resource. After this, the ExoPlayer is released to the Player pool and the * Playable must call {@link #prepare(boolean)} again to use it again. */ void release(); /** * Get current {@link PlaybackInfo} of the playback. * * @return current PlaybackInfo of the playback. */ @NonNull PlaybackInfo getPlaybackInfo(); /** * Set the custom {@link PlaybackInfo} for this playback. This could suggest a seek. * * @param playbackInfo the PlaybackInfo to set for this playback. */ void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo); /** * Add a new {@link EventListener} to this Playable. As calling {@link #prepare(boolean)} also * triggers some internal events, this method should be called before {@link #prepare(boolean)} so * that Client could received them all. * * @param listener the EventListener to add, must be not {@code null}. */ void addEventListener(@NonNull EventListener listener); /** * Remove an {@link EventListener} from this Playable. * * @param listener the EventListener to be removed. If null, nothing happens. */ void removeEventListener(EventListener listener); /** * !This must only work if the Player in use is a {@link ToroExoPlayer}. */ void addOnVolumeChangeListener(@NonNull ToroPlayer.OnVolumeChangeListener listener); void removeOnVolumeChangeListener(@Nullable ToroPlayer.OnVolumeChangeListener listener); /** * Check if current Playable is playing or not. * * @return {@code true} if this Playable is playing, {@code false} otherwise. */ boolean isPlaying(); /** * Change the volume of current playback. * * @param volume the volume value to be set. Must be a {@code float} of range from 0 to 1. * @deprecated use {@link #setVolumeInfo(VolumeInfo)} instead. */ @RemoveIn(version = "3.6.0") @Deprecated // void setVolume(@FloatRange(from = 0.0, to = 1.0) float volume); /** * Obtain current volume value. The returned value is a {@code float} of range from 0 to 1. * * @return current volume value. * @deprecated use {@link #getVolumeInfo()} instead. */ @RemoveIn(version = "3.6.0") @Deprecated // @FloatRange(from = 0.0, to = 1.0) float getVolume(); /** * Update playback's volume. * * @param volumeInfo the {@link VolumeInfo} to update to. * @return {@code true} if current Volume info is updated, {@code false} otherwise. */ boolean setVolumeInfo(@NonNull VolumeInfo volumeInfo); /** * Get current {@link VolumeInfo}. */ @NonNull VolumeInfo getVolumeInfo(); /** * Same as {@link Player#setPlaybackParameters(PlaybackParameters)} */ void setParameters(@Nullable PlaybackParameters parameters); /** * Same as {@link Player#getPlaybackParameters()} */ @Nullable PlaybackParameters getParameters(); void addErrorListener(@NonNull ToroPlayer.OnErrorListener listener); void removeErrorListener(@Nullable ToroPlayer.OnErrorListener listener); // Combine necessary interfaces. @UnstableApi interface EventListener extends Player.Listener, TextOutput, MetadataOutput { @Override default void onCues(@NonNull List cues) { } @Override default void onCues(@NonNull CueGroup cueGroup) { } @Override default void onMetadata(@NonNull Metadata metadata) { } } /** * Default empty implementation */ @UnstableApi class DefaultEventListener implements EventListener { @Override public void onTimelineChanged(@NonNull Timeline timeline, int reason) { } @Override public void onTracksChanged(@NonNull Tracks tracks) { } @Override public void onIsLoadingChanged(boolean isLoading) { } @Override public void onPlaybackStateChanged(int playbackState) { } @Override public void onRepeatModeChanged(int repeatMode) { } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { } @Override public void onPlayerError(@NonNull PlaybackException error) { } @Override public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) { } @Override public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) { } @Override public void onVideoSizeChanged(@NonNull VideoSize videoSize) { EventListener.super.onVideoSizeChanged(videoSize); } @Override public void onRenderedFirstFrame() { } @Override public void onCues(@NonNull CueGroup cueGroup) { EventListener.super.onCues(cueGroup); } @Override public void onMetadata(@NonNull Metadata metadata) { } } /** * List of EventListener */ @UnstableApi class EventListeners extends CopyOnWriteArraySet implements EventListener { EventListeners() { } @Override public void onEvents(@NonNull Player player, @NonNull Player.Events events) { for (EventListener eventListener : this) { eventListener.onEvents(player, events); } } @Override public void onVideoSizeChanged(@NonNull VideoSize videoSize) { for (EventListener eventListener : this) { eventListener.onVideoSizeChanged(videoSize); } } @Override public void onRenderedFirstFrame() { for (EventListener eventListener : this) { eventListener.onRenderedFirstFrame(); } } @Override public void onTimelineChanged(@NonNull Timeline timeline, int reason) { for (EventListener eventListener : this) { eventListener.onTimelineChanged(timeline, reason); } } @Override public void onTracksChanged(@NonNull Tracks tracks) { for (EventListener eventListener : this) { eventListener.onTracksChanged(tracks); } } @Override public void onIsLoadingChanged(boolean isLoading) { for (EventListener eventListener : this) { eventListener.onIsLoadingChanged(isLoading); } } @Override public void onPlaybackStateChanged(int playbackState) { for (EventListener eventListener : this) { eventListener.onPlaybackStateChanged(playbackState); } } @Override public void onRepeatModeChanged(int repeatMode) { for (EventListener eventListener : this) { eventListener.onRepeatModeChanged(repeatMode); } } @Override public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { for (EventListener eventListener : this) { eventListener.onShuffleModeEnabledChanged(shuffleModeEnabled); } } @Override public void onPlayerError(@NonNull PlaybackException error) { for (EventListener eventListener : this) { eventListener.onPlayerError(error); } } @Override public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) { for (EventListener eventListener : this) { eventListener.onPositionDiscontinuity(oldPosition, newPosition, reason); } } @Override public void onPlaybackParametersChanged(@NonNull PlaybackParameters playbackParameters) { for (EventListener eventListener : this) { eventListener.onPlaybackParametersChanged(playbackParameters); } } @Override public void onCues(@NonNull CueGroup cueGroup) { for (EventListener eventListener : this) { eventListener.onCues(cueGroup); } } @Override public void onMetadata(@NonNull Metadata metadata) { for (EventListener eventListener : this) { eventListener.onMetadata(metadata); } } } }