Skip to content

Commit b81eb35

Browse files
committed
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.
1 parent 69646e5 commit b81eb35

File tree

8 files changed

+121
-62
lines changed

8 files changed

+121
-62
lines changed

app/src/main/java/org/schabi/newpipe/player/Player.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2791,7 +2791,7 @@ public void onPlaybackSynchronize(@NonNull final PlayQueueItem item, final boole
27912791
+ "index=[" + currentPlayQueueIndex + "] with "
27922792
+ "playlist length=[" + currentPlaylistSize + "]");
27932793

2794-
} else if (wasBlocked || currentPlaylistIndex != currentPlayQueueIndex) {
2794+
} else if (wasBlocked || currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) {
27952795
if (DEBUG) {
27962796
Log.d(TAG, "Playback - Rewinding to correct "
27972797
+ "index=[" + currentPlayQueueIndex + "], "

app/src/main/java/org/schabi/newpipe/player/mediaitem/ExceptionTag.java

-10
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,6 @@ public StreamType getStreamType() {
8787
return item.getStreamType();
8888
}
8989

90-
@Override
91-
public Optional<StreamInfo> getMaybeStreamInfo() {
92-
return Optional.empty();
93-
}
94-
95-
@Override
96-
public Optional<Quality> getMaybeQuality() {
97-
return Optional.empty();
98-
}
99-
10090
@Override
10191
public <T> Optional<T> getMaybeExtras(@NonNull final Class<T> type) {
10292
return Optional.ofNullable(extras).map(type::cast);

app/src/main/java/org/schabi/newpipe/player/mediaitem/MediaItemTag.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,15 @@ public interface MediaItemTag {
4444

4545
StreamType getStreamType();
4646

47-
Optional<StreamInfo> getMaybeStreamInfo();
47+
@NonNull
48+
default Optional<StreamInfo> getMaybeStreamInfo() {
49+
return Optional.empty();
50+
}
4851

49-
Optional<Quality> getMaybeQuality();
52+
@NonNull
53+
default Optional<Quality> getMaybeQuality() {
54+
return Optional.empty();
55+
}
5056

5157
<T> Optional<T> getMaybeExtras(@NonNull Class<T> type);
5258

@@ -86,7 +92,7 @@ default MediaItem asMediaItem() {
8692
.build();
8793
}
8894

89-
class Quality {
95+
final class Quality {
9096
@NonNull
9197
private final List<VideoStream> sortedVideoStreams;
9298
private final int selectedVideoStreamIndex;

app/src/main/java/org/schabi/newpipe/player/mediaitem/PlaceholderTag.java

-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.schabi.newpipe.player.mediaitem;
22

3-
import org.schabi.newpipe.extractor.stream.StreamInfo;
43
import org.schabi.newpipe.extractor.stream.StreamType;
54
import org.schabi.newpipe.util.Constants;
65

@@ -74,16 +73,6 @@ public StreamType getStreamType() {
7473
return StreamType.NONE;
7574
}
7675

77-
@Override
78-
public Optional<StreamInfo> getMaybeStreamInfo() {
79-
return Optional.empty();
80-
}
81-
82-
@Override
83-
public Optional<Quality> getMaybeQuality() {
84-
return Optional.empty();
85-
}
86-
8776
@Override
8877
public <T> Optional<T> getMaybeExtras(@NonNull final Class<T> type) {
8978
return Optional.ofNullable(extras).map(type::cast);

app/src/main/java/org/schabi/newpipe/player/mediaitem/StreamInfoTag.java

+2
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,13 @@ public StreamType getStreamType() {
9191
return streamInfo.getStreamType();
9292
}
9393

94+
@NonNull
9495
@Override
9596
public Optional<StreamInfo> getMaybeStreamInfo() {
9697
return Optional.of(streamInfo);
9798
}
9899

100+
@NonNull
99101
@Override
100102
public Optional<Quality> getMaybeQuality() {
101103
return Optional.ofNullable(quality);

app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java

+71-30
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
import android.util.Log;
44

55
import com.google.android.exoplayer2.MediaItem;
6+
import com.google.android.exoplayer2.PlaybackException;
67
import com.google.android.exoplayer2.Timeline;
7-
import com.google.android.exoplayer2.source.CompositeMediaSource;
8+
import com.google.android.exoplayer2.source.BaseMediaSource;
89
import com.google.android.exoplayer2.source.MediaPeriod;
9-
import com.google.android.exoplayer2.source.MediaSource;
1010
import com.google.android.exoplayer2.source.SilenceMediaSource;
11+
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
1112
import com.google.android.exoplayer2.upstream.Allocator;
1213
import com.google.android.exoplayer2.upstream.TransferListener;
1314

1415
import org.schabi.newpipe.player.mediaitem.ExceptionTag;
15-
import org.schabi.newpipe.player.mediaitem.MediaItemTag;
1616
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
1717

1818
import java.io.IOException;
@@ -22,7 +22,7 @@
2222
import androidx.annotation.NonNull;
2323
import androidx.annotation.Nullable;
2424

25-
public class FailedMediaSource extends CompositeMediaSource<Void> implements ManagedMediaSource {
25+
public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource {
2626
/**
2727
* Play 2 seconds of silenced audio when a stream fails to resolve due to a known issue,
2828
* such as {@link org.schabi.newpipe.extractor.exceptions.ExtractionException}.
@@ -32,12 +32,12 @@ public class FailedMediaSource extends CompositeMediaSource<Void> implements Man
3232
* not recommended, it may cause ExoPlayer to buffer for a while.
3333
* */
3434
public static final long SILENCE_DURATION_US = TimeUnit.SECONDS.toMicros(2);
35+
public static final MediaPeriod SILENT_MEDIA = makeSilentMediaPeriod(SILENCE_DURATION_US);
3536

3637
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
3738
private final PlayQueueItem playQueueItem;
3839
private final Throwable error;
3940
private final long retryTimestamp;
40-
private final MediaSource source;
4141
private final MediaItem mediaItem;
4242
/**
4343
* Fail the play queue item associated with this source, with potential future retries.
@@ -56,15 +56,10 @@ public FailedMediaSource(@NonNull final PlayQueueItem playQueueItem,
5656
this.playQueueItem = playQueueItem;
5757
this.error = error;
5858
this.retryTimestamp = retryTimestamp;
59-
60-
final MediaItemTag tag = ExceptionTag
59+
this.mediaItem = ExceptionTag
6160
.of(playQueueItem, Collections.singletonList(error))
62-
.withExtras(this);
63-
this.mediaItem = tag.asMediaItem();
64-
this.source = new SilenceMediaSource.Factory()
65-
.setDurationUs(SILENCE_DURATION_US)
66-
.setTag(tag)
67-
.createMediaSource();
61+
.withExtras(this)
62+
.asMediaItem();
6863
}
6964

7065
public static FailedMediaSource of(@NonNull final PlayQueueItem playQueueItem,
@@ -91,49 +86,77 @@ private boolean canRetry() {
9186
return System.currentTimeMillis() >= retryTimestamp;
9287
}
9388

94-
/**
95-
* Returns the {@link MediaItem} whose media is provided by the source.
96-
*/
9789
@Override
9890
public MediaItem getMediaItem() {
9991
return mediaItem;
10092
}
10193

94+
/**
95+
* Prepares the source with {@link Timeline} info on the silence playback when the error
96+
* is classed as {@link FailedMediaSourceException}, for example, when the error is
97+
* {@link org.schabi.newpipe.extractor.exceptions.ExtractionException ExtractionException}.
98+
* These types of error are swallowed by {@link FailedMediaSource}, and the underlying
99+
* exception is carried to the {@link MediaItem} metadata during playback.
100+
* <br><br>
101+
* If the exception is not known, e.g. {@link java.net.UnknownHostException} or some
102+
* other network issue, then no source info is refreshed and
103+
* {@link #maybeThrowSourceInfoRefreshError()} be will triggered.
104+
* <br><br>
105+
* Note that this method is called only once until {@link #releaseSourceInternal()} is called,
106+
* so if no action is done in here, playback will stall unless
107+
* {@link #maybeThrowSourceInfoRefreshError()} is called.
108+
*
109+
* @param mediaTransferListener No data transfer listener needed, ignored here.
110+
*/
102111
@Override
103112
protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) {
104-
super.prepareSourceInternal(mediaTransferListener);
105113
Log.e(TAG, "Loading failed source: ", error);
106114
if (error instanceof FailedMediaSourceException) {
107-
prepareChildSource(null, source);
115+
refreshSourceInfo(makeSilentMediaTimeline(SILENCE_DURATION_US, mediaItem));
108116
}
109117
}
110118

111-
119+
/**
120+
* If the error is not known, e.g. network issue, then the exception is not swallowed here in
121+
* {@link FailedMediaSource}. The exception is then propagated to the player, which
122+
* {@link org.schabi.newpipe.player.Player Player} can react to inside
123+
* {@link com.google.android.exoplayer2.Player.Listener#onPlayerError(PlaybackException)}.
124+
*
125+
* @throws IOException An error which will always result in
126+
* {@link com.google.android.exoplayer2.PlaybackException#ERROR_CODE_IO_UNSPECIFIED}.
127+
*/
112128
@Override
113129
public void maybeThrowSourceInfoRefreshError() throws IOException {
114130
if (!(error instanceof FailedMediaSourceException)) {
115131
throw new IOException(error);
116132
}
117-
super.maybeThrowSourceInfoRefreshError();
118133
}
119134

135+
/**
136+
* This method is only called if {@link #prepareSourceInternal(TransferListener)}
137+
* refreshes the source info with no exception. All parameters are ignored as this
138+
* returns a static and reused piece of silent audio.
139+
*
140+
* @param id The identifier of the period.
141+
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
142+
* @param startPositionUs The expected start position, in microseconds.
143+
* @return The common {@link MediaPeriod} holding the silence.
144+
*/
120145
@Override
121-
protected void onChildSourceInfoRefreshed(final Void id,
122-
final MediaSource mediaSource,
123-
final Timeline timeline) {
124-
refreshSourceInfo(timeline);
146+
public MediaPeriod createPeriod(final MediaPeriodId id,
147+
final Allocator allocator,
148+
final long startPositionUs) {
149+
return SILENT_MEDIA;
125150
}
126151

127-
128152
@Override
129-
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
130-
final long startPositionUs) {
131-
return source.createPeriod(id, allocator, startPositionUs);
153+
public void releasePeriod(final MediaPeriod mediaPeriod) {
154+
/* Do Nothing (we want to keep re-using the Silent MediaPeriod) */
132155
}
133156

134157
@Override
135-
public void releasePeriod(final MediaPeriod mediaPeriod) {
136-
source.releasePeriod(mediaPeriod);
158+
protected void releaseSourceInternal() {
159+
/* Do Nothing, no clean-up for processing/extra thread is needed by this MediaSource */
137160
}
138161

139162
@Override
@@ -168,4 +191,22 @@ public StreamInfoLoadException(final Throwable cause) {
168191
super(cause);
169192
}
170193
}
194+
195+
private static Timeline makeSilentMediaTimeline(final long durationUs,
196+
@NonNull final MediaItem mediaItem) {
197+
return new SinglePeriodTimeline(
198+
durationUs,
199+
/* isSeekable= */ true,
200+
/* isDynamic= */ false,
201+
/* useLiveConfiguration= */ false,
202+
/* manifest= */ null,
203+
mediaItem);
204+
}
205+
206+
private static MediaPeriod makeSilentMediaPeriod(final long durationUs) {
207+
return new SilenceMediaSource.Factory()
208+
.setDurationUs(durationUs)
209+
.createMediaSource()
210+
.createPeriod(null, null, 0);
211+
}
171212
}

app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java

+36-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,24 @@
1414
import androidx.annotation.NonNull;
1515
import androidx.annotation.Nullable;
1616

17-
public class LoadedMediaSource extends CompositeMediaSource<Void> implements ManagedMediaSource {
17+
public class LoadedMediaSource extends CompositeMediaSource<Integer> implements ManagedMediaSource {
1818
private final MediaSource source;
1919
private final PlayQueueItem stream;
2020
private final MediaItem mediaItem;
2121
private final long expireTimestamp;
2222

23+
/**
24+
* Uses a {@link CompositeMediaSource} to wrap one or more child {@link MediaSource}s
25+
* containing actual media. This wrapper {@link LoadedMediaSource} holds the expiration
26+
* timestamp as a {@link ManagedMediaSource} to allow explicit playlist management under
27+
* {@link ManagedMediaSourcePlaylist}.
28+
*
29+
* @param source The child media source with actual media.
30+
* @param tag Metadata for the child media source.
31+
* @param stream The queue item associated with the media source.
32+
* @param expireTimestamp The timestamp when the media source expires and might not be
33+
* available for playback.
34+
*/
2335
public LoadedMediaSource(@NonNull final MediaSource source,
2436
@NonNull final MediaItemTag tag,
2537
@NonNull final PlayQueueItem stream,
@@ -39,14 +51,35 @@ private boolean isExpired() {
3951
return System.currentTimeMillis() >= expireTimestamp;
4052
}
4153

54+
/**
55+
* Delegates the preparation of child {@link MediaSource}s to the
56+
* {@link CompositeMediaSource} wrapper. Since all {@link LoadedMediaSource}s use only
57+
* a single child media, the child id of 0 is always used (sonar doesn't like null as id here).
58+
*
59+
* @param mediaTransferListener A data transfer listener that will be registered by the
60+
* {@link CompositeMediaSource} for child source preparation.
61+
*/
4262
@Override
4363
protected void prepareSourceInternal(@Nullable final TransferListener mediaTransferListener) {
4464
super.prepareSourceInternal(mediaTransferListener);
45-
prepareChildSource(null, source);
65+
prepareChildSource(0, source);
4666
}
4767

68+
/**
69+
* When any child {@link MediaSource} is prepared, the refreshed {@link Timeline} can
70+
* be listened to here. But since {@link LoadedMediaSource} has only a single child source,
71+
* this method is called only once until {@link #releaseSourceInternal()} is called.
72+
* <br><br>
73+
* On refresh, the {@link CompositeMediaSource} delegate will be notified with the
74+
* new {@link Timeline}, otherwise {@link #createPeriod(MediaPeriodId, Allocator, long)}
75+
* will not be called and playback may be stalled.
76+
*
77+
* @param id The unique id used to prepare the child source.
78+
* @param mediaSource The child source whose source info has been refreshed.
79+
* @param timeline The new timeline of the child source.
80+
*/
4881
@Override
49-
protected void onChildSourceInfoRefreshed(final Void id,
82+
protected void onChildSourceInfoRefreshed(final Integer id,
5083
final MediaSource mediaSource,
5184
final Timeline timeline) {
5285
refreshSourceInfo(timeline);

app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ final class PlaceholderMediaSource
1818
private static final MediaItem MEDIA_ITEM = PlaceholderTag.EMPTY.withExtras(COPY).asMediaItem();
1919

2020
private PlaceholderMediaSource() { }
21-
/**
22-
* Returns the {@link MediaItem} whose media is provided by the source.
23-
*/
21+
2422
@Override
2523
public MediaItem getMediaItem() {
2624
return MEDIA_ITEM;
@@ -30,7 +28,7 @@ public MediaItem getMediaItem() {
3028
protected void onChildSourceInfoRefreshed(final Void id,
3129
final MediaSource mediaSource,
3230
final Timeline timeline) {
33-
/* Do nothing, no timeline updates will stall playback */
31+
/* Do nothing, no timeline updates or error will stall playback */
3432
}
3533

3634
@Override

0 commit comments

Comments
 (0)