mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 08:30:44 +00:00
Merge pull request #7349 from TiA4f8R/seamless-transition-players
Add seamless transition between background and video players when putting the app in background (for video-only streams and audio-only streams only)
This commit is contained in:
commit
a95318a4f9
@ -633,7 +633,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
final List<VideoStream> sortedVideoStreams = ListHelper
|
final List<VideoStream> sortedVideoStreams = ListHelper
|
||||||
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
.getSortedStreamVideosList(this, result.getVideoStreams(),
|
||||||
result.getVideoOnlyStreams(), false);
|
result.getVideoOnlyStreams(), false, false);
|
||||||
final int selectedVideoStreamIndex = ListHelper
|
final int selectedVideoStreamIndex = ListHelper
|
||||||
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
.getDefaultResolutionIndex(this, sortedVideoStreams);
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
public static DownloadDialog newInstance(final Context context, final StreamInfo info) {
|
public static DownloadDialog newInstance(final Context context, final StreamInfo info) {
|
||||||
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper
|
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper
|
||||||
.getSortedStreamVideosList(context, info.getVideoStreams(),
|
.getSortedStreamVideosList(context, info.getVideoStreams(),
|
||||||
info.getVideoOnlyStreams(), false));
|
info.getVideoOnlyStreams(), false, false));
|
||||||
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
|
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
|
||||||
|
|
||||||
final DownloadDialog instance = newInstance(info);
|
final DownloadDialog instance = newInstance(info);
|
||||||
|
@ -1617,6 +1617,7 @@ public final class VideoDetailFragment
|
|||||||
activity,
|
activity,
|
||||||
info.getVideoStreams(),
|
info.getVideoStreams(),
|
||||||
info.getVideoOnlyStreams(),
|
info.getVideoOnlyStreams(),
|
||||||
|
false,
|
||||||
false);
|
false);
|
||||||
selectedVideoStreamIndex = ListHelper
|
selectedVideoStreamIndex = ListHelper
|
||||||
.getDefaultResolutionIndex(activity, sortedVideoStreams);
|
.getDefaultResolutionIndex(activity, sortedVideoStreams);
|
||||||
|
@ -112,6 +112,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player.PositionInfo;
|
import com.google.android.exoplayer2.Player.PositionInfo;
|
||||||
import com.google.android.exoplayer2.RenderersFactory;
|
import com.google.android.exoplayer2.RenderersFactory;
|
||||||
@ -122,6 +123,7 @@ import com.google.android.exoplayer2.source.MediaSource;
|
|||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
import com.google.android.exoplayer2.ui.CaptionStyleCompat;
|
||||||
@ -144,6 +146,7 @@ import org.schabi.newpipe.error.UserAction;
|
|||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
@ -175,6 +178,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
|||||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
|
||||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
|
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper;
|
||||||
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
|
import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
@ -193,6 +197,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Observable;
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
@ -2449,8 +2454,8 @@ public final class Player implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPositionDiscontinuity(
|
public void onPositionDiscontinuity(@NonNull final PositionInfo oldPosition,
|
||||||
final PositionInfo oldPosition, final PositionInfo newPosition,
|
@NonNull final PositionInfo newPosition,
|
||||||
@DiscontinuityReason final int discontinuityReason) {
|
@DiscontinuityReason final int discontinuityReason) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with "
|
Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with "
|
||||||
@ -2499,7 +2504,7 @@ public final class Player implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCues(final List<Cue> cues) {
|
public void onCues(@NonNull final List<Cue> cues) {
|
||||||
binding.subtitleView.onCues(cues);
|
binding.subtitleView.onCues(cues);
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
@ -3005,18 +3010,19 @@ public final class Player implements
|
|||||||
|
|
||||||
final MediaSourceTag metadata;
|
final MediaSourceTag metadata;
|
||||||
try {
|
try {
|
||||||
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
final MediaItem currentMediaItem = simpleExoPlayer.getCurrentMediaItem();
|
||||||
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
if (currentMediaItem == null || currentMediaItem.playbackProperties == null
|
||||||
|
|| currentMediaItem.playbackProperties.tag == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
metadata = (MediaSourceTag) currentMediaItem.playbackProperties.tag;
|
||||||
|
} catch (final IndexOutOfBoundsException | ClassCastException ex) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "Could not update metadata: " + error.getMessage());
|
Log.d(TAG, "Could not update metadata", ex);
|
||||||
error.printStackTrace();
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
maybeAutoQueueNextStream(metadata);
|
maybeAutoQueueNextStream(metadata);
|
||||||
|
|
||||||
if (currentMetadata == metadata) {
|
if (currentMetadata == metadata) {
|
||||||
@ -3292,7 +3298,27 @@ public final class Player implements
|
|||||||
@Override // own playback listener
|
@Override // own playback listener
|
||||||
@Nullable
|
@Nullable
|
||||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||||
return (isAudioOnly ? audioResolver : videoResolver).resolve(info);
|
if (audioPlayerSelected()) {
|
||||||
|
return audioResolver.resolve(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAudioOnly && videoResolver.getStreamSourceType().orElse(
|
||||||
|
SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY)
|
||||||
|
== SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY) {
|
||||||
|
// If the current info has only video streams with audio and if the stream is played as
|
||||||
|
// audio, we need to use the audio resolver, otherwise the video stream will be played
|
||||||
|
// in background.
|
||||||
|
return audioResolver.resolve(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if the stream is played in background, we need to use the video resolver if the
|
||||||
|
// info played is separated video-only and audio-only streams; otherwise, if the audio
|
||||||
|
// resolver was called when the app was in background, the app will only stream audio when
|
||||||
|
// the user come back to the app and will never fetch the video stream.
|
||||||
|
// Note that the video is not fetched when the app is in background because the video
|
||||||
|
// renderer is fully disabled (see useVideoSource method), except for HLS streams
|
||||||
|
// (see https://github.com/google/ExoPlayer/issues/9282).
|
||||||
|
return videoResolver.resolve(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disablePreloadingOfCurrentTrack() {
|
public void disablePreloadingOfCurrentTrack() {
|
||||||
@ -4147,19 +4173,125 @@ public final class Player implements
|
|||||||
return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext();
|
return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void useVideoSource(final boolean video) {
|
private void useVideoSource(final boolean videoEnabled) {
|
||||||
if (playQueue == null || isAudioOnly == !video || audioPlayerSelected()) {
|
if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isAudioOnly = !video;
|
isAudioOnly = !videoEnabled;
|
||||||
// When a user returns from background controls could be hidden
|
// When a user returns from background, controls could be hidden but SystemUI will be shown
|
||||||
// but systemUI will be shown 100%. Hide it
|
// 100%. Hide it.
|
||||||
if (!isAudioOnly && !isControlsVisible()) {
|
if (!isAudioOnly && !isControlsVisible()) {
|
||||||
hideSystemUIIfNeeded();
|
hideSystemUIIfNeeded();
|
||||||
}
|
}
|
||||||
setRecovery();
|
|
||||||
|
// The current metadata may be null sometimes (for e.g. when using an unstable connection
|
||||||
|
// in livestreams) so we will be not able to execute the block below.
|
||||||
|
// Reload the play queue manager in this case, which is the behavior when we don't know the
|
||||||
|
// index of the video renderer or playQueueManagerReloadingNeeded returns true.
|
||||||
|
if (currentMetadata == null) {
|
||||||
reloadPlayQueueManager();
|
reloadPlayQueueManager();
|
||||||
|
setRecovery();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int videoRenderIndex = getVideoRendererIndex();
|
||||||
|
final StreamInfo info = currentMetadata.getMetadata();
|
||||||
|
|
||||||
|
// In the case we don't know the source type, fallback to the one with video with audio or
|
||||||
|
// audio-only source.
|
||||||
|
final SourceType sourceType = videoResolver.getStreamSourceType().orElse(
|
||||||
|
SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY);
|
||||||
|
|
||||||
|
if (playQueueManagerReloadingNeeded(sourceType, info, videoRenderIndex)) {
|
||||||
|
reloadPlayQueueManager();
|
||||||
|
} else {
|
||||||
|
final StreamType streamType = info.getStreamType();
|
||||||
|
if (streamType == StreamType.AUDIO_STREAM
|
||||||
|
|| streamType == StreamType.AUDIO_LIVE_STREAM) {
|
||||||
|
// Nothing to do more than setting the recovery position
|
||||||
|
setRecovery();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final TrackGroupArray videoTrackGroupArray = Objects.requireNonNull(
|
||||||
|
trackSelector.getCurrentMappedTrackInfo()).getTrackGroups(videoRenderIndex);
|
||||||
|
if (videoEnabled) {
|
||||||
|
// Clearing the null selection override enable again the video stream (and its
|
||||||
|
// fetching).
|
||||||
|
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
|
.clearSelectionOverride(videoRenderIndex, videoTrackGroupArray));
|
||||||
|
} else {
|
||||||
|
// Using setRendererDisabled still fetch the video stream in background, contrary
|
||||||
|
// to setSelectionOverride with a null override.
|
||||||
|
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||||
|
.setSelectionOverride(videoRenderIndex, videoTrackGroupArray, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRecovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the play queue manager needs to be reloaded when switching player type.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The play queue manager needs to be reloaded if the video renderer index is not known and if
|
||||||
|
* the content is not an audio content, but also if none of the following cases is met:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>the content is an {@link StreamType#AUDIO_STREAM audio stream} or an
|
||||||
|
* {@link StreamType#AUDIO_LIVE_STREAM audio live stream};</li>
|
||||||
|
* <li>the content is a {@link StreamType#LIVE_STREAM live stream} and the source type is a
|
||||||
|
* {@link SourceType#LIVE_STREAM live source};</li>
|
||||||
|
* <li>the content's source is {@link SourceType#VIDEO_WITH_SEPARATED_AUDIO a video stream
|
||||||
|
* with a separated audio source} or has no audio-only streams available <b>and</b> is a
|
||||||
|
* {@link StreamType#LIVE_STREAM live stream} or a
|
||||||
|
* {@link StreamType#LIVE_STREAM live stream}.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param sourceType the {@link SourceType} of the stream
|
||||||
|
* @param streamInfo the {@link StreamInfo} of the stream
|
||||||
|
* @param videoRendererIndex the video renderer index of the video source, if that's a video
|
||||||
|
* source (or {@link #RENDERER_UNAVAILABLE})
|
||||||
|
* @return whether the play queue manager needs to be reloaded
|
||||||
|
*/
|
||||||
|
private boolean playQueueManagerReloadingNeeded(final SourceType sourceType,
|
||||||
|
@NonNull final StreamInfo streamInfo,
|
||||||
|
final int videoRendererIndex) {
|
||||||
|
final StreamType streamType = streamInfo.getStreamType();
|
||||||
|
|
||||||
|
if (videoRendererIndex == RENDERER_UNAVAILABLE && streamType != StreamType.AUDIO_STREAM
|
||||||
|
&& streamType != StreamType.AUDIO_LIVE_STREAM) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The content is an audio stream, an audio live stream, or a live stream with a live
|
||||||
|
// source: it's not needed to reload the play queue manager because the stream source will
|
||||||
|
// be the same
|
||||||
|
if ((streamType == StreamType.AUDIO_STREAM || streamType == StreamType.AUDIO_LIVE_STREAM)
|
||||||
|
|| (streamType == StreamType.LIVE_STREAM
|
||||||
|
&& sourceType == SourceType.LIVE_STREAM)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The content's source is a video with separated audio or a video with audio -> the video
|
||||||
|
// and its fetch may be disabled
|
||||||
|
// The content's source is a video with embedded audio and the content has no separated
|
||||||
|
// audio stream available: it's probably not needed to reload the play queue manager
|
||||||
|
// because the stream source will be probably the same as the current played
|
||||||
|
if (sourceType == SourceType.VIDEO_WITH_SEPARATED_AUDIO
|
||||||
|
|| (sourceType == SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY
|
||||||
|
&& isNullOrEmpty(streamInfo.getAudioStreams()))) {
|
||||||
|
// It's not needed to reload the play queue manager only if the content's stream type
|
||||||
|
// is a video stream or a live stream
|
||||||
|
return streamType != StreamType.VIDEO_STREAM && streamType != StreamType.LIVE_STREAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other cases: the play queue manager reload is needed
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@ -4197,7 +4329,7 @@ public final class Player implements
|
|||||||
private boolean isLive() {
|
private boolean isLive() {
|
||||||
try {
|
try {
|
||||||
return !exoPlayerIsNull() && simpleExoPlayer.isCurrentWindowDynamic();
|
return !exoPlayerIsNull() && simpleExoPlayer.isCurrentWindowDynamic();
|
||||||
} catch (@NonNull final IndexOutOfBoundsException e) {
|
} catch (final IndexOutOfBoundsException e) {
|
||||||
// Why would this even happen =(... but lets log it anyway, better safe than sorry
|
// Why would this even happen =(... but lets log it anyway, better safe than sorry
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "player.isCurrentWindowDynamic() failed: ", e);
|
Log.d(TAG, "player.isCurrentWindowDynamic() failed: ", e);
|
||||||
@ -4375,8 +4507,8 @@ public final class Player implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupVideoSurface() {
|
private void cleanupVideoSurface() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23
|
// Only for API >= 23
|
||||||
if (surfaceHolderCallback != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && surfaceHolderCallback != null) {
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
|
binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback);
|
||||||
}
|
}
|
||||||
@ -4384,6 +4516,33 @@ public final class Player implements
|
|||||||
surfaceHolderCallback = null;
|
surfaceHolderCallback = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the video renderer index of the current playing stream.
|
||||||
|
*
|
||||||
|
* This method returns the video renderer index of the current
|
||||||
|
* {@link MappingTrackSelector.MappedTrackInfo} or {@link #RENDERER_UNAVAILABLE} if the current
|
||||||
|
* {@link MappingTrackSelector.MappedTrackInfo} is null or if there is no video renderer index.
|
||||||
|
*
|
||||||
|
* @return the video renderer index or {@link #RENDERER_UNAVAILABLE} if it cannot be get
|
||||||
|
*/
|
||||||
|
private int getVideoRendererIndex() {
|
||||||
|
final MappingTrackSelector.MappedTrackInfo mappedTrackInfo = trackSelector
|
||||||
|
.getCurrentMappedTrackInfo();
|
||||||
|
|
||||||
|
if (mappedTrackInfo == null) {
|
||||||
|
return RENDERER_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every renderer
|
||||||
|
return IntStream.range(0, mappedTrackInfo.getRendererCount())
|
||||||
|
// Check the renderer is a video renderer and has at least one track
|
||||||
|
.filter(i -> !mappedTrackInfo.getTrackGroups(i).isEmpty()
|
||||||
|
&& simpleExoPlayer.getRendererType(i) == C.TRACK_TYPE_VIDEO)
|
||||||
|
// Return the first index found (there is at most one renderer per renderer type)
|
||||||
|
.findFirst()
|
||||||
|
// No video renderer index with at least one track found: return unavailable index
|
||||||
|
.orElse(RENDERER_UNAVAILABLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import org.schabi.newpipe.util.ListHelper;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||||
|
|
||||||
@ -31,10 +32,17 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||||||
private final PlayerDataSource dataSource;
|
private final PlayerDataSource dataSource;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final QualityResolver qualityResolver;
|
private final QualityResolver qualityResolver;
|
||||||
|
private SourceType streamSourceType;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String playbackQuality;
|
private String playbackQuality;
|
||||||
|
|
||||||
|
public enum SourceType {
|
||||||
|
LIVE_STREAM,
|
||||||
|
VIDEO_WITH_SEPARATED_AUDIO,
|
||||||
|
VIDEO_WITH_AUDIO_OR_AUDIO_ONLY
|
||||||
|
}
|
||||||
|
|
||||||
public VideoPlaybackResolver(@NonNull final Context context,
|
public VideoPlaybackResolver(@NonNull final Context context,
|
||||||
@NonNull final PlayerDataSource dataSource,
|
@NonNull final PlayerDataSource dataSource,
|
||||||
@NonNull final QualityResolver qualityResolver) {
|
@NonNull final QualityResolver qualityResolver) {
|
||||||
@ -48,6 +56,7 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||||||
public MediaSource resolve(@NonNull final StreamInfo info) {
|
public MediaSource resolve(@NonNull final StreamInfo info) {
|
||||||
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||||
if (liveSource != null) {
|
if (liveSource != null) {
|
||||||
|
streamSourceType = SourceType.LIVE_STREAM;
|
||||||
return liveSource;
|
return liveSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +64,7 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||||||
|
|
||||||
// Create video stream source
|
// Create video stream source
|
||||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
||||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
info.getVideoStreams(), info.getVideoOnlyStreams(), false, true);
|
||||||
final int index;
|
final int index;
|
||||||
if (videos.isEmpty()) {
|
if (videos.isEmpty()) {
|
||||||
index = -1;
|
index = -1;
|
||||||
@ -85,6 +94,9 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||||||
PlayerHelper.cacheKeyOf(info, audio),
|
PlayerHelper.cacheKeyOf(info, audio),
|
||||||
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||||
mediaSources.add(audioSource);
|
mediaSources.add(audioSource);
|
||||||
|
streamSourceType = SourceType.VIDEO_WITH_SEPARATED_AUDIO;
|
||||||
|
} else {
|
||||||
|
streamSourceType = SourceType.VIDEO_WITH_AUDIO_OR_AUDIO_ONLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no audio or video sources, then this media source cannot be played back
|
// If there is no audio or video sources, then this media source cannot be played back
|
||||||
@ -118,6 +130,16 @@ public class VideoPlaybackResolver implements PlaybackResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last resolved {@link StreamInfo}'s {@link SourceType source type}.
|
||||||
|
*
|
||||||
|
* @return {@link Optional#empty()} if nothing was resolved, otherwise the {@link SourceType}
|
||||||
|
* of the last resolved {@link StreamInfo} inside an {@link Optional}
|
||||||
|
*/
|
||||||
|
public Optional<SourceType> getStreamSourceType() {
|
||||||
|
return Optional.ofNullable(streamSourceType);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getPlaybackQuality() {
|
public String getPlaybackQuality() {
|
||||||
return playbackQuality;
|
return playbackQuality;
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
@ -19,7 +20,11 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class ListHelper {
|
public final class ListHelper {
|
||||||
// Video format in order of quality. 0=lowest quality, n=highest quality
|
// Video format in order of quality. 0=lowest quality, n=highest quality
|
||||||
@ -33,8 +38,9 @@ public final class ListHelper {
|
|||||||
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||||
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
||||||
|
|
||||||
private static final List<String> HIGH_RESOLUTION_LIST
|
private static final Set<String> HIGH_RESOLUTION_LIST
|
||||||
= Arrays.asList("1440p", "2160p", "1440p60", "2160p60");
|
// Uses a HashSet for better performance
|
||||||
|
= new HashSet<>(Arrays.asList("1440p", "2160p", "1440p60", "2160p60"));
|
||||||
|
|
||||||
private ListHelper() { }
|
private ListHelper() { }
|
||||||
|
|
||||||
@ -108,17 +114,21 @@ public final class ListHelper {
|
|||||||
* Join the two lists of video streams (video_only and normal videos),
|
* Join the two lists of video streams (video_only and normal videos),
|
||||||
* and sort them according with default format chosen by the user.
|
* and sort them according with default format chosen by the user.
|
||||||
*
|
*
|
||||||
* @param context context to search for the format to give preference
|
* @param context the context to search for the format to give preference
|
||||||
* @param videoStreams normal videos list
|
* @param videoStreams the normal videos list
|
||||||
* @param videoOnlyStreams video only stream list
|
* @param videoOnlyStreams the video-only stream list
|
||||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||||
|
* @param preferVideoOnlyStreams if video-only streams should preferred when both video-only
|
||||||
|
* streams and normal video streams are available
|
||||||
* @return the sorted list
|
* @return the sorted list
|
||||||
*/
|
*/
|
||||||
public static List<VideoStream> getSortedStreamVideosList(final Context context,
|
@NonNull
|
||||||
final List<VideoStream> videoStreams,
|
public static List<VideoStream> getSortedStreamVideosList(
|
||||||
final List<VideoStream>
|
@NonNull final Context context,
|
||||||
videoOnlyStreams,
|
@Nullable final List<VideoStream> videoStreams,
|
||||||
final boolean ascendingOrder) {
|
@Nullable final List<VideoStream> videoOnlyStreams,
|
||||||
|
final boolean ascendingOrder,
|
||||||
|
final boolean preferVideoOnlyStreams) {
|
||||||
final SharedPreferences preferences
|
final SharedPreferences preferences
|
||||||
= PreferenceManager.getDefaultSharedPreferences(context);
|
= PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
@ -128,7 +138,7 @@ public final class ListHelper {
|
|||||||
R.string.default_video_format_key, R.string.default_video_format_value);
|
R.string.default_video_format_key, R.string.default_video_format_value);
|
||||||
|
|
||||||
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams,
|
return getSortedStreamVideosList(defaultFormat, showHigherResolutions, videoStreams,
|
||||||
videoOnlyStreams, ascendingOrder);
|
videoOnlyStreams, ascendingOrder, preferVideoOnlyStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -197,51 +207,50 @@ public final class ListHelper {
|
|||||||
* @param videoStreams normal videos list
|
* @param videoStreams normal videos list
|
||||||
* @param videoOnlyStreams video only stream list
|
* @param videoOnlyStreams video only stream list
|
||||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||||
|
* @param preferVideoOnlyStreams if video-only streams should preferred when both video-only
|
||||||
|
* streams and normal video streams are available
|
||||||
* @return the sorted list
|
* @return the sorted list
|
||||||
*/
|
*/
|
||||||
static List<VideoStream> getSortedStreamVideosList(final MediaFormat defaultFormat,
|
@NonNull
|
||||||
|
static List<VideoStream> getSortedStreamVideosList(
|
||||||
|
@Nullable final MediaFormat defaultFormat,
|
||||||
final boolean showHigherResolutions,
|
final boolean showHigherResolutions,
|
||||||
final List<VideoStream> videoStreams,
|
@Nullable final List<VideoStream> videoStreams,
|
||||||
final List<VideoStream> videoOnlyStreams,
|
@Nullable final List<VideoStream> videoOnlyStreams,
|
||||||
final boolean ascendingOrder) {
|
final boolean ascendingOrder,
|
||||||
final ArrayList<VideoStream> retList = new ArrayList<>();
|
final boolean preferVideoOnlyStreams
|
||||||
|
) {
|
||||||
|
// Determine order of streams
|
||||||
|
// The last added list is preferred
|
||||||
|
final List<List<VideoStream>> videoStreamsOrdered =
|
||||||
|
preferVideoOnlyStreams
|
||||||
|
? Arrays.asList(videoStreams, videoOnlyStreams)
|
||||||
|
: Arrays.asList(videoOnlyStreams, videoStreams);
|
||||||
|
|
||||||
|
final List<VideoStream> allInitialStreams = videoStreamsOrdered.stream()
|
||||||
|
// Ignore lists that are null
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
// Filter out higher resolutions (or not if high resolutions should always be shown)
|
||||||
|
.filter(stream -> showHigherResolutions
|
||||||
|
|| !HIGH_RESOLUTION_LIST.contains(stream.getResolution()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
final HashMap<String, VideoStream> hashMap = new HashMap<>();
|
final HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||||
|
|
||||||
if (videoOnlyStreams != null) {
|
|
||||||
for (final VideoStream stream : videoOnlyStreams) {
|
|
||||||
if (!showHigherResolutions
|
|
||||||
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
retList.add(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (videoStreams != null) {
|
|
||||||
for (final VideoStream stream : videoStreams) {
|
|
||||||
if (!showHigherResolutions
|
|
||||||
&& HIGH_RESOLUTION_LIST.contains(stream.getResolution())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
retList.add(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all to the hashmap
|
// Add all to the hashmap
|
||||||
for (final VideoStream videoStream : retList) {
|
for (final VideoStream videoStream : allInitialStreams) {
|
||||||
hashMap.put(videoStream.getResolution(), videoStream);
|
hashMap.put(videoStream.getResolution(), videoStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the values when the key == resolution, with the defaultFormat
|
// Override the values when the key == resolution, with the defaultFormat
|
||||||
for (final VideoStream videoStream : retList) {
|
for (final VideoStream videoStream : allInitialStreams) {
|
||||||
if (videoStream.getFormat() == defaultFormat) {
|
if (videoStream.getFormat() == defaultFormat) {
|
||||||
hashMap.put(videoStream.getResolution(), videoStream);
|
hashMap.put(videoStream.getResolution(), videoStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
retList.clear();
|
// Return the sorted list
|
||||||
retList.addAll(hashMap.values());
|
return sortStreamList(new ArrayList<>(hashMap.values()), ascendingOrder);
|
||||||
sortStreamList(retList, ascendingOrder);
|
|
||||||
return retList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -262,11 +271,13 @@ public final class ListHelper {
|
|||||||
*
|
*
|
||||||
* @param videoStreams list that the sorting will be applied
|
* @param videoStreams list that the sorting will be applied
|
||||||
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
* @param ascendingOrder true -> smallest to greatest | false -> greatest to smallest
|
||||||
|
* @return The sorted list (same reference as parameter videoStreams)
|
||||||
*/
|
*/
|
||||||
private static void sortStreamList(final List<VideoStream> videoStreams,
|
private static List<VideoStream> sortStreamList(final List<VideoStream> videoStreams,
|
||||||
final boolean ascendingOrder) {
|
final boolean ascendingOrder) {
|
||||||
final Comparator<VideoStream> comparator = ListHelper::compareVideoStreamResolution;
|
final Comparator<VideoStream> comparator = ListHelper::compareVideoStreamResolution;
|
||||||
Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed());
|
Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed());
|
||||||
|
return videoStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -277,28 +288,12 @@ public final class ListHelper {
|
|||||||
* @param audioStreams List of audio streams
|
* @param audioStreams List of audio streams
|
||||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||||
*/
|
*/
|
||||||
static int getHighestQualityAudioIndex(@Nullable MediaFormat format,
|
static int getHighestQualityAudioIndex(@Nullable final MediaFormat format,
|
||||||
final List<AudioStream> audioStreams) {
|
@Nullable final List<AudioStream> audioStreams) {
|
||||||
int result = -1;
|
return getAudioIndexByHighestRank(format, audioStreams,
|
||||||
if (audioStreams != null) {
|
// Compares descending (last = highest rank)
|
||||||
while (result == -1) {
|
(s1, s2) -> compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_QUALITY_RANKING)
|
||||||
AudioStream prevStream = null;
|
);
|
||||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
|
||||||
final AudioStream stream = audioStreams.get(idx);
|
|
||||||
if ((format == null || stream.getFormat() == format)
|
|
||||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
|
||||||
AUDIO_FORMAT_QUALITY_RANKING) < 0)) {
|
|
||||||
prevStream = stream;
|
|
||||||
result = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result == -1 && format == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
format = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -309,28 +304,47 @@ public final class ListHelper {
|
|||||||
* @param audioStreams List of audio streams
|
* @param audioStreams List of audio streams
|
||||||
* @return Index of audio stream that produces the most compact results or -1 if not found
|
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||||
*/
|
*/
|
||||||
static int getMostCompactAudioIndex(@Nullable MediaFormat format,
|
static int getMostCompactAudioIndex(@Nullable final MediaFormat format,
|
||||||
final List<AudioStream> audioStreams) {
|
@Nullable final List<AudioStream> audioStreams) {
|
||||||
int result = -1;
|
|
||||||
if (audioStreams != null) {
|
return getAudioIndexByHighestRank(format, audioStreams,
|
||||||
while (result == -1) {
|
// The "-" is important -> Compares ascending (first = highest rank)
|
||||||
AudioStream prevStream = null;
|
(s1, s2) -> -compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_EFFICIENCY_RANKING)
|
||||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
);
|
||||||
final AudioStream stream = audioStreams.get(idx);
|
|
||||||
if ((format == null || stream.getFormat() == format)
|
|
||||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
|
||||||
AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) {
|
|
||||||
prevStream = stream;
|
|
||||||
result = idx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the audio-stream from the list with the highest rank, depending on the comparator.
|
||||||
|
* Format will be ignored if it yields no results.
|
||||||
|
*
|
||||||
|
* @param targetedFormat The target format type or null if it doesn't matter
|
||||||
|
* @param audioStreams List of audio streams
|
||||||
|
* @param comparator The comparator used for determining the max/best/highest ranked value
|
||||||
|
* @return Index of audio stream that produces the highest ranked result or -1 if not found
|
||||||
|
*/
|
||||||
|
private static int getAudioIndexByHighestRank(@Nullable final MediaFormat targetedFormat,
|
||||||
|
@Nullable final List<AudioStream> audioStreams,
|
||||||
|
final Comparator<AudioStream> comparator) {
|
||||||
|
if (audioStreams == null || audioStreams.isEmpty()) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
if (result == -1 && format == null) {
|
|
||||||
break;
|
final AudioStream highestRankedAudioStream = audioStreams.stream()
|
||||||
|
.filter(audioStream -> targetedFormat == null
|
||||||
|
|| audioStream.getFormat() == targetedFormat)
|
||||||
|
.max(comparator)
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (highestRankedAudioStream == null) {
|
||||||
|
// Fallback: Ignore targetedFormat if not null
|
||||||
|
if (targetedFormat != null) {
|
||||||
|
return getAudioIndexByHighestRank(null, audioStreams, comparator);
|
||||||
}
|
}
|
||||||
format = null;
|
// targetedFormat is already null -> return -1
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result;
|
return audioStreams.indexOf(highestRankedAudioStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,7 +214,8 @@ public final class NavigationHelper {
|
|||||||
// External Players
|
// External Players
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public static void playOnExternalAudioPlayer(final Context context, final StreamInfo info) {
|
public static void playOnExternalAudioPlayer(@NonNull final Context context,
|
||||||
|
@NonNull final StreamInfo info) {
|
||||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@ -226,9 +227,11 @@ public final class NavigationHelper {
|
|||||||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
|
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void playOnExternalVideoPlayer(final Context context, final StreamInfo info) {
|
public static void playOnExternalVideoPlayer(@NonNull final Context context,
|
||||||
|
@NonNull final StreamInfo info) {
|
||||||
final ArrayList<VideoStream> videoStreamsList = new ArrayList<>(
|
final ArrayList<VideoStream> videoStreamsList = new ArrayList<>(
|
||||||
ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false));
|
ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false,
|
||||||
|
false));
|
||||||
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
|
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@ -240,8 +243,10 @@ public final class NavigationHelper {
|
|||||||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
|
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void playOnExternalPlayer(final Context context, final String name,
|
public static void playOnExternalPlayer(@NonNull final Context context,
|
||||||
final String artist, final Stream stream) {
|
@Nullable final String name,
|
||||||
|
@Nullable final String artist,
|
||||||
|
@NonNull final Stream stream) {
|
||||||
final Intent intent = new Intent();
|
final Intent intent = new Intent();
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
|
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
|
||||||
@ -253,7 +258,8 @@ public final class NavigationHelper {
|
|||||||
resolveActivityOrAskToInstall(context, intent);
|
resolveActivityOrAskToInstall(context, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) {
|
public static void resolveActivityOrAskToInstall(@NonNull final Context context,
|
||||||
|
@NonNull final Intent intent) {
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||||
ShareUtils.openIntentInApp(context, intent, false);
|
ShareUtils.openIntentInApp(context, intent, false);
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,6 +10,8 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class ListHelperTest {
|
public class ListHelperTest {
|
||||||
private static final String BEST_RESOLUTION_KEY = "best_resolution";
|
private static final String BEST_RESOLUTION_KEY = "best_resolution";
|
||||||
@ -47,19 +49,14 @@ public class ListHelperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getSortedStreamVideosListTest() {
|
public void getSortedStreamVideosListTest() {
|
||||||
List<VideoStream> result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
List<VideoStream> result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
||||||
VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true);
|
VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, false);
|
||||||
|
|
||||||
List<String> expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60",
|
List<String> expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60",
|
||||||
"1080p", "1080p60", "1440p60", "2160p", "2160p60");
|
"1080p", "1080p60", "1440p60", "2160p", "2160p60");
|
||||||
// for (VideoStream videoStream : result) {
|
|
||||||
// System.out.println(videoStream.resolution + " > "
|
|
||||||
// + MediaFormat.getSuffixById(videoStream.format) + " > "
|
|
||||||
// + videoStream.isVideoOnly);
|
|
||||||
// }
|
|
||||||
|
|
||||||
assertEquals(result.size(), expected.size());
|
assertEquals(expected.size(), result.size());
|
||||||
for (int i = 0; i < result.size(); i++) {
|
for (int i = 0; i < result.size(); i++) {
|
||||||
assertEquals(result.get(i).resolution, expected.get(i));
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
@ -67,12 +64,59 @@ public class ListHelperTest {
|
|||||||
//////////////////
|
//////////////////
|
||||||
|
|
||||||
result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
||||||
VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false);
|
VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false);
|
||||||
expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60",
|
expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60",
|
||||||
"720p", "480p", "360p", "240p", "144p");
|
"720p", "480p", "360p", "240p", "144p");
|
||||||
assertEquals(result.size(), expected.size());
|
assertEquals(expected.size(), result.size());
|
||||||
for (int i = 0; i < result.size(); i++) {
|
for (int i = 0; i < result.size(); i++) {
|
||||||
assertEquals(result.get(i).resolution, expected.get(i));
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getSortedStreamVideosListWithPreferVideoOnlyStreamsTest() {
|
||||||
|
List<VideoStream> result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
||||||
|
null, VIDEO_ONLY_STREAMS_TEST_LIST, true, true);
|
||||||
|
|
||||||
|
List<String> expected =
|
||||||
|
Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60");
|
||||||
|
|
||||||
|
assertEquals(expected.size(), result.size());
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
|
assertTrue(result.get(i).isVideoOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// No video only streams -> should return mixed streams //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
||||||
|
VIDEO_STREAMS_TEST_LIST, null, false, true);
|
||||||
|
expected = Arrays.asList("720p", "480p", "360p", "240p", "144p");
|
||||||
|
assertEquals(expected.size(), result.size());
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
|
assertFalse(result.get(i).isVideoOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// Both types of streams -> should return correct one streams //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true,
|
||||||
|
VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, true);
|
||||||
|
expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60",
|
||||||
|
"1080p", "1080p60", "1440p60", "2160p", "2160p60");
|
||||||
|
final List<String> expectedVideoOnly =
|
||||||
|
Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60");
|
||||||
|
|
||||||
|
assertEquals(expected.size(), result.size());
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
|
assertEquals(
|
||||||
|
expectedVideoOnly.contains(result.get(i).resolution),
|
||||||
|
result.get(i).isVideoOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,12 +127,12 @@ public class ListHelperTest {
|
|||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
|
|
||||||
final List<VideoStream> result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4,
|
final List<VideoStream> result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4,
|
||||||
false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false);
|
false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false);
|
||||||
final List<String> expected = Arrays.asList(
|
final List<String> expected = Arrays.asList(
|
||||||
"1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p");
|
"1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p");
|
||||||
assertEquals(result.size(), expected.size());
|
assertEquals(expected.size(), result.size());
|
||||||
for (int i = 0; i < result.size(); i++) {
|
for (int i = 0; i < result.size(); i++) {
|
||||||
assertEquals(result.get(i).resolution, expected.get(i));
|
assertEquals(expected.get(i), result.get(i).resolution);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
lines="232,304"/>
|
lines="232,304"/>
|
||||||
|
|
||||||
<suppress checks="FinalParameters"
|
<suppress checks="FinalParameters"
|
||||||
files="ListHelper.java"
|
files="InfoListAdapter.java"
|
||||||
lines="280,312"/>
|
lines="253,325"/>
|
||||||
|
|
||||||
<suppress checks="EmptyBlock"
|
<suppress checks="EmptyBlock"
|
||||||
files="ContentSettingsFragment.java"
|
files="ContentSettingsFragment.java"
|
||||||
|
Loading…
Reference in New Issue
Block a user