fix conflict

This commit is contained in:
Christian Schabesberger 2018-04-08 18:49:27 +02:00
commit 665b9087b1
9 changed files with 462 additions and 224 deletions

View File

@ -73,6 +73,31 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
mCookies = cookies; mCookies = cookies;
} }
/**
* Get the size of the content that the url is pointing by firing a HEAD request.
*
* @param url an url pointing to the content
* @return the size of the content, in bytes
*/
public long getContentLength(String url) throws IOException {
Response response = null;
try {
final Request request = new Request.Builder()
.head().url(url)
.addHeader("User-Agent", USER_AGENT)
.build();
response = client.newCall(request).execute();
return Long.parseLong(response.header("Content-Length"));
} catch (NumberFormatException e) {
throw new IOException("Invalid content length", e);
} finally {
if (response != null) {
response.close();
}
}
}
/** /**
* Download the text file at the supplied URL as in download(String), * Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string. * but set the HTTP header field "Accept-Language" to the supplied string.

View File

@ -1,56 +1,61 @@
package org.schabi.newpipe.download; package org.schabi.newpipe.download;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.IdRes; import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.Stream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.detail.SpinnerToolbarAdapter;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilenameUtils; import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment"; private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG; private static final boolean DEBUG = MainActivity.DEBUG;
private static final String INFO_KEY = "info_key"; @State protected StreamInfo currentInfo;
private static final String SORTED_VIDEOS_LIST_KEY = "sorted_videos_list_key"; @State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
private static final String SELECTED_VIDEO_KEY = "selected_video_key"; @State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
private static final String SELECTED_AUDIO_KEY = "selected_audio_key"; @State protected int selectedVideoIndex = 0;
@State protected int selectedAudioIndex = 0;
private StreamInfo currentInfo; private StreamItemAdapter<AudioStream> audioStreamsAdapter;
private ArrayList<VideoStream> sortedStreamVideosList; private StreamItemAdapter<VideoStream> videoStreamsAdapter;
private int selectedVideoIndex;
private int selectedAudioIndex; private CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText; private EditText nameEditText;
private Spinner streamsSpinner; private Spinner streamsSpinner;
@ -58,17 +63,50 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private TextView threadsCountTextView; private TextView threadsCountTextView;
private SeekBar threadsSeekBar; private SeekBar threadsSeekBar;
public static DownloadDialog newInstance(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) { public static DownloadDialog newInstance(StreamInfo info) {
DownloadDialog dialog = new DownloadDialog(); DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info, sortedStreamVideosList, selectedVideoIndex); dialog.setInfo(info);
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
return dialog; return dialog;
} }
private void setInfo(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) { public static DownloadDialog newInstance(Context context, StreamInfo info) {
final ArrayList<VideoStream> streamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context,
info.getVideoStreams(), info.getVideoOnlyStreams(), false));
final int selectedStreamIndex = ListHelper.getDefaultResolutionIndex(context, streamsList);
final DownloadDialog instance = newInstance(info);
instance.setVideoStreams(streamsList);
instance.setSelectedVideoStream(selectedStreamIndex);
instance.setAudioStreams(info.getAudioStreams());
return instance;
}
private void setInfo(StreamInfo info) {
this.currentInfo = info; this.currentInfo = info;
}
public void setAudioStreams(List<AudioStream> audioStreams) {
setAudioStreams(new StreamSizeWrapper<>(audioStreams));
}
public void setAudioStreams(StreamSizeWrapper<AudioStream> wrappedAudioStreams) {
this.wrappedAudioStreams = wrappedAudioStreams;
}
public void setVideoStreams(List<VideoStream> videoStreams) {
setVideoStreams(new StreamSizeWrapper<>(videoStreams));
}
public void setVideoStreams(StreamSizeWrapper<VideoStream> wrappedVideoStreams) {
this.wrappedVideoStreams = wrappedVideoStreams;
}
public void setSelectedVideoStream(int selectedVideoIndex) {
this.selectedVideoIndex = selectedVideoIndex; this.selectedVideoIndex = selectedVideoIndex;
this.sortedStreamVideosList = sortedStreamVideosList; }
public void setSelectedAudioStream(int selectedAudioIndex) {
this.selectedAudioIndex = selectedAudioIndex;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -84,28 +122,21 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
return; return;
} }
if (savedInstanceState != null) { setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
Serializable serial = savedInstanceState.getSerializable(INFO_KEY); Icepick.restoreInstanceState(this, savedInstanceState);
if (serial instanceof StreamInfo) currentInfo = (StreamInfo) serial;
serial = savedInstanceState.getSerializable(SORTED_VIDEOS_LIST_KEY); this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true);
if (serial instanceof ArrayList) { //noinspection unchecked this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
sortedStreamVideosList = (ArrayList<VideoStream>) serial;
}
selectedVideoIndex = savedInstanceState.getInt(SELECTED_VIDEO_KEY, 0);
selectedAudioIndex = savedInstanceState.getInt(SELECTED_AUDIO_KEY, 0);
}
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]"); if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.dialog_url, container); return inflater.inflate(R.layout.download_dialog, container);
} }
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
nameEditText = view.findViewById(R.id.file_name); nameEditText = view.findViewById(R.id.file_name);
nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName())); nameEditText.setText(FilenameUtils.createFilename(getContext(), currentInfo.getName()));
@ -116,12 +147,12 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
threadsCountTextView = view.findViewById(R.id.threads_count); threadsCountTextView = view.findViewById(R.id.threads_count);
threadsSeekBar = view.findViewById(R.id.threads); threadsSeekBar = view.findViewById(R.id.threads);
radioVideoAudioGroup = view.findViewById(R.id.video_audio_group); radioVideoAudioGroup = view.findViewById(R.id.video_audio_group);
radioVideoAudioGroup.setOnCheckedChangeListener(this); radioVideoAudioGroup.setOnCheckedChangeListener(this);
initToolbar(view.<Toolbar>findViewById(R.id.toolbar)); initToolbar(view.findViewById(R.id.toolbar));
checkDownloadOptions(view); setupDownloadOptions();
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
int def = 3; int def = 3;
threadsCountTextView.setText(String.valueOf(def)); threadsCountTextView.setText(String.valueOf(def));
@ -141,15 +172,35 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
public void onStopTrackingTouch(SeekBar p1) { public void onStopTrackingTouch(SeekBar p1) {
} }
}); });
fetchStreamsSize();
}
private void fetchStreamsSize() {
disposables.clear();
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> {
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) {
setupVideoSpinner();
}
}));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> {
if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
setupAudioSpinner();
}
}));
}
@Override
public void onDestroy() {
super.onDestroy();
disposables.clear();
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, currentInfo); Icepick.saveInstanceState(this, outState);
outState.putSerializable(SORTED_VIDEOS_LIST_KEY, sortedStreamVideosList);
outState.putInt(SELECTED_VIDEO_KEY, selectedVideoIndex);
outState.putInt(SELECTED_AUDIO_KEY, selectedAudioIndex);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -161,39 +212,31 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
toolbar.setTitle(R.string.download_dialog_title); toolbar.setTitle(R.string.download_dialog_title);
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp); toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url); toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() { toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
@Override
public void onClick(View v) {
getDialog().dismiss();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { toolbar.setOnMenuItemClickListener(item -> {
@Override if (item.getItemId() == R.id.okay) {
public boolean onMenuItemClick(MenuItem item) { downloadSelected();
if (item.getItemId() == R.id.okay) { return true;
downloadSelected();
return true;
} else return false;
} }
return false;
}); });
} }
public void setupAudioSpinner(final List<AudioStream> audioStreams, Spinner spinner) { private void setupAudioSpinner() {
String[] items = new String[audioStreams.size()]; if (getContext() == null) return;
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
items[i] = audioStream.getFormat().getName() + " " + audioStream.getAverageBitrate() + "kbps";
}
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items); streamsSpinner.setAdapter(audioStreamsAdapter);
spinner.setAdapter(itemAdapter); streamsSpinner.setSelection(selectedAudioIndex);
spinner.setSelection(selectedAudioIndex); setRadioButtonsState(true);
} }
public void setupVideoSpinner(final List<VideoStream> videoStreams, Spinner spinner) { private void setupVideoSpinner() {
spinner.setAdapter(new SpinnerToolbarAdapter(getContext(), videoStreams, true)); if (getContext() == null) return;
spinner.setSelection(selectedVideoIndex);
streamsSpinner.setAdapter(videoStreamsAdapter);
streamsSpinner.setSelection(selectedVideoIndex);
setRadioButtonsState(true);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -205,10 +248,10 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]"); if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
switch (checkedId) { switch (checkedId) {
case R.id.audio_button: case R.id.audio_button:
setupAudioSpinner(currentInfo.getAudioStreams(), streamsSpinner); setupAudioSpinner();
break; break;
case R.id.video_button: case R.id.video_button:
setupVideoSpinner(sortedStreamVideosList, streamsSpinner); setupVideoSpinner();
break; break;
} }
} }
@ -238,37 +281,53 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// Utils // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected void checkDownloadOptions(View view) { protected void setupDownloadOptions() {
RadioButton audioButton = view.findViewById(R.id.audio_button); setRadioButtonsState(false);
RadioButton videoButton = view.findViewById(R.id.video_button);
if (currentInfo.getAudioStreams() == null || currentInfo.getAudioStreams().size() == 0) { final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button);
audioButton.setVisibility(View.GONE); final RadioButton videoButton = radioVideoAudioGroup.findViewById(R.id.video_button);
final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
audioButton.setVisibility(isAudioStreamsAvailable ? View.VISIBLE : View.GONE);
videoButton.setVisibility(isVideoStreamsAvailable ? View.VISIBLE : View.GONE);
if (isVideoStreamsAvailable) {
videoButton.setChecked(true); videoButton.setChecked(true);
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) { setupVideoSpinner();
videoButton.setVisibility(View.GONE); } else if (isAudioStreamsAvailable) {
audioButton.setChecked(true); audioButton.setChecked(true);
setupAudioSpinner();
} else {
Toast.makeText(getContext(), R.string.no_streams_available_download, Toast.LENGTH_SHORT).show();
getDialog().dismiss();
} }
} }
private void setRadioButtonsState(boolean enabled) {
radioVideoAudioGroup.findViewById(R.id.audio_button).setEnabled(enabled);
radioVideoAudioGroup.findViewById(R.id.video_button).setEnabled(enabled);
}
private void downloadSelected() { private void downloadSelected() {
String url, location; Stream stream;
String location;
String fileName = nameEditText.getText().toString().trim(); String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName()); if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button; boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
if (isAudio) { if (isAudio) {
url = currentInfo.getAudioStreams().get(selectedAudioIndex).getUrl(); stream = audioStreamsAdapter.getItem(selectedAudioIndex);
location = NewPipeSettings.getAudioDownloadPath(getContext()); location = NewPipeSettings.getAudioDownloadPath(getContext());
fileName += "." + currentInfo.getAudioStreams().get(selectedAudioIndex).getFormat().getSuffix();
} else { } else {
url = sortedStreamVideosList.get(selectedVideoIndex).getUrl(); stream = videoStreamsAdapter.getItem(selectedVideoIndex);
location = NewPipeSettings.getVideoDownloadPath(getContext()); location = NewPipeSettings.getVideoDownloadPath(getContext());
fileName += "." + sortedStreamVideosList.get(selectedVideoIndex).getFormat().getSuffix();
} }
String url = stream.getUrl();
fileName += "." + stream.getFormat().getSuffix();
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1); DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
getDialog().dismiss(); getDialog().dismiss();
} }

View File

@ -1,74 +0,0 @@
package org.schabi.newpipe.fragments.detail;
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.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.List;
public class SpinnerToolbarAdapter extends BaseAdapter {
private final List<VideoStream> videoStreams;
private final boolean showIconNoAudio;
private final Context context;
public SpinnerToolbarAdapter(Context context, List<VideoStream> videoStreams, boolean showIconNoAudio) {
this.context = context;
this.videoStreams = videoStreams;
this.showIconNoAudio = showIconNoAudio;
}
@Override
public int getCount() {
return videoStreams.size();
}
@Override
public Object getItem(int position) {
return videoStreams.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.resolutions_spinner_item, parent, false);
}
ImageView woSoundIcon = convertView.findViewById(R.id.wo_sound_icon);
TextView text = convertView.findViewById(android.R.id.text1);
VideoStream item = (VideoStream) getItem(position);
text.setText(item.getFormat().getName() + " " + item.getResolution());
int visibility = !showIconNoAudio ? View.GONE
: item.isVideoOnly ? View.VISIBLE
: isDropdownItem ? View.INVISIBLE
: View.GONE;
woSoundIcon.setVisibility(visibility);
return convertView;
}
}

View File

@ -12,6 +12,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.FloatRange; import android.support.annotation.FloatRange;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
@ -61,6 +62,8 @@ 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.BackPressable; import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
@ -83,9 +86,9 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import icepick.State; import icepick.State;
import io.reactivex.Single; import io.reactivex.Single;
@ -107,8 +110,6 @@ public class VideoDetailFragment
// Amount of videos to show on start // Amount of videos to show on start
private static final int INITIAL_RELATED_VIDEOS = 8; private static final int INITIAL_RELATED_VIDEOS = 8;
private ArrayList<VideoStream> sortedStreamVideosList;
private InfoItemBuilder infoItemBuilder = null; private InfoItemBuilder infoItemBuilder = null;
private int updateFlags = 0; private int updateFlags = 0;
@ -120,18 +121,16 @@ public class VideoDetailFragment
private boolean showRelatedStreams; private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false; private boolean wasRelatedStreamsExpanded = false;
@State @State protected int serviceId = Constants.NO_SERVICE_ID;
protected int serviceId = Constants.NO_SERVICE_ID; @State protected String name;
@State @State protected String url;
protected String name;
@State
protected String url;
private StreamInfo currentInfo; private StreamInfo currentInfo;
private Disposable currentWorker; private Disposable currentWorker;
private CompositeDisposable disposables = new CompositeDisposable(); private CompositeDisposable disposables = new CompositeDisposable();
private int selectedVideoStream = -1; private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
@ -708,27 +707,25 @@ public class VideoDetailFragment
private void setupActionBar(final StreamInfo info) { private void setupActionBar(final StreamInfo info) {
if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]"); if (DEBUG) Log.d(TAG, "setupActionBarHandler() called with: info = [" + info + "]");
sortedStreamVideosList = new ArrayList<>(ListHelper.getSortedStreamVideosList(
activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false));
selectedVideoStream = ListHelper.getDefaultResolutionIndex(activity, sortedStreamVideosList);
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity) boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_video_player_key), false); .getBoolean(activity.getString(R.string.use_external_video_player_key), false);
spinnerToolbar.setAdapter(new SpinnerToolbarAdapter(activity, sortedStreamVideosList,
isExternalPlayerEnabled)); sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
spinnerToolbar.setSelection(selectedVideoStream); selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
final StreamItemAdapter<VideoStream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams), isExternalPlayerEnabled);
spinnerToolbar.setAdapter(streamsAdapter);
spinnerToolbar.setSelection(selectedVideoStreamIndex);
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedVideoStream = position; selectedVideoStreamIndex = position;
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
} }
}); });
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -940,8 +937,9 @@ public class VideoDetailFragment
this.autoPlayEnabled = autoplay; this.autoPlayEnabled = autoplay;
} }
@Nullable
private VideoStream getSelectedVideoStream() { private VideoStream getSelectedVideoStream() {
return sortedStreamVideosList.get(selectedVideoStream); return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
} }
private void prepareDescription(final String descriptionHtml) { private void prepareDescription(final String descriptionHtml) {
@ -1216,18 +1214,19 @@ public class VideoDetailFragment
public void openDownloadDialog() { public void openDownloadDialog() {
try { try {
DownloadDialog downloadDialog = DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
DownloadDialog.newInstance(currentInfo, downloadDialog.setVideoStreams(sortedVideoStreams);
sortedStreamVideosList, downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
selectedVideoStream); downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) { downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
Toast.makeText(activity, } catch (Exception e) {
R.string.could_not_setup_download_menu, Toast.makeText(activity,
Toast.LENGTH_LONG).show(); R.string.could_not_setup_download_menu,
e.printStackTrace(); Toast.LENGTH_LONG).show();
} e.printStackTrace();
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,196 @@
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<T extends Stream> extends BaseAdapter {
private final Context context;
private StreamSizeWrapper<T> streamsWrapper;
private final boolean showIconNoAudio;
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, boolean showIconNoAudio) {
this.context = context;
this.streamsWrapper = streamsWrapper;
this.showIconNoAudio = showIconNoAudio;
}
public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper) {
this(context, streamsWrapper, false);
}
public List<T> 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<T extends Stream> implements Serializable {
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(Collections.emptyList());
private final List<T> streamsList;
private long[] streamSizes;
public StreamSizeWrapper(List<T> 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 <X extends Stream> Single<Boolean> fetchSizeForWrapper(StreamSizeWrapper<X> streamsWrapper) {
final Callable<Boolean> 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<T> 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 <X extends Stream> StreamSizeWrapper<X> empty() {
//noinspection unchecked
return (StreamSizeWrapper<X>) EMPTY;
}
}
}

View File

@ -35,8 +35,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/file_name" android:layout_below="@+id/file_name"
android:layout_marginLeft="20dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="6dp"
android:layout_marginLeft="20dp"
android:gravity="left" android:gravity="left"
android:orientation="horizontal" android:orientation="horizontal"
tools:ignore="RtlHardcoded"> tools:ignore="RtlHardcoded">
@ -59,12 +59,12 @@
android:id="@+id/quality_spinner" android:id="@+id/quality_spinner"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="150dp"
android:layout_below="@+id/video_audio_group" android:layout_below="@+id/video_audio_group"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:layout_marginLeft="20dp" android:layout_marginLeft="20dp"
android:layout_marginRight="20dp" android:layout_marginRight="20dp"
tools:listitem="@layout/resolutions_spinner_item"/> android:minWidth="150dp"
tools:listitem="@layout/stream_quality_item"/>
<TextView <TextView
android:id="@+id/threads_text_view" android:id="@+id/threads_text_view"
@ -82,8 +82,8 @@
android:layout_below="@+id/threads_text_view" android:layout_below="@+id/threads_text_view"
android:layout_marginLeft="24dp" android:layout_marginLeft="24dp"
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:paddingBottom="12dp" android:orientation="horizontal"
android:orientation="horizontal"> android:paddingBottom="12dp">
<TextView <TextView
android:id="@+id/threads_count" android:id="@+id/threads_count"

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp">
<ImageView
android:id="@+id/wo_sound_icon"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="4dp"
android:scaleType="fitCenter"
android:src="?attr/volume_off"
tools:ignore="ContentDescription,RtlHardcoded"/>
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@+id/wo_sound_icon"
android:ellipsize="end"
android:gravity="left"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="14sp"
tools:ignore="RtlHardcoded"
tools:text="MPEG-4 1080p60"/>
</RelativeLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp">
<ImageView
android:id="@+id/wo_sound_icon"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="6dp"
android:scaleType="fitCenter"
android:src="?attr/volume_off"
tools:ignore="ContentDescription,RtlHardcoded"/>
<TextView
android:id="@+id/stream_format_name"
android:layout_width="wrap_content"
android:layout_height="22dp"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/wo_sound_icon"
android:ellipsize="end"
android:gravity="left|bottom"
android:maxLines="1"
android:paddingLeft="12dp"
android:paddingRight="18dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textSize="12sp"
tools:ignore="RtlHardcoded"
tools:text="MPEG-4"/>
<TextView
android:id="@+id/stream_quality"
android:layout_width="wrap_content"
android:layout_height="26dp"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/wo_sound_icon"
android:ellipsize="end"
android:gravity="left|top"
android:maxLines="1"
android:paddingLeft="12dp"
android:paddingRight="18dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="14sp"
tools:ignore="RtlHardcoded"
tools:text="1080p60"/>
<TextView
android:id="@+id/stream_size"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:gravity="right|center_vertical"
android:maxLines="1"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="RtlHardcoded"
tools:text="123.4 MB"
tools:visibility="visible"/>
</RelativeLayout>

View File

@ -180,6 +180,7 @@
<string name="invalid_file">File doesn\'t exist or insufficient permission to read or write to it</string> <string name="invalid_file">File doesn\'t exist or insufficient permission to read or write to it</string>
<string name="file_name_empty_error">File name cannot be empty</string> <string name="file_name_empty_error">File name cannot be empty</string>
<string name="error_occurred_detail">An error occurred: %1$s</string> <string name="error_occurred_detail">An error occurred: %1$s</string>
<string name="no_streams_available_download">No streams available to download</string>
<!-- error activity --> <!-- error activity -->
<string name="sorry_string">Sorry, that should not have happened.</string> <string name="sorry_string">Sorry, that should not have happened.</string>