/*
* 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 static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.with;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroUtil.checkNotNull;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerView;
import ml.docilealligator.infinityforreddit.videoautoplay.annotations.RemoveIn;
import ml.docilealligator.infinityforreddit.videoautoplay.helper.ToroPlayerHelper;
import ml.docilealligator.infinityforreddit.videoautoplay.media.PlaybackInfo;
import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
import ml.docilealligator.infinityforreddit.videoautoplay.widget.Container;
/**
* An implementation of {@link ToroPlayerHelper} where the actual Player is an {@link ExoPlayer}
* implementation. This is a bridge between ExoPlayer's callback and ToroPlayerHelper behaviors.
*
* @author eneim (2018/01/24).
* @since 3.4.0
*/
public class ExoPlayerViewHelper extends ToroPlayerHelper {
@NonNull private final ExoPlayable playable;
@UnstableApi
@NonNull private final MyEventListeners listeners;
private final boolean lazyPrepare;
// Container is no longer required for constructing new instance.
@SuppressWarnings("unused") @RemoveIn(version = "3.6.0") @Deprecated //
public ExoPlayerViewHelper(Container container, @NonNull ToroPlayer player, @NonNull Uri uri) {
this(player, uri);
}
public ExoPlayerViewHelper(@NonNull ToroPlayer player, @NonNull Uri uri) {
this(player, uri, null);
}
@OptIn(markerClass = UnstableApi.class)
public ExoPlayerViewHelper(@NonNull ToroPlayer player, @NonNull Uri uri,
@Nullable String fileExt) {
this(player, uri, fileExt, with(player.getPlayerView().getContext()).getDefaultCreator());
}
/** Config instance should be kept as global instance. */
@OptIn(markerClass = UnstableApi.class)
public ExoPlayerViewHelper(@NonNull ToroPlayer player, @NonNull Uri uri, @Nullable String fileExt,
@NonNull Config config) {
this(player, uri, fileExt,
with(player.getPlayerView().getContext()).getCreator(checkNotNull(config)));
}
public ExoPlayerViewHelper(@NonNull ToroPlayer player, @NonNull Uri uri, @Nullable String fileExt,
@NonNull ExoCreator creator) {
this(player, new ExoPlayable(creator, uri, fileExt));
}
@OptIn(markerClass = UnstableApi.class)
public ExoPlayerViewHelper(@NonNull ToroPlayer player, @NonNull ExoPlayable playable) {
super(player);
//noinspection ConstantConditions
if (player.getPlayerView() == null || !(player.getPlayerView() instanceof PlayerView)) {
throw new IllegalArgumentException("Require non-null PlayerView");
}
listeners = new MyEventListeners();
this.playable = playable;
this.lazyPrepare = true;
}
@OptIn(markerClass = UnstableApi.class)
@Override protected void initialize(@NonNull PlaybackInfo playbackInfo) {
playable.setPlaybackInfo(playbackInfo);
playable.addEventListener(listeners);
playable.addErrorListener(super.getErrorListeners());
playable.addOnVolumeChangeListener(super.getVolumeChangeListeners());
playable.prepare(!lazyPrepare);
playable.setPlayerView((PlayerView) player.getPlayerView());
}
@OptIn(markerClass = UnstableApi.class)
@Override public void release() {
super.release();
playable.setPlayerView(null);
playable.removeOnVolumeChangeListener(super.getVolumeChangeListeners());
playable.removeErrorListener(super.getErrorListeners());
playable.removeEventListener(listeners);
playable.release();
}
@Override public void play() {
playable.play();
}
@Override public void pause() {
playable.pause();
}
@Override public boolean isPlaying() {
return playable.isPlaying();
}
@Override public void setVolume(float volume) {
playable.setVolume(volume);
}
@Override public float getVolume() {
return playable.getVolume();
}
@Override public void setVolumeInfo(@NonNull VolumeInfo volumeInfo) {
playable.setVolumeInfo(volumeInfo);
}
@Override @NonNull public VolumeInfo getVolumeInfo() {
return playable.getVolumeInfo();
}
@NonNull @Override public PlaybackInfo getLatestPlaybackInfo() {
return playable.getPlaybackInfo();
}
@Override public void setPlaybackInfo(@NonNull PlaybackInfo playbackInfo) {
this.playable.setPlaybackInfo(playbackInfo);
}
@OptIn(markerClass = UnstableApi.class)
public void addEventListener(@NonNull Playable.EventListener listener) {
//noinspection ConstantConditions
if (listener != null) this.listeners.add(listener);
}
@OptIn(markerClass = UnstableApi.class)
public void removeEventListener(Playable.EventListener listener) {
this.listeners.remove(listener);
}
// A proxy, to also hook into ToroPlayerHelper's state change event.
@UnstableApi
private class MyEventListeners extends Playable.EventListeners {
MyEventListeners() {
}
@Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
ExoPlayerViewHelper.super.onPlayerStateUpdated(playWhenReady, playbackState); // important
super.onPlayerStateChanged(playWhenReady, playbackState);
}
@Override public void onRenderedFirstFrame() {
super.onRenderedFirstFrame();
internalListener.onFirstFrameRendered();
for (ToroPlayer.EventListener listener : ExoPlayerViewHelper.super.getEventListeners()) {
listener.onFirstFrameRendered();
}
}
}
}