mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	added: documentations on lifecycles for FailedMediaSource and LoadedMediaSource.
fixed: onPlaybackSynchronize to rewind when not playing, which was incorrectly removed in previous commit. fixed: sonar and checkstyle issues.
This commit is contained in:
		| @@ -2791,7 +2791,7 @@ public final class Player implements | ||||
|                     + "index=[" + currentPlayQueueIndex + "] with " | ||||
|                     + "playlist length=[" + currentPlaylistSize + "]"); | ||||
|  | ||||
|         } else if (wasBlocked || currentPlaylistIndex != currentPlayQueueIndex) { | ||||
|         } else if (wasBlocked || currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { | ||||
|             if (DEBUG) { | ||||
|                 Log.d(TAG, "Playback - Rewinding to correct " | ||||
|                         + "index=[" + currentPlayQueueIndex + "], " | ||||
|   | ||||
| @@ -87,16 +87,6 @@ public final class ExceptionTag implements MediaItemTag { | ||||
|         return item.getStreamType(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<StreamInfo> getMaybeStreamInfo() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<Quality> getMaybeQuality() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public <T> Optional<T> getMaybeExtras(@NonNull final Class<T> type) { | ||||
|         return Optional.ofNullable(extras).map(type::cast); | ||||
|   | ||||
| @@ -44,9 +44,15 @@ public interface MediaItemTag { | ||||
|  | ||||
|     StreamType getStreamType(); | ||||
|  | ||||
|     Optional<StreamInfo> getMaybeStreamInfo(); | ||||
|     @NonNull | ||||
|     default Optional<StreamInfo> getMaybeStreamInfo() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     Optional<Quality> getMaybeQuality(); | ||||
|     @NonNull | ||||
|     default Optional<Quality> getMaybeQuality() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     <T> Optional<T> getMaybeExtras(@NonNull Class<T> type); | ||||
|  | ||||
| @@ -86,7 +92,7 @@ public interface MediaItemTag { | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     class Quality { | ||||
|     final class Quality { | ||||
|         @NonNull | ||||
|         private final List<VideoStream> sortedVideoStreams; | ||||
|         private final int selectedVideoStreamIndex; | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| 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; | ||||
|  | ||||
| @@ -74,16 +73,6 @@ public final class PlaceholderTag implements MediaItemTag { | ||||
|         return StreamType.NONE; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<StreamInfo> getMaybeStreamInfo() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Optional<Quality> getMaybeQuality() { | ||||
|         return Optional.empty(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public <T> Optional<T> getMaybeExtras(@NonNull final Class<T> type) { | ||||
|         return Optional.ofNullable(extras).map(type::cast); | ||||
|   | ||||
| @@ -91,11 +91,13 @@ public final class StreamInfoTag implements MediaItemTag { | ||||
|         return streamInfo.getStreamType(); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Optional<StreamInfo> getMaybeStreamInfo() { | ||||
|         return Optional.of(streamInfo); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Optional<Quality> getMaybeQuality() { | ||||
|         return Optional.ofNullable(quality); | ||||
|   | ||||
| @@ -3,16 +3,16 @@ package org.schabi.newpipe.player.mediasource; | ||||
| import android.util.Log; | ||||
|  | ||||
| import com.google.android.exoplayer2.MediaItem; | ||||
| import com.google.android.exoplayer2.PlaybackException; | ||||
| import com.google.android.exoplayer2.Timeline; | ||||
| import com.google.android.exoplayer2.source.CompositeMediaSource; | ||||
| import com.google.android.exoplayer2.source.BaseMediaSource; | ||||
| 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.source.SinglePeriodTimeline; | ||||
| 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; | ||||
| @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
|  | ||||
| public class FailedMediaSource extends CompositeMediaSource<Void> implements ManagedMediaSource { | ||||
| public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource { | ||||
|     /** | ||||
|      * 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}. | ||||
| @@ -32,12 +32,12 @@ public class FailedMediaSource extends CompositeMediaSource<Void> implements Man | ||||
|      * not recommended, it may cause ExoPlayer to buffer for a while. | ||||
|      * */ | ||||
|     public static final long SILENCE_DURATION_US = TimeUnit.SECONDS.toMicros(2); | ||||
|     public static final MediaPeriod SILENT_MEDIA = makeSilentMediaPeriod(SILENCE_DURATION_US); | ||||
|  | ||||
|     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; | ||||
|     /** | ||||
|      * Fail the play queue item associated with this source, with potential future retries. | ||||
| @@ -56,15 +56,10 @@ public class FailedMediaSource extends CompositeMediaSource<Void> implements Man | ||||
|         this.playQueueItem = playQueueItem; | ||||
|         this.error = error; | ||||
|         this.retryTimestamp = retryTimestamp; | ||||
|  | ||||
|         final MediaItemTag tag = ExceptionTag | ||||
|         this.mediaItem = ExceptionTag | ||||
|                 .of(playQueueItem, Collections.singletonList(error)) | ||||
|                 .withExtras(this); | ||||
|         this.mediaItem = tag.asMediaItem(); | ||||
|         this.source = new SilenceMediaSource.Factory() | ||||
|                 .setDurationUs(SILENCE_DURATION_US) | ||||
|                 .setTag(tag) | ||||
|                 .createMediaSource(); | ||||
|                 .withExtras(this) | ||||
|                 .asMediaItem(); | ||||
|     } | ||||
|  | ||||
|     public static FailedMediaSource of(@NonNull final PlayQueueItem playQueueItem, | ||||
| @@ -91,49 +86,77 @@ public class FailedMediaSource extends CompositeMediaSource<Void> implements Man | ||||
|         return System.currentTimeMillis() >= retryTimestamp; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the {@link MediaItem} whose media is provided by the source. | ||||
|      */ | ||||
|     @Override | ||||
|     public MediaItem getMediaItem() { | ||||
|         return mediaItem; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Prepares the source with {@link Timeline} info on the silence playback when the error | ||||
|      * is classed as {@link FailedMediaSourceException}, for example, when the error is | ||||
|      * {@link org.schabi.newpipe.extractor.exceptions.ExtractionException ExtractionException}. | ||||
|      * These types of error are swallowed by {@link FailedMediaSource}, and the underlying | ||||
|      * exception is carried to the {@link MediaItem} metadata during playback. | ||||
|      * <br><br> | ||||
|      * If the exception is not known, e.g. {@link java.net.UnknownHostException} or some | ||||
|      * other network issue, then no source info is refreshed and | ||||
|      * {@link #maybeThrowSourceInfoRefreshError()} be will triggered. | ||||
|      * <br><br> | ||||
|      * Note that this method is called only once until {@link #releaseSourceInternal()} is called, | ||||
|      * so if no action is done in here, playback will stall unless | ||||
|      * {@link #maybeThrowSourceInfoRefreshError()} is called. | ||||
|      * | ||||
|      * @param mediaTransferListener No data transfer listener needed, ignored here. | ||||
|      */ | ||||
|     @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); | ||||
|             refreshSourceInfo(makeSilentMediaTimeline(SILENCE_DURATION_US, mediaItem)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * If the error is not known, e.g. network issue, then the exception is not swallowed here in | ||||
|      * {@link FailedMediaSource}. The exception is then propagated to the player, which | ||||
|      * {@link org.schabi.newpipe.player.Player Player} can react to inside | ||||
|      * {@link com.google.android.exoplayer2.Player.Listener#onPlayerError(PlaybackException)}. | ||||
|      * | ||||
|      * @throws IOException An error which will always result in | ||||
|      * {@link com.google.android.exoplayer2.PlaybackException#ERROR_CODE_IO_UNSPECIFIED}. | ||||
|      */ | ||||
|     @Override | ||||
|     public void maybeThrowSourceInfoRefreshError() throws IOException { | ||||
|         if (!(error instanceof FailedMediaSourceException)) { | ||||
|             throw new IOException(error); | ||||
|         } | ||||
|         super.maybeThrowSourceInfoRefreshError(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This method is only called if {@link #prepareSourceInternal(TransferListener)} | ||||
|      * refreshes the source info with no exception. All parameters are ignored as this | ||||
|      * returns a static and reused piece of silent audio. | ||||
|      * | ||||
|      * @param id                The identifier of the period. | ||||
|      * @param allocator         An {@link Allocator} from which to obtain media buffer allocations. | ||||
|      * @param startPositionUs   The expected start position, in microseconds. | ||||
|      * @return The common {@link MediaPeriod} holding the silence. | ||||
|      */ | ||||
|     @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, | ||||
|     public MediaPeriod createPeriod(final MediaPeriodId id, | ||||
|                                     final Allocator allocator, | ||||
|                                     final long startPositionUs) { | ||||
|         return source.createPeriod(id, allocator, startPositionUs); | ||||
|         return SILENT_MEDIA; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void releasePeriod(final MediaPeriod mediaPeriod) { | ||||
|         source.releasePeriod(mediaPeriod); | ||||
|         /* Do Nothing (we want to keep re-using the Silent MediaPeriod) */ | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void releaseSourceInternal() { | ||||
|         /* Do Nothing, no clean-up for processing/extra thread is needed by this MediaSource */ | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -168,4 +191,22 @@ public class FailedMediaSource extends CompositeMediaSource<Void> implements Man | ||||
|             super(cause); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static Timeline makeSilentMediaTimeline(final long durationUs, | ||||
|                                                     @NonNull final MediaItem mediaItem) { | ||||
|         return new SinglePeriodTimeline( | ||||
|                 durationUs, | ||||
|                 /* isSeekable= */ true, | ||||
|                 /* isDynamic= */ false, | ||||
|                 /* useLiveConfiguration= */ false, | ||||
|                 /* manifest= */ null, | ||||
|                 mediaItem); | ||||
|     } | ||||
|  | ||||
|     private static MediaPeriod makeSilentMediaPeriod(final long durationUs) { | ||||
|         return new SilenceMediaSource.Factory() | ||||
|                 .setDurationUs(durationUs) | ||||
|                 .createMediaSource() | ||||
|                 .createPeriod(null, null, 0); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,12 +14,24 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
|  | ||||
| public class LoadedMediaSource extends CompositeMediaSource<Void> implements ManagedMediaSource { | ||||
| public class LoadedMediaSource extends CompositeMediaSource<Integer> implements ManagedMediaSource { | ||||
|     private final MediaSource source; | ||||
|     private final PlayQueueItem stream; | ||||
|     private final MediaItem mediaItem; | ||||
|     private final long expireTimestamp; | ||||
|  | ||||
|     /** | ||||
|      * Uses a {@link CompositeMediaSource} to wrap one or more child {@link MediaSource}s | ||||
|      * containing actual media. This wrapper {@link LoadedMediaSource} holds the expiration | ||||
|      * timestamp as a {@link ManagedMediaSource} to allow explicit playlist management under | ||||
|      * {@link ManagedMediaSourcePlaylist}. | ||||
|      * | ||||
|      * @param source            The child media source with actual media. | ||||
|      * @param tag               Metadata for the child media source. | ||||
|      * @param stream            The queue item associated with the media source. | ||||
|      * @param expireTimestamp   The timestamp when the media source expires and might not be | ||||
|      *                          available for playback. | ||||
|      */ | ||||
|     public LoadedMediaSource(@NonNull final MediaSource source, | ||||
|                              @NonNull final MediaItemTag tag, | ||||
|                              @NonNull final PlayQueueItem stream, | ||||
| @@ -39,14 +51,35 @@ public class LoadedMediaSource extends CompositeMediaSource<Void> implements Man | ||||
|         return System.currentTimeMillis() >= expireTimestamp; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Delegates the preparation of child {@link MediaSource}s to the | ||||
|      * {@link CompositeMediaSource} wrapper. Since all {@link LoadedMediaSource}s use only | ||||
|      * a single child media, the child id of 0 is always used (sonar doesn't like null as id here). | ||||
|      * | ||||
|      * @param mediaTransferListener A data transfer listener that will be registered by the | ||||
|      *                              {@link CompositeMediaSource} for child source preparation. | ||||
|      */ | ||||
|     @Override | ||||
|     protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) { | ||||
|         super.prepareSourceInternal(mediaTransferListener); | ||||
|         prepareChildSource(null, source); | ||||
|         prepareChildSource(0, source); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * When any child {@link MediaSource} is prepared, the refreshed {@link Timeline} can | ||||
|      * be listened to here. But since {@link LoadedMediaSource} has only a single child source, | ||||
|      * this method is called only once until {@link #releaseSourceInternal()} is called. | ||||
|      * <br><br> | ||||
|      * On refresh, the {@link CompositeMediaSource} delegate will be notified with the | ||||
|      * new {@link Timeline}, otherwise {@link #createPeriod(MediaPeriodId, Allocator, long)} | ||||
|      * will not be called and playback may be stalled. | ||||
|      * | ||||
|      * @param id            The unique id used to prepare the child source. | ||||
|      * @param mediaSource   The child source whose source info has been refreshed. | ||||
|      * @param timeline      The new timeline of the child source. | ||||
|      */ | ||||
|     @Override | ||||
|     protected void onChildSourceInfoRefreshed(final Void id, | ||||
|     protected void onChildSourceInfoRefreshed(final Integer id, | ||||
|                                               final MediaSource mediaSource, | ||||
|                                               final Timeline timeline) { | ||||
|         refreshSourceInfo(timeline); | ||||
|   | ||||
| @@ -18,9 +18,7 @@ final class PlaceholderMediaSource | ||||
|     private static final MediaItem MEDIA_ITEM = PlaceholderTag.EMPTY.withExtras(COPY).asMediaItem(); | ||||
|  | ||||
|     private PlaceholderMediaSource() { } | ||||
|     /** | ||||
|      * Returns the {@link MediaItem} whose media is provided by the source. | ||||
|      */ | ||||
|  | ||||
|     @Override | ||||
|     public MediaItem getMediaItem() { | ||||
|         return MEDIA_ITEM; | ||||
| @@ -30,7 +28,7 @@ final class PlaceholderMediaSource | ||||
|     protected void onChildSourceInfoRefreshed(final Void id, | ||||
|                                               final MediaSource mediaSource, | ||||
|                                               final Timeline timeline) { | ||||
|         /* Do nothing, no timeline updates will stall playback */ | ||||
|         /* Do nothing, no timeline updates or error will stall playback */ | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 karyogamy
					karyogamy