/* * 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 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 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; } }