Add support of other delivery methods than progressive HTTP (in the player only)
Detailed changes: - External players: - Add a message instruction about stream selection; - Add a message when there is no stream available for external players; - Return now HLS, DASH and SmoothStreaming URL contents, in addition to progressive HTTP ones. - Player: - Support DASH, HLS and SmoothStreaming streams for videos, whether they are content URLs or the manifests themselves, in addition to progressive HTTP ones; - Use a custom HttpDataSource to play YouTube contents, based of ExoPlayer's default one, which allows better spoofing of official clients (custom user-agent and headers (depending of the client used), use of range and rn (set dynamically by the DataSource) parameters); - Fetch YouTube progressive contents as DASH streams, like official clients, support fully playback of livestreams which have ended recently and OTF streams; - Use ExoPlayer's default retries count for contents on non-fatal errors (instead of Integer.MAX_VALUE for non-live contents and 5 for live contents). - Download dialog: - Add message about support of progressive HTTP streams only for downloading; - Remove several duplicated code and update relevant usages; - Support downloading of contents with an unknown media format. - ListHelper: - Catch NumberFormatException when trying to compare two video streams between them. - Tests: - Update ListHelperTest and StreamItemAdapterTest to fix breaking changes in the extractor. - Other places: - Fixes deprecation of changes made in the extractor; - Improve some code related to the files changed. - Issues fixed and/or improved with the changes: - Seeking of PeerTube HLS streams (the duration shown was the one from the stream duration and not the one parsed, incomplete because HLS streams are fragmented MP4s with multiple sidx boxes, for which seeking is not supported by ExoPlayer) (the app now uses the HLS manifest returned for each quality, in the master playlist (not fetched and computed by the extractor)); - Crash when loading PeerTube streams with a separated audio; - Lack of some streams on some YouTube videos (OTF streams); - Loading times of YouTube streams, after a quality change or a playback start; - View count of YouTube ended livestreams interpreted as watching count (this type of streams is not interpreted anymore as livestreams); - Watchable time of YouTube ended livestreams; - Playback of SoundCloud HLS-only tracks (which cannot be downloaded anymore because the workaround which was used is being removed by SoundCloud, so it has been removed from the extractor).
This commit is contained in:
parent
a59660f421
commit
210834fbe9
27 changed files with 2417 additions and 539 deletions
|
|
@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -21,6 +23,7 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
|
@ -37,10 +40,9 @@ public final class ListHelper {
|
|||
// Audio format in order of efficiency. 0=most efficient, n=least efficient
|
||||
private static final List<MediaFormat> AUDIO_FORMAT_EFFICIENCY_RANKING =
|
||||
Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3);
|
||||
|
||||
private static final Set<String> HIGH_RESOLUTION_LIST
|
||||
// Uses a HashSet for better performance
|
||||
= new HashSet<>(Arrays.asList("1440p", "2160p", "1440p60", "2160p60"));
|
||||
// Use a HashSet for better performance
|
||||
private static final Set<String> HIGH_RESOLUTION_LIST = new HashSet<>(
|
||||
Arrays.asList("1440p", "2160p"));
|
||||
|
||||
private ListHelper() { }
|
||||
|
||||
|
|
@ -110,6 +112,83 @@ public final class ListHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} list which uses the given delivery method from a {@link Stream}
|
||||
* list.
|
||||
*
|
||||
* @param streamList the original stream list
|
||||
* @param deliveryMethod the delivery method
|
||||
* @param <S> the item type's class that extends {@link Stream}
|
||||
* @return a stream list which uses the given delivery method
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> keepStreamsWithDelivery(
|
||||
@NonNull final List<S> streamList,
|
||||
final DeliveryMethod deliveryMethod) {
|
||||
if (streamList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Iterator<S> streamListIterator = streamList.iterator();
|
||||
while (streamListIterator.hasNext()) {
|
||||
if (streamListIterator.next().getDeliveryMethod() != deliveryMethod) {
|
||||
streamListIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return streamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} list which only contains URL streams and non-torrent streams.
|
||||
*
|
||||
* @param streamList the original stream list
|
||||
* @param <S> the item type's class that extends {@link Stream}
|
||||
* @return a stream list which only contains URL streams and non-torrent streams
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> removeNonUrlAndTorrentStreams(
|
||||
@NonNull final List<S> streamList) {
|
||||
if (streamList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Iterator<S> streamListIterator = streamList.iterator();
|
||||
while (streamListIterator.hasNext()) {
|
||||
final S stream = streamListIterator.next();
|
||||
if (!stream.isUrl() || stream.getDeliveryMethod() == DeliveryMethod.TORRENT) {
|
||||
streamListIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return streamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} list which only contains non-torrent streams.
|
||||
*
|
||||
* @param streamList the original stream list
|
||||
* @param <S> the item type's class that extends {@link Stream}
|
||||
* @return a stream list which only contains non-torrent streams
|
||||
*/
|
||||
@NonNull
|
||||
public static <S extends Stream> List<S> removeTorrentStreams(
|
||||
@NonNull final List<S> streamList) {
|
||||
if (streamList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Iterator<S> streamListIterator = streamList.iterator();
|
||||
while (streamListIterator.hasNext()) {
|
||||
final S stream = streamListIterator.next();
|
||||
if (stream.getDeliveryMethod() == DeliveryMethod.TORRENT) {
|
||||
streamListIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return streamList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the two lists of video streams (video_only and normal videos),
|
||||
* and sort them according with default format chosen by the user.
|
||||
|
|
@ -177,7 +256,7 @@ public final class ListHelper {
|
|||
static int getDefaultResolutionIndex(final String defaultResolution,
|
||||
final String bestResolutionKey,
|
||||
final MediaFormat defaultFormat,
|
||||
final List<VideoStream> videoStreams) {
|
||||
@Nullable final List<VideoStream> videoStreams) {
|
||||
if (videoStreams == null || videoStreams.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -233,7 +312,9 @@ public final class ListHelper {
|
|||
.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()))
|
||||
|| !HIGH_RESOLUTION_LIST.contains(stream.getResolution()
|
||||
// Replace any frame rate with nothing
|
||||
.replaceAll("p\\d+$", "p")))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final HashMap<String, VideoStream> hashMap = new HashMap<>();
|
||||
|
|
@ -366,8 +447,9 @@ public final class ListHelper {
|
|||
* @param videoStreams the available video streams
|
||||
* @return the index of the preferred video stream
|
||||
*/
|
||||
static int getVideoStreamIndex(final String targetResolution, final MediaFormat targetFormat,
|
||||
final List<VideoStream> videoStreams) {
|
||||
static int getVideoStreamIndex(@NonNull final String targetResolution,
|
||||
final MediaFormat targetFormat,
|
||||
@NonNull final List<VideoStream> videoStreams) {
|
||||
int fullMatchIndex = -1;
|
||||
int fullMatchNoRefreshIndex = -1;
|
||||
int resMatchOnlyIndex = -1;
|
||||
|
|
@ -428,7 +510,7 @@ public final class ListHelper {
|
|||
* @param videoStreams the list of video streams to check
|
||||
* @return the index of the preferred video stream
|
||||
*/
|
||||
private static int getDefaultResolutionWithDefaultFormat(final Context context,
|
||||
private static int getDefaultResolutionWithDefaultFormat(@NonNull final Context context,
|
||||
final String defaultResolution,
|
||||
final List<VideoStream> videoStreams) {
|
||||
final MediaFormat defaultFormat = getDefaultFormat(context,
|
||||
|
|
@ -437,7 +519,7 @@ public final class ListHelper {
|
|||
context.getString(R.string.best_resolution_key), defaultFormat, videoStreams);
|
||||
}
|
||||
|
||||
private static MediaFormat getDefaultFormat(final Context context,
|
||||
private static MediaFormat getDefaultFormat(@NonNull final Context context,
|
||||
@StringRes final int defaultFormatKey,
|
||||
@StringRes final int defaultFormatValueKey) {
|
||||
final SharedPreferences preferences
|
||||
|
|
@ -457,8 +539,8 @@ public final class ListHelper {
|
|||
return defaultMediaFormat;
|
||||
}
|
||||
|
||||
private static MediaFormat getMediaFormatFromKey(final Context context,
|
||||
final String formatKey) {
|
||||
private static MediaFormat getMediaFormatFromKey(@NonNull final Context context,
|
||||
@NonNull final String formatKey) {
|
||||
MediaFormat format = null;
|
||||
if (formatKey.equals(context.getString(R.string.video_webm_key))) {
|
||||
format = MediaFormat.WEBM;
|
||||
|
|
@ -496,12 +578,20 @@ public final class ListHelper {
|
|||
- formatRanking.indexOf(streamB.getFormat());
|
||||
}
|
||||
|
||||
private static int compareVideoStreamResolution(final String r1, final String r2) {
|
||||
final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
|
||||
.replaceAll("[^\\d.]", ""));
|
||||
final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
|
||||
.replaceAll("[^\\d.]", ""));
|
||||
return res1 - res2;
|
||||
private static int compareVideoStreamResolution(@NonNull final String r1,
|
||||
@NonNull final String r2) {
|
||||
try {
|
||||
final int res1 = Integer.parseInt(r1.replaceAll("0p\\d+$", "1")
|
||||
.replaceAll("[^\\d.]", ""));
|
||||
final int res2 = Integer.parseInt(r2.replaceAll("0p\\d+$", "1")
|
||||
.replaceAll("[^\\d.]", ""));
|
||||
return res1 - res2;
|
||||
} catch (final NumberFormatException e) {
|
||||
// Consider the first one greater because we don't know if the two streams are
|
||||
// different or not (a NumberFormatException was thrown so we don't know the resolution
|
||||
// of one stream or of all streams)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Compares the quality of two video streams.
|
||||
|
|
@ -536,7 +626,7 @@ public final class ListHelper {
|
|||
* @param context App context
|
||||
* @return maximum resolution allowed or null if there is no maximum
|
||||
*/
|
||||
private static String getResolutionLimit(final Context context) {
|
||||
private static String getResolutionLimit(@NonNull final Context context) {
|
||||
String resolutionLimit = null;
|
||||
if (isMeteredNetwork(context)) {
|
||||
final SharedPreferences preferences
|
||||
|
|
@ -555,7 +645,7 @@ public final class ListHelper {
|
|||
* @param context App context
|
||||
* @return {@code true} if connected to a metered network
|
||||
*/
|
||||
public static boolean isMeteredNetwork(final Context context) {
|
||||
public static boolean isMeteredNetwork(@NonNull final Context context) {
|
||||
final ConnectivityManager manager
|
||||
= ContextCompat.getSystemService(context, ConnectivityManager.class);
|
||||
if (manager == null || manager.getActiveNetworkInfo() == null) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.schabi.newpipe.extractor.NewPipe;
|
|||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
|
@ -60,7 +61,9 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.util.ListHelper.removeNonUrlAndTorrentStreams;
|
||||
|
||||
public final class NavigationHelper {
|
||||
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
|
||||
|
|
@ -217,30 +220,44 @@ public final class NavigationHelper {
|
|||
|
||||
public static void playOnExternalAudioPlayer(@NonNull final Context context,
|
||||
@NonNull final StreamInfo info) {
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||
|
||||
if (index == -1) {
|
||||
final List<AudioStream> audioStreams = info.getAudioStreams();
|
||||
if (audioStreams.isEmpty()) {
|
||||
Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final List<AudioStream> audioStreamsForExternalPlayers = removeNonUrlAndTorrentStreams(
|
||||
audioStreams);
|
||||
if (audioStreamsForExternalPlayers.isEmpty()) {
|
||||
Toast.makeText(context, R.string.no_audio_streams_available_for_external_players,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final int index = ListHelper.getDefaultAudioFormat(context,
|
||||
audioStreamsForExternalPlayers);
|
||||
|
||||
final AudioStream audioStream = info.getAudioStreams().get(index);
|
||||
final AudioStream audioStream = audioStreamsForExternalPlayers.get(index);
|
||||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream);
|
||||
}
|
||||
|
||||
public static void playOnExternalVideoPlayer(@NonNull final Context context,
|
||||
public static void playOnExternalVideoPlayer(final Context context,
|
||||
@NonNull final StreamInfo info) {
|
||||
final ArrayList<VideoStream> videoStreamsList = new ArrayList<>(
|
||||
ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false,
|
||||
false));
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList);
|
||||
|
||||
if (index == -1) {
|
||||
final List<VideoStream> videoStreams = info.getVideoStreams();
|
||||
if (videoStreams.isEmpty()) {
|
||||
Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final List<VideoStream> videoStreamsForExternalPlayers =
|
||||
ListHelper.getSortedStreamVideosList(context,
|
||||
removeNonUrlAndTorrentStreams(videoStreams), null, false, false);
|
||||
if (videoStreamsForExternalPlayers.isEmpty()) {
|
||||
Toast.makeText(context, R.string.no_video_streams_available_for_external_players,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final int index = ListHelper.getDefaultResolutionIndex(context,
|
||||
videoStreamsForExternalPlayers);
|
||||
|
||||
final VideoStream videoStream = videoStreamsList.get(index);
|
||||
final VideoStream videoStream = videoStreamsForExternalPlayers.get(index);
|
||||
playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream);
|
||||
}
|
||||
|
||||
|
|
@ -248,9 +265,49 @@ public final class NavigationHelper {
|
|||
@Nullable final String name,
|
||||
@Nullable final String artist,
|
||||
@NonNull final Stream stream) {
|
||||
final DeliveryMethod deliveryMethod = stream.getDeliveryMethod();
|
||||
final String mimeType;
|
||||
if (deliveryMethod == DeliveryMethod.PROGRESSIVE_HTTP) {
|
||||
if (stream.getFormat() != null) {
|
||||
mimeType = stream.getFormat().getMimeType();
|
||||
} else {
|
||||
if (stream instanceof AudioStream) {
|
||||
mimeType = "audio/*";
|
||||
} else if (stream instanceof VideoStream) {
|
||||
mimeType = "video/*";
|
||||
} else {
|
||||
// This should never be reached, because subtitles are not opened in external
|
||||
// players
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!stream.isUrl() || deliveryMethod == DeliveryMethod.TORRENT) {
|
||||
Toast.makeText(context, R.string.selected_stream_external_player_not_supported,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
} else {
|
||||
switch (deliveryMethod) {
|
||||
case HLS:
|
||||
mimeType = "application/x-mpegURL";
|
||||
break;
|
||||
case DASH:
|
||||
mimeType = "application/dash+xml";
|
||||
break;
|
||||
case SS:
|
||||
mimeType = "application/vnd.ms-sstr+xml";
|
||||
break;
|
||||
default:
|
||||
// Progressive HTTP streams are handled above and torrents streams are not
|
||||
// exposed to external players
|
||||
mimeType = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType());
|
||||
intent.setDataAndType(Uri.parse(stream.getContent()), mimeType);
|
||||
intent.putExtra(Intent.EXTRA_TITLE, name);
|
||||
intent.putExtra("title", name);
|
||||
intent.putExtra("artist", artist);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
|
|
@ -14,7 +15,8 @@ public class SecondaryStreamHelper<T extends Stream> {
|
|||
private final int position;
|
||||
private final StreamSizeWrapper<T> streams;
|
||||
|
||||
public SecondaryStreamHelper(final StreamSizeWrapper<T> streams, final T selectedStream) {
|
||||
public SecondaryStreamHelper(@NonNull final StreamSizeWrapper<T> streams,
|
||||
final T selectedStream) {
|
||||
this.streams = streams;
|
||||
this.position = streams.getStreamsList().indexOf(selectedStream);
|
||||
if (this.position < 0) {
|
||||
|
|
@ -29,33 +31,37 @@ public class SecondaryStreamHelper<T extends Stream> {
|
|||
* @param videoStream desired video ONLY stream
|
||||
* @return selected audio stream or null if a candidate was not found
|
||||
*/
|
||||
@Nullable
|
||||
public static AudioStream getAudioStreamFor(@NonNull final List<AudioStream> audioStreams,
|
||||
@NonNull final VideoStream videoStream) {
|
||||
switch (videoStream.getFormat()) {
|
||||
case WEBM:
|
||||
case MPEG_4:// ¿is mpeg-4 DASH?
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
final boolean m4v = videoStream.getFormat() == MediaFormat.MPEG_4;
|
||||
|
||||
for (final AudioStream audio : audioStreams) {
|
||||
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) {
|
||||
return audio;
|
||||
final MediaFormat mediaFormat = videoStream.getFormat();
|
||||
if (mediaFormat != null) {
|
||||
switch (mediaFormat) {
|
||||
case WEBM:
|
||||
case MPEG_4:// ¿is mpeg-4 DASH?
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (m4v) {
|
||||
return null;
|
||||
}
|
||||
final boolean m4v = (mediaFormat == MediaFormat.MPEG_4);
|
||||
|
||||
// retry, but this time in reverse order
|
||||
for (int i = audioStreams.size() - 1; i >= 0; i--) {
|
||||
final AudioStream audio = audioStreams.get(i);
|
||||
if (audio.getFormat() == MediaFormat.WEBMA_OPUS) {
|
||||
return audio;
|
||||
for (final AudioStream audio : audioStreams) {
|
||||
if (audio.getFormat() == (m4v ? MediaFormat.M4A : MediaFormat.WEBMA)) {
|
||||
return audio;
|
||||
}
|
||||
}
|
||||
|
||||
if (m4v) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// retry, but this time in reverse order
|
||||
for (int i = audioStreams.size() - 1; i >= 0; i--) {
|
||||
final AudioStream audio = audioStreams.get(i);
|
||||
if (audio.getFormat() == MediaFormat.WEBMA_OPUS) {
|
||||
return audio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import android.widget.ImageView;
|
|||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
|
|
@ -87,7 +89,8 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(final int position, final View convertView,
|
||||
public View getDropDownView(final int position,
|
||||
final View convertView,
|
||||
final ViewGroup parent) {
|
||||
return getCustomView(position, convertView, parent, true);
|
||||
}
|
||||
|
|
@ -98,7 +101,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
convertView, parent, false);
|
||||
}
|
||||
|
||||
private View getCustomView(final int position, final View view, final ViewGroup parent,
|
||||
@NonNull
|
||||
private View getCustomView(final int position,
|
||||
final View view,
|
||||
final ViewGroup parent,
|
||||
final boolean isDropdownItem) {
|
||||
View convertView = view;
|
||||
if (convertView == null) {
|
||||
|
|
@ -112,6 +118,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
final TextView sizeView = convertView.findViewById(R.id.stream_size);
|
||||
|
||||
final T stream = getItem(position);
|
||||
final MediaFormat mediaFormat = stream.getFormat();
|
||||
|
||||
int woSoundIconVisibility = View.GONE;
|
||||
String qualityString;
|
||||
|
|
@ -135,24 +142,32 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
}
|
||||
} else if (stream instanceof AudioStream) {
|
||||
final AudioStream audioStream = ((AudioStream) stream);
|
||||
qualityString = audioStream.getAverageBitrate() > 0
|
||||
? audioStream.getAverageBitrate() + "kbps"
|
||||
: audioStream.getFormat().getName();
|
||||
if (audioStream.getAverageBitrate() > 0) {
|
||||
qualityString = audioStream.getAverageBitrate() + "kbps";
|
||||
} else if (mediaFormat != null) {
|
||||
qualityString = mediaFormat.getName();
|
||||
} else {
|
||||
qualityString = context.getString(R.string.unknown_quality);
|
||||
}
|
||||
} else if (stream instanceof SubtitlesStream) {
|
||||
qualityString = ((SubtitlesStream) stream).getDisplayLanguageName();
|
||||
if (((SubtitlesStream) stream).isAutoGenerated()) {
|
||||
qualityString += " (" + context.getString(R.string.caption_auto_generated) + ")";
|
||||
}
|
||||
} else {
|
||||
qualityString = stream.getFormat().getSuffix();
|
||||
if (mediaFormat != null) {
|
||||
qualityString = mediaFormat.getSuffix();
|
||||
} else {
|
||||
qualityString = context.getString(R.string.unknown_quality);
|
||||
}
|
||||
}
|
||||
|
||||
if (streamsWrapper.getSizeInBytes(position) > 0) {
|
||||
final SecondaryStreamHelper<U> secondary = secondaryStreams == null ? null
|
||||
: secondaryStreams.get(position);
|
||||
if (secondary != null) {
|
||||
final long size
|
||||
= secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position);
|
||||
final long size = secondary.getSizeInBytes()
|
||||
+ streamsWrapper.getSizeInBytes(position);
|
||||
sizeView.setText(Utility.formatBytes(size));
|
||||
} else {
|
||||
sizeView.setText(streamsWrapper.getFormattedSize(position));
|
||||
|
|
@ -164,11 +179,15 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
|
||||
if (stream instanceof SubtitlesStream) {
|
||||
formatNameView.setText(((SubtitlesStream) stream).getLanguageTag());
|
||||
} else if (stream.getFormat() == MediaFormat.WEBMA_OPUS) {
|
||||
// noinspection AndroidLintSetTextI18n
|
||||
formatNameView.setText("opus");
|
||||
} else {
|
||||
formatNameView.setText(stream.getFormat().getName());
|
||||
if (mediaFormat == null) {
|
||||
formatNameView.setText(context.getString(R.string.unknown_format));
|
||||
} else if (mediaFormat == MediaFormat.WEBMA_OPUS) {
|
||||
// noinspection AndroidLintSetTextI18n
|
||||
formatNameView.setText("opus");
|
||||
} else {
|
||||
formatNameView.setText(mediaFormat.getName());
|
||||
}
|
||||
}
|
||||
|
||||
qualityView.setText(qualityString);
|
||||
|
|
@ -233,6 +252,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
* @param streamsWrapper the wrapper
|
||||
* @return a {@link Single} that returns a boolean indicating if any elements were changed
|
||||
*/
|
||||
@NonNull
|
||||
public static <X extends Stream> Single<Boolean> fetchSizeForWrapper(
|
||||
final StreamSizeWrapper<X> streamsWrapper) {
|
||||
final Callable<Boolean> fetchAndSet = () -> {
|
||||
|
|
@ -243,7 +263,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||
}
|
||||
|
||||
final long contentLength = DownloaderImpl.getInstance().getContentLength(
|
||||
stream.getUrl());
|
||||
stream.getContent());
|
||||
streamsWrapper.setSize(stream, contentLength);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.util;
|
|||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
/**
|
||||
* Utility class for {@link org.schabi.newpipe.extractor.stream.StreamType}.
|
||||
* Utility class for {@link StreamType}.
|
||||
*/
|
||||
public final class StreamTypeUtil {
|
||||
private StreamTypeUtil() {
|
||||
|
|
@ -11,10 +11,10 @@ public final class StreamTypeUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the streamType is a livestream.
|
||||
* Check if the {@link StreamType} of a stream is a livestream.
|
||||
*
|
||||
* @param streamType
|
||||
* @return <code>true</code> when the streamType is a
|
||||
* @param streamType the stream type of the stream
|
||||
* @return <code>true</code> if the streamType is a
|
||||
* {@link StreamType#LIVE_STREAM} or {@link StreamType#AUDIO_LIVE_STREAM}
|
||||
*/
|
||||
public static boolean isLiveStream(final StreamType streamType) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue