Merge branch 'dev' of https://github.com/TeamNewPipe/NewPipe into dev
This commit is contained in:
commit
1d0c3de65f
45 changed files with 1787 additions and 1598 deletions
38
.github/CONTRIBUTING.md
vendored
38
.github/CONTRIBUTING.md
vendored
|
@ -12,57 +12,53 @@ add a comment to it. You'll see exactly what is sent, the system is 100% transpa
|
||||||
## Issue reporting/feature requests
|
## Issue reporting/feature requests
|
||||||
|
|
||||||
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
* Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature
|
||||||
hasn't been reported/requested before
|
hasn't been reported/requested before.
|
||||||
* Check whether your issue/feature is already fixed/implemented
|
* Check whether your issue/feature is already fixed/implemented.
|
||||||
* Check if the issue still exists in the latest release/beta version
|
* Check if the issue still exists in the latest release/beta version.
|
||||||
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
|
* If you are an Android/Java developer, you are always welcome to fix an issue or implement a feature yourself. PRs welcome!
|
||||||
* We use English for development. Issues in other languages will be closed and ignored.
|
* We use English for development. Issues in other languages will be closed and ignored.
|
||||||
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
||||||
* When reporting a bug please give us a context, and a description how to reproduce it.
|
* Follow the template! Issues or feature requests not matching the template might be closed.
|
||||||
* Issues that only contain a generated bug report, but no description might be closed.
|
|
||||||
|
|
||||||
## Bug Fixing
|
## Bug Fixing
|
||||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to
|
||||||
tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request,
|
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a> to let us know that you intend to help. We'll send you further instructions. You may, on request,
|
||||||
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information).
|
||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
* NewPipe can be translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
* NewPipe is translated via [Weblate](https://hosted.weblate.org/projects/newpipe/strings/). You can log in there
|
||||||
with your GitHub account.
|
with your GitHub account.
|
||||||
|
* If the language you want to translate is not on Weblate, you can add it: see [How to add a new language](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-add-a-new-language-to-NewPipe) in the wiki.
|
||||||
|
|
||||||
## Code contribution
|
## Code contribution
|
||||||
|
|
||||||
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
|
* Stick to NewPipe's style conventions: follow [checkStyle](https://github.com/checkstyle/checkstyle). It will run each time you build the project.
|
||||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google
|
* Do not bring non-free software (e.g. binary blobs) into the project. Also, make sure you do not introduce Google
|
||||||
libraries.
|
libraries.
|
||||||
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
|
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy).
|
||||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You
|
* Make changes on a separate branch with a meaningful name, not on the master neither dev branch. This is commonly known as *feature branch workflow*. You
|
||||||
may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might
|
may then send your changes as a pull request (PR) on GitHub.
|
||||||
not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe)
|
|
||||||
* When submitting changes, you confirm that your code is licensed under the terms of the
|
* When submitting changes, you confirm that your code is licensed under the terms of the
|
||||||
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
[GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR
|
||||||
description. Untested code will **not** be merged!
|
description. Untested code will **not** be merged!
|
||||||
* Try to figure out yourself why builds on our CI fail.
|
* Try to figure out yourself why builds on our CI fail.
|
||||||
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
* Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job,
|
||||||
but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the
|
but if not, you are asked to rebase the dev branch manually and resolve the problems on your own. You can find help [on the wiki](https://github.com/TeamNewPipe/NewPipe/wiki/How-to-merge-a-PR). That will make the
|
||||||
maintainers' jobs way easier.
|
maintainers' jobs way easier.
|
||||||
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
* Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for
|
||||||
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again
|
||||||
about submission, or clearly state that in the description of your PR.
|
about submission, or clearly state that in the description of your PR.
|
||||||
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
* Respond yourselves if someone requests changes or otherwise raises issues about your PRs.
|
||||||
* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/).
|
|
||||||
* Check if your submission can be build with the current fdroid build server setup.
|
|
||||||
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
* Send PR that only cover one specific issue/solution/bug. Do not send PRs that are huge and consists of multiple
|
||||||
independent solutions.
|
independent solutions.
|
||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe).
|
|
||||||
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
* There is an IRC channel on Freenode which is regularly visited by the core team and other developers:
|
||||||
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
[#newpipe](irc:irc.freenode.net/newpipe). [Click here for Webchat](https://webchat.freenode.net/?channels=newpipe)!
|
||||||
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
* If you want to get in touch with the core team or one of our other contributors you can send an email to
|
||||||
tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
<a href="mailto:tnp@newpipe.schabi.org">tnp@newpipe.schabi.org</a>. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue
|
||||||
tracker described above!
|
tracker described above!
|
||||||
* Feel free to post suggestions, changes, ideas etc. on GitHub, IRC or the mailing list!
|
* Feel free to post suggestions, changes, ideas etc. on GitHub or IRC!
|
||||||
|
|
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -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
|
||||||
|
|
||||||
|
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
|
### Version
|
||||||
<!-- Which version are you using? -->
|
<!-- 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! -->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -17,8 +17,11 @@ import android.util.Log;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import com.grack.nanojson.JsonObject;
|
||||||
import org.json.JSONObject;
|
import com.grack.nanojson.JsonParser;
|
||||||
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@ -31,11 +34,6 @@ import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
|
||||||
|
@ -45,14 +43,11 @@ import okhttp3.Response;
|
||||||
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
|
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
|
||||||
|
|
||||||
private static final Application APP = App.getApp();
|
private static final Application APP = App.getApp();
|
||||||
private static final String GITHUB_APK_SHA1
|
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";
|
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||||
private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
|
private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
|
||||||
private static final int TIMEOUT_PERIOD = 30;
|
|
||||||
|
|
||||||
private SharedPreferences mPrefs;
|
|
||||||
private OkHttpClient client;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||||
|
@ -60,31 +55,30 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
* @return String with the apk's SHA1 fingeprint in hexadecimal
|
* @return String with the apk's SHA1 fingeprint in hexadecimal
|
||||||
*/
|
*/
|
||||||
private static String getCertificateSHA1Fingerprint() {
|
private static String getCertificateSHA1Fingerprint() {
|
||||||
PackageManager pm = APP.getPackageManager();
|
final PackageManager pm = APP.getPackageManager();
|
||||||
String packageName = APP.getPackageName();
|
final String packageName = APP.getPackageName();
|
||||||
int flags = PackageManager.GET_SIGNATURES;
|
final int flags = PackageManager.GET_SIGNATURES;
|
||||||
PackageInfo packageInfo = null;
|
PackageInfo packageInfo = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
packageInfo = pm.getPackageInfo(packageName, flags);
|
packageInfo = pm.getPackageInfo(packageName, flags);
|
||||||
} catch (PackageManager.NameNotFoundException ex) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
ErrorActivity.reportError(APP, ex, null, null,
|
ErrorActivity.reportError(APP, e, null, null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Could not find package info", R.string.app_ui_crash));
|
"Could not find package info", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
|
|
||||||
Signature[] signatures = packageInfo.signatures;
|
final Signature[] signatures = packageInfo.signatures;
|
||||||
byte[] cert = signatures[0].toByteArray();
|
final byte[] cert = signatures[0].toByteArray();
|
||||||
InputStream input = new ByteArrayInputStream(cert);
|
final InputStream input = new ByteArrayInputStream(cert);
|
||||||
|
|
||||||
CertificateFactory cf = null;
|
|
||||||
X509Certificate c = null;
|
X509Certificate c = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cf = CertificateFactory.getInstance("X509");
|
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||||
c = (X509Certificate) cf.generateCertificate(input);
|
c = (X509Certificate) cf.generateCertificate(input);
|
||||||
} catch (CertificateException ex) {
|
} catch (CertificateException e) {
|
||||||
ErrorActivity.reportError(APP, ex, null, null,
|
ErrorActivity.reportError(APP, e, null, null,
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Certificate error", R.string.app_ui_crash));
|
"Certificate error", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
|
@ -93,14 +87,10 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
byte[] publicKey = md.digest(c.getEncoded());
|
final byte[] publicKey = md.digest(c.getEncoded());
|
||||||
hexString = byte2HexFormatted(publicKey);
|
hexString = byte2HexFormatted(publicKey);
|
||||||
} catch (NoSuchAlgorithmException ex1) {
|
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||||
ErrorActivity.reportError(APP, ex1, null, null,
|
ErrorActivity.reportError(APP, e, 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",
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
|
||||||
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
"Could not retrieve SHA1 key", R.string.app_ui_crash));
|
||||||
}
|
}
|
||||||
|
@ -109,11 +99,11 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String byte2HexFormatted(final byte[] arr) {
|
private static String byte2HexFormatted(final byte[] arr) {
|
||||||
StringBuilder str = new StringBuilder(arr.length * 2);
|
final StringBuilder str = new StringBuilder(arr.length * 2);
|
||||||
|
|
||||||
for (int i = 0; i < arr.length; i++) {
|
for (int i = 0; i < arr.length; i++) {
|
||||||
String h = Integer.toHexString(arr[i]);
|
String h = Integer.toHexString(arr[i]);
|
||||||
int l = h.length();
|
final int l = h.length();
|
||||||
if (l == 1) {
|
if (l == 1) {
|
||||||
h = "0" + h;
|
h = "0" + h;
|
||||||
}
|
}
|
||||||
|
@ -134,11 +124,11 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(APP);
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(APP);
|
||||||
|
|
||||||
// Check if user has enabled/ disabled update checking
|
// Check if user has enabled/disabled update checking
|
||||||
// and if the current apk is a github one or not.
|
// and if the current apk is a github one or not.
|
||||||
if (!mPrefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
|
if (!prefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
|
||||||
this.cancel(true);
|
this.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,22 +140,12 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a network request to get latest NewPipe data.
|
// Make a network request to get latest NewPipe data.
|
||||||
// FIXME: Use DownloaderImp
|
|
||||||
if (client == null) {
|
|
||||||
|
|
||||||
client = new OkHttpClient.Builder()
|
|
||||||
.readTimeout(TIMEOUT_PERIOD, TimeUnit.SECONDS).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Request request = new Request.Builder().url(NEWPIPE_API_URL).build();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Response response = client.newCall(request).execute();
|
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
|
||||||
return response.body().string();
|
} catch (IOException | ReCaptchaException e) {
|
||||||
} catch (IOException ex) {
|
|
||||||
// connectivity problems, do not alarm user and fail silently
|
// connectivity problems, do not alarm user and fail silently
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, Log.getStackTraceString(ex));
|
Log.w(TAG, Log.getStackTraceString(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,21 +158,19 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject mainObject = new JSONObject(response);
|
final JsonObject githubStableObject = JsonParser.object().from(response)
|
||||||
JSONObject flavoursObject = mainObject.getJSONObject("flavors");
|
.getObject("flavors").getObject("github").getObject("stable");
|
||||||
JSONObject githubObject = flavoursObject.getJSONObject("github");
|
|
||||||
JSONObject githubStableObject = githubObject.getJSONObject("stable");
|
|
||||||
|
|
||||||
String versionName = githubStableObject.getString("version");
|
final String versionName = githubStableObject.getString("version");
|
||||||
String versionCode = githubStableObject.getString("version_code");
|
final int versionCode = githubStableObject.getInt("version_code");
|
||||||
String apkLocationUrl = githubStableObject.getString("apk");
|
final String apkLocationUrl = githubStableObject.getString("apk");
|
||||||
|
|
||||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
|
||||||
|
|
||||||
} catch (JSONException ex) {
|
} catch (JsonParserException e) {
|
||||||
// connectivity problems, do not alarm user and fail silently
|
// connectivity problems, do not alarm user and fail silently
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.w(TAG, Log.getStackTraceString(ex));
|
Log.w(TAG, Log.getStackTraceString(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,17 +186,17 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
*/
|
*/
|
||||||
private void compareAppVersionAndShowNotification(final String versionName,
|
private void compareAppVersionAndShowNotification(final String versionName,
|
||||||
final String apkLocationUrl,
|
final String apkLocationUrl,
|
||||||
final String versionCode) {
|
final int versionCode) {
|
||||||
int notificationId = 2000;
|
int notificationId = 2000;
|
||||||
|
|
||||||
if (BuildConfig.VERSION_CODE < Integer.valueOf(versionCode)) {
|
if (BuildConfig.VERSION_CODE < versionCode) {
|
||||||
|
|
||||||
// A pending intent to open the apk location url in the browser.
|
// A pending intent to open the apk location url in the browser.
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||||
PendingIntent pendingIntent
|
final PendingIntent pendingIntent
|
||||||
= PendingIntent.getActivity(APP, 0, intent, 0);
|
= PendingIntent.getActivity(APP, 0, intent, 0);
|
||||||
|
|
||||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
final NotificationCompat.Builder notificationBuilder = new NotificationCompat
|
||||||
.Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
|
.Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
|
||||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
@ -228,13 +206,14 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
|
||||||
.setContentText(APP.getString(R.string.app_update_notification_content_text)
|
.setContentText(APP.getString(R.string.app_update_notification_content_text)
|
||||||
+ " " + versionName);
|
+ " " + versionName);
|
||||||
|
|
||||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(APP);
|
final NotificationManagerCompat notificationManager
|
||||||
|
= NotificationManagerCompat.from(APP);
|
||||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
notificationManager.notify(notificationId, notificationBuilder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isConnected() {
|
private boolean isConnected() {
|
||||||
ConnectivityManager cm =
|
final ConnectivityManager cm =
|
||||||
(ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
|
(ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
return cm.getActiveNetworkInfo() != null
|
return cm.getActiveNetworkInfo() != null
|
||||||
&& cm.getActiveNetworkInfo().isConnected();
|
&& cm.getActiveNetworkInfo().isConnected();
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -301,6 +302,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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -103,6 +103,7 @@ import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
||||||
|
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
|
@ -806,19 +807,25 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
currentWorker.dispose();
|
currentWorker.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
|
|
||||||
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
|
currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe((@NonNull StreamInfo result) -> {
|
.subscribe((@NonNull final StreamInfo result) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
|
if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean(
|
||||||
|
getString(R.string.show_age_restricted_content), false)) {
|
||||||
|
hideAgeRestrictedContent();
|
||||||
|
} else {
|
||||||
currentInfo = result;
|
currentInfo = result;
|
||||||
handleResult(result);
|
handleResult(result);
|
||||||
showContent();
|
showContent();
|
||||||
}, (@NonNull Throwable throwable) -> {
|
}
|
||||||
|
}, (@NonNull final Throwable throwable) -> {
|
||||||
isLoading.set(false);
|
isLoading.set(false);
|
||||||
onError(throwable);
|
onError(throwable);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTabs() {
|
private void initTabs() {
|
||||||
|
@ -1232,6 +1239,16 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hideAgeRestrictedContent() {
|
||||||
|
showError(getString(R.string.restricted_video), false);
|
||||||
|
|
||||||
|
if (relatedStreamsLayout != null) { // tablet
|
||||||
|
relatedStreamsLayout.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewPager.setVisibility(View.GONE);
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
public void openDownloadDialog() {
|
public void openDownloadDialog() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -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();
|
||||||
|
@ -417,10 +431,24 @@ 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()) {
|
||||||
|
|
||||||
|
// 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);
|
NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (disposables != null) {
|
if (disposables != null) {
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -53,9 +53,6 @@ import org.schabi.newpipe.util.FireTvUtils;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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("");
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,30 +1264,36 @@ 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);
|
||||||
|
|
||||||
final float firstPointerX = event.getX(0);
|
// minimum threshold beyond which pinch gesture will work
|
||||||
final float secondPointerX = event.getX(1);
|
int minimumMove = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop();
|
||||||
|
|
||||||
final float diff = Math.abs(firstPointerX - secondPointerX);
|
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
|
||||||
if (firstPointerX > secondPointerX) {
|
// calculate current distance between the pointers
|
||||||
// second pointer is the anchor (the leftmost pointer)
|
double currentPointerDistance =
|
||||||
popupLayoutParams.x = (int) (event.getRawX() - diff);
|
Math.hypot(event.getX(0) - event.getX(1),
|
||||||
} else {
|
event.getY(0) - event.getY(1));
|
||||||
// first pointer is the anchor
|
|
||||||
popupLayoutParams.x = (int) event.getRawX();
|
// 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();
|
checkPopupPositionBounds();
|
||||||
updateScreenSize();
|
updateScreenSize();
|
||||||
|
|
||||||
final int width = (int) Math.min(screenWidth, diff);
|
updatePopupSize((int) Math.min(screenWidth, newWidth), -1);
|
||||||
updatePopupSize(width, -1);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
|
|
|
@ -100,7 +100,6 @@ public class Mp4DashReader {
|
||||||
moov = parseMoov(box);
|
moov = parseMoov(box);
|
||||||
break;
|
break;
|
||||||
case ATOM_SIDX:
|
case ATOM_SIDX:
|
||||||
break;
|
|
||||||
case ATOM_MFRA:
|
case ATOM_MFRA:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -148,52 +147,6 @@ public class Mp4DashReader {
|
||||||
return tracks[index];
|
return tracks[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Count all fragments present. This operation requires a seekable stream
|
|
||||||
*
|
|
||||||
* @return list with a basic info
|
|
||||||
* @throws IOException if the source stream is not seekeable
|
|
||||||
*/
|
|
||||||
int getFragmentsCount() throws IOException {
|
|
||||||
if (selectedTrack < 0) {
|
|
||||||
throw new IllegalStateException("track no selected");
|
|
||||||
}
|
|
||||||
if (!stream.canRewind()) {
|
|
||||||
throw new IOException("The provided stream doesn't allow seek");
|
|
||||||
}
|
|
||||||
|
|
||||||
Box tmp;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
if (box.type == ATOM_MOOF) {
|
|
||||||
tmp = box;
|
|
||||||
} else {
|
|
||||||
ensure(box);
|
|
||||||
tmp = readBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (tmp.type == ATOM_MOOF) {
|
|
||||||
ensure(readBox(ATOM_MFHD));
|
|
||||||
Box traf;
|
|
||||||
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
|
|
||||||
Box tfhd = readBox(ATOM_TFHD);
|
|
||||||
if (parseTfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
|
|
||||||
count++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ensure(tfhd);
|
|
||||||
ensure(traf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ensure(tmp);
|
|
||||||
} while (stream.available() && (tmp = readBox()) != null);
|
|
||||||
|
|
||||||
rewind();
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getBrands() {
|
public int[] getBrands() {
|
||||||
if (brands == null) {
|
if (brands == null) {
|
||||||
throw new IllegalStateException("Not parsed");
|
throw new IllegalStateException("Not parsed");
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 // ????????
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
82
app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
Normal file
82
app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,9 +152,31 @@ public final class Localization {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String localizeStreamCount(final Context context, final long streamCount) {
|
public static String localizeStreamCount(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);
|
||||||
|
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,
|
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
|
||||||
localizeNumber(context, 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) {
|
||||||
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +208,7 @@ 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
|
||||||
|
|
|
@ -45,22 +45,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
|
||||||
|
|
|
@ -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-->
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
<string name="songs">Nummers</string>
|
||||||
|
<string name="albums">Albums</string>
|
||||||
|
<string name="artists">Artiesten</string>
|
||||||
</resources>
|
</resources>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -136,8 +136,9 @@
|
||||||
<string name="play_btn_text">Play</string>
|
<string name="play_btn_text">Play</string>
|
||||||
<string name="content">Content</string>
|
<string name="content">Content</string>
|
||||||
<string name="show_age_restricted_content_title">Age restricted content</string>
|
<string name="show_age_restricted_content_title">Age restricted content</string>
|
||||||
<string name="duration_live">Live</string>
|
|
||||||
<string name="video_is_age_restricted">Show age restricted video. Future changes are possible from the settings.</string>
|
<string name="video_is_age_restricted">Show age restricted video. Future changes are possible from the settings.</string>
|
||||||
|
<string name="restricted_video">This video is age restricted.\n\nIf you want to view it, enable \"Age restricted content\" in the settings.</string>
|
||||||
|
<string name="duration_live">Live</string>
|
||||||
<string name="downloads">Downloads</string>
|
<string name="downloads">Downloads</string>
|
||||||
<string name="downloads_title">Downloads</string>
|
<string name="downloads_title">Downloads</string>
|
||||||
<string name="error_report_title">Error report</string>
|
<string name="error_report_title">Error report</string>
|
||||||
|
@ -150,6 +151,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 +288,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>
|
||||||
|
@ -643,4 +651,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>
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
||||||
files="OggFromWebMWriter.java"
|
|
||||||
lines="378,420"/>
|
|
||||||
<suppress checks="LineLength"
|
|
||||||
files="OggFromWebMWriter.java"
|
|
||||||
lines="292,296"/>
|
|
||||||
|
|
||||||
<suppress checks="FinalParameters"
|
<suppress checks="FinalParameters"
|
||||||
files="Mp4FromDashWriter.java"
|
files="Mp4FromDashWriter.java"
|
||||||
lines="643"/>
|
lines="643"/>
|
||||||
<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="OggFromWebMWriter.java"
|
||||||
lines="46,93"/>
|
lines="380,422"/>
|
||||||
|
|
||||||
|
<suppress checks="FinalParameters"
|
||||||
|
files="WebMWriter.java"
|
||||||
|
lines="419,591"/>
|
||||||
|
<suppress checks="LineLength"
|
||||||
|
files="WebMWriter.java"
|
||||||
|
lines="156,158"/>
|
||||||
</suppressions>
|
</suppressions>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Improved
|
Improved
|
||||||
|
|
||||||
• Added upload date on stream grid items
|
• Added upload date and view count on stream grid items
|
||||||
• Improvements for the drawer header layout
|
• Improvements for the drawer header layout
|
||||||
|
|
||||||
Fixed
|
Fixed
|
||||||
|
|
Loading…
Reference in a new issue