|
|
/*
* 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 androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS;
import static ml.docilealligator.infinityforreddit.videoautoplay.ToroExo.toro;
import android.net.Uri;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.BehindLiveWindowException;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector;
import androidx.media3.exoplayer.trackselection.TrackSelector;
import androidx.media3.ui.PlayerView;
import com.google.common.collect.ImmutableList;
import ml.docilealligator.infinityforreddit.R;
/**
* Making {@link Playable} extensible. This can be used with custom {@link ExoCreator}. Extending
* this class must make sure the re-usability of the implementation.
*
* @author eneim (2018/02/26).
* @since 3.4.0
*/
@OptIn(markerClass = UnstableApi.class)
@SuppressWarnings("WeakerAccess")
public class ExoPlayable extends PlayableImpl {
@SuppressWarnings("unused")
private static final String TAG = "ToroExo:Playable";
private EventListener listener;
// Adapt from ExoPlayer demo.
protected boolean inErrorState = false;
protected ImmutableList<Tracks.Group> lastSeenTrackGroupArray;
/**
* Construct an instance of {@link ExoPlayable} from an {@link ExoCreator} and {@link Uri}. The
* {@link ExoCreator} is used to request {@link ExoPlayer} instance, while {@link Uri}
* defines the media to play.
*
* @param creator the {@link ExoCreator} instance.
* @param uri the {@link Uri} of the media.
* @param fileExt the custom extension of the media Uri.
*/
public ExoPlayable(ExoCreator creator, Uri uri, String fileExt) {
super(creator, uri, fileExt);
}
@Override
public void prepare(boolean prepareSource) {
if (listener == null) {
listener = new Listener();
super.addEventListener(listener);
}
super.prepare(prepareSource);
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
}
@Override
public void setPlayerView(@Nullable PlayerView playerView) {
// This will also clear these flags
if (playerView != this.playerView) {
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
}
super.setPlayerView(playerView);
}
@Override
public void reset() {
super.reset();
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
}
@Override
public void release() {
if (listener != null) {
super.removeEventListener(listener);
listener = null;
}
super.release();
this.lastSeenTrackGroupArray = null;
this.inErrorState = false;
}
@SuppressWarnings({"unused"}) //
protected void onErrorMessage(@NonNull String message) {
// Sub class can have custom reaction about the error here, including not to show this toast
// (by not calling super.onErrorMessage(message)).
if (this.errorListeners.size() > 0) {
this.errorListeners.onError(new RuntimeException(message));
} else if (playerView != null) {
Toast.makeText(playerView.getContext(), message, Toast.LENGTH_SHORT).show();
}
}
class Listener extends DefaultEventListener {
@Override
public void onTracksChanged(@NonNull Tracks tracks) {
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
if (trackGroups == lastSeenTrackGroupArray) return;
lastSeenTrackGroupArray = trackGroups;
if (!(creator instanceof DefaultExoCreator)) return;
TrackSelector selector = ((DefaultExoCreator) creator).getTrackSelector();
if (selector instanceof DefaultTrackSelector) {
MappingTrackSelector.MappedTrackInfo trackInfo = ((DefaultTrackSelector) selector).getCurrentMappedTrackInfo();
if (trackInfo != null) {
if (trackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
onErrorMessage(toro.getString(R.string.error_unsupported_video));
}
if (trackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO) == RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
onErrorMessage(toro.getString(R.string.error_unsupported_audio));
}
}
}
}
@Override
public void onPlayerError(@NonNull PlaybackException error) {
inErrorState = true;
if (isBehindLiveWindow(error)) {
ExoPlayable.super.reset();
} else {
ExoPlayable.super.updatePlaybackInfo();
}
super.onPlayerError(error);
}
@Override
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
if (inErrorState) {
// Adapt from ExoPlayer demo.
// "This will only occur if the user has performed a seek whilst in the error state. Update
// the resume position so that if the user then retries, playback will resume from the
// position to which they seek." - ExoPlayer
ExoPlayable.super.updatePlaybackInfo();
}
super.onPositionDiscontinuity(oldPosition, newPosition, reason);
}
}
static boolean isBehindLiveWindow(PlaybackException error) {
if (error instanceof ExoPlaybackException && ((ExoPlaybackException) error).type != ExoPlaybackException.TYPE_SOURCE) return false;
Throwable cause = error.getCause();
while (cause != null) {
if (cause instanceof BehindLiveWindowException) return true;
cause = cause.getCause();
}
return false;
}
}
|