Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Weblate 2018-04-09 00:13:42 +02:00
commit f7f86a2f62
16 changed files with 537 additions and 246 deletions

View File

@ -73,6 +73,31 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
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),
* but set the HTTP header field "Accept-Language" to the supplied string.

View File

@ -22,11 +22,13 @@ package org.schabi.newpipe;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
@ -54,6 +56,7 @@ import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper;
@ -94,10 +97,9 @@ public class MainActivity extends AppCompatActivity {
drawerItems = findViewById(R.id.navigation);
for(StreamingService s : NewPipe.getServices()) {
String title =
s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
MenuItem item = drawerItems.getMenu()
final String title = s.getServiceInfo().getName() +
(ServiceHelper.isBeta(s) ? " (beta)" : "");
final MenuItem item = drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), 0, title);
item.setIcon(ServiceHelper.getIcon(s.getServiceId()));
}
@ -233,6 +235,26 @@ public class MainActivity extends AppCompatActivity {
} else super.onBackPressed();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
for (int i: grantResults){
if (i == PackageManager.PERMISSION_DENIED){
return;
}
}
switch (requestCode) {
case PermissionHelper.DOWNLOADS_REQUEST_CODE:
NavigationHelper.openDownloads(this);
break;
case PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE:
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) {
((VideoDetailFragment) fragment).openDownloadDialog();
}
break;
}
}
/**
* Implement the following diagram behavior for the up button:
* <pre>
@ -313,6 +335,9 @@ public class MainActivity extends AppCompatActivity {
case R.id.action_about:
NavigationHelper.openAbout(this);
return true;
case R.id.action_history:
NavigationHelper.openHistory(this);
return true;
default:
return super.onOptionsItemSelected(item);
}

View File

@ -1,56 +1,61 @@
package org.schabi.newpipe.download;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
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.VideoStream;
import org.schabi.newpipe.fragments.detail.SpinnerToolbarAdapter;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper;
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 java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import icepick.Icepick;
import icepick.State;
import io.reactivex.disposables.CompositeDisposable;
import us.shandian.giga.service.DownloadManagerService;
public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String INFO_KEY = "info_key";
private static final String SORTED_VIDEOS_LIST_KEY = "sorted_videos_list_key";
private static final String SELECTED_VIDEO_KEY = "selected_video_key";
private static final String SELECTED_AUDIO_KEY = "selected_audio_key";
@State protected StreamInfo currentInfo;
@State protected StreamSizeWrapper<AudioStream> wrappedAudioStreams = StreamSizeWrapper.empty();
@State protected StreamSizeWrapper<VideoStream> wrappedVideoStreams = StreamSizeWrapper.empty();
@State protected int selectedVideoIndex = 0;
@State protected int selectedAudioIndex = 0;
private StreamInfo currentInfo;
private ArrayList<VideoStream> sortedStreamVideosList;
private int selectedVideoIndex;
private int selectedAudioIndex;
private StreamItemAdapter<AudioStream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream> videoStreamsAdapter;
private CompositeDisposable disposables = new CompositeDisposable();
private EditText nameEditText;
private Spinner streamsSpinner;
@ -58,17 +63,50 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
private TextView threadsCountTextView;
private SeekBar threadsSeekBar;
public static DownloadDialog newInstance(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
public static DownloadDialog newInstance(StreamInfo info) {
DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info, sortedStreamVideosList, selectedVideoIndex);
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
dialog.setInfo(info);
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;
}
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.sortedStreamVideosList = sortedStreamVideosList;
}
public void setSelectedAudioStream(int selectedAudioIndex) {
this.selectedAudioIndex = selectedAudioIndex;
}
/*//////////////////////////////////////////////////////////////////////////
@ -79,33 +117,26 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
if (!PermissionHelper.checkStoragePermissions(getActivity())) {
if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
getDialog().dismiss();
return;
}
if (savedInstanceState != null) {
Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
if (serial instanceof StreamInfo) currentInfo = (StreamInfo) serial;
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext()));
Icepick.restoreInstanceState(this, savedInstanceState);
serial = savedInstanceState.getSerializable(SORTED_VIDEOS_LIST_KEY);
if (serial instanceof ArrayList) { //noinspection unchecked
sortedStreamVideosList = (ArrayList<VideoStream>) serial;
}
selectedVideoIndex = savedInstanceState.getInt(SELECTED_VIDEO_KEY, 0);
selectedAudioIndex = savedInstanceState.getInt(SELECTED_AUDIO_KEY, 0);
}
this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, true);
this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams);
}
@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 + "]");
return inflater.inflate(R.layout.dialog_url, container);
return inflater.inflate(R.layout.download_dialog, container);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
nameEditText = view.findViewById(R.id.file_name);
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);
threadsSeekBar = view.findViewById(R.id.threads);
radioVideoAudioGroup = view.findViewById(R.id.video_audio_group);
radioVideoAudioGroup.setOnCheckedChangeListener(this);
initToolbar(view.<Toolbar>findViewById(R.id.toolbar));
checkDownloadOptions(view);
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
initToolbar(view.findViewById(R.id.toolbar));
setupDownloadOptions();
int def = 3;
threadsCountTextView.setText(String.valueOf(def));
@ -141,15 +172,35 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
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
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, currentInfo);
outState.putSerializable(SORTED_VIDEOS_LIST_KEY, sortedStreamVideosList);
outState.putInt(SELECTED_VIDEO_KEY, selectedVideoIndex);
outState.putInt(SELECTED_AUDIO_KEY, selectedAudioIndex);
Icepick.saveInstanceState(this, outState);
}
/*//////////////////////////////////////////////////////////////////////////
@ -161,39 +212,31 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
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.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDialog().dismiss();
}
});
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.okay) {
downloadSelected();
return true;
} else return false;
toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.okay) {
downloadSelected();
return true;
}
return false;
});
}
public void setupAudioSpinner(final List<AudioStream> audioStreams, Spinner spinner) {
String[] items = new String[audioStreams.size()];
for (int i = 0; i < audioStreams.size(); i++) {
AudioStream audioStream = audioStreams.get(i);
items[i] = audioStream.getFormat().getName() + " " + audioStream.getAverageBitrate() + "kbps";
}
private void setupAudioSpinner() {
if (getContext() == null) return;
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
spinner.setAdapter(itemAdapter);
spinner.setSelection(selectedAudioIndex);
streamsSpinner.setAdapter(audioStreamsAdapter);
streamsSpinner.setSelection(selectedAudioIndex);
setRadioButtonsState(true);
}
public void setupVideoSpinner(final List<VideoStream> videoStreams, Spinner spinner) {
spinner.setAdapter(new SpinnerToolbarAdapter(getContext(), videoStreams, true));
spinner.setSelection(selectedVideoIndex);
private void setupVideoSpinner() {
if (getContext() == null) return;
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 + "]");
switch (checkedId) {
case R.id.audio_button:
setupAudioSpinner(currentInfo.getAudioStreams(), streamsSpinner);
setupAudioSpinner();
break;
case R.id.video_button:
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
setupVideoSpinner();
break;
}
}
@ -238,37 +281,53 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
// Utils
//////////////////////////////////////////////////////////////////////////*/
protected void checkDownloadOptions(View view) {
RadioButton audioButton = view.findViewById(R.id.audio_button);
RadioButton videoButton = view.findViewById(R.id.video_button);
protected void setupDownloadOptions() {
setRadioButtonsState(false);
if (currentInfo.getAudioStreams() == null || currentInfo.getAudioStreams().size() == 0) {
audioButton.setVisibility(View.GONE);
final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button);
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);
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
videoButton.setVisibility(View.GONE);
setupVideoSpinner();
} else if (isAudioStreamsAvailable) {
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() {
String url, location;
Stream stream;
String location;
String fileName = nameEditText.getText().toString().trim();
if (fileName.isEmpty()) fileName = FilenameUtils.createFilename(getContext(), currentInfo.getName());
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
if (isAudio) {
url = currentInfo.getAudioStreams().get(selectedAudioIndex).getUrl();
stream = audioStreamsAdapter.getItem(selectedAudioIndex);
location = NewPipeSettings.getAudioDownloadPath(getContext());
fileName += "." + currentInfo.getAudioStreams().get(selectedAudioIndex).getFormat().getSuffix();
} else {
url = sortedStreamVideosList.get(selectedVideoIndex).getUrl();
stream = videoStreamsAdapter.getItem(selectedVideoIndex);
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);
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.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
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.fragments.BackPressable;
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.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoItemDialog;
@ -83,9 +86,9 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import icepick.State;
import io.reactivex.Single;
@ -107,8 +110,6 @@ public class VideoDetailFragment
// Amount of videos to show on start
private static final int INITIAL_RELATED_VIDEOS = 8;
private ArrayList<VideoStream> sortedStreamVideosList;
private InfoItemBuilder infoItemBuilder = null;
private int updateFlags = 0;
@ -120,18 +121,16 @@ public class VideoDetailFragment
private boolean showRelatedStreams;
private boolean wasRelatedStreamsExpanded = false;
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@State
protected String url;
@State protected int serviceId = Constants.NO_SERVICE_ID;
@State protected String name;
@State protected String url;
private StreamInfo currentInfo;
private Disposable currentWorker;
private CompositeDisposable disposables = new CompositeDisposable();
private int selectedVideoStream = -1;
private List<VideoStream> sortedVideoStreams;
private int selectedVideoStreamIndex = -1;
/*//////////////////////////////////////////////////////////////////////////
// Views
@ -355,21 +354,8 @@ public class VideoDetailFragment
}
break;
case R.id.detail_controls_download:
if (!PermissionHelper.checkStoragePermissions(activity)) {
return;
}
try {
DownloadDialog downloadDialog =
DownloadDialog.newInstance(currentInfo,
sortedStreamVideosList,
selectedVideoStream);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
if (PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
this.openDownloadDialog();
}
break;
case R.id.detail_uploader_root_layout:
@ -411,6 +397,9 @@ public class VideoDetailFragment
case R.id.detail_controls_popup:
openPopupPlayer(true);
break;
case R.id.detail_controls_download:
NavigationHelper.openDownloads(getActivity());
break;
}
return true;
@ -532,6 +521,7 @@ public class VideoDetailFragment
detailControlsPopup.setOnClickListener(this);
detailControlsAddToPlaylist.setOnClickListener(this);
detailControlsDownload.setOnClickListener(this);
detailControlsDownload.setOnLongClickListener(this);
relatedStreamExpandButton.setOnClickListener(this);
detailControlsBackground.setLongClickable(true);
@ -721,27 +711,25 @@ public class VideoDetailFragment
private void setupActionBar(final StreamInfo 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)
.getBoolean(activity.getString(R.string.use_external_video_player_key), false);
spinnerToolbar.setAdapter(new SpinnerToolbarAdapter(activity, sortedStreamVideosList,
isExternalPlayerEnabled));
spinnerToolbar.setSelection(selectedVideoStream);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
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() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedVideoStream = position;
selectedVideoStreamIndex = position;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
/*//////////////////////////////////////////////////////////////////////////
@ -953,8 +941,9 @@ public class VideoDetailFragment
this.autoPlayEnabled = autoplay;
}
@Nullable
private VideoStream getSelectedVideoStream() {
return sortedStreamVideosList.get(selectedVideoStream);
return sortedVideoStreams != null ? sortedVideoStreams.get(selectedVideoStreamIndex) : null;
}
private void prepareDescription(final String descriptionHtml) {
@ -1227,6 +1216,23 @@ public class VideoDetailFragment
}
}
public void openDownloadDialog() {
try {
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Stream Results
//////////////////////////////////////////////////////////////////////////*/

View File

@ -428,7 +428,7 @@ public class NavigationHelper {
}
public static boolean openDownloads(Activity activity) {
if (!PermissionHelper.checkStoragePermissions(activity)) {
if (!PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) {
return false;
}
Intent intent = new Intent(activity, DownloadActivity.class);

View File

@ -18,26 +18,26 @@ import android.widget.Toast;
import org.schabi.newpipe.R;
public class PermissionHelper {
public static final int PERMISSION_WRITE_STORAGE = 778;
public static final int PERMISSION_READ_STORAGE = 777;
public static final int DOWNLOAD_DIALOG_REQUEST_CODE = 778;
public static final int DOWNLOADS_REQUEST_CODE = 777;
public static boolean checkStoragePermissions(Activity activity) {
public static boolean checkStoragePermissions(Activity activity, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if(!checkReadStoragePermissions(activity)) return false;
if(!checkReadStoragePermissions(activity, requestCode)) return false;
}
return checkWriteStoragePermissions(activity);
return checkWriteStoragePermissions(activity, requestCode);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public static boolean checkReadStoragePermissions(Activity activity) {
public static boolean checkReadStoragePermissions(Activity activity, int requestCode) {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity,
new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_READ_STORAGE);
requestCode);
return false;
}
@ -45,7 +45,7 @@ public class PermissionHelper {
}
public static boolean checkWriteStoragePermissions(Activity activity) {
public static boolean checkWriteStoragePermissions(Activity activity, int requestCode) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
@ -63,7 +63,7 @@ public class PermissionHelper {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_WRITE_STORAGE);
requestCode);
// PERMISSION_WRITE_STORAGE is an
// app-defined int constant. The callback method gets the

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

@ -1,8 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_about"
android:orderInCategory="1000"
android:title="@string/action_about"/>
<item
android:id="@+id/action_show_downloads"
android:orderInCategory="980"
android:title="@string/downloads"
app:showAsAction="never"/>
<item
android:id="@+id/action_history"
android:orderInCategory="981"
android:title="@string/action_history"
app:showAsAction="never"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="990"
android:title="@string/settings"
app:showAsAction="never"/>
<item
android:id="@+id/action_about"
android:orderInCategory="1000"
android:title="@string/action_about"/>
</menu>

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="file_name_empty_error">File name cannot be empty</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 -->
<string name="sorry_string">Sorry, that should not have happened.</string>

View File

@ -0,0 +1 @@
NewPipe verwendet keine Bibliotheken des Google Frameworks oder der YouTube API. Es analysiert die Webseite, um die benötigten Informationen zu erlangen. Aus diesem Grund kann die App ohne die Google Services verwendet werden. Ebenso wird kein YouTube-Konto für NewPipe benötigt und es ist FLOSS.

View File

@ -0,0 +1 @@
Eine freie, leichtgewichtige YouTube App für Android