package org.schabi.newpipe.util; import android.content.Context; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import us.shandian.giga.util.Utility; /** * A list adapter for a list of {@link Stream streams}, * currently supporting {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream} */ public class StreamItemAdapter extends BaseAdapter { private final Context context; private final StreamSizeWrapper streamsWrapper; private final SparseArray> secondaryStreams; public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, SparseArray> secondaryStreams) { this.context = context; this.streamsWrapper = streamsWrapper; this.secondaryStreams = secondaryStreams; } public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, boolean showIconNoAudio) { this(context, streamsWrapper, showIconNoAudio ? new SparseArray<>() : null); } public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper) { this(context, streamsWrapper, null); } public List getAll() { return streamsWrapper.getStreamsList(); } public SparseArray> getAllSecondary() { return secondaryStreams; } @Override public int getCount() { return streamsWrapper.getStreamsList().size(); } @Override public T getItem(int position) { return streamsWrapper.getStreamsList().get(position); } @Override public long getItemId(int position) { return position; } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return getCustomView(position, convertView, parent, true); } @Override public View getView(int position, View convertView, ViewGroup parent) { return getCustomView(((Spinner) parent).getSelectedItemPosition(), convertView, parent, false); } private View getCustomView(int position, View convertView, ViewGroup parent, boolean isDropdownItem) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.stream_quality_item, parent, false); } final ImageView woSoundIconView = convertView.findViewById(R.id.wo_sound_icon); final TextView formatNameView = convertView.findViewById(R.id.stream_format_name); final TextView qualityView = convertView.findViewById(R.id.stream_quality); final TextView sizeView = convertView.findViewById(R.id.stream_size); final T stream = getItem(position); int woSoundIconVisibility = View.GONE; String qualityString; if (stream instanceof VideoStream) { VideoStream videoStream = ((VideoStream) stream); qualityString = videoStream.getResolution(); if (secondaryStreams != null) { if (videoStream.isVideoOnly()) { woSoundIconVisibility = secondaryStreams.get(position) == null ? View.VISIBLE : View.INVISIBLE; } else if (isDropdownItem) { woSoundIconVisibility = View.INVISIBLE; } } } else if (stream instanceof AudioStream) { AudioStream audioStream = ((AudioStream) stream); qualityString = audioStream.getAverageBitrate() > 0 ? audioStream.getAverageBitrate() + "kbps" : audioStream.getFormat().getName(); } 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 (streamsWrapper.getSizeInBytes(position) > 0) { SecondaryStreamHelper secondary = secondaryStreams == null ? null : secondaryStreams.get(position); if (secondary != null) { long size = secondary.getSizeInBytes() + streamsWrapper.getSizeInBytes(position); sizeView.setText(Utility.formatBytes(size)); } else { sizeView.setText(streamsWrapper.getFormattedSize(position)); } sizeView.setVisibility(View.VISIBLE); } else { sizeView.setVisibility(View.GONE); } if (stream instanceof SubtitlesStream) { formatNameView.setText(((SubtitlesStream) stream).getLanguageTag()); } else { formatNameView.setText(stream.getFormat().getName()); } qualityView.setText(qualityString); woSoundIconView.setVisibility(woSoundIconVisibility); return convertView; } /** * A wrapper class that includes a way of storing the stream sizes. */ public static class StreamSizeWrapper implements Serializable { private static final StreamSizeWrapper EMPTY = new StreamSizeWrapper<>(Collections.emptyList(), null); private final List streamsList; private final long[] streamSizes; private final String unknownSize; public StreamSizeWrapper(List sL, Context context) { this.streamsList = sL != null ? sL : Collections.emptyList(); this.streamSizes = new long[streamsList.size()]; this.unknownSize = context == null ? "--.-" : context.getString(R.string.unknown_content); for (int i = 0; i < streamSizes.length; i++) streamSizes[i] = -2; } /** * Helper method to fetch the sizes of all the streams in a wrapper. * * @param streamsWrapper the wrapper * @return a {@link Single} that returns a boolean indicating if any elements were changed */ public static Single fetchSizeForWrapper(StreamSizeWrapper streamsWrapper) { final Callable fetchAndSet = () -> { boolean hasChanged = false; for (X stream : streamsWrapper.getStreamsList()) { if (streamsWrapper.getSizeInBytes(stream) > -2) { continue; } final long contentLength = Downloader.getInstance().getContentLength(stream.getUrl()); streamsWrapper.setSize(stream, contentLength); hasChanged = true; } return hasChanged; }; return Single.fromCallable(fetchAndSet) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturnItem(true); } public List getStreamsList() { return streamsList; } public long getSizeInBytes(int streamIndex) { return streamSizes[streamIndex]; } public long getSizeInBytes(T stream) { return streamSizes[streamsList.indexOf(stream)]; } public String getFormattedSize(int streamIndex) { return formatSize(getSizeInBytes(streamIndex)); } public String getFormattedSize(T stream) { return formatSize(getSizeInBytes(stream)); } private String formatSize(long size) { if (size > -1) { return Utility.formatBytes(size); } return unknownSize; } public void setSize(int streamIndex, long sizeInBytes) { streamSizes[streamIndex] = sizeInBytes; } public void setSize(T stream, long sizeInBytes) { streamSizes[streamsList.indexOf(stream)] = sizeInBytes; } public static StreamSizeWrapper empty() { //noinspection unchecked return (StreamSizeWrapper) EMPTY; } } }