Merge remote-tracking branch 'newpipe/dev' into rebase

This commit is contained in:
Alexander-- 2020-04-11 08:30:40 +06:59
commit ac5571a363
41 changed files with 1701 additions and 1505 deletions

View File

@ -7,38 +7,40 @@ assignees: ''
--- ---
<!-- <!--
Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. If this is your first bug report, read the following information before proceeding:
Use this template to notify us if you found a bug. Please note, we only support the latest version of NewPipe. In order to check your app version, open the left drawer and click on "About". If you don't have the latest version, upgrade to it and reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is where you can get it.
To make it easier for us to help you please enter detailed information below.
Please note, we only support the latest version of NewPipe and the master branch. Make sure you have that version installed. If you don't, upgrade & reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is the go-to place to get this version. In order to check your app version, open the left drawer and click on "About".
P.S.: Our contribution guidelines might be a nice document to read before you fill out the report :) You can find it at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md P.S.: Our contribution guidelines might be a nice document to read before you fill out the report :) You can find it at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md
-->
### Version
<!-- Which version are you using? -->
-
To make it easier for us to help you please enter detailed information in the template we have provided below. If a section isn't relevant, just delete it, though it would be helpful to still provide as much detail as possible.
-->
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the preview). -->
### Version
<!-- Which version are you using? Hopefully the latest! We just told you that above! -->
-
### Steps to reproduce the bug ### Steps to reproduce the bug
<!-- If you can't reproduce it, please try to give as many details as possible on how you think you got to the bug. --> <!--
Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Press on '....' 2. Press on '....'
3. Swipe down to '....' 3. Swipe down to '....'
-->
<!-- If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. -->
### Expected behavior ### Expected behavior
Tell us what you expected to happen. <!-- Tell us what you expect to happen. -->
### Actual behaviour ### Actual behaviour
Tell us what happens instead. <!-- Tell us what happens instead. -->
### Screenshots/Screen records ### Screenshots/Screen recordings
If applicable, add screenshots or a screen recording to help explain your problem. GitHub should support uploading them directly in the issue field. If your file is too big, feel free to paste a link from an image/video hoster here instead. <!-- If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the issue text box. If your file is too big for Github to accept, feel free to paste a link from an image/video hoster here instead. -->
### Logs ### Logs
If your bug includes a crash, please head over to the [incredible bugreport to markdown converter](https://teamnewpipe.github.io/CrashReportToMarkdown/). Copy the result. Paste it here: <!-- If your bug includes a crash (where you're shown the Error Report page with a bunch of info), copy it to the clipboard (there is a share button for this), head over to our bug report to markdown converter at https://teamnewpipe.github.io/CrashReportToMarkdown/ and paste it. Copy the converted text (it is MUCH easier to read this way) from there and paste it here: -->
<!-- That's right, here! --> <!-- That's right, here! -->

View File

@ -4,10 +4,10 @@
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p> <p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
<p align="center"> <p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a> <a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a> <a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a> <a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg"></a> <a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a> <a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a> <a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p> </p>

View File

@ -136,7 +136,7 @@ dependencies {
exclude module: 'support-annotations' exclude module: 'support-annotations'
}) })
implementation 'com.github.TeamNewPipe:NewPipeExtractor:69e0624e3' implementation 'com.github.TeamNewPipe:NewPipeExtractor:a5155fb562ca99ca4a9c8caa2fd60f2f0a305eb0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.23.0' testImplementation 'org.mockito:mockito-core:2.23.0'

View File

@ -27,7 +27,7 @@ import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
@ -173,7 +173,7 @@ public class App extends Application {
private boolean isThrowableIgnored(@NonNull final Throwable throwable) { private boolean isThrowableIgnored(@NonNull final Throwable throwable) {
// Don't crash the application over a simple network problem // Don't crash the application over a simple network problem
return ExtractorHelper.hasAssignableCauseThrowable(throwable, return ExceptionUtils.hasAssignableCause(throwable,
// network api cancellation // network api cancellation
IOException.class, SocketException.class, IOException.class, SocketException.class,
// blocking code disposed // blocking code disposed
@ -182,7 +182,7 @@ public class App extends Application {
private boolean isThrowableCritical(@NonNull final Throwable throwable) { private boolean isThrowableCritical(@NonNull final Throwable throwable) {
// Though these exceptions cannot be ignored // Though these exceptions cannot be ignored
return ExtractorHelper.hasAssignableCauseThrowable(throwable, return ExceptionUtils.hasAssignableCause(throwable,
NullPointerException.class, IllegalArgumentException.class, // bug in app NullPointerException.class, IllegalArgumentException.class, // bug in app
OnErrorNotImplementedException.class, MissingBackpressureException.class, OnErrorNotImplementedException.class, MissingBackpressureException.class,
IllegalStateException.class); // bug in operator IllegalStateException.class); // bug in operator

View File

@ -33,6 +33,7 @@ import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -307,6 +308,20 @@ public class MainActivity extends AppCompatActivity {
headerServiceView = hView.findViewById(R.id.drawer_header_service_view); headerServiceView = hView.findViewById(R.id.drawer_header_service_view);
toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button); toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button);
toggleServiceButton.setOnClickListener(view -> toggleServices()); toggleServiceButton.setOnClickListener(view -> toggleServices());
// If the current app name is bigger than the default "NewPipe" (7 chars),
// let the text view grow a little more as well.
if (getString(R.string.app_name).length() > "NewPipe".length()) {
final TextView headerTitle = hView.findViewById(R.id.drawer_header_newpipe_title);
final ViewGroup.LayoutParams layoutParams = headerTitle.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
headerTitle.setLayoutParams(layoutParams);
headerTitle.setMaxLines(2);
headerTitle.setMinWidth(getResources()
.getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_default_width));
headerTitle.setMaxWidth(getResources()
.getDimensionPixelSize(R.dimen.drawer_header_newpipe_title_max_width));
}
} }
private void toggleServices() { private void toggleServices() {

View File

@ -80,7 +80,12 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM
if (!isNewerStreamLive) { if (!isNewerStreamLive) {
if (existentMinimalStream.uploadDate != null && existentMinimalStream.isUploadDateApproximation != true) {
// Use the existent upload date if the newer stream does not have a better precision
// (i.e. is an approximation). This is done to prevent unnecessary changes.
val hasBetterPrecision =
newerStream.uploadDate != null && newerStream.isUploadDateApproximation != true
if (existentMinimalStream.uploadDate != null && !hasBetterPrecision) {
newerStream.uploadDate = existentMinimalStream.uploadDate newerStream.uploadDate = existentMinimalStream.uploadDate
newerStream.textualUploadDate = existentMinimalStream.textualUploadDate newerStream.textualUploadDate = existentMinimalStream.textualUploadDate
newerStream.isUploadDateApproximation = existentMinimalStream.isUploadDateApproximation newerStream.isUploadDateApproximation = existentMinimalStream.isUploadDateApproximation

View File

@ -20,13 +20,13 @@ import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.InfoCache; import org.schabi.newpipe.util.InfoCache;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -200,7 +200,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
return true; return true;
} }
if (ExtractorHelper.isInterruptedCaused(exception)) { if (ExceptionUtils.isInterruptedCaused(exception)) {
if (DEBUG) { if (DEBUG) {
Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]");
} }
@ -213,9 +213,12 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
} else if (exception instanceof ContentNotAvailableException) { } else if (exception instanceof ContentNotAvailableException) {
showError(getString(R.string.content_not_available), false); showError(getString(R.string.content_not_available), false);
return true; return true;
} else if (exception instanceof IOException) { } else if (ExceptionUtils.isNetworkRelated(exception)) {
showError(getString(R.string.network_error), true); showError(getString(R.string.network_error), true);
return true; return true;
} else if (exception instanceof ContentNotSupportedException) {
showError(getString(R.string.content_not_supported), false);
return true;
} }
return false; return false;

View File

@ -6,6 +6,7 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -30,6 +31,7 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
@ -45,6 +47,7 @@ import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ShareUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -83,6 +86,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
private LinearLayout headerPopupButton; private LinearLayout headerPopupButton;
private LinearLayout headerBackgroundButton; private LinearLayout headerBackgroundButton;
private MenuItem menuRssButton; private MenuItem menuRssButton;
private TextView contentNotSupportedTextView;
private TextView kaomojiTextView;
private TextView noVideosTextView;
public static ChannelFragment getInstance(final int serviceId, final String url, public static ChannelFragment getInstance(final int serviceId, final String url,
final String name) { final String name) {
@ -118,6 +124,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
return inflater.inflate(R.layout.fragment_channel, container, false); return inflater.inflate(R.layout.fragment_channel, container, false);
} }
@Override
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
contentNotSupportedTextView = rootView.findViewById(R.id.error_content_not_supported);
kaomojiTextView = rootView.findViewById(R.id.channel_kaomoji);
noVideosTextView = rootView.findViewById(R.id.channel_no_videos);
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -234,7 +248,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.debounce(100, TimeUnit.MILLISECONDS) .debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe((List<SubscriptionEntity> subscriptionEntities) -> .subscribe((List<SubscriptionEntity> subscriptionEntities) ->
updateSubscribeButton(!subscriptionEntities.isEmpty()), onError)); updateSubscribeButton(!subscriptionEntities.isEmpty()), onError));
} }
@ -417,9 +431,23 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
playlistCtrl.setVisibility(View.VISIBLE); playlistCtrl.setVisibility(View.VISIBLE);
if (!result.getErrors().isEmpty()) { List<Throwable> errors = new ArrayList<>(result.getErrors());
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, if (!errors.isEmpty()) {
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
// handling ContentNotSupportedException not to show the error but an appropriate string
// so that crashes won't be sent uselessly and the user will understand what happened
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
Throwable throwable = it.next();
if (throwable instanceof ContentNotSupportedException) {
showContentNotSupported();
it.remove();
}
}
if (!errors.isEmpty()) {
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
}
} }
if (disposables != null) { if (disposables != null) {
@ -439,6 +467,13 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
.playOnBackgroundPlayer(activity, getPlayQueue(), false)); .playOnBackgroundPlayer(activity, getPlayQueue(), false));
} }
private void showContentNotSupported() {
contentNotSupportedTextView.setVisibility(View.VISIBLE);
kaomojiTextView.setText("(︶︹︺)");
kaomojiTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
noVideosTextView.setVisibility(View.GONE);
}
private PlayQueue getPlayQueue() { private PlayQueue getPlayQueue() {
return getPlayQueue(0); return getPlayQueue(0);
} }

View File

@ -39,6 +39,7 @@ import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.StreamDialogEntry;
@ -302,8 +303,8 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar,
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, headerStreamCount.setText(Localization
(int) result.getStreamCount(), (int) result.getStreamCount())); .localizeStreamCount(getContext(), result.getStreamCount()));
if (!result.getErrors().isEmpty()) { if (!result.getErrors().isEmpty()) {
showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST,

View File

@ -53,9 +53,6 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -416,6 +413,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
boolean isFirstItem = true; boolean isFirstItem = true;
final Context c = getContext(); final Context c = getContext();
for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) { for (String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
if (filter.equals("music_songs")) {
MenuItem musicItem = menu.add(2,
itemId++,
0,
"YouTube Music");
musicItem.setEnabled(false);
}
menuItemToFilterName.put(itemId, filter); menuItemToFilterName.put(itemId, filter);
MenuItem item = menu.add(1, MenuItem item = menu.add(1,
itemId++, itemId++,
@ -763,12 +767,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
if (listNotification.isOnNext()) { if (listNotification.isOnNext()) {
handleSuggestions(listNotification.getValue()); handleSuggestions(listNotification.getValue());
} else if (listNotification.isOnError()) { } else if (listNotification.isOnError()) {
Throwable error = listNotification.getError(); onSuggestionError(listNotification.getError());
if (!ExtractorHelper.hasAssignableCauseThrowable(error,
IOException.class, SocketException.class,
InterruptedException.class, InterruptedIOException.class)) {
onSuggestionError(error);
}
} }
}); });
} }

View File

@ -10,6 +10,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
public final ImageView itemThumbnailView; public final ImageView itemThumbnailView;
@ -41,7 +42,8 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; final PlaylistInfoItem item = (PlaylistInfoItem) infoItem;
itemTitleView.setText(item.getName()); itemTitleView.setText(item.getName());
itemStreamCountView.setText(String.valueOf(item.getStreamCount())); itemStreamCountView.setText(Localization
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
itemUploaderView.setText(item.getUploaderName()); itemUploaderView.setText(item.getUploaderName());
itemBuilder.getImageLoader() itemBuilder.getImageLoader()

View File

@ -52,6 +52,7 @@ import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.* import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.local.subscription.SubscriptionManager
import org.schabi.newpipe.util.ExceptionUtils
import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.ExtractorHelper
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@ -333,11 +334,12 @@ class FeedLoadService : Service() {
val cause = error.cause val cause = error.cause
when { when {
error is IOException -> throw error
cause is IOException -> throw cause
error is ReCaptchaException -> throw error error is ReCaptchaException -> throw error
cause is ReCaptchaException -> throw cause cause is ReCaptchaException -> throw cause
error is IOException -> throw error
cause is IOException -> throw cause
ExceptionUtils.isNetworkRelated(error) -> throw IOException(error)
} }
} }
} }

View File

@ -8,6 +8,7 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import java.text.DateFormat; import java.text.DateFormat;
@ -31,7 +32,8 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
itemTitleView.setText(item.name); itemTitleView.setText(item.name);
itemStreamCountView.setText(String.valueOf(item.streamCount)); itemStreamCountView.setText(Localization.localizeStreamCountMini(
itemStreamCountView.getContext(), item.streamCount));
itemUploaderView.setVisibility(View.INVISIBLE); itemUploaderView.setVisibility(View.INVISIBLE);
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,

View File

@ -34,7 +34,8 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
itemTitleView.setText(item.getName()); itemTitleView.setText(item.getName());
itemStreamCountView.setText(String.valueOf(item.getStreamCount())); itemStreamCountView.setText(Localization.localizeStreamCountMini(
itemStreamCountView.getContext(), item.getStreamCount()));
// Here is where the uploader name is set in the bookmarked playlists library // Here is where the uploader name is set in the bookmarked playlists library
if (!TextUtils.isEmpty(item.getUploader())) { if (!TextUtils.isEmpty(item.getUploader())) {
itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(),

View File

@ -31,6 +31,7 @@ import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionS
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
import org.schabi.newpipe.local.subscription.item.* import org.schabi.newpipe.local.subscription.item.*
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
@ -361,11 +362,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
feedGroupsListState = null feedGroupsListState = null
} }
if (groups.size < 2) { feedGroupsSortMenuItem.showMenuItem = groups.size > 1
items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_HIDE_MENU_ITEM) } items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
} else {
items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_SHOW_MENU_ITEM) }
}
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

View File

@ -10,23 +10,19 @@ import org.schabi.newpipe.R
class HeaderWithMenuItem( class HeaderWithMenuItem(
val title: String, val title: String,
@DrawableRes val itemIcon: Int = 0, @DrawableRes val itemIcon: Int = 0,
var showMenuItem: Boolean = true,
private val onClickListener: (() -> Unit)? = null, private val onClickListener: (() -> Unit)? = null,
private val menuItemOnClickListener: (() -> Unit)? = null private val menuItemOnClickListener: (() -> Unit)? = null
) : Item() { ) : Item() {
companion object { companion object {
const val PAYLOAD_SHOW_MENU_ITEM = 1 const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
const val PAYLOAD_HIDE_MENU_ITEM = 2
} }
override fun getLayout(): Int = R.layout.header_with_menu_item override fun getLayout(): Int = R.layout.header_with_menu_item
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) { override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.contains(PAYLOAD_SHOW_MENU_ITEM)) { if (payloads.contains(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM)) {
viewHolder.header_menu_item.visibility = VISIBLE updateMenuItemVisibility(viewHolder)
return
} else if (payloads.contains(PAYLOAD_HIDE_MENU_ITEM)) {
viewHolder.header_menu_item.visibility = GONE
return return
} }
@ -44,5 +40,10 @@ class HeaderWithMenuItem(
val menuItemListener: OnClickListener? = val menuItemListener: OnClickListener? =
menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } } menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } }
viewHolder.header_menu_item.setOnClickListener(menuItemListener) viewHolder.header_menu_item.setOnClickListener(menuItemListener)
updateMenuItemVisibility(viewHolder)
}
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE
} }
} }

View File

@ -38,9 +38,9 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.ExceptionUtils;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -227,7 +227,7 @@ public abstract class BaseImportExportService extends Service {
message = getString(R.string.invalid_source); message = getString(R.string.invalid_source);
} else if (error instanceof FileNotFoundException) { } else if (error instanceof FileNotFoundException) {
message = getString(R.string.invalid_file); message = getString(R.string.invalid_file);
} else if (error instanceof IOException) { } else if (ExceptionUtils.isNetworkRelated(error)) {
message = getString(R.string.network_error); message = getString(R.string.network_error);
} }
return message; return message;

View File

@ -35,6 +35,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import org.schabi.newpipe.extractor.subscription.SubscriptionItem;
import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExceptionUtils;
import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ExtractorHelper;
import java.io.File; import java.io.File;
@ -245,8 +246,10 @@ public class SubscriptionsImportService extends BaseImportExportService {
final Throwable cause = error.getCause(); final Throwable cause = error.getCause();
if (error instanceof IOException) { if (error instanceof IOException) {
throw (IOException) error; throw (IOException) error;
} else if (cause != null && cause instanceof IOException) { } else if (cause instanceof IOException) {
throw (IOException) cause; throw (IOException) cause;
} else if (ExceptionUtils.isNetworkRelated(error)) {
throw new IOException(error);
} }
eventListener.onItemCompleted(""); eventListener.onItemCompleted("");

View File

@ -118,12 +118,6 @@ public abstract class BasePlayer implements
@NonNull @NonNull
public static final String REPEAT_MODE = "repeat_mode"; public static final String REPEAT_MODE = "repeat_mode";
@NonNull @NonNull
public static final String PLAYBACK_PITCH = "playback_pitch";
@NonNull
public static final String PLAYBACK_SPEED = "playback_speed";
@NonNull
public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
@NonNull
public static final String PLAYBACK_QUALITY = "playback_quality"; public static final String PLAYBACK_QUALITY = "playback_quality";
@NonNull @NonNull
public static final String PLAY_QUEUE_KEY = "play_queue_key"; public static final String PLAY_QUEUE_KEY = "play_queue_key";
@ -287,11 +281,12 @@ public abstract class BasePlayer implements
return; return;
} }
final PlaybackParameters savedParameters = retrievePlaybackParametersFromPreferences();
final float playbackSpeed = savedParameters.speed;
final float playbackPitch = savedParameters.pitch;
final boolean playbackSkipSilence = savedParameters.skipSilence;
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode()); final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed());
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE,
getPlaybackSkipSilence());
final boolean isMuted = intent final boolean isMuted = intent
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted()); .getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
@ -330,6 +325,20 @@ public abstract class BasePlayer implements
/*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted); /*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false), isMuted);
} }
private PlaybackParameters retrievePlaybackParametersFromPreferences() {
final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
final float speed = preferences
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
getPlaybackPitch());
final boolean skipSilence = preferences
.getBoolean(context.getString(R.string.playback_skip_silence_key),
getPlaybackSkipSilence());
return new PlaybackParameters(speed, pitch, skipSilence);
}
protected void initPlayback(@NonNull final PlayQueue queue, protected void initPlayback(@NonNull final PlayQueue queue,
@Player.RepeatMode final int repeatMode, @Player.RepeatMode final int repeatMode,
final float playbackSpeed, final float playbackSpeed,
@ -1470,9 +1479,20 @@ public abstract class BasePlayer implements
public void setPlaybackParameters(final float speed, final float pitch, public void setPlaybackParameters(final float speed, final float pitch,
final boolean skipSilence) { final boolean skipSilence) {
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence)); simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
} }
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
final boolean skipSilence) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putFloat(context.getString(R.string.playback_speed_key), speed)
.putFloat(context.getString(R.string.playback_pitch_key), pitch)
.putBoolean(context.getString(R.string.playback_skip_silence_key), skipSilence)
.apply();
}
public PlayQueue getPlayQueue() { public PlayQueue getPlayQueue() {
return playQueue; return playQueue;
} }

View File

@ -41,6 +41,7 @@ import android.view.GestureDetector;
import android.view.Gravity; import android.view.Gravity;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.AnticipateInterpolator; import android.view.animation.AnticipateInterpolator;
@ -1010,6 +1011,14 @@ public final class PopupVideoPlayer extends Service {
private boolean isMoving; private boolean isMoving;
private boolean isResizing; private boolean isResizing;
//initial co-ordinates and distance between fingers
private double initPointerDistance = -1;
private float initFirstPointerX = -1;
private float initFirstPointerY = -1;
private float initSecPointerX = -1;
private float initSecPointerY = -1;
@Override @Override
public boolean onDoubleTap(final MotionEvent e) { public boolean onDoubleTap(final MotionEvent e) {
if (DEBUG) { if (DEBUG) {
@ -1201,6 +1210,16 @@ public final class PopupVideoPlayer extends Service {
playerImpl.hideControls(0, 0); playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0); animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0); animateView(playerImpl.getResizingIndicator(), true, 200, 0);
//record co-ordinates of fingers
initFirstPointerX = event.getX(0);
initFirstPointerY = event.getY(0);
initSecPointerX = event.getX(1);
initSecPointerY = event.getY(1);
//record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
initFirstPointerY - initSecPointerY);
isResizing = true; isResizing = true;
} }
@ -1224,6 +1243,13 @@ public final class PopupVideoPlayer extends Service {
if (isResizing) { if (isResizing) {
isResizing = false; isResizing = false;
initPointerDistance = -1;
initFirstPointerX = -1;
initFirstPointerY = -1;
initSecPointerX = -1;
initSecPointerY = -1;
animateView(playerImpl.getResizingIndicator(), false, 100, 0); animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState()); playerImpl.changeState(playerImpl.getCurrentState());
} }
@ -1238,29 +1264,35 @@ public final class PopupVideoPlayer extends Service {
} }
private boolean handleMultiDrag(final MotionEvent event) { private boolean handleMultiDrag(final MotionEvent event) {
if (event.getPointerCount() != 2) { if (initPointerDistance != -1 && event.getPointerCount() == 2) {
return false; // get the movements of the fingers
double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
event.getY(0) - initFirstPointerY);
double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
event.getY(1) - initSecPointerY);
// minimum threshold beyond which pinch gesture will work
int minimumMove = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop();
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
double currentPointerDistance =
Math.hypot(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
// change co-ordinates of popup so the center stays at the same position
double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
initPointerDistance = currentPointerDistance;
popupLayoutParams.x += (popupWidth - newWidth) / 2;
checkPopupPositionBounds();
updateScreenSize();
updatePopupSize((int) Math.min(screenWidth, newWidth), -1);
return true;
}
} }
return false;
final float firstPointerX = event.getX(0);
final float secondPointerX = event.getX(1);
final float diff = Math.abs(firstPointerX - secondPointerX);
if (firstPointerX > secondPointerX) {
// second pointer is the anchor (the leftmost pointer)
popupLayoutParams.x = (int) (event.getRawX() - diff);
} else {
// first pointer is the anchor
popupLayoutParams.x = (int) event.getRawX();
}
checkPopupPositionBounds();
updateScreenSize();
final int width = (int) Math.min(screenWidth, diff);
updatePopupSize(width, -1);
return true;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -1,262 +1,262 @@
package org.schabi.newpipe.streams; package org.schabi.newpipe.streams;
import org.schabi.newpipe.streams.io.SharpStream; import org.schabi.newpipe.streams.io.SharpStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
* @author kapodamy * @author kapodamy
*/ */
public class DataReader { public class DataReader {
public static final int SHORT_SIZE = 2; public static final int SHORT_SIZE = 2;
public static final int LONG_SIZE = 8; public static final int LONG_SIZE = 8;
public static final int INTEGER_SIZE = 4; public static final int INTEGER_SIZE = 4;
public static final int FLOAT_SIZE = 4; public static final int FLOAT_SIZE = 4;
private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB
private long position = 0; private long position = 0;
private final SharpStream stream; private final SharpStream stream;
private InputStream view; private InputStream view;
private int viewSize; private int viewSize;
public DataReader(final SharpStream stream) { public DataReader(final SharpStream stream) {
this.stream = stream; this.stream = stream;
this.readOffset = this.readBuffer.length; this.readOffset = this.readBuffer.length;
} }
public long position() { public long position() {
return position; return position;
} }
public int read() throws IOException { public int read() throws IOException {
if (fillBuffer()) { if (fillBuffer()) {
return -1; return -1;
} }
position++; position++;
readCount--; readCount--;
return readBuffer[readOffset++] & 0xFF; return readBuffer[readOffset++] & 0xFF;
} }
public long skipBytes(long amount) throws IOException { public long skipBytes(long amount) throws IOException {
if (readCount < 0) { if (readCount < 0) {
return 0; return 0;
} else if (readCount == 0) { } else if (readCount == 0) {
amount = stream.skip(amount); amount = stream.skip(amount);
} else { } else {
if (readCount > amount) { if (readCount > amount) {
readCount -= (int) amount; readCount -= (int) amount;
readOffset += (int) amount; readOffset += (int) amount;
} else { } else {
amount = readCount + stream.skip(amount - readCount); amount = readCount + stream.skip(amount - readCount);
readCount = 0; readCount = 0;
readOffset = readBuffer.length; readOffset = readBuffer.length;
} }
} }
position += amount; position += amount;
return amount; return amount;
} }
public int readInt() throws IOException { public int readInt() throws IOException {
primitiveRead(INTEGER_SIZE); primitiveRead(INTEGER_SIZE);
return primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; return primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
} }
public long readUnsignedInt() throws IOException { public long readUnsignedInt() throws IOException {
long value = readInt(); long value = readInt();
return value & 0xffffffffL; return value & 0xffffffffL;
} }
public short readShort() throws IOException { public short readShort() throws IOException {
primitiveRead(SHORT_SIZE); primitiveRead(SHORT_SIZE);
return (short) (primitive[0] << 8 | primitive[1]); return (short) (primitive[0] << 8 | primitive[1]);
} }
public long readLong() throws IOException { public long readLong() throws IOException {
primitiveRead(LONG_SIZE); primitiveRead(LONG_SIZE);
long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3];
long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7];
return high << 32 | low; return high << 32 | low;
} }
public int read(final byte[] buffer) throws IOException { public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length); return read(buffer, 0, buffer.length);
} }
public int read(final byte[] buffer, int offset, int count) throws IOException { public int read(final byte[] buffer, int offset, int count) throws IOException {
if (readCount < 0) { if (readCount < 0) {
return -1; return -1;
} }
int total = 0; int total = 0;
if (count >= readBuffer.length) { if (count >= readBuffer.length) {
if (readCount > 0) { if (readCount > 0) {
System.arraycopy(readBuffer, readOffset, buffer, offset, readCount); System.arraycopy(readBuffer, readOffset, buffer, offset, readCount);
readOffset += readCount; readOffset += readCount;
offset += readCount; offset += readCount;
count -= readCount; count -= readCount;
total = readCount; total = readCount;
readCount = 0; readCount = 0;
} }
total += Math.max(stream.read(buffer, offset, count), 0); total += Math.max(stream.read(buffer, offset, count), 0);
} else { } else {
while (count > 0 && !fillBuffer()) { while (count > 0 && !fillBuffer()) {
int read = Math.min(readCount, count); int read = Math.min(readCount, count);
System.arraycopy(readBuffer, readOffset, buffer, offset, read); System.arraycopy(readBuffer, readOffset, buffer, offset, read);
readOffset += read; readOffset += read;
readCount -= read; readCount -= read;
offset += read; offset += read;
count -= read; count -= read;
total += read; total += read;
} }
} }
position += total; position += total;
return total; return total;
} }
public boolean available() { public boolean available() {
return readCount > 0 || stream.available() > 0; return readCount > 0 || stream.available() > 0;
} }
public void rewind() throws IOException { public void rewind() throws IOException {
stream.rewind(); stream.rewind();
if ((position - viewSize) > 0) { if ((position - viewSize) > 0) {
viewSize = 0; // drop view viewSize = 0; // drop view
} else { } else {
viewSize += position; viewSize += position;
} }
position = 0; position = 0;
readOffset = readBuffer.length; readOffset = readBuffer.length;
readCount = 0; readCount = 0;
} }
public boolean canRewind() { public boolean canRewind() {
return stream.canRewind(); return stream.canRewind();
} }
/** /**
* Wraps this instance of {@code DataReader} into {@code InputStream} * Wraps this instance of {@code DataReader} into {@code InputStream}
* object. Note: Any read in the {@code DataReader} will not modify * object. Note: Any read in the {@code DataReader} will not modify
* (decrease) the view size * (decrease) the view size
* *
* @param size the size of the view * @param size the size of the view
* @return the view * @return the view
*/ */
public InputStream getView(final int size) { public InputStream getView(final int size) {
if (view == null) { if (view == null) {
view = new InputStream() { view = new InputStream() {
@Override @Override
public int read() throws IOException { public int read() throws IOException {
if (viewSize < 1) { if (viewSize < 1) {
return -1; return -1;
} }
int res = DataReader.this.read(); int res = DataReader.this.read();
if (res > 0) { if (res > 0) {
viewSize--; viewSize--;
} }
return res; return res;
} }
@Override @Override
public int read(final byte[] buffer) throws IOException { public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length); return read(buffer, 0, buffer.length);
} }
@Override @Override
public int read(final byte[] buffer, final int offset, final int count) public int read(final byte[] buffer, final int offset, final int count)
throws IOException { throws IOException {
if (viewSize < 1) { if (viewSize < 1) {
return -1; return -1;
} }
int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count)); int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count));
viewSize -= res; viewSize -= res;
return res; return res;
} }
@Override @Override
public long skip(final long amount) throws IOException { public long skip(final long amount) throws IOException {
if (viewSize < 1) { if (viewSize < 1) {
return 0; return 0;
} }
int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize)); int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize));
viewSize -= res; viewSize -= res;
return res; return res;
} }
@Override @Override
public int available() { public int available() {
return viewSize; return viewSize;
} }
@Override @Override
public void close() { public void close() {
viewSize = 0; viewSize = 0;
} }
@Override @Override
public boolean markSupported() { public boolean markSupported() {
return false; return false;
} }
}; };
} }
viewSize = size; viewSize = size;
return view; return view;
} }
private final short[] primitive = new short[LONG_SIZE]; private final short[] primitive = new short[LONG_SIZE];
private void primitiveRead(final int amount) throws IOException { private void primitiveRead(final int amount) throws IOException {
byte[] buffer = new byte[amount]; byte[] buffer = new byte[amount];
int read = read(buffer, 0, amount); int read = read(buffer, 0, amount);
if (read != amount) { if (read != amount) {
throw new EOFException("Truncated stream, missing " throw new EOFException("Truncated stream, missing "
+ String.valueOf(amount - read) + " bytes"); + String.valueOf(amount - read) + " bytes");
} }
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
// the "byte" data type in java is signed and is very annoying // the "byte" data type in java is signed and is very annoying
primitive[i] = (short) (buffer[i] & 0xFF); primitive[i] = (short) (buffer[i] & 0xFF);
} }
} }
private final byte[] readBuffer = new byte[BUFFER_SIZE]; private final byte[] readBuffer = new byte[BUFFER_SIZE];
private int readOffset; private int readOffset;
private int readCount; private int readCount;
private boolean fillBuffer() throws IOException { private boolean fillBuffer() throws IOException {
if (readCount < 0) { if (readCount < 0) {
return true; return true;
} }
if (readOffset >= readBuffer.length) { if (readOffset >= readBuffer.length) {
readCount = stream.read(readBuffer); readCount = stream.read(readBuffer);
if (readCount < 1) { if (readCount < 1) {
readCount = -1; readCount = -1;
return true; return true;
} }
readOffset = 0; readOffset = 0;
} }
return readCount < 1; return readCount < 1;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -674,8 +674,9 @@ public class Mp4FromDashWriter {
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values
// default matrix // default matrix
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00 0x40, 0x00, 0x00, 0x00
}); });
auxWrite(new byte[24]); // predefined auxWrite(new byte[24]); // predefined
@ -717,12 +718,13 @@ public class Mp4FromDashWriter {
// udta/meta/ilst/©too // udta/meta/ilst/©too
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72,
0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string
}); });
@ -734,8 +736,10 @@ public class Mp4FromDashWriter {
int start = auxOffset(); int start = auxOffset();
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B, // trak header // trak header
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header 0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B,
// tkhd header
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03
}); });
ByteBuffer buffer = ByteBuffer.allocate(48); ByteBuffer buffer = ByteBuffer.allocate(48);
@ -759,7 +763,8 @@ public class Mp4FromDashWriter {
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, // edts header 0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, // edts header
0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header 0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header
}); });
int bMediaRate; int bMediaRate;
@ -845,14 +850,18 @@ public class Mp4FromDashWriter {
private byte[] makeHdlr(final Hdlr hdlr) { private byte[] makeHdlr(final Hdlr hdlr) {
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr 0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// binary string "ISO Media file created in NewPipe (A libre lightweight streaming frontend for Android)." // binary string
0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65, 0x20, 0x63, // "ISO Media file created in NewPipe (
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, // A libre lightweight streaming frontend for Android)."
0x65, 0x20, 0x28, 0x41, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x65, 0x20, 0x6C, 0x69, 0x67, 0x68, 0x74, 0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65,
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x69, 0x6E, 0x67, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65,
0x20, 0x66, 0x72, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x41, 0x6E, 0x77, 0x50, 0x69, 0x70, 0x65, 0x20, 0x28, 0x41, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x65,
0x20, 0x6C, 0x69, 0x67, 0x68, 0x74, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x73,
0x74, 0x72, 0x65, 0x61, 0x6D, 0x69, 0x6E, 0x67,
0x20, 0x66, 0x72, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20,
0x41, 0x6E,
0x64, 0x72, 0x6F, 0x69, 0x64, 0x29, 0x2E 0x64, 0x72, 0x6F, 0x69, 0x64, 0x29, 0x2E
}); });

View File

@ -289,11 +289,13 @@ public class OggFromWebMWriter implements Closeable {
/* /*
// whole file duration (not implemented) // whole file duration (not implemented)
0x44,// tag string size 0x44,// tag string size
0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30,
0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x2E, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30
*/ */
0x0F, // tag string size 0x0F, // tag string size
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D, // "ENCODER=" binary string 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F,
0x44, 0x45, 0x52, 0x3D, // "ENCODER=" binary string
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ???????? 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ????????
}; };

View File

@ -102,10 +102,6 @@ public class WebMWriter implements Closeable {
return done; return done;
} }
public boolean isParsed() {
return parsed;
}
@Override @Override
public void close() { public void close() {
done = true; done = true;
@ -360,7 +356,7 @@ public class WebMWriter implements Closeable {
Block bloq = new Block(); Block bloq = new Block();
bloq.data = res.data; bloq.data = res.data;
bloq.dataSize = (int) res.dataSize; bloq.dataSize = res.dataSize;
bloq.trackNumber = internalTrackId; bloq.trackNumber = internalTrackId;
bloq.flags = res.flags; bloq.flags = res.flags;
bloq.absoluteTimecode = res.absoluteTimeCodeNs / DEFAULT_TIMECODE_SCALE; bloq.absoluteTimecode = res.absoluteTimeCodeNs / DEFAULT_TIMECODE_SCALE;
@ -728,7 +724,7 @@ public class WebMWriter implements Closeable {
return 0; return 0;
} }
class KeyFrame { static class KeyFrame {
KeyFrame(final long segment, final long cluster, final long block, final long timecode) { KeyFrame(final long segment, final long cluster, final long block, final long timecode) {
clusterPosition = cluster - segment; clusterPosition = cluster - segment;
relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE); relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE);
@ -740,7 +736,7 @@ public class WebMWriter implements Closeable {
final long duration; final long duration;
} }
class Block { static class Block {
InputStream data; InputStream data;
int trackNumber; int trackNumber;
byte flags; byte flags;
@ -759,7 +755,7 @@ public class WebMWriter implements Closeable {
} }
} }
class ClusterInfo { static class ClusterInfo {
long offset; long offset;
int size; int size;
} }

View File

@ -0,0 +1,82 @@
package org.schabi.newpipe.util
import java.io.IOException
import java.io.InterruptedIOException
class ExceptionUtils {
companion object {
/**
* @return if throwable is related to Interrupted exceptions, or one of its causes is.
*/
@JvmStatic
fun isInterruptedCaused(throwable: Throwable): Boolean {
return hasExactCause(throwable,
InterruptedIOException::class.java,
InterruptedException::class.java)
}
/**
* @return if throwable is related to network issues, or one of its causes is.
*/
@JvmStatic
fun isNetworkRelated(throwable: Throwable): Boolean {
return hasAssignableCause(throwable,
IOException::class.java)
}
/**
* Calls [hasCause] with the `checkSubtypes` parameter set to false.
*/
@JvmStatic
fun hasExactCause(throwable: Throwable, vararg causesToCheck: Class<*>): Boolean {
return hasCause(throwable, false, *causesToCheck)
}
/**
* Calls [hasCause] with the `checkSubtypes` parameter set to true.
*/
@JvmStatic
fun hasAssignableCause(throwable: Throwable?, vararg causesToCheck: Class<*>): Boolean {
return hasCause(throwable, true, *causesToCheck)
}
/**
* Check if throwable has some cause from the causes to check, or is itself in it.
*
* If `checkIfAssignable` is true, not only the exact type will be considered equals, but also its subtypes.
*
* @param throwable throwable that will be checked.
* @param checkSubtypes if subtypes are also checked.
* @param causesToCheck an array of causes to check.
*
* @see Class.isAssignableFrom
*/
@JvmStatic
tailrec fun hasCause(throwable: Throwable?, checkSubtypes: Boolean, vararg causesToCheck: Class<*>): Boolean {
if (throwable == null) {
return false
}
// Check if throwable is a subtype of any of the causes to check
causesToCheck.forEach { causeClass ->
if (checkSubtypes) {
if (causeClass.isAssignableFrom(throwable.javaClass)) {
return true
}
} else {
if (causeClass == throwable.javaClass) {
return true
}
}
}
val currentCause: Throwable? = throwable.cause
// Check if cause is not pointing to the same instance, to avoid infinite loops.
if (throwable !== currentCause) {
return hasCause(currentCause, checkSubtypes, *causesToCheck)
}
return false
}
}
}

View File

@ -37,6 +37,7 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfo;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.feed.FeedExtractor; import org.schabi.newpipe.extractor.feed.FeedExtractor;
@ -51,8 +52,6 @@ import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -289,10 +288,12 @@ public final class ExtractorHelper {
Intent intent = new Intent(context, ReCaptchaActivity.class); Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
} else if (exception instanceof IOException) { } else if (ExceptionUtils.isNetworkRelated(exception)) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotAvailableException) { } else if (exception instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else if (exception instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else { } else {
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
? R.string.youtube_signature_decryption_error ? R.string.youtube_signature_decryption_error
@ -306,85 +307,4 @@ public final class ExtractorHelper {
} }
}); });
} }
/**
* Check if throwable have the cause that can be assignable from the causes to check.
*
* @see Class#isAssignableFrom(Class)
* @param throwable the throwable to be checked
* @param causesToCheck the causes to check
* @return whether the exception is an instance of a subclass of one of the causes
* or is caused by an instance of a subclass of one of the causes
*/
public static boolean hasAssignableCauseThrowable(final Throwable throwable,
final Class<?>... causesToCheck) {
// Check if getCause is not the same as cause (the getCause is already the root),
// as it will cause a infinite loop if it is
Throwable cause;
Throwable getCause = throwable;
// Check if throwable is a subclass of any of the filtered classes
final Class throwableClass = throwable.getClass();
for (Class<?> causesEl : causesToCheck) {
if (causesEl.isAssignableFrom(throwableClass)) {
return true;
}
}
// Iteratively checks if the root cause of the throwable is a subclass of the filtered class
while ((cause = throwable.getCause()) != null && getCause != cause) {
getCause = cause;
final Class causeClass = cause.getClass();
for (Class<?> causesEl : causesToCheck) {
if (causesEl.isAssignableFrom(causeClass)) {
return true;
}
}
}
return false;
}
/**
* Check if throwable have the exact cause from one of the causes to check.
*
* @param throwable the throwable to be checked
* @param causesToCheck the causes to check
* @return whether the exception is an instance of one of the causes
* or is caused by an instance of one of the causes
*/
public static boolean hasExactCauseThrowable(final Throwable throwable,
final Class<?>... causesToCheck) {
// Check if getCause is not the same as cause (the getCause is already the root),
// as it will cause a infinite loop if it is
Throwable cause;
Throwable getCause = throwable;
for (Class<?> causesEl : causesToCheck) {
if (throwable.getClass().equals(causesEl)) {
return true;
}
}
while ((cause = throwable.getCause()) != null && getCause != cause) {
getCause = cause;
for (Class<?> causesEl : causesToCheck) {
if (cause.getClass().equals(causesEl)) {
return true;
}
}
}
return false;
}
/**
* Check if throwable have Interrupted* exception as one of its causes.
*
* @param throwable the throwable to be checkes
* @return whether the throwable is caused by an interruption
*/
public static boolean isInterruptedCaused(final Throwable throwable) {
return ExtractorHelper.hasExactCauseThrowable(throwable,
InterruptedIOException.class,
InterruptedException.class);
}
} }

View File

@ -16,6 +16,7 @@ import androidx.annotation.StringRes;
import org.ocpsoft.prettytime.PrettyTime; import org.ocpsoft.prettytime.PrettyTime;
import org.ocpsoft.prettytime.units.Decade; import org.ocpsoft.prettytime.units.Decade;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.ContentCountry;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -151,8 +152,30 @@ public final class Localization {
} }
public static String localizeStreamCount(final Context context, final long streamCount) { public static String localizeStreamCount(final Context context, final long streamCount) {
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, switch ((int) streamCount) {
localizeNumber(context, streamCount)); case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
return "";
case (int) ListExtractor.ITEM_COUNT_INFINITE:
return context.getResources().getString(R.string.infinite_videos);
case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100:
return context.getResources().getString(R.string.more_than_100_videos);
default:
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
localizeNumber(context, streamCount));
}
}
public static String localizeStreamCountMini(final Context context, final long streamCount) {
switch ((int) streamCount) {
case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
return "";
case (int) ListExtractor.ITEM_COUNT_INFINITE:
return context.getResources().getString(R.string.infinite_videos_mini);
case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100:
return context.getResources().getString(R.string.more_than_100_videos_mini);
default:
return String.valueOf(streamCount);
}
} }
public static String localizeWatchingCount(final Context context, final long watchingCount) { public static String localizeWatchingCount(final Context context, final long watchingCount) {

View File

@ -120,9 +120,6 @@ public final class NavigationHelper {
final boolean isMuted) { final boolean isMuted) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode) .putExtra(BasePlayer.REPEAT_MODE, repeatMode)
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch)
.putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence)
.putExtra(BasePlayer.START_PAUSED, startPaused) .putExtra(BasePlayer.START_PAUSED, startPaused)
.putExtra(BasePlayer.IS_MUTED, isMuted); .putExtra(BasePlayer.IS_MUTED, isMuted);
} }

View File

@ -48,10 +48,12 @@ public final class ServiceHelper {
case "all": case "all":
return c.getString(R.string.all); return c.getString(R.string.all);
case "videos": case "videos":
case "music_videos":
return c.getString(R.string.videos_string); return c.getString(R.string.videos_string);
case "channels": case "channels":
return c.getString(R.string.channels); return c.getString(R.string.channels);
case "playlists": case "playlists":
case "music_playlists":
return c.getString(R.string.playlists); return c.getString(R.string.playlists);
case "tracks": case "tracks":
return c.getString(R.string.tracks); return c.getString(R.string.tracks);
@ -61,6 +63,12 @@ public final class ServiceHelper {
return c.getString(R.string.conferences); return c.getString(R.string.conferences);
case "events": case "events":
return c.getString(R.string.events); return c.getString(R.string.events);
case "music_songs":
return c.getString(R.string.songs);
case "music_albums":
return c.getString(R.string.albums);
case "music_artists":
return c.getString(R.string.artists);
default: default:
return filter; return filter;
} }

View File

@ -194,7 +194,7 @@ public final class ThemeHelper {
} }
/** /**
* Get a resource id from a resource styled according to the the context's theme. * Get a resource id from a resource styled according to the context's theme.
* *
* @param context Android app context * @param context Android app context
* @param attr attribute reference of the resource * @param attr attribute reference of the resource
@ -208,10 +208,10 @@ public final class ThemeHelper {
} }
/** /**
* Get a color from an attr styled according to the the context's theme. * Get a color from an attr styled according to the context's theme.
* *
* @param context Android app context * @param context Android app context
* @param attrColor attribute reference of the resource * @param attrColor attribute reference of the resource
* @return the color * @return the color
*/ */
public static int resolveColorFromAttr(final Context context, @AttrRes final int attrColor) { public static int resolveColorFromAttr(final Context context, @AttrRes final int attrColor) {

View File

@ -46,22 +46,21 @@
<TextView <TextView
android:id="@+id/drawer_header_newpipe_title" android:id="@+id/drawer_header_newpipe_title"
android:layout_width="wrap_content" android:layout_width="@dimen/drawer_header_newpipe_title_default_width"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="@dimen/drawer_header_newpipe_icon_title_space" android:layout_marginEnd="@dimen/drawer_header_newpipe_icon_title_space"
android:ellipsize="end" android:ellipsize="end"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:hyphenationFrequency="full" android:hyphenationFrequency="full"
android:maxWidth="@dimen/drawer_header_newpipe_title_max_width" android:maxLines="1"
android:maxLines="2"
android:minWidth="@dimen/drawer_header_newpipe_title_min_width"
android:text="@string/app_name" android:text="@string/app_name"
android:textColor="@color/drawer_header_font_color" android:textColor="@color/drawer_header_font_color"
android:textStyle="bold" android:textStyle="bold"
app:autoSizeMaxTextSize="@dimen/drawer_header_newpipe_title_max_text_size" app:autoSizeMaxTextSize="@dimen/drawer_header_newpipe_title_max_text_size"
app:autoSizeMinTextSize="@dimen/drawer_header_newpipe_title_min_text_size" app:autoSizeMinTextSize="@dimen/drawer_header_newpipe_title_min_text_size"
app:autoSizeTextType="uniform" app:autoSizeTextType="uniform"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute"
tools:text="NewPipe" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@ -32,6 +32,7 @@
tools:visibility="visible"> tools:visibility="visible">
<TextView <TextView
android:id="@+id/channel_kaomoji"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
@ -42,12 +43,22 @@
tools:ignore="HardcodedText,UnusedAttribute"/> tools:ignore="HardcodedText,UnusedAttribute"/>
<TextView <TextView
android:id="@+id/channel_no_videos"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/empty_view_no_videos" android:text="@string/empty_view_no_videos"
android:textSize="24sp"/> android:textSize="24sp"/>
<TextView
android:layout_marginTop="20dp"
android:id="@+id/error_content_not_supported"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/content_not_supported"
android:textSize="15sp"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
<!--ERROR PANEL--> <!--ERROR PANEL-->

View File

@ -586,4 +586,5 @@
<string name="mute">Mutigi</string> <string name="mute">Mutigi</string>
<string name="unmute">Malmutigi</string> <string name="unmute">Malmutigi</string>
<string name="help">Helpo</string> <string name="help">Helpo</string>
<string name="content_not_supported">Tio enhavo ne estas ankoraŭ subtenata per NewPipe.\n\nĜi espereble estos en sekvanta versio.</string>
</resources> </resources>

View File

@ -598,4 +598,5 @@
\n \n
\n Donc le choix vous revient : Préferez-vous la vitesse ou des informations précises \?</string> \n Donc le choix vous revient : Préferez-vous la vitesse ou des informations précises \?</string>
<string name="help">Aide</string> <string name="help">Aide</string>
<string name="content_not_supported">Ce contenu n\'est pas encore supporté par NewPipe.\n\nIl le sera peut-être dans une version future.</string>
</resources> </resources>

View File

@ -596,4 +596,7 @@
<string name="drawer_header_description">Toggle service, momenteel geselecteerd:</string> <string name="drawer_header_description">Toggle service, momenteel geselecteerd:</string>
<string name="most_liked">Meest geliked</string> <string name="most_liked">Meest geliked</string>
<string name="error_postprocessing_stopped">NewPipe werd gesloten terwijl het bezig was met het bestand</string> <string name="error_postprocessing_stopped">NewPipe werd gesloten terwijl het bezig was met het bestand</string>
</resources> <string name="songs">Nummers</string>
<string name="albums">Albums</string>
<string name="artists">Artiesten</string>
</resources>

View File

@ -5,8 +5,8 @@
<dimen name="drawer_header_padding_top">16dp</dimen> <dimen name="drawer_header_padding_top">16dp</dimen>
<dimen name="drawer_header_newpipe_icon_size">48dp</dimen> <dimen name="drawer_header_newpipe_icon_size">48dp</dimen>
<dimen name="drawer_header_newpipe_icon_title_space">12dp</dimen> <dimen name="drawer_header_newpipe_icon_title_space">12dp</dimen>
<dimen name="drawer_header_newpipe_title_min_width">120dp</dimen> <dimen name="drawer_header_newpipe_title_default_width">130dp</dimen>
<dimen name="drawer_header_newpipe_title_max_width">220dp</dimen> <dimen name="drawer_header_newpipe_title_max_width">200dp</dimen>
<dimen name="drawer_header_newpipe_title_min_text_size">18sp</dimen> <dimen name="drawer_header_newpipe_title_min_text_size">18sp</dimen>
<dimen name="drawer_header_newpipe_title_max_text_size">32sp</dimen> <dimen name="drawer_header_newpipe_title_max_text_size">32sp</dimen>
<dimen name="drawer_header_service_icon_size">16dp</dimen> <dimen name="drawer_header_service_icon_size">16dp</dimen>

View File

@ -177,6 +177,9 @@
<string name="enable_playback_resume_key" translatable="false">enable_playback_resume</string> <string name="enable_playback_resume_key" translatable="false">enable_playback_resume</string>
<string name="enable_playback_state_lists_key" translatable="false">enable_playback_state_lists</string> <string name="enable_playback_state_lists_key" translatable="false">enable_playback_state_lists</string>
<string name="playback_unhook_key" translatable="false">playback_unhook_key</string> <string name="playback_unhook_key" translatable="false">playback_unhook_key</string>
<string name="playback_speed_key" translatable="false">playback_speed_key</string>
<string name="playback_pitch_key" translatable="false">playback_pitch_key</string>
<string name="playback_skip_silence_key" translatable="false">playback_skip_silence_key</string>
<string name="app_language_key" translatable="false">app_language_key</string> <string name="app_language_key" translatable="false">app_language_key</string>
<string name="enable_lock_screen_video_thumbnail_key" translatable="false">enable_lock_screen_video_thumbnail</string> <string name="enable_lock_screen_video_thumbnail_key" translatable="false">enable_lock_screen_video_thumbnail</string>

View File

@ -150,6 +150,9 @@
<string name="tracks">Tracks</string> <string name="tracks">Tracks</string>
<string name="users">Users</string> <string name="users">Users</string>
<string name="events">Events</string> <string name="events">Events</string>
<string name="songs">Songs</string>
<string name="albums">Albums</string>
<string name="artists">Artists</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="later">Later</string> <string name="later">Later</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
@ -284,6 +287,10 @@
<item quantity="other">%s listeners</item> <item quantity="other">%s listeners</item>
</plurals> </plurals>
<string name="no_videos">No videos</string> <string name="no_videos">No videos</string>
<string name="more_than_100_videos">100+ videos</string>
<string name="infinite_videos">∞ videos</string>
<string name="more_than_100_videos_mini" translatable="false">100+</string>
<string name="infinite_videos_mini" translatable="false"></string>
<plurals name="videos"> <plurals name="videos">
<item quantity="one">%s video</item> <item quantity="one">%s video</item>
<item quantity="other">%s videos</item> <item quantity="other">%s videos</item>
@ -637,4 +644,5 @@
<string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string> <string name="feed_use_dedicated_fetch_method_enable_button">Enable fast mode</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string> <string name="feed_use_dedicated_fetch_method_disable_button">Disable fast mode</string>
<string name="feed_use_dedicated_fetch_method_help_text">Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information.</string> <string name="feed_use_dedicated_fetch_method_help_text">Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information.</string>
<string name="content_not_supported">This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version.</string>
</resources> </resources>

View File

@ -0,0 +1,69 @@
package org.schabi.newpipe.util
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasAssignableCause
import org.schabi.newpipe.util.ExceptionUtils.Companion.hasExactCause
import java.io.IOException
import java.io.InterruptedIOException
import java.net.SocketException
import javax.net.ssl.SSLException
class ExceptionUtilsTest {
@Test fun `assignable causes`() {
assertTrue(hasAssignableCause(Throwable(), Throwable::class.java))
assertTrue(hasAssignableCause(Exception(), Exception::class.java))
assertTrue(hasAssignableCause(IOException(), Exception::class.java))
assertTrue(hasAssignableCause(IOException(), IOException::class.java))
assertTrue(hasAssignableCause(Exception(SocketException()), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException()), RuntimeException::class.java))
assertTrue(hasAssignableCause(Exception(Exception(IOException())), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(SSLException("IO")))), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), RuntimeException::class.java))
assertTrue(hasAssignableCause(IllegalStateException(), Throwable::class.java))
assertTrue(hasAssignableCause(IllegalStateException(), Exception::class.java))
assertTrue(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java))
}
@Test fun `no assignable causes`() {
assertFalse(hasAssignableCause(Throwable(), Exception::class.java))
assertFalse(hasAssignableCause(Exception(), IOException::class.java))
assertFalse(hasAssignableCause(Exception(IllegalStateException()), IOException::class.java))
assertFalse(hasAssignableCause(Exception(NullPointerException()), IOException::class.java))
assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(Exception()))), IOException::class.java))
assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(SocketException()))), InterruptedIOException::class.java))
assertFalse(hasAssignableCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedException::class.java))
}
@Test fun `exact causes`() {
assertTrue(hasExactCause(Throwable(), Throwable::class.java))
assertTrue(hasExactCause(Exception(), Exception::class.java))
assertTrue(hasExactCause(IOException(), IOException::class.java))
assertTrue(hasExactCause(Exception(SocketException()), SocketException::class.java))
assertTrue(hasExactCause(Exception(Exception(IOException())), IOException::class.java))
assertTrue(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), IOException::class.java))
assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), SocketException::class.java))
assertTrue(hasExactCause(Exception(IllegalStateException(Exception(SSLException("IO")))), SSLException::class.java))
assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), InterruptedIOException::class.java))
assertTrue(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IllegalStateException::class.java))
}
@Test fun `no exact causes`() {
assertFalse(hasExactCause(Throwable(), Exception::class.java))
assertFalse(hasExactCause(Exception(), Throwable::class.java))
assertFalse(hasExactCause(SocketException(), IOException::class.java))
assertFalse(hasExactCause(IllegalStateException(), RuntimeException::class.java))
assertFalse(hasExactCause(Exception(SocketException()), IOException::class.java))
assertFalse(hasExactCause(Exception(IllegalStateException(Exception(IOException()))), RuntimeException::class.java))
assertFalse(hasExactCause(Exception(IllegalStateException(Exception(SocketException()))), IOException::class.java))
assertFalse(hasExactCause(Exception(IllegalStateException(Exception(InterruptedIOException()))), IOException::class.java))
}
}

View File

@ -13,31 +13,21 @@
<!-- org.schabi.newpipe.streams --> <!-- org.schabi.newpipe.streams -->
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="WebMWriter.java" files="DataReader.java"
lines="423,595"/> lines="46,93"/>
<suppress checks="LineLength"
files="WebMWriter.java"
lines="160,162"/>
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="OggFromWebMWriter.java" files="Mp4FromDashWriter.java"
lines="378,420"/> lines="643"/>
<suppress checks="LineLength"
files="OggFromWebMWriter.java"
lines="292,296"/>
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="Mp4FromDashWriter.java" files="OggFromWebMWriter.java"
lines="643"/> lines="380,422"/>
<suppress checks="LineLength"
files="Mp4FromDashWriter.java"
lines="677,678,720-724,738,762,848,850-855"/>
<suppress checks="InnerAssignment"
files="Mp4DashReader.java"
lines="190"/>
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="DataReader.java" files="WebMWriter.java"
lines="46,93"/> lines="419,591"/>
<suppress checks="LineLength"
files="WebMWriter.java"
lines="156,158"/>
</suppressions> </suppressions>