Merge remote-tracking branch 'origin/master'
|
@ -1,113 +1,135 @@
|
||||||
package org.schabi.newpipe.download;
|
package org.schabi.newpipe.download;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.IdRes;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
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.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
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.SeekBar;
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
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_info.AudioStream;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
|
||||||
|
import org.schabi.newpipe.extractor.stream_info.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.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
import org.schabi.newpipe.util.Utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import us.shandian.giga.service.DownloadManagerService;
|
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";
|
||||||
* Created by Christian Schabesberger on 21.09.15.
|
private static final String SORTED_VIDEOS_LIST_KEY = "sorted_videos_list_key";
|
||||||
* <p>
|
private static final String SELECTED_VIDEO_KEY = "selected_video_key";
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
private static final String SELECTED_AUDIO_KEY = "selected_audio_key";
|
||||||
* DownloadDialog.java is part of NewPipe.
|
|
||||||
* <p>
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
* <p>
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
* <p>
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DownloadDialog extends DialogFragment {
|
private StreamInfo currentInfo;
|
||||||
private static final String TAG = DialogFragment.class.getName();
|
private ArrayList<VideoStream> sortedStreamVideosList;
|
||||||
|
private int selectedVideoIndex;
|
||||||
|
private int selectedAudioIndex;
|
||||||
|
|
||||||
public static final String TITLE = "name";
|
private EditText nameEditText;
|
||||||
public static final String FILE_SUFFIX_AUDIO = "file_suffix_audio";
|
private Spinner streamsSpinner;
|
||||||
public static final String FILE_SUFFIX_VIDEO = "file_suffix_video";
|
private RadioGroup radioVideoAudioGroup;
|
||||||
public static final String AUDIO_URL = "audio_url";
|
private TextView threadsCountTextView;
|
||||||
public static final String VIDEO_URL = "video_url";
|
private SeekBar threadsSeekBar;
|
||||||
|
|
||||||
public DownloadDialog() {
|
public static DownloadDialog newInstance(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DownloadDialog newInstance(Bundle args) {
|
|
||||||
DownloadDialog dialog = new DownloadDialog();
|
DownloadDialog dialog = new DownloadDialog();
|
||||||
dialog.setArguments(args);
|
dialog.setInfo(info, sortedStreamVideosList, selectedVideoIndex);
|
||||||
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
|
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setInfo(StreamInfo info, ArrayList<VideoStream> sortedStreamVideosList, int selectedVideoIndex) {
|
||||||
|
this.currentInfo = info;
|
||||||
|
this.selectedVideoIndex = selectedVideoIndex;
|
||||||
|
this.sortedStreamVideosList = sortedStreamVideosList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// LifeCycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
Bundle savedInstanceState) {
|
super.onCreate(savedInstanceState);
|
||||||
|
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||||
|
if (!PermissionHelper.checkStoragePermissions(getActivity())) {
|
||||||
|
getDialog().dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(ContextCompat.checkSelfPermission(this.getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED)
|
if (savedInstanceState != null) {
|
||||||
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0);
|
Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
|
||||||
|
if (serial instanceof StreamInfo) currentInfo = (StreamInfo) serial;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(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.dialog_url, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
nameEditText = ((EditText) view.findViewById(R.id.file_name));
|
||||||
|
nameEditText.setText(createFileName(currentInfo.title));
|
||||||
|
selectedAudioIndex = Utils.getPreferredAudioFormat(getContext(), currentInfo.audio_streams);
|
||||||
|
|
||||||
Bundle arguments = getArguments();
|
streamsSpinner = (Spinner) view.findViewById(R.id.quality_spinner);
|
||||||
final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
|
streamsSpinner.setOnItemSelectedListener(this);
|
||||||
final EditText name = (EditText) view.findViewById(R.id.file_name);
|
|
||||||
final TextView tCount = (TextView) view.findViewById(R.id.threads_count);
|
|
||||||
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
|
|
||||||
|
|
||||||
toolbar.setTitle(R.string.download_dialog_title);
|
threadsCountTextView = (TextView) view.findViewById(R.id.threads_count);
|
||||||
toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
|
threadsSeekBar = (SeekBar) view.findViewById(R.id.threads);
|
||||||
toolbar.inflateMenu(R.menu.dialog_url);
|
radioVideoAudioGroup = (RadioGroup) view.findViewById(R.id.video_audio_group);
|
||||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
radioVideoAudioGroup.setOnCheckedChangeListener(this);
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
getDialog().dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
threads.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
initToolbar((Toolbar) view.findViewById(R.id.toolbar));
|
||||||
|
checkDownloadOptions(view);
|
||||||
|
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
|
||||||
|
|
||||||
|
int def = 3;
|
||||||
|
threadsCountTextView.setText(String.valueOf(def));
|
||||||
|
threadsSeekBar.setProgress(def - 1);
|
||||||
|
threadsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
|
||||||
tCount.setText(String.valueOf(progress + 1));
|
threadsCountTextView.setText(String.valueOf(progress + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,41 +142,111 @@ public class DownloadDialog extends DialogFragment {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
checkDownloadOptions();
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
//int def = mPrefs.getInt("threads", 4);
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
int def = 3;
|
// Inits
|
||||||
threads.setProgress(def - 1);
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
tCount.setText(String.valueOf(def));
|
|
||||||
|
|
||||||
name.setText(createFileName(arguments.getString(TITLE)));
|
|
||||||
|
|
||||||
|
private void initToolbar(Toolbar toolbar) {
|
||||||
|
if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||||
|
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.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.okay) {
|
if (item.getItemId() == R.id.okay) {
|
||||||
download();
|
downloadSelected();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkDownloadOptions() {
|
public void setupAudioSpinner(final List<AudioStream> audioStreams, Spinner spinner) {
|
||||||
View view = getView();
|
String[] items = new String[audioStreams.size()];
|
||||||
Bundle arguments = getArguments();
|
for (int i = 0; i < audioStreams.size(); i++) {
|
||||||
|
AudioStream audioStream = audioStreams.get(i);
|
||||||
|
items[i] = MediaFormat.getNameById(audioStream.format) + " " + audioStream.avgBitrate + "kbps";
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, items);
|
||||||
|
spinner.setAdapter(itemAdapter);
|
||||||
|
spinner.setSelection(selectedAudioIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupVideoSpinner(final List<VideoStream> videoStreams, Spinner spinner) {
|
||||||
|
spinner.setAdapter(new SpinnerToolbarAdapter(getContext(), videoStreams, true));
|
||||||
|
spinner.setSelection(selectedVideoIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Radio group Video&Audio options - Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
|
||||||
|
if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]");
|
||||||
|
switch (checkedId) {
|
||||||
|
case R.id.audio_button:
|
||||||
|
setupAudioSpinner(currentInfo.audio_streams, streamsSpinner);
|
||||||
|
break;
|
||||||
|
case R.id.video_button:
|
||||||
|
setupVideoSpinner(sortedStreamVideosList, streamsSpinner);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Streams Spinner Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (DEBUG) Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
|
||||||
|
switch (radioVideoAudioGroup.getCheckedRadioButtonId()) {
|
||||||
|
case R.id.audio_button:
|
||||||
|
selectedAudioIndex = position;
|
||||||
|
break;
|
||||||
|
case R.id.video_button:
|
||||||
|
selectedVideoIndex = position;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
protected void checkDownloadOptions(View view) {
|
||||||
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
|
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
|
||||||
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
|
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
|
||||||
|
|
||||||
if (arguments.getString(AUDIO_URL) == null) {
|
if (currentInfo.audio_streams == null || currentInfo.audio_streams.size() == 0) {
|
||||||
audioButton.setVisibility(View.GONE);
|
audioButton.setVisibility(View.GONE);
|
||||||
videoButton.setChecked(true);
|
videoButton.setChecked(true);
|
||||||
} else if (arguments.getString(VIDEO_URL) == null) {
|
} else if (sortedStreamVideosList == null || sortedStreamVideosList.size() == 0) {
|
||||||
videoButton.setVisibility(View.GONE);
|
videoButton.setVisibility(View.GONE);
|
||||||
audioButton.setChecked(true);
|
audioButton.setChecked(true);
|
||||||
}
|
}
|
||||||
|
@ -164,14 +256,14 @@ public class DownloadDialog extends DialogFragment {
|
||||||
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
|
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
|
||||||
* This should fix some of the "cannot download" problems.
|
* This should fix some of the "cannot download" problems.
|
||||||
*/
|
*/
|
||||||
private String createFileName(String fName) {
|
private String createFileName(String fileName) {
|
||||||
// from http://eng-przemelek.blogspot.de/2009/07/how-to-create-valid-file-name.html
|
// from http://eng-przemelek.blogspot.de/2009/07/how-to-create-valid-file-name.html
|
||||||
|
|
||||||
List<String> forbiddenCharsPatterns = new ArrayList<>();
|
List<String> forbiddenCharsPatterns = new ArrayList<>();
|
||||||
forbiddenCharsPatterns.add("[:]+"); // Mac OS, but it looks that also Windows XP
|
forbiddenCharsPatterns.add("[:]+"); // Mac OS, but it looks that also Windows XP
|
||||||
forbiddenCharsPatterns.add("[\\*\"/\\\\\\[\\]\\:\\;\\|\\=\\,]+"); // Windows
|
forbiddenCharsPatterns.add("[\\*\"/\\\\\\[\\]\\:\\;\\|\\=\\,]+"); // Windows
|
||||||
forbiddenCharsPatterns.add("[^\\w\\d\\.]+"); // last chance... only latin letters and digits
|
forbiddenCharsPatterns.add("[^\\w\\d\\.]+"); // last chance... only latin letters and digits
|
||||||
String nameToTest = fName;
|
String nameToTest = fileName;
|
||||||
for (String pattern : forbiddenCharsPatterns) {
|
for (String pattern : forbiddenCharsPatterns) {
|
||||||
nameToTest = nameToTest.replaceAll(pattern, "_");
|
nameToTest = nameToTest.replaceAll(pattern, "_");
|
||||||
}
|
}
|
||||||
|
@ -179,55 +271,20 @@ public class DownloadDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//download audio, video or both?
|
private void downloadSelected() {
|
||||||
private void download() {
|
String url, location;
|
||||||
View view = getView();
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
final EditText name = (EditText) view.findViewById(R.id.file_name);
|
|
||||||
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
|
|
||||||
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
|
|
||||||
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
|
|
||||||
|
|
||||||
String fName = name.getText().toString().trim();
|
String fileName = nameEditText.getText().toString().trim();
|
||||||
|
if (fileName.isEmpty()) fileName = createFileName(currentInfo.title);
|
||||||
|
|
||||||
boolean isAudio = audioButton.isChecked();
|
boolean isAudio = radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button;
|
||||||
String url, location, filename;
|
url = isAudio ? currentInfo.audio_streams.get(selectedAudioIndex).url : sortedStreamVideosList.get(selectedVideoIndex).url;
|
||||||
if (isAudio) {
|
location = isAudio ? NewPipeSettings.getAudioDownloadPath(getContext()) : NewPipeSettings.getVideoDownloadPath(getContext());
|
||||||
url = arguments.getString(AUDIO_URL);
|
|
||||||
location = NewPipeSettings.getAudioDownloadPath(getContext());
|
|
||||||
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
|
|
||||||
} else {
|
|
||||||
url = arguments.getString(VIDEO_URL);
|
|
||||||
location = NewPipeSettings.getVideoDownloadPath(getContext());
|
|
||||||
filename = fName + arguments.getString(FILE_SUFFIX_VIDEO);
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadManagerService.startMission(getContext(), url, location, filename, isAudio,
|
if (isAudio) fileName += "." + MediaFormat.getSuffixById(currentInfo.audio_streams.get(selectedAudioIndex).format);
|
||||||
threads.getProgress() + 1);
|
else fileName += "." + MediaFormat.getSuffixById(sortedStreamVideosList.get(selectedVideoIndex).format);
|
||||||
|
|
||||||
|
DownloadManagerService.startMission(getContext(), url, location, fileName, isAudio, threadsSeekBar.getProgress() + 1);
|
||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(String url, String title,
|
|
||||||
String fileSuffix, File downloadDir, Context context) {
|
|
||||||
|
|
||||||
File saveFilePath = new File(downloadDir, createFileName(title) + fileSuffix);
|
|
||||||
|
|
||||||
long id = 0;
|
|
||||||
|
|
||||||
Log.i(TAG, "Started downloading '" + url +
|
|
||||||
"' => '" + saveFilePath + "' #" + id);
|
|
||||||
|
|
||||||
if (App.isUsingTor()) {
|
|
||||||
//if using Tor, do not use DownloadManager because the proxy cannot be set
|
|
||||||
//we'll see later
|
|
||||||
FileDownloader.downloadFile(getContext(), url, saveFilePath, title);
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(getContext(), DownloadActivity.class);
|
|
||||||
intent.setAction(DownloadActivity.INTENT_DOWNLOAD);
|
|
||||||
intent.setData(Uri.parse(url));
|
|
||||||
intent.putExtra("fileName", createFileName(title) + fileSuffix);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
package org.schabi.newpipe.download;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
import info.guardianproject.netcipher.NetCipher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Christian Schabesberger on 14.08.15.
|
|
||||||
*
|
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
|
||||||
* FileDownloader.java is part of NewPipe.
|
|
||||||
*
|
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: FOR HEAVEN SAKE !!! DO NOT SIMPLY USE ASYNCTASK. MAKE THIS A PROPER SERVICE !!!
|
|
||||||
public class FileDownloader extends AsyncTask<Void, Integer, Void> {
|
|
||||||
public static final String TAG = "FileDownloader";
|
|
||||||
|
|
||||||
|
|
||||||
private NotificationManager nm;
|
|
||||||
private NotificationCompat.Builder builder;
|
|
||||||
private int notifyId = 0x1234;
|
|
||||||
private int fileSize = 0xffffffff;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String fileURL;
|
|
||||||
private final File saveFilePath;
|
|
||||||
private final String title;
|
|
||||||
|
|
||||||
private final String debugContext;
|
|
||||||
|
|
||||||
public FileDownloader(Context context, String fileURL, File saveFilePath, String title) {
|
|
||||||
this.context = context;
|
|
||||||
this.fileURL = fileURL;
|
|
||||||
this.saveFilePath = saveFilePath;
|
|
||||||
this.title = title;
|
|
||||||
|
|
||||||
this.debugContext = "'" + fileURL +
|
|
||||||
"' => '" + saveFilePath + "'";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a file from a URL in the background using an {@link AsyncTask}.
|
|
||||||
*
|
|
||||||
* @param fileURL HTTP URL of the file to be downloaded
|
|
||||||
* @param saveFilePath path of the directory to save the file
|
|
||||||
* @param title
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static void downloadFile(final Context context, final String fileURL, final File saveFilePath, String title) {
|
|
||||||
new FileDownloader(context, fileURL, saveFilePath, title).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** AsyncTask impl: executed in gui thread */
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
Drawable icon = context.getResources().getDrawable(R.mipmap.ic_launcher);
|
|
||||||
builder = new NotificationCompat.Builder(context)
|
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
|
||||||
.setLargeIcon(((BitmapDrawable) icon).getBitmap())
|
|
||||||
.setContentTitle(saveFilePath.getName())
|
|
||||||
.setContentText(saveFilePath.getAbsolutePath())
|
|
||||||
.setProgress(fileSize, 0, false);
|
|
||||||
nm.notify(notifyId, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** AsyncTask impl: executed in background thread does the download */
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
HttpsURLConnection con = null;
|
|
||||||
InputStream inputStream = null;
|
|
||||||
FileOutputStream outputStream = null;
|
|
||||||
try {
|
|
||||||
con = NetCipher.getHttpsURLConnection(fileURL);
|
|
||||||
int responseCode = con.getResponseCode();
|
|
||||||
|
|
||||||
// always check HTTP response code first
|
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
|
||||||
fileSize = con.getContentLength();
|
|
||||||
inputStream = new BufferedInputStream(con.getInputStream());
|
|
||||||
outputStream = new FileOutputStream(saveFilePath);
|
|
||||||
|
|
||||||
int bufferSize = 8192;
|
|
||||||
int downloaded = 0;
|
|
||||||
|
|
||||||
int bytesRead = -1;
|
|
||||||
byte[] buffer = new byte[bufferSize];
|
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead);
|
|
||||||
downloaded += bytesRead;
|
|
||||||
if (downloaded % 50000 < bufferSize) {
|
|
||||||
publishProgress(downloaded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
publishProgress(bufferSize);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, "No file to download. Server replied HTTP code: " + responseCode);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "No file to download. Server replied HTTP code: ", e);
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (outputStream != null) {
|
|
||||||
outputStream.close();
|
|
||||||
}
|
|
||||||
if (inputStream != null) {
|
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
if (con != null) {
|
|
||||||
con.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onProgressUpdate(Integer... progress) {
|
|
||||||
builder.setProgress(fileSize, progress[0], false);
|
|
||||||
nm.notify(notifyId, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
super.onPostExecute(aVoid);
|
|
||||||
nm.cancel(notifyId);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,11 +9,9 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
|
||||||
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
import org.schabi.newpipe.extractor.stream_info.VideoStream;
|
||||||
import org.schabi.newpipe.util.Utils;
|
import org.schabi.newpipe.util.Utils;
|
||||||
|
|
||||||
|
@ -71,16 +69,9 @@ class ActionBarHandler {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
selectedVideoStream = 0;
|
selectedVideoStream = 0;
|
||||||
|
|
||||||
// this array will be shown in the dropdown menu for selecting the stream/resolution.
|
|
||||||
String[] itemArray = new String[videoStreams.size()];
|
|
||||||
for (int i = 0; i < videoStreams.size(); i++) {
|
|
||||||
VideoStream item = videoStreams.get(i);
|
|
||||||
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
|
|
||||||
}
|
|
||||||
|
|
||||||
int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
|
int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
|
||||||
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(), android.R.layout.simple_spinner_dropdown_item, itemArray);
|
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(activity.getString(R.string.use_external_video_player_key), false);
|
||||||
toolbarSpinner.setAdapter(itemAdapter);
|
toolbarSpinner.setAdapter(new SpinnerToolbarAdapter(activity, videoStreams, isExternalPlayerEnabled));
|
||||||
toolbarSpinner.setSelection(defaultResolutionIndex);
|
toolbarSpinner.setSelection(defaultResolutionIndex);
|
||||||
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
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_info.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 = (ImageView) convertView.findViewById(R.id.wo_sound_icon);
|
||||||
|
TextView text = (TextView) convertView.findViewById(android.R.id.text1);
|
||||||
|
VideoStream item = (VideoStream) getItem(position);
|
||||||
|
text.setText(MediaFormat.getNameById(item.format) + " " + item.resolution);
|
||||||
|
|
||||||
|
int visibility = !showIconNoAudio ? View.GONE
|
||||||
|
: item.isVideoOnly ? View.VISIBLE
|
||||||
|
: isDropdownItem ? View.INVISIBLE
|
||||||
|
: View.GONE;
|
||||||
|
woSoundIcon.setVisibility(visibility);
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -330,7 +330,8 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
updateFlags |= RELATED_STREAMS_UPDATE_FLAG;
|
updateFlags |= RELATED_STREAMS_UPDATE_FLAG;
|
||||||
} else if (key.equals(getString(R.string.preferred_video_format_key))
|
} else if (key.equals(getString(R.string.preferred_video_format_key))
|
||||||
|| key.equals(getString(R.string.default_resolution_key))
|
|| key.equals(getString(R.string.default_resolution_key))
|
||||||
|| key.equals(getString(R.string.show_higher_resolutions_key))) {
|
|| key.equals(getString(R.string.show_higher_resolutions_key))
|
||||||
|
|| key.equals(getString(R.string.use_external_video_player_key))) {
|
||||||
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
|
updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG;
|
||||||
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
|
} else if (key.equals(getString(R.string.show_play_with_kodi_key))) {
|
||||||
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
|
updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG;
|
||||||
|
@ -682,33 +683,10 @@ public class VideoDetailFragment extends BaseFragment implements StreamExtractor
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Bundle args = new Bundle();
|
DownloadDialog downloadDialog = DownloadDialog.newInstance(info, sortedStreamVideosList, selectedStreamId);
|
||||||
|
|
||||||
// Sometimes it may be that some information is not available due to changes fo the
|
|
||||||
// website which was crawled. Then the ui has to understand this and act right.
|
|
||||||
|
|
||||||
if (info.audio_streams != null) {
|
|
||||||
AudioStream audioStream =
|
|
||||||
info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
|
|
||||||
|
|
||||||
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
|
|
||||||
args.putString(DownloadDialog.AUDIO_URL, audioStream.url);
|
|
||||||
args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortedStreamVideosList != null) {
|
|
||||||
VideoStream selectedStreamItem = sortedStreamVideosList.get(selectedStreamId);
|
|
||||||
String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format);
|
|
||||||
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
|
|
||||||
args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
args.putString(DownloadDialog.TITLE, info.title);
|
|
||||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(args);
|
|
||||||
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(activity,
|
Toast.makeText(activity, R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
|
||||||
R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 407 B |
After Width: | Height: | Size: 433 B |
After Width: | Height: | Size: 279 B |
After Width: | Height: | Size: 301 B |
After Width: | Height: | Size: 493 B |
After Width: | Height: | Size: 532 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 753 B |
After Width: | Height: | Size: 924 B |
After Width: | Height: | Size: 1005 B |
|
@ -32,12 +32,11 @@
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
android:id="@+id/video_audio_group"
|
android:id="@+id/video_audio_group"
|
||||||
android:layout_width="match_parent"
|
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_marginBottom="12dp"
|
|
||||||
android:layout_marginLeft="20dp"
|
android:layout_marginLeft="20dp"
|
||||||
android:layout_marginRight="20dp"
|
android:layout_marginBottom="6dp"
|
||||||
android:gravity="left"
|
android:gravity="left"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
tools:ignore="RtlHardcoded">
|
tools:ignore="RtlHardcoded">
|
||||||
|
@ -56,11 +55,22 @@
|
||||||
android:text="@string/audio"/>
|
android:text="@string/audio"/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
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"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/threads_text_view"
|
android:id="@+id/threads_text_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/video_audio_group"
|
android:layout_below="@+id/quality_spinner"
|
||||||
android:layout_marginBottom="6dp"
|
android:layout_marginBottom="6dp"
|
||||||
android:layout_marginLeft="24dp"
|
android:layout_marginLeft="24dp"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?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="16sp"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="MPEG-4 1080p60 very long res"/>
|
||||||
|
</RelativeLayout>
|
|
@ -13,5 +13,6 @@
|
||||||
<attr name="popup" format="reference"/>
|
<attr name="popup" format="reference"/>
|
||||||
<attr name="expand" format="reference"/>
|
<attr name="expand" format="reference"/>
|
||||||
<attr name="collapse" format="reference"/>
|
<attr name="collapse" format="reference"/>
|
||||||
|
<attr name="volume_off" format="reference"/>
|
||||||
<attr name="separatorColor" format="color"/>
|
<attr name="separatorColor" format="color"/>
|
||||||
</resources>
|
</resources>
|
|
@ -24,6 +24,7 @@
|
||||||
<string name="screen_rotation">rotation</string>
|
<string name="screen_rotation">rotation</string>
|
||||||
<string name="settings_activity_title">Settings</string>
|
<string name="settings_activity_title">Settings</string>
|
||||||
<string name="use_external_video_player_title">Use external video player</string>
|
<string name="use_external_video_player_title">Use external video player</string>
|
||||||
|
<string name="use_external_video_player_summary">Some resolutions will NOT have audio when this option is enabled</string>
|
||||||
<string name="use_external_audio_player_title">Use external audio player</string>
|
<string name="use_external_audio_player_title">Use external audio player</string>
|
||||||
<string name="popup_mode_share_menu_title">NewPipe Popup mode</string>
|
<string name="popup_mode_share_menu_title">NewPipe Popup mode</string>
|
||||||
<string name="rss_button_title" translatable="false">RSS</string>
|
<string name="rss_button_title" translatable="false">RSS</string>
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<item name="popup">@drawable/ic_picture_in_picture_black_24dp</item>
|
<item name="popup">@drawable/ic_picture_in_picture_black_24dp</item>
|
||||||
<item name="expand">@drawable/ic_expand_more_black_24dp</item>
|
<item name="expand">@drawable/ic_expand_more_black_24dp</item>
|
||||||
<item name="collapse">@drawable/ic_expand_less_black_24dp</item>
|
<item name="collapse">@drawable/ic_expand_less_black_24dp</item>
|
||||||
|
<item name="volume_off">@drawable/ic_volume_off_black_24dp</item>
|
||||||
<item name="separatorColor">@color/light_separator_color</item>
|
<item name="separatorColor">@color/light_separator_color</item>
|
||||||
<item name="colorControlHighlight">@color/light_ripple_color</item>
|
<item name="colorControlHighlight">@color/light_ripple_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
<item name="popup">@drawable/ic_picture_in_picture_white_24dp</item>
|
<item name="popup">@drawable/ic_picture_in_picture_white_24dp</item>
|
||||||
<item name="expand">@drawable/ic_expand_more_white_24dp</item>
|
<item name="expand">@drawable/ic_expand_more_white_24dp</item>
|
||||||
<item name="collapse">@drawable/ic_expand_less_white_24dp</item>
|
<item name="collapse">@drawable/ic_expand_less_white_24dp</item>
|
||||||
|
<item name="volume_off">@drawable/ic_volume_off_white_24dp</item>
|
||||||
<item name="separatorColor">@color/dark_separator_color</item>
|
<item name="separatorColor">@color/dark_separator_color</item>
|
||||||
<item name="colorControlHighlight">@color/dark_ripple_color</item>
|
<item name="colorControlHighlight">@color/dark_ripple_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="@string/use_external_video_player_key"
|
android:key="@string/use_external_video_player_key"
|
||||||
android:title="@string/use_external_video_player_title"
|
android:title="@string/use_external_video_player_title"
|
||||||
|
android:summary="@string/use_external_video_player_summary"
|
||||||
android:defaultValue="false"/>
|
android:defaultValue="false"/>
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.1'
|
classpath 'com.android.tools.build:gradle:2.3.2'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|