package org.schabi.newpipe.player.mediasource; import android.util.Log; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.SilenceMediaSource; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.TransferListener; import org.schabi.newpipe.player.mediaitem.ExceptionTag; import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import java.io.IOException; import java.util.Collections; import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class FailedMediaSource extends CompositeMediaSource implements ManagedMediaSource { private static final long SILENCE_DURATION_US = TimeUnit.SECONDS.toMicros(2); private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode()); private final PlayQueueItem playQueueItem; private final Throwable error; private final long retryTimestamp; private final MediaSource source; private final MediaItem mediaItem; /** * Permanently fail the play queue item associated with this source, with no hope of retrying. * * The error will be propagated if the cause for load exception is unspecified. * This means the error might be caused by reasons outside of extraction (e.g. no network). * Otherwise, a silenced stream will play instead. * * @param playQueueItem play queue item * @param error exception that was the reason to fail * @param retryTimestamp epoch timestamp when this MediaSource can be refreshed */ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem, @NonNull final Throwable error, final long retryTimestamp) { this.playQueueItem = playQueueItem; this.error = error; this.retryTimestamp = retryTimestamp; final MediaItemTag tag = ExceptionTag .of(playQueueItem, Collections.singletonList(error)) .withExtras(this); this.mediaItem = tag.asMediaItem(); this.source = new SilenceMediaSource.Factory() .setDurationUs(SILENCE_DURATION_US) .setTag(tag) .createMediaSource(); } public static FailedMediaSource of(@NonNull final PlayQueueItem playQueueItem, @NonNull final FailedMediaSourceException error) { return new FailedMediaSource(playQueueItem, error, Long.MAX_VALUE); } public static FailedMediaSource of(@NonNull final PlayQueueItem playQueueItem, @NonNull final Throwable error, final long retryWaitMillis) { return new FailedMediaSource(playQueueItem, error, System.currentTimeMillis() + retryWaitMillis); } public PlayQueueItem getStream() { return playQueueItem; } public Throwable getError() { return error; } private boolean canRetry() { return System.currentTimeMillis() >= retryTimestamp; } /** * Returns the {@link MediaItem} whose media is provided by the source. */ @Override public MediaItem getMediaItem() { return mediaItem; } @Override protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) { super.prepareSourceInternal(mediaTransferListener); Log.e(TAG, "Loading failed source: ", error); if (error instanceof FailedMediaSourceException) { prepareChildSource(null, source); } } @Override public void maybeThrowSourceInfoRefreshError() throws IOException { if (!(error instanceof FailedMediaSourceException)) { throw new IOException(error); } super.maybeThrowSourceInfoRefreshError(); } @Override protected void onChildSourceInfoRefreshed(final Void id, final MediaSource mediaSource, final Timeline timeline) { refreshSourceInfo(timeline); } @Override public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator, final long startPositionUs) { return source.createPeriod(id, allocator, startPositionUs); } @Override public void releasePeriod(final MediaPeriod mediaPeriod) { source.releasePeriod(mediaPeriod); } @Override public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, final boolean isInterruptable) { return newIdentity != playQueueItem || canRetry(); } @Override public boolean isStreamEqual(@NonNull final PlayQueueItem stream) { return playQueueItem == stream; } public static class FailedMediaSourceException extends Exception { FailedMediaSourceException(final String message) { super(message); } FailedMediaSourceException(final Throwable cause) { super(cause); } } public static final class MediaSourceResolutionException extends FailedMediaSourceException { public MediaSourceResolutionException(final String message) { super(message); } } public static final class StreamInfoLoadException extends FailedMediaSourceException { public StreamInfoLoadException(final Throwable cause) { super(cause); } } }