merged upstream/dev, changes for peertube support

This commit is contained in:
Ritvik Saraf 2019-03-10 01:02:25 +05:30
commit d90b1ca5be
187 changed files with 6831 additions and 3208 deletions

View file

@ -1,5 +1,6 @@
package org.schabi.newpipe;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@ -65,6 +66,7 @@ import io.reactivex.plugins.RxJavaPlugins;
public class App extends Application {
protected static final String TAG = App.class.toString();
private RefWatcher refWatcher;
private static App app;
@SuppressWarnings("unchecked")
private static final Class<? extends ReportSenderFactory>[]
@ -88,6 +90,8 @@ public class App extends Application {
}
refWatcher = installLeakCanary();
app = this;
// Initialize settings first because others inits can use its values
SettingsActivity.initSettings(this);
@ -100,6 +104,9 @@ public class App extends Application {
ImageLoader.getInstance().init(getImageLoaderConfigurations(10, 50));
configureRxJavaErrorHandler();
// Check for new version
new CheckForNewAppVersionTask().execute();
}
protected Downloader getDownloader() {
@ -211,6 +218,31 @@ public class App extends Application {
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);
setUpUpdateNotificationChannel(importance);
}
/**
* Set up notification channel for app update.
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(int importance) {
final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
= getString(R.string.app_update_notification_channel_name);
final String appUpdateDescription
= getString(R.string.app_update_notification_channel_description);
NotificationChannel appUpdateChannel
= new NotificationChannel(appUpdateId, appUpdateName, importance);
appUpdateChannel.setDescription(appUpdateDescription);
NotificationManager appUpdateNotificationManager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
}
@Nullable
@ -226,4 +258,8 @@ public class App extends Application {
protected boolean isDisposedRxExceptionsReported() {
return false;
}
public static App getApp() {
return app;
}
}

View file

@ -0,0 +1,242 @@
package org.schabi.newpipe;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import org.json.JSONException;
import org.json.JSONObject;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
* If there is a newer version we show a notification, informing the user. On tapping
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private static final Application app = App.getApp();
private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json";
private static final int timeoutPeriod = 30;
private SharedPreferences mPrefs;
private OkHttpClient client;
@Override
protected void onPreExecute() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/ disabled update checking
// and if the current apk is a github one or not.
if (!mPrefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk()) {
this.cancel(true);
}
}
@Override
protected String doInBackground(Void... voids) {
if(isCancelled() || !isConnected()) return null;
// Make a network request to get latest NewPipe data.
if (client == null) {
client = new OkHttpClient
.Builder()
.readTimeout(timeoutPeriod, TimeUnit.SECONDS)
.build();
}
Request request = new Request.Builder()
.url(newPipeApiUrl)
.build();
try {
Response response = client.newCall(request).execute();
return response.body().string();
} catch (IOException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"app update API fail", R.string.app_ui_crash));
}
return null;
}
@Override
protected void onPostExecute(String response) {
// Parse the json from the response.
if (response != null) {
try {
JSONObject mainObject = new JSONObject(response);
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
JSONObject githubObject = flavoursObject.getJSONObject("github");
JSONObject githubStableObject = githubObject.getJSONObject("stable");
String versionName = githubStableObject.getString("version");
String versionCode = githubStableObject.getString("version_code");
String apkLocationUrl = githubStableObject.getString("apk");
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
} catch (JSONException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"could not parse app update JSON data", R.string.app_ui_crash));
}
}
}
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
* @param versionName
* @param apkLocationUrl
*/
private void compareAppVersionAndShowNotification(String versionName,
String apkLocationUrl,
String versionCode) {
int NOTIFICATION_ID = 2000;
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
// A pending intent to open the apk location url in the browser.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
PendingIntent pendingIntent
= PendingIntent.getActivity(app, 0, intent, 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(app, app.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
.setContentText(app.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(app);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}
/**
* Method to get the apk's SHA1 key.
* https://stackoverflow.com/questions/9293019/get-certificate-fingerprint-from-android-app#22506133
*/
private static String getCertificateSHA1Fingerprint() {
PackageManager pm = app.getPackageManager();
String packageName = app.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
X509Certificate c = null;
try {
cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException ex) {
ErrorActivity.reportError(app, ex, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}
String hexString = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException ex1) {
ErrorActivity.reportError(app, ex1, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
} catch (CertificateEncodingException ex2) {
ErrorActivity.reportError(app, ex2, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}
return hexString;
}
private static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}
public static boolean isGithubApk() {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
}
private boolean isConnected() {
ConnectivityManager cm =
(ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
}
}

View file

@ -50,7 +50,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.fragments.BackPressable;
@ -303,6 +302,7 @@ public class MainActivity extends AppCompatActivity {
drawerItems.getMenu()
.add(R.id.menu_services_group, s.getServiceId(), ORDER, title)
.setIcon(ServiceHelper.getIcon(s.getServiceId()));
}
drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true);
}
@ -367,6 +367,7 @@ public class MainActivity extends AppCompatActivity {
String selectedServiceName = NewPipe.getService(
ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName();
headerServiceView.setText(selectedServiceName);
headerServiceView.post(() -> headerServiceView.setSelected(true));
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}

View file

@ -36,12 +36,12 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
@ -81,10 +81,13 @@ public class RouterActivity extends AppCompatActivity {
protected int selectedPreviously = -1;
protected String currentUrl;
protected boolean internalRoute = false;
protected final CompositeDisposable disposables = new CompositeDisposable();
private boolean selectionIsDownload = false;
public static final String internalRouteKey = "internalRoute";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -94,11 +97,13 @@ public class RouterActivity extends AppCompatActivity {
currentUrl = getUrl(getIntent());
if (TextUtils.isEmpty(currentUrl)) {
Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show();
handleText();
finish();
}
}
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@ -112,7 +117,7 @@ public class RouterActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
handleUrl(currentUrl);
}
@ -353,6 +358,15 @@ public class RouterActivity extends AppCompatActivity {
positiveButton.setEnabled(state);
}
private void handleText(){
String searchString = getIntent().getStringExtra(Intent.EXTRA_TEXT);
int serviceId = getIntent().getIntExtra(Constants.KEY_SERVICE_ID, 0);
Intent intent = new Intent(getThemeWrapperContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
NavigationHelper.openSearch(getThemeWrapperContext(),serviceId,searchString);
}
private void handleChoice(final String selectedChoiceKey) {
final List<String> validChoicesList = Arrays.asList(getResources().getStringArray(R.array.preferred_open_action_values_list));
if (validChoicesList.contains(selectedChoiceKey)) {
@ -383,8 +397,10 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
if(!internalRoute){
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
startActivity(intent);
finish();

View file

@ -457,7 +457,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
break;
case R.id.subtitle_button:
stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video go together
location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video files go together
kind = 's';
break;
default:
@ -477,7 +477,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
final String finalFileName = fileName;
DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> {
// should be safe run the following code without "getActivity().runOnUiThread()"
if (listed) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.download_dialog_title)
@ -511,11 +510,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck
if (secondaryStream != null) {
secondaryStreamUrl = secondaryStream.getStream().getUrl();
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_DASH_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER;
psArgs = null;
long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream);
// set nearLength, only, if both sizes are fetched or known. this probably does not work on weak internet connections
// set nearLength, only, if both sizes are fetched or known. this probably does not work on slow networks
if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) {
nearLength = secondaryStream.getSizeInBytes() + videoSize;
}

View file

@ -1,38 +0,0 @@
package org.schabi.newpipe.download;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
}
@Override
protected void onStart() {
super.onStart();
new AlertDialog.Builder(this)
.setTitle(R.string.download_to_sdcard_error_title)
.setMessage(R.string.download_to_sdcard_error_message)
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
NewPipeSettings.resetDownloadFolders(this);
finish();
})
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
dialogInterface.dismiss();
finish();
})
.create()
.show();
}
}

View file

@ -0,0 +1,17 @@
package org.schabi.newpipe.fragments;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.schabi.newpipe.BaseFragment;
import org.schabi.newpipe.R;
public class EmptyFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_empty, container, false);
}
}

View file

@ -52,6 +52,7 @@ import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@ -64,6 +65,7 @@ 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.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
@ -100,6 +102,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment
@ -177,6 +180,7 @@ public class VideoDetailFragment
private static final String COMMENTS_TAB_TAG = "COMMENTS";
private static final String RELATED_TAB_TAG = "NEXT VIDEO";
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
private AppBarLayout appBarLayout;
private ViewPager viewPager;
@ -365,7 +369,8 @@ public class VideoDetailFragment
}
break;
case R.id.detail_controls_download:
if (PermissionHelper.checkStoragePermissions(activity, PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
if (PermissionHelper.checkStoragePermissions(activity,
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
this.openDownloadDialog();
}
break;
@ -623,13 +628,13 @@ public class VideoDetailFragment
switch (id) {
case R.id.menu_item_share: {
if (currentInfo != null) {
shareUrl(currentInfo.getName(), currentInfo.getUrl());
shareUrl(currentInfo.getName(), currentInfo.getOriginalUrl());
}
return true;
}
case R.id.menu_item_openInBrowser: {
if (currentInfo != null) {
openUrlInBrowser(currentInfo.getUrl());
openUrlInBrowser(currentInfo.getOriginalUrl());
}
return true;
}
@ -667,10 +672,16 @@ public class VideoDetailFragment
boolean isExternalPlayerEnabled = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_video_player_key), false);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(activity, info.getVideoStreams(), info.getVideoOnlyStreams(), false);
sortedVideoStreams = ListHelper.getSortedStreamVideosList(
activity,
info.getVideoStreams(),
info.getVideoOnlyStreams(),
false);
selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(activity, sortedVideoStreams);
final StreamItemAdapter<VideoStream, Stream> streamsAdapter = new StreamItemAdapter<>(activity, new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled);
final StreamItemAdapter<VideoStream, Stream> streamsAdapter =
new StreamItemAdapter<>(activity,
new StreamSizeWrapper<>(sortedVideoStreams, activity), isExternalPlayerEnabled);
spinnerToolbar.setAdapter(streamsAdapter);
spinnerToolbar.setSelection(selectedVideoStreamIndex);
spinnerToolbar.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@ -695,17 +706,17 @@ public class VideoDetailFragment
*/
protected final LinkedList<StackItem> stack = new LinkedList<>();
public void clearHistory() {
stack.clear();
}
public void pushToStack(int serviceId, String videoUrl, String name) {
if (DEBUG) {
Log.d(TAG, "pushToStack() called with: serviceId = [" + serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "]");
Log.d(TAG, "pushToStack() called with: serviceId = ["
+ serviceId + "], videoUrl = [" + videoUrl + "], name = [" + name + "]");
}
if (stack.size() > 0 && stack.peek().getServiceId() == serviceId && stack.peek().getUrl().equals(videoUrl)) {
Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = [" + serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]");
if (stack.size() > 0
&& stack.peek().getServiceId() == serviceId
&& stack.peek().getUrl().equals(videoUrl)) {
Log.d(TAG, "pushToStack() called with: serviceId == peek.serviceId = ["
+ serviceId + "], videoUrl == peek.getUrl = [" + videoUrl + "]");
return;
} else {
Log.d(TAG, "pushToStack() wasn't equal");
@ -736,7 +747,11 @@ public class VideoDetailFragment
// Get stack item from the new top
StackItem peek = stack.peek();
selectAndLoadVideo(peek.getServiceId(), peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
selectAndLoadVideo(peek.getServiceId(),
peek.getUrl(),
!TextUtils.isEmpty(peek.getTitle())
? peek.getTitle()
: "");
return true;
}
@ -756,10 +771,10 @@ public class VideoDetailFragment
}
public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) {
if (DEBUG)
Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = ["
+ info + "], scrollToTop = [" + scrollToTop + "]");
setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName());
setInitialData(info.getServiceId(), info.getUrl(), info.getName());
pushToStack(serviceId, url, name);
showLoading();
initTabs();
@ -811,6 +826,10 @@ public class VideoDetailFragment
pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG);
}
if(pageAdapter.getCount() == 0){
pageAdapter.addFragment(new EmptyFragment(), EMPTY_TAB_TAG);
}
pageAdapter.notifyDataSetUpdate();
if(pageAdapter.getCount() < 2){
@ -822,7 +841,10 @@ public class VideoDetailFragment
private boolean shouldShowComments() {
try {
return showComments && NewPipe.getService(serviceId).isCommentsSupported();
return showComments && NewPipe.getService(serviceId)
.getServiceInfo()
.getMediaCapabilities()
.contains(COMMENTS);
} catch (ExtractionException e) {
return false;
}
@ -1038,7 +1060,7 @@ public class VideoDetailFragment
}
}
pushToStack(serviceId, url, name);
//pushToStack(serviceId, url, name);
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
@ -1089,11 +1111,13 @@ public class VideoDetailFragment
if (info.getDuration() > 0) {
detailDurationView.setText(Localization.getDurationString(info.getDuration()));
detailDurationView.setBackgroundColor(ContextCompat.getColor(activity, R.color.duration_background_color));
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.duration_background_color));
animateView(detailDurationView, true, 100);
} else if (info.getStreamType() == StreamType.LIVE_STREAM) {
detailDurationView.setText(R.string.duration_live);
detailDurationView.setBackgroundColor(ContextCompat.getColor(activity, R.color.live_duration_background_color));
detailDurationView.setBackgroundColor(
ContextCompat.getColor(activity, R.color.live_duration_background_color));
animateView(detailDurationView, true, 100);
} else {
detailDurationView.setVisibility(View.GONE);
@ -1150,20 +1174,28 @@ public class VideoDetailFragment
public void openDownloadDialog() {
try {
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setSubtitleStreams(currentInfo.getSubtitles());
try {
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
downloadDialog.setVideoStreams(sortedVideoStreams);
downloadDialog.setAudioStreams(currentInfo.getAudioStreams());
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.setSubtitleStreams(currentInfo.getSubtitles());
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
Toast.makeText(activity,
R.string.could_not_setup_download_menu,
Toast.LENGTH_LONG).show();
e.printStackTrace();
}
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
ServiceList.all()
.get(currentInfo
.getServiceId())
.getServiceInfo()
.getName(), "",
R.string.could_not_setup_download_menu);
ErrorActivity.reportError(getActivity(),
e,
getActivity().getClass(),
getActivity().findViewById(android.R.id.content), info);
}
}
/*//////////////////////////////////////////////////////////////////////////

View file

@ -31,6 +31,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
@ -233,10 +234,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
openRssFeed();
break;
case R.id.menu_item_openInBrowser:
openUrlInBrowser(url);
openUrlInBrowser(currentInfo.getOriginalUrl());
break;
case R.id.menu_item_share:
shareUrl(name, url);
shareUrl(name, currentInfo.getOriginalUrl());
break;
default:
return super.onOptionsItemSelected(item);
@ -487,12 +488,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
protected boolean onError(Throwable exception) {
if (super.onError(exception)) return true;
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception,
UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId),
url,
errorId);
if(exception instanceof ContentNotAvailableException){
showError(getString(R.string.content_not_available), false);
}else{
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
onUnrecoverableError(exception,
UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(serviceId),
url,
errorId);
}
return true;
}

View file

@ -305,6 +305,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()));
headerBackgroundButton.setOnClickListener(view ->
NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()));
headerPopupButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue());
return true;
});
headerBackgroundButton.setOnLongClickListener(view -> {
NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue());
return true;
});
}
private PlayQueue getPlayQueue() {

View file

@ -12,6 +12,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.TooltipCompat;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@ -39,15 +40,15 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.util.FireTvUtils;
import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.list.BaseListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper;
@ -72,8 +73,8 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags;
import static java.util.Arrays.asList;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class SearchFragment
@ -104,8 +105,13 @@ public class SearchFragment
// this three represet the current search query
@State
protected String searchString;
/**
* No content filter should add like contentfilter = all
* be aware of this when implementing an extractor.
*/
@State
protected String[] contentFilter;
protected String[] contentFilter = new String[0];
@State
protected String sortFilter;
@ -292,7 +298,23 @@ public class SearchFragment
suggestionsPanel = rootView.findViewById(R.id.suggestions_panel);
suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list);
suggestionsRecyclerView.setAdapter(suggestionListAdapter);
suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity));
new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return getSuggestionMovementFlags(recyclerView, viewHolder);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder viewHolder1) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
onSuggestionItemSwiped(viewHolder, i);
}
}).attachToRecyclerView(suggestionsRecyclerView);
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
@ -335,7 +357,7 @@ public class SearchFragment
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
search(!TextUtils.isEmpty(searchString)
? searchString
: searchEditText.getText().toString(), new String[0], "");
: searchEditText.getText().toString(), this.contentFilter, "");
} else {
if (searchEditText != null) {
searchEditText.setText("");
@ -449,6 +471,9 @@ public class SearchFragment
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
showSuggestionsPanel();
}
if(FireTvUtils.isFireTv()){
showKeyboardSearch();
}
});
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
@ -499,7 +524,9 @@ public class SearchFragment
if (DEBUG) {
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
}
if (event != null
if(actionId == EditorInfo.IME_ACTION_PREVIOUS){
hideKeyboardSearch();
} else if (event != null
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
search(searchEditText.getText().toString(), new String[0], "");
@ -541,7 +568,7 @@ public class SearchFragment
if (searchEditText.requestFocus()) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
}
}
@ -551,8 +578,7 @@ public class SearchFragment
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
searchEditText.clearFocus();
}
@ -736,6 +762,7 @@ public class SearchFragment
@Override
protected void loadMoreItems() {
if(nextPageUrl == null || nextPageUrl.isEmpty()) return;
isLoading.set(true);
showListFooter(true);
if (searchDisposable != null) searchDisposable.dispose();
@ -890,4 +917,28 @@ public class SearchFragment
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// Suggestion item touch helper
//////////////////////////////////////////////////////////////////////////*/
public int getSuggestionMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
final int position = viewHolder.getAdapterPosition();
final SuggestionItem item = suggestionListAdapter.getItem(position);
return item.fromHistory ? makeMovementFlags(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
}
public void onSuggestionItemSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
final int position = viewHolder.getAdapterPosition();
final String query = suggestionListAdapter.getItem(position).query;
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> suggestionPublisher
.onNext(searchEditText.getText().toString()),
throwable -> showSnackBarError(throwable,
UserAction.DELETE_FROM_HISTORY, "none",
"Deleting item failed", R.string.general_error));
disposables.add(onDelete);
}
}

View file

@ -75,7 +75,7 @@ public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAd
});
}
private SuggestionItem getItem(int position) {
SuggestionItem getItem(int position) {
return items.get(position);
}

View file

@ -1,7 +1,7 @@
package org.schabi.newpipe.info_list.holder;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.text.util.Linkify;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@ -11,9 +11,13 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.hdodenhof.circleimageview.CircleImageView;
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
@ -26,6 +30,25 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
private static final int commentDefaultLines = 2;
private static final int commentExpandedLines = 1000;
private String commentText;
private String streamUrl;
private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)");
private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() {
@Override
public String transformUrl(Matcher match, String url) {
int timestamp = 0;
String hours = match.group(1);
String minutes = match.group(2);
String seconds = match.group(3);
if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600);
if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60);
if(seconds != null) timestamp += (Integer.parseInt(seconds));
return streamUrl + url.replace(match.group(0), "&t=" + String.valueOf(timestamp));
}
};
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
super(infoItemBuilder, layoutId, parent);
@ -66,34 +89,59 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
}
});
// ellipsize if not already ellipsized
if (null == itemContentView.getEllipsize()) {
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
itemContentView.setMaxLines(commentDefaultLines);
streamUrl = item.getUrl();
itemContentView.setMaxLines(commentDefaultLines);
commentText = item.getCommentText();
itemContentView.setText(commentText);
linkify();
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
if(itemContentView.getLineCount() == 0){
itemContentView.post(() -> ellipsize());
}else{
ellipsize();
}
itemContentView.setText(item.getCommentText());
if (null != item.getLikeCount()) {
itemLikesCountView.setText(String.valueOf(item.getLikeCount()));
}
itemPublishedTime.setText(item.getPublishedTime());
itemView.setOnClickListener(view -> {
toggleEllipsize(item.getCommentText());
toggleEllipsize();
if (itemBuilder.getOnCommentsSelectedListener() != null) {
itemBuilder.getOnCommentsSelectedListener().selected(item);
}
});
}
private void toggleEllipsize(String text) {
// toggle ellipsize
if (null == itemContentView.getEllipsize()) {
itemContentView.setEllipsize(TextUtils.TruncateAt.END);
itemContentView.setMaxLines(commentDefaultLines);
} else {
itemContentView.setEllipsize(null);
itemContentView.setMaxLines(commentExpandedLines);
private void ellipsize() {
if (itemContentView.getLineCount() > commentDefaultLines){
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "...";
itemContentView.setText(newVal);
linkify();
}
}
private void toggleEllipsize() {
if (itemContentView.getText().toString().equals(commentText)) {
ellipsize();
} else {
expand();
}
}
private void expand() {
itemContentView.setMaxLines(commentExpandedLines);
itemContentView.setText(commentText);
linkify();
}
private void linkify(){
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
itemContentView.setMovementMethod(null);
}
}

View file

@ -8,7 +8,11 @@ import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@ -21,11 +25,13 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.ThemeHelper;
@ -104,6 +110,12 @@ public class StatisticsPlaylistFragment
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_history, menu);
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Views
///////////////////////////////////////////////////////////////////////////
@ -155,6 +167,53 @@ public class StatisticsPlaylistFragment
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_history_clear:
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_view_history_alert)
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
.setPositiveButton(R.string.delete, ((dialog, which) -> {
final Disposable onDelete = recordManager.deleteWholeStreamHistory()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> Toast.makeText(getContext(),
R.string.view_history_deleted,
Toast.LENGTH_SHORT).show(),
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete view history",
R.string.general_error)));
final Disposable onClearOrphans = recordManager.removeOrphanedRecords()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
howManyDeleted -> {},
throwable -> ErrorActivity.reportError(getContext(),
throwable,
SettingsActivity.class, null,
ErrorActivity.ErrorInfo.make(
UserAction.DELETE_FROM_HISTORY,
"none",
"Delete search history",
R.string.general_error)));
disposables.add(onClearOrphans);
disposables.add(onDelete);
}))
.create()
.show();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
///////////////////////////////////////////////////////////////////////////
// Fragment LifeCycle - Loading
///////////////////////////////////////////////////////////////////////////

View file

@ -147,11 +147,16 @@ public class SubscriptionService {
}
private boolean isSubscriptionUpToDate(final ChannelInfo info, final SubscriptionEntity entity) {
return info.getUrl().equals(entity.getUrl()) &&
return equalsAndNotNull(info.getUrl(), entity.getUrl()) &&
info.getServiceId() == entity.getServiceId() &&
info.getName().equals(entity.getName()) &&
info.getAvatarUrl().equals(entity.getAvatarUrl()) &&
info.getDescription().equals(entity.getDescription()) &&
equalsAndNotNull(info.getAvatarUrl(), entity.getAvatarUrl()) &&
equalsAndNotNull(info.getDescription(), entity.getDescription()) &&
info.getSubscriberCount() == entity.getSubscriberCount();
}
private boolean equalsAndNotNull(final Object o1, final Object o2) {
return (o1 != null && o2 != null)
&& o1.equals(o2);
}
}

View file

@ -0,0 +1,30 @@
package org.schabi.newpipe.player;
import android.content.Context;
import android.content.ContextWrapper;
/**
* Fixes a leak caused by AudioManager using an Activity context.
* Tracked at https://android-review.googlesource.com/#/c/140481/1 and
* https://github.com/square/leakcanary/issues/205
* Source:
* https://gist.github.com/jankovd/891d96f476f7a9ce24e2
*/
public class AudioServiceLeakFix extends ContextWrapper {
AudioServiceLeakFix(Context base) {
super(base);
}
public static ContextWrapper preventLeakOf(Context base) {
return new AudioServiceLeakFix(base);
}
@Override
public Object getSystemService(String name) {
if (Context.AUDIO_SERVICE.equals(name)) {
return getApplicationContext().getSystemService(name);
}
return super.getSystemService(name);
}
}

View file

@ -130,6 +130,11 @@ public final class BackgroundPlayer extends Service {
onClose();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@ -270,6 +275,8 @@ public final class BackgroundPlayer extends Service {
protected class BasePlayerImpl extends BasePlayer {
@NonNull final private AudioPlaybackResolver resolver;
private int cachedDuration;
private String cachedDurationString;
BasePlayerImpl(Context context) {
super(context);
@ -344,10 +351,14 @@ public final class BackgroundPlayer extends Service {
if (!shouldUpdateOnProgress) return;
resetNotification();
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*Oreo*/) updateNotificationThumbnail();
if (bigNotRemoteView != null) {
if(cachedDuration != duration) {
cachedDuration = duration;
cachedDurationString = getTimeString(duration);
}
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + cachedDurationString);
}
if (notRemoteView != null) {
notRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);

View file

@ -241,6 +241,11 @@ public final class MainVideoPlayer extends AppCompatActivity
isBackPressed = false;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(newBase));
}
/*//////////////////////////////////////////////////////////////////////////
// State Saving
//////////////////////////////////////////////////////////////////////////*/

View file

@ -181,6 +181,11 @@ public final class PopupVideoPlayer extends Service {
closePopup();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@ -626,6 +631,7 @@ public final class PopupVideoPlayer extends Service {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
if (playerImpl == null) return;
// rebuild notification here since remote view does not release bitmaps,
// causing memory leaks
resetNotification();

View file

@ -131,7 +131,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener,
private void onAudioFocusLossCanDuck() {
Log.d(TAG, "onAudioFocusLossCanDuck() called");
// Set the volume to 1/10 on ducking
animateAudio(player.getVolume(), DUCK_AUDIO_TO);
player.setVolume(DUCK_AUDIO_TO);
}
private void animateAudio(final float from, final float to) {

View file

@ -70,10 +70,10 @@ public class PlayerHelper {
////////////////////////////////////////////////////////////////////////////
public static String getTimeString(int milliSeconds) {
long seconds = (milliSeconds % 60000L) / 1000L;
long minutes = (milliSeconds % 3600000L) / 60000L;
long hours = (milliSeconds % 86400000L) / 3600000L;
long days = (milliSeconds % (86400000L * 7L)) / 86400000L;
int seconds = (milliSeconds % 60000) / 1000;
int minutes = (milliSeconds % 3600000) / 60000;
int hours = (milliSeconds % 86400000) / 3600000;
int days = (milliSeconds % (86400000 * 7)) / 86400000;
stringBuilder.setLength(0);
return days > 0 ? stringFormatter.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds).toString()

View file

@ -1,7 +1,9 @@
package org.schabi.newpipe.player.playback;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.mediasession.MediaSessionCallback;
@ -54,6 +56,12 @@ public class BasePlayerMediaSession implements MediaSessionCallback {
.setTitle(item.getTitle())
.setSubtitle(item.getUploader());
// set additional metadata for A2DP/AVRCP
Bundle additionalMetadata = new Bundle();
additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader());
additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration());
descriptionBuilder.setExtras(additionalMetadata);
final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl());
if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri);

View file

@ -94,15 +94,17 @@ public class VideoPlaybackResolver implements PlaybackResolver {
// Below are auxiliary media sources
// Create subtitle sources
for (final SubtitlesStream subtitle : info.getSubtitles()) {
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
if (mimeType == null) continue;
if(info.getSubtitles() != null) {
for (final SubtitlesStream subtitle : info.getSubtitles()) {
final String mimeType = PlayerHelper.subtitleMimeTypesOf(subtitle.getFormat());
if (mimeType == null) continue;
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
mediaSources.add(textSource);
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
.createMediaSource(Uri.parse(subtitle.getURL()), textFormat, TIME_UNSET);
mediaSources.add(textSource);
}
}
if (mediaSources.size() == 1) {

View file

@ -9,6 +9,7 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.preference.EditTextPreference;
import android.support.v7.preference.Preference;
import android.util.Log;
import android.widget.Toast;
@ -16,14 +17,15 @@ import android.widget.Toast;
import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ZipHelper;
import java.io.BufferedOutputStream;
@ -42,6 +44,9 @@ import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class ContentSettingsFragment extends BasePreferenceFragment {
@ -58,6 +63,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private String thumbnailLoadToggleKey;
private CompositeDisposable disposables = new CompositeDisposable();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -135,30 +142,39 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
peerTubeInstance.setSummary(sharedPreferences.getString(getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl()));
peerTubeInstance.setOnPreferenceChangeListener((Preference p, Object newInstance) -> {
EditTextPreference pEt = (EditTextPreference) p;
String url = (String) newInstance;
if(!url.startsWith("https://")){
if (!url.startsWith("https://")) {
Toast.makeText(getActivity(), "instance url should start with https://",
Toast.LENGTH_SHORT).show();
return false;
}else{
boolean shouldUpdate = Single.fromCallable(() -> {
} else {
pEt.setSummary("fetching instance details..");
Disposable disposable = Single.fromCallable(() -> {
ServiceList.PeerTube.setInstance(url);
return true;
}).subscribeOn(Schedulers.io())
.onErrorReturnItem(false)
.blockingGet();
if (shouldUpdate) {
p.setSummary(url);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
editor.putString(getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
editor.putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply();
}else{
Toast.makeText(getActivity(), "unable to update instance",
Toast.LENGTH_SHORT).show();
}
return shouldUpdate;
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result) {
pEt.setSummary(url);
pEt.setText(url);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(App.getApp().getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
editor.putString(App.getApp().getString(R.string.current_service_key), ServiceList.PeerTube.getServiceInfo().getName()).apply();
NavigationHelper.openMainActivity(App.getApp());
} else {
pEt.setSummary(ServiceList.PeerTube.getBaseUrl());
Toast.makeText(getActivity(), "unable to update instance",
Toast.LENGTH_SHORT).show();
}
}, error -> {
pEt.setSummary(ServiceList.PeerTube.getBaseUrl());
Toast.makeText(getActivity(), "unable to update instance",
Toast.LENGTH_SHORT).show();
});
disposables.add(disposable);
return false;
}
});
}
@ -218,7 +234,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
} finally {
try {
if (output != null) {
output.flush();
@ -242,7 +258,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
} finally {
try {
zipFile.close();
} catch (Exception ignored){}
} catch (Exception ignored) {
}
}
try {
@ -265,7 +282,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
}
//If settings file exist, ask if it should be imported.
if(ZipHelper.extractFileFromZip(filePath, newpipe_settings.getPath(), "newpipe.settings")) {
if (ZipHelper.extractFileFromZip(filePath, newpipe_settings.getPath(), "newpipe.settings")) {
AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle(R.string.import_settings);
@ -320,7 +337,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
} finally {
try {
if (input != null) {
input.close();
@ -343,4 +360,5 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
"none", "", R.string.app_ui_crash));
}
}

View file

@ -4,6 +4,7 @@ import android.os.Bundle;
import android.support.v7.preference.Preference;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.R;
public class MainSettingsFragment extends BasePreferenceFragment {
@ -13,6 +14,13 @@ public class MainSettingsFragment extends BasePreferenceFragment {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.main_settings);
if (!CheckForNewAppVersionTask.isGithubApk()) {
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
getPreferenceScreen().removePreference(update);
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
}
if (!DEBUG) {
final Preference debug = findPreference(getString(R.string.debug_pref_screen_key));
getPreferenceScreen().removePreference(debug);

View file

@ -0,0 +1,33 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.preference.Preference;
import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.R;
public class UpdateSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
}
private Preference.OnPreferenceChangeListener updatePreferenceChange
= (preference, newValue) -> {
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key),
(boolean) newValue).apply();
return true;
};
}

View file

@ -0,0 +1,128 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
private static final Pattern timestampPattern = Pattern.compile(".*&t=(\\d+)");
@Override
public boolean onTouch(View v, MotionEvent event) {
if(!(v instanceof TextView)){
return false;
}
TextView widget = (TextView) v;
Object text = widget.getText();
if (text instanceof Spanned) {
Spannable buffer = (Spannable) text;
int action = event.getAction();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off,
ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
boolean handled = false;
if(link[0] instanceof URLSpan){
handled = handleUrl(v.getContext(), (URLSpan) link[0]);
}
if(!handled) link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
}
}
return false;
}
private boolean handleUrl(Context context, URLSpan urlSpan) {
String url = urlSpan.getURL();
StreamingService service;
StreamingService.LinkType linkType;
try {
service = NewPipe.getServiceByUrl(url);
linkType = service.getLinkTypeByUrl(url);
} catch (ExtractionException e) {
return false;
}
if(linkType == StreamingService.LinkType.NONE){
return false;
}
Matcher matcher = timestampPattern.matcher(url);
if(linkType == StreamingService.LinkType.STREAM && matcher.matches()){
int seconds = Integer.parseInt(matcher.group(1));
return playOnPopup(context, url, service, seconds);
}else{
NavigationHelper.openRouterActivity(context, url);
return true;
}
}
private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) {
LinkHandlerFactory factory = service.getStreamLHFactory();
String cleanUrl = null;
try {
cleanUrl = factory.getUrl(factory.getId(url));
} catch (ParsingException e) {
return false;
}
Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info);
((StreamInfo) info).setStartPosition(seconds);
NavigationHelper.enqueueOnPopupPlayer(context, playQueue, true);
});
return true;
}
}

View file

@ -32,6 +32,7 @@ import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.SuggestionExtractor;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
@ -47,6 +48,7 @@ import org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Collections;
import java.util.List;
import io.reactivex.Maybe;
@ -96,10 +98,13 @@ public final class ExtractorHelper {
public static Single<List<String>> suggestionsFor(final int serviceId,
final String query) {
checkServiceId(serviceId);
return Single.fromCallable(() ->
NewPipe.getService(serviceId)
.getSuggestionExtractor()
.suggestionList(query));
return Single.fromCallable(() -> {
SuggestionExtractor extractor = NewPipe.getService(serviceId)
.getSuggestionExtractor();
return extractor != null
? extractor.suggestionList(query)
: Collections.emptyList();
});
}
public static Single<StreamInfo> getStreamInfo(final int serviceId,

View file

@ -0,0 +1,10 @@
package org.schabi.newpipe.util;
import org.schabi.newpipe.App;
public class FireTvUtils {
public static boolean isFireTv(){
final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
return App.getApp().getPackageManager().hasSystemFeature(AMAZON_FEATURE_FIRE_TV);
}
}

View file

@ -31,6 +31,8 @@ public class KioskTranslator {
return c.getString(R.string.top_50);
case "New & hot":
return c.getString(R.string.new_and_hot);
case "conferences":
return c.getString(R.string.conferences);
default:
return kioskId;
}
@ -48,6 +50,8 @@ public class KioskTranslator {
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
case "Recently added":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
case "conferences":
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
default:
return 0;
}

View file

@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.RouterActivity;
import org.schabi.newpipe.about.AboutActivity;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.NewPipe;
@ -34,11 +35,11 @@ import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.local.feed.FeedFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
import org.schabi.newpipe.local.feed.FeedFragment;
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
@ -422,6 +423,13 @@ public class NavigationHelper {
context.startActivity(mIntent);
}
public static void openRouterActivity(Context context, String url) {
Intent mIntent = new Intent(context, RouterActivity.class);
mIntent.setData(Uri.parse(url));
mIntent.putExtra(RouterActivity.internalRouteKey, true);
context.startActivity(mIntent);
}
public static void openAbout(Context context) {
Intent intent = new Intent(context, AboutActivity.class);
context.startActivity(intent);

View file

@ -36,7 +36,6 @@ public class SecondaryStreamHelper<T extends Stream> {
* @return selected audio stream or null if a candidate was not found
*/
public static AudioStream getAudioStreamFor(@NonNull List<AudioStream> audioStreams, @NonNull VideoStream videoStream) {
// TODO: check if m4v and m4a selected streams are DASH compliant
switch (videoStream.getFormat()) {
case WEBM:
case MPEG_4:

View file

@ -11,9 +11,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
@ -30,6 +28,8 @@ public class ServiceHelper {
return R.drawable.place_holder_cloud;
case 2:
return R.drawable.place_holder_peertube;
case 3:
return R.drawable.place_holder_gadse;
default:
return R.drawable.place_holder_circle;
}
@ -43,6 +43,8 @@ public class ServiceHelper {
case "playlists": return c.getString(R.string.playlists);
case "tracks": return c.getString(R.string.tracks);
case "users": return c.getString(R.string.users);
case "conferences" : return c.getString(R.string.conferences);
case "events" : return c.getString(R.string.events);
default: return filter;
}
}
@ -134,7 +136,7 @@ public class ServiceHelper {
}
public static void initService(Context context, int serviceId) {
if(serviceId == 2){
if(serviceId == ServiceList.PeerTube.getServiceId()){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String peerTubeInstanceUrl = sharedPreferences.getString(context.getString(R.string.peertube_instance_url_key), ServiceList.PeerTube.getBaseUrl());
String peerTubeInstanceName = sharedPreferences.getString(context.getString(R.string.peertube_instance_name_key), ServiceList.PeerTube.getServiceInfo().getName());

View file

@ -28,7 +28,8 @@ 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}.
* A list adapter for a list of {@link Stream streams},
* currently supporting {@link VideoStream}, {@link AudioStream} and {@link SubtitlesStream}
*/
public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
private final Context context;
@ -110,7 +111,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
}
}
} else if (stream instanceof AudioStream) {
qualityString = ((AudioStream) stream).getAverageBitrate() + "kbps";
AudioStream audioStream = ((AudioStream) stream);
qualityString = audioStream.getAverageBitrate() > 0
? audioStream.getAverageBitrate() + "kbps"
: audioStream.getFormat().getName();
} else if (stream instanceof SubtitlesStream) {
qualityString = ((SubtitlesStream) stream).getDisplayLanguageName();
if (((SubtitlesStream) stream).isAutoGenerated()) {
@ -154,8 +158,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
private final long[] streamSizes;
private final String unknownSize;
public StreamSizeWrapper(List<T> streamsList, Context context) {
this.streamsList = streamsList;
public StreamSizeWrapper(List<T> sL, Context context) {
this.streamsList = sL != null
? sL
: Collections.emptyList();
this.streamSizes = new long[streamsList.size()];
this.unknownSize = context == null ? "--.-" : context.getString(R.string.unknown_content);

View file

@ -30,6 +30,7 @@ import android.view.ContextThemeWrapper;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@ -136,15 +137,16 @@ public class ThemeHelper {
else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme";
else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme";
switch (serviceId) {
case 2:
//service name for peertube depends on the instance
themeName += ".PeerTube";
break;
default:
themeName += "." + service.getServiceInfo().getName();
if(serviceId == ServiceList.PeerTube.getServiceId()){
//service name for peertube depends on the instance
themeName += ".PeerTube";
}else{
themeName += "." + service.getServiceInfo().getName();
}
int resourceId = context.getResources().getIdentifier(themeName, "style", context.getPackageName());
int resourceId = context
.getResources()
.getIdentifier(themeName, "style", context.getPackageName());
if (resourceId > 0) {
return resourceId;