diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java
index 2305eb9d0..355cd4079 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -133,7 +133,6 @@ import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.PositionInfo;
import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.TracksInfo;
import com.google.android.exoplayer2.source.MediaSource;
@@ -2487,6 +2486,7 @@ public final class Player implements
//////////////////////////////////////////////////////////////////////////*/
//region ExoPlayer listeners (that didn't fit in other categories)
+ @Override
public void onEvents(@NonNull final com.google.android.exoplayer2.Player player,
@NonNull final com.google.android.exoplayer2.Player.Events events) {
Listener.super.onEvents(player, events);
@@ -2546,14 +2546,6 @@ public final class Player implements
return;
}
- if (newPosition.contentPositionMs == 0 &&
- simpleExoPlayer.getTotalBufferedDuration() < 500L) {
- Log.d(TAG, "Playback - skipping to initial keyframe.");
- simpleExoPlayer.setSeekParameters(SeekParameters.CLOSEST_SYNC);
- simpleExoPlayer.seekTo(1L);
- simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
- }
-
// Refresh the playback if there is a transition to the next video
final int newIndex = newPosition.mediaItemIndex;
switch (discontinuityReason) {
@@ -2605,7 +2597,29 @@ public final class Player implements
//region Errors
/**
* Process exceptions produced by {@link com.google.android.exoplayer2.ExoPlayer ExoPlayer}.
- *
+ *
There are multiple types of errors:
+ *
+ * - {@link PlaybackException#ERROR_CODE_BEHIND_LIVE_WINDOW BEHIND_LIVE_WINDOW}:
+ * If the playback on livestreams are lagged too far behind the current playable
+ * window. Then we seek to the latest timestamp and restart the playback.
+ *
+ * - From {@link PlaybackException#ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE BAD_IO} to
+ * {@link PlaybackException#ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED UNSUPPORTED_FORMATS}:
+ * If the stream source is validated by the extractor but not recognized by the player,
+ * then we can try to recover playback by signal an error on the {@link PlayQueue}.
+ * - For {@link PlaybackException#ERROR_CODE_TIMEOUT PLAYER_TIMEOUT},
+ * {@link PlaybackException#ERROR_CODE_IO_UNSPECIFIED MEDIA_SOURCE_RESOLVER_TIMEOUT} and
+ * {@link PlaybackException#ERROR_CODE_IO_NETWORK_CONNECTION_FAILED NO_NETWORK}:
+ * We can keep set the recovery record and keep to player at the current state until
+ * it is ready to play by restarting the {@link MediaSourceManager}.
+ * - On any ExoPlayer specific issue internal to its device interaction, such as
+ * {@link PlaybackException#ERROR_CODE_DECODER_INIT_FAILED DECODER_ERROR}:
+ * We terminate the playback.
+ * - For any other unspecified issue internal: We set a recovery and try to restart
+ * the playback.
+ * In the case of decoder/renderer or unspecified errors, the player will create a
+ * notification so the users are aware.
+ *
* @see com.google.android.exoplayer2.Player.Listener#onPlayerError(PlaybackException)
* */
@SuppressLint("SwitchIntDef")
@@ -2648,6 +2662,9 @@ public final class Player implements
case ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT:
// Don't create notification on timeout/networking errors:
isCatchableException = true;
+ setRecovery();
+ reloadPlayQueueManager();
+ break;
case ERROR_CODE_UNSPECIFIED:
// Reload playback on unexpected errors:
setRecovery();
@@ -2749,7 +2766,6 @@ public final class Player implements
return;
}
- final boolean onPlaybackInitial = currentItem == null;
final boolean hasPlayQueueItemChanged = currentItem != item;
final int currentPlayQueueIndex = playQueue.indexOf(item);
@@ -2953,10 +2969,8 @@ public final class Player implements
//region StreamInfo history: views and progress
private void registerStreamViewed() {
- getCurrentStreamInfo().ifPresent(info -> {
- databaseUpdateDisposable
- .add(recordManager.onViewed(info).onErrorComplete().subscribe());
- });
+ getCurrentStreamInfo().ifPresent(info -> databaseUpdateDisposable
+ .add(recordManager.onViewed(info).onErrorComplete().subscribe()));
}
private void saveStreamProgressState(final long progressMillis) {
@@ -3134,7 +3148,7 @@ public final class Player implements
return;
}
- if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentWindowIndex() == index) {
+ if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentMediaItemIndex() == index) {
seekToDefault();
} else {
saveStreamProgressState();
@@ -3880,9 +3894,10 @@ public final class Player implements
}
private void onOpenInBrowserClicked() {
- getCurrentStreamInfo().map(Info::getOriginalUrl).ifPresent(originalUrl -> {
- ShareUtils.openUrlInBrowser(Objects.requireNonNull(getParentActivity()), originalUrl);
- });
+ getCurrentStreamInfo()
+ .map(Info::getOriginalUrl)
+ .ifPresent(originalUrl -> ShareUtils.openUrlInBrowser(
+ Objects.requireNonNull(getParentActivity()), originalUrl));
}
//endregion
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/ExceptionTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/ExceptionTag.java
index 2deffcf65..3e41262d6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/ExceptionTag.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/ExceptionTag.java
@@ -10,6 +10,16 @@ import java.util.Optional;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+/**
+ * This {@link MediaItemTag} object is designed to contain metadata for a stream
+ * that has failed to load. It supplies metadata from an underlying
+ * {@link PlayQueueItem}, which is used by the internal players to resolve actual
+ * playback info.
+ *
+ * This {@link MediaItemTag} does not contain any {@link StreamInfo} that can be
+ * used to start playback and can be detected by checking {@link ExceptionTag#getErrors()}
+ * when in generic form.
+ **/
public final class ExceptionTag implements MediaItemTag {
@NonNull
private final PlayQueueItem item;
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java
index 872a10a57..6f9b9e9f2 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java
@@ -4,6 +4,7 @@ import android.net.Uri;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.MediaMetadata;
+import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
@@ -16,6 +17,13 @@ import java.util.UUID;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+/**
+ * Metadata container and accessor used by player internals.
+ *
+ * This interface ensures consistency of fetching metadata on each stream,
+ * which is encapsulated in a {@link MediaItem} and delivered via ExoPlayer's
+ * {@link Player.Listener} on event triggers to the downstream users.
+ **/
public interface MediaItemTag {
List getErrors();
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/PlaceholderTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/PlaceholderTag.java
index c4998e9af..5b53e5660 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/PlaceholderTag.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/PlaceholderTag.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.player.mediaitem;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
+import org.schabi.newpipe.util.Constants;
import java.util.Collections;
import java.util.List;
@@ -10,6 +11,12 @@ import java.util.Optional;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+/**
+ * This is a Placeholding {@link MediaItemTag}, designed as a dummy metadata object for
+ * any stream that has not been resolved.
+ *
+ * This object cannot be instantiated and does not hold real metadata of any form.
+ * */
public final class PlaceholderTag implements MediaItemTag {
public static final PlaceholderTag EMPTY = new PlaceholderTag(null);
private static final String UNKNOWN_VALUE_INTERNAL = "Placeholder";
@@ -29,7 +36,7 @@ public final class PlaceholderTag implements MediaItemTag {
@Override
public int getServiceId() {
- return -1;
+ return Constants.NO_SERVICE_ID;
}
@Override
@@ -44,7 +51,7 @@ public final class PlaceholderTag implements MediaItemTag {
@Override
public long getDurationSeconds() {
- return -1;
+ return 0;
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java
index 93cf081f9..9ae25e07b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java
@@ -1,5 +1,7 @@
package org.schabi.newpipe.player.mediaitem;
+import com.google.android.exoplayer2.MediaItem;
+
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
@@ -11,6 +13,12 @@ import java.util.Optional;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+/**
+ * This {@link MediaItemTag} object contains metadata for a resolved stream
+ * that is ready for playback. This object guarantees the {@link StreamInfo}
+ * is available and may provide the {@link Quality} of video stream used in
+ * the {@link MediaItem}.
+ **/
public final class StreamInfoTag implements MediaItemTag {
@NonNull
private final StreamInfo streamInfo;
diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
index 75fbbe433..d68eb1a1a 100644
--- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
+++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java
@@ -23,7 +23,15 @@ 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);
+ /**
+ * Play 2 seconds of silenced audio when a stream fails to resolve due to a known issue,
+ * such as {@link org.schabi.newpipe.extractor.exceptions.ExtractionException}.
+ *
+ * This silence duration allows user to react and have time to jump to a previous stream,
+ * while still provide a smooth playback experience. A duration lower than 1 second is
+ * not recommended, it may cause ExoPlayer to buffer for a while.
+ * */
+ public static final long SILENCE_DURATION_US = TimeUnit.SECONDS.toMicros(2);
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
private final PlayQueueItem playQueueItem;
@@ -32,7 +40,7 @@ public class FailedMediaSource extends CompositeMediaSource implements Man
private final MediaSource source;
private final MediaItem mediaItem;
/**
- * Permanently fail the play queue item associated with this source, with no hope of retrying.
+ * Fail the play queue item associated with this source, with potential future retries.
*
* 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).