package org.schabi.newpipe.util; import android.content.Context; 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.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} and {@link AudioStream}. */ public class StreamItemAdapter extends BaseAdapter { private final Context context; private StreamSizeWrapper streamsWrapper; private final boolean showIconNoAudio; public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, boolean showIconNoAudio) { this.context = context; this.streamsWrapper = streamsWrapper; this.showIconNoAudio = showIconNoAudio; } public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper) { this(context, streamsWrapper, false); } public List getAll() { return streamsWrapper.getStreamsList(); } @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) { qualityString = ((VideoStream) stream).getResolution(); if (!showIconNoAudio) { woSoundIconVisibility = View.GONE; } else if (((VideoStream) stream).isVideoOnly()) { woSoundIconVisibility = View.VISIBLE; } else if (isDropdownItem) { woSoundIconVisibility = View.INVISIBLE; } } else if (stream instanceof AudioStream) { qualityString = ((AudioStream) stream).getAverageBitrate() + "kbps"; } else { qualityString = stream.getFormat().getSuffix(); } if (streamsWrapper.getSizeInBytes(position) > 0) { sizeView.setText(streamsWrapper.getFormattedSize(position)); sizeView.setVisibility(View.VISIBLE); } else { sizeView.setVisibility(View.GONE); } 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()); private final List streamsList; private long[] streamSizes; public StreamSizeWrapper(List streamsList) { this.streamsList = streamsList; this.streamSizes = new long[streamsList.size()]; for (int i = 0; i < streamSizes.length; i++) streamSizes[i] = -1; } /** * 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) > 0) { 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 Utility.formatBytes(getSizeInBytes(streamIndex)); } public String getFormattedSize(T stream) { return Utility.formatBytes(getSizeInBytes(stream)); } 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; } } }