From edff3c35f297291ae527733947d0213509628ec5 Mon Sep 17 00:00:00 2001 From: Linus Jahn Date: Thu, 26 Mar 2020 01:50:32 +0100 Subject: [PATCH 01/33] ThemeHelper: Fix 'the the' typo --- app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index bd51919c7..70984b7ce 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -175,7 +175,7 @@ public 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. */ public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); @@ -185,7 +185,7 @@ public 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. */ public static int resolveColorFromAttr(Context context, @AttrRes int attrColor) { final TypedValue value = new TypedValue(); From 41061d0289941c528cc1ccbcbe7d26fea9b722b7 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Thu, 2 Apr 2020 19:58:06 +0200 Subject: [PATCH 02/33] Use DownloaderImpl in CheckForNewAppVersionTask --- .../newpipe/CheckForNewAppVersionTask.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java index 1f2808bed..7229aa0a2 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java @@ -19,6 +19,7 @@ import androidx.core.app.NotificationManagerCompat; import org.json.JSONException; import org.json.JSONObject; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; @@ -31,11 +32,8 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; /** * AsyncTask to check if there is a newer version of the NewPipe github apk available or not. @@ -150,19 +148,9 @@ public class CheckForNewAppVersionTask extends AsyncTask { } // 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 { - Response response = client.newCall(request).execute(); - return response.body().string(); - } catch (IOException ex) { + return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody(); + } catch (IOException | ReCaptchaException ex) { // connectivity problems, do not alarm user and fail silently if (DEBUG) { Log.w(TAG, Log.getStackTraceString(ex)); From 5683ad666611e7db74abd802367630d70f86cbdc Mon Sep 17 00:00:00 2001 From: bopol Date: Fri, 3 Apr 2020 23:34:16 +0200 Subject: [PATCH 03/33] mention view count added in 0.19.2 in changelog --- fastlane/metadata/android/en-US/changelogs/920.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/changelogs/920.txt b/fastlane/metadata/android/en-US/changelogs/920.txt index fd93c28c1..85bce5286 100644 --- a/fastlane/metadata/android/en-US/changelogs/920.txt +++ b/fastlane/metadata/android/en-US/changelogs/920.txt @@ -1,6 +1,6 @@ Improved -• Added upload date on stream grid items +• Added upload date and view count on stream grid items • Improvements for the drawer header layout Fixed From fe2858bc75de8a3ee43c6756b97aa2c7746ff01a Mon Sep 17 00:00:00 2001 From: bopol Date: Sat, 4 Apr 2020 12:10:54 +0200 Subject: [PATCH 04/33] update contributing.md --- .github/CONTRIBUTING.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 873c1780f..8e8939af4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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 * Search the [existing issues](https://github.com/TeamNewPipe/NewPipe/issues) first to make sure your issue/feature -hasn't been reported/requested before -* Check whether your issue/feature is already fixed/implemented -* 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! +hasn't been reported/requested before. +* Check whether your issue/feature is already fixed/implemented. +* 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 an issue or implement a feature yourself. PRs welcome! * 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. -* When reporting a bug please give us a context, and a description how to reproduce it. -* Issues that only contain a generated bug report, but no description might be closed. +* Follow the template! Issues or feature requests not matching the template might be closed. ## 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 -tnp@newpipe.schabi.org to let me 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. +tnp@newpipe.schabi.org to let me 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). ## 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. +* 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 -* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :)) -* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google +* 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 libraries. * 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 - may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might - not be considered, GitHub is the primary platform. (This only affects you if you are a member of TeamNewPipe) +* Make changes on a separate branch with 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 (PR) on GitHub. * 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). * 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! * 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, - 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. * 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 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. -* 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 independent solutions. ## 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: [#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 - tnp(at)schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue + tnp@newpipe.schabi.org. Please do not send issue reports, they will be ignored and remain unanswered! Use the GitHub issue 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! From 8d6965713c495650438a4e3fb3e62bf94aa11ec8 Mon Sep 17 00:00:00 2001 From: B0pol Date: Sat, 4 Apr 2020 12:28:20 +0200 Subject: [PATCH 05/33] Update .github/CONTRIBUTING.md Co-Authored-By: Tobias Groza --- .github/CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8e8939af4..f61e320c9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -22,7 +22,7 @@ hasn't been reported/requested before. ## 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 -tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, +tnp@newpipe.schabi.org 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). ## Translation @@ -36,8 +36,8 @@ with your GitHub account. * 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 libraries. -* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy) -* Make changes on a separate branch with meaningful name, not on the master neither dev branch. This is commonly known as *feature branch workflow*. You +* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy). +* 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 (PR) on GitHub. * 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). From 12405f40595bfa848fbf11a716018febdd7ec110 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Sat, 4 Apr 2020 11:22:00 +0000 Subject: [PATCH 06/33] Made the bug report template less daunting The biggest problem was that the reporter needed to delete a lot of text to be able to give a clean report. Now most of that is comments. --- .github/ISSUE_TEMPLATE/bug_report.md | 36 +++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 202e8a71a..dbc1c05a5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,38 +7,40 @@ assignees: '' --- -### Version - -- +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. +--> + + + +### Version + +- ### Steps to reproduce the bug - -Steps to reproduce the behavior: + + + ### Expected behavior -Tell us what you expected to happen. + ### Actual behaviour -Tell us what happens instead. + -### Screenshots/Screen records -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. +### Screenshots/Screen recordings + ### 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: + From a5a497c4eaa5125f0171adf1bc40763db0f49ff6 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Fri, 3 Apr 2020 19:44:24 +0200 Subject: [PATCH 07/33] Clean up CheckForNewAppVersionTask --- .../newpipe/CheckForNewAppVersionTask.java | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java index 7229aa0a2..625f514e9 100644 --- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java +++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersionTask.java @@ -17,8 +17,10 @@ import android.util.Log; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import org.json.JSONException; -import org.json.JSONObject; +import com.grack.nanojson.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.UserAction; @@ -33,8 +35,6 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import okhttp3.OkHttpClient; - /** * AsyncTask to check if there is a newer version of the NewPipe github apk available or not. * If there is a newer version we show a notification, informing the user. On tapping @@ -43,14 +43,11 @@ import okhttp3.OkHttpClient; public class CheckForNewAppVersionTask extends AsyncTask { private static final boolean DEBUG = MainActivity.DEBUG; private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName(); + private static final Application APP = App.getApp(); private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"; private static final String 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. @@ -58,31 +55,30 @@ public class CheckForNewAppVersionTask extends AsyncTask { * @return String with the apk's SHA1 fingeprint in hexadecimal */ private static String getCertificateSHA1Fingerprint() { - PackageManager pm = APP.getPackageManager(); - String packageName = APP.getPackageName(); - int flags = PackageManager.GET_SIGNATURES; + final PackageManager pm = APP.getPackageManager(); + final String packageName = APP.getPackageName(); + final int flags = PackageManager.GET_SIGNATURES; PackageInfo packageInfo = null; try { packageInfo = pm.getPackageInfo(packageName, flags); - } catch (PackageManager.NameNotFoundException ex) { - ErrorActivity.reportError(APP, ex, null, null, + } catch (PackageManager.NameNotFoundException e) { + ErrorActivity.reportError(APP, e, null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Could not find package info", R.string.app_ui_crash)); } - Signature[] signatures = packageInfo.signatures; - byte[] cert = signatures[0].toByteArray(); - InputStream input = new ByteArrayInputStream(cert); + final Signature[] signatures = packageInfo.signatures; + final byte[] cert = signatures[0].toByteArray(); + final InputStream input = new ByteArrayInputStream(cert); - CertificateFactory cf = null; X509Certificate c = null; try { - cf = CertificateFactory.getInstance("X509"); + final CertificateFactory cf = CertificateFactory.getInstance("X509"); c = (X509Certificate) cf.generateCertificate(input); - } catch (CertificateException ex) { - ErrorActivity.reportError(APP, ex, null, null, + } catch (CertificateException e) { + ErrorActivity.reportError(APP, e, null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Certificate error", R.string.app_ui_crash)); } @@ -91,14 +87,10 @@ public class CheckForNewAppVersionTask extends AsyncTask { try { MessageDigest md = MessageDigest.getInstance("SHA1"); - byte[] publicKey = md.digest(c.getEncoded()); + final byte[] publicKey = md.digest(c.getEncoded()); hexString = byte2HexFormatted(publicKey); - } catch (NoSuchAlgorithmException ex1) { - ErrorActivity.reportError(APP, ex1, null, null, - ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", - "Could not retrieve SHA1 key", R.string.app_ui_crash)); - } catch (CertificateEncodingException ex2) { - ErrorActivity.reportError(APP, ex2, null, null, + } catch (NoSuchAlgorithmException | CertificateEncodingException e) { + ErrorActivity.reportError(APP, e, null, null, ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Could not retrieve SHA1 key", R.string.app_ui_crash)); } @@ -107,11 +99,11 @@ public class CheckForNewAppVersionTask extends AsyncTask { } 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++) { String h = Integer.toHexString(arr[i]); - int l = h.length(); + final int l = h.length(); if (l == 1) { h = "0" + h; } @@ -132,11 +124,11 @@ public class CheckForNewAppVersionTask extends AsyncTask { @Override 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. - 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); } } @@ -150,10 +142,10 @@ public class CheckForNewAppVersionTask extends AsyncTask { // Make a network request to get latest NewPipe data. try { return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody(); - } catch (IOException | ReCaptchaException ex) { + } catch (IOException | ReCaptchaException e) { // connectivity problems, do not alarm user and fail silently if (DEBUG) { - Log.w(TAG, Log.getStackTraceString(ex)); + Log.w(TAG, Log.getStackTraceString(e)); } } @@ -166,21 +158,19 @@ public class CheckForNewAppVersionTask extends AsyncTask { if (response != null) { try { - JSONObject mainObject = new JSONObject(response); - JSONObject flavoursObject = mainObject.getJSONObject("flavors"); - JSONObject githubObject = flavoursObject.getJSONObject("github"); - JSONObject githubStableObject = githubObject.getJSONObject("stable"); + final JsonObject githubStableObject = JsonParser.object().from(response) + .getObject("flavors").getObject("github").getObject("stable"); - String versionName = githubStableObject.getString("version"); - String versionCode = githubStableObject.getString("version_code"); - String apkLocationUrl = githubStableObject.getString("apk"); + final String versionName = githubStableObject.getString("version"); + final int versionCode = githubStableObject.getInt("version_code"); + final String apkLocationUrl = githubStableObject.getString("apk"); compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode); - } catch (JSONException ex) { + } catch (JsonParserException e) { // connectivity problems, do not alarm user and fail silently if (DEBUG) { - Log.w(TAG, Log.getStackTraceString(ex)); + Log.w(TAG, Log.getStackTraceString(e)); } } } @@ -196,17 +186,17 @@ public class CheckForNewAppVersionTask extends AsyncTask { */ private void compareAppVersionAndShowNotification(final String versionName, final String apkLocationUrl, - final String versionCode) { + final int versionCode) { 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. - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl)); - PendingIntent pendingIntent + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl)); + final PendingIntent pendingIntent = 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)) .setSmallIcon(R.drawable.ic_newpipe_update) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -216,13 +206,14 @@ public class CheckForNewAppVersionTask extends AsyncTask { .setContentText(APP.getString(R.string.app_update_notification_content_text) + " " + versionName); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(APP); + final NotificationManagerCompat notificationManager + = NotificationManagerCompat.from(APP); notificationManager.notify(notificationId, notificationBuilder.build()); } } private boolean isConnected() { - ConnectivityManager cm = + final ConnectivityManager cm = (ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE); return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected(); From c72663948499ad64968b77dc98718004ea4d33c8 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sun, 5 Apr 2020 10:57:54 -0300 Subject: [PATCH 08/33] Avoid unnecessary changes to the upload date of saved streams The upload date was being updated regardless if the new one had more precision or not, this caused items on the feed to jump around when the user opened one of them. This changes it to only update when the existent upload date is null or the new one has a higher precision (i.e. is not an approximation). --- .../org/schabi/newpipe/database/stream/dao/StreamDAO.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt index 43793becb..517f3cf0b 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.kt @@ -80,7 +80,12 @@ abstract class StreamDAO : BasicDAO { val isNewerStreamLive = newerStream.streamType == AUDIO_LIVE_STREAM || newerStream.streamType == LIVE_STREAM 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.textualUploadDate = existentMinimalStream.textualUploadDate newerStream.isUploadDateApproximation = existentMinimalStream.isUploadDateApproximation From 3855e488cb78959dbf6327ff187fda8867e42b43 Mon Sep 17 00:00:00 2001 From: Xiang Rong Lin <41164160+XiangRongLin@users.noreply.github.com> Date: Fri, 3 Apr 2020 18:47:53 +0200 Subject: [PATCH 09/33] Save and restore playback parameters into/from preferences Playback parameters are speed, pitch and skip silence. Remove parameters being passed on as intent to the player, since the parameters can be restored from the preferences instead. # Conflicts: # app/src/main/java/org/schabi/newpipe/player/BasePlayer.java --- .../org/schabi/newpipe/player/BasePlayer.java | 40 ++++++++++++++----- .../schabi/newpipe/util/NavigationHelper.java | 3 -- app/src/main/res/values/settings_keys.xml | 3 ++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 601fd96bf..ecae13290 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -118,12 +118,6 @@ public abstract class BasePlayer implements @NonNull public static final String REPEAT_MODE = "repeat_mode"; @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"; @NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key"; @@ -287,11 +281,12 @@ public abstract class BasePlayer implements 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 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 .getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted()); @@ -330,6 +325,20 @@ public abstract class BasePlayer implements /*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, @Player.RepeatMode final int repeatMode, final float playbackSpeed, @@ -1470,9 +1479,20 @@ public abstract class BasePlayer implements public void setPlaybackParameters(final float speed, final float pitch, final boolean skipSilence) { + savePlaybackParametersToPreferences(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() { return playQueue; } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 32c062571..ccaa79f98 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -120,9 +120,6 @@ public final class NavigationHelper { final boolean isMuted) { return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) .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.IS_MUTED, isMuted); } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 980af0943..b5eeae6aa 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -177,6 +177,9 @@ enable_playback_resume enable_playback_state_lists playback_unhook_key + playback_speed_key + playback_pitch_key + playback_skip_silence_key app_language_key enable_lock_screen_video_thumbnail From 225b9e1b159edbf167268b51307eadf8988e1a77 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sun, 5 Apr 2020 16:57:28 -0300 Subject: [PATCH 10/33] Fix visibility of group sort button in the subscriptions fragment --- .../local/subscription/SubscriptionFragment.kt | 8 +++----- .../subscription/item/HeaderWithMenuItem.kt | 17 +++++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 295c9d0a7..eae406ed2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -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.FeedGroupReorderDialog 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.EXPORT_COMPLETE_ACTION import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH @@ -361,11 +362,8 @@ class SubscriptionFragment : BaseStateFragment() { feedGroupsListState = null } - if (groups.size < 2) { - items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_HIDE_MENU_ITEM) } - } else { - items_list.post { feedGroupsSortMenuItem.notifyChanged(HeaderWithMenuItem.PAYLOAD_SHOW_MENU_ITEM) } - } + feedGroupsSortMenuItem.showMenuItem = groups.size > 1 + items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) } } /////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt index 5ffdfe7c1..cf7a4c01c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt @@ -10,23 +10,19 @@ import org.schabi.newpipe.R class HeaderWithMenuItem( val title: String, @DrawableRes val itemIcon: Int = 0, + var showMenuItem: Boolean = true, private val onClickListener: (() -> Unit)? = null, private val menuItemOnClickListener: (() -> Unit)? = null ) : Item() { companion object { - const val PAYLOAD_SHOW_MENU_ITEM = 1 - const val PAYLOAD_HIDE_MENU_ITEM = 2 + const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1 } override fun getLayout(): Int = R.layout.header_with_menu_item - override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList) { - if (payloads.contains(PAYLOAD_SHOW_MENU_ITEM)) { - viewHolder.header_menu_item.visibility = VISIBLE - return - } else if (payloads.contains(PAYLOAD_HIDE_MENU_ITEM)) { - viewHolder.header_menu_item.visibility = GONE + if (payloads.contains(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM)) { + updateMenuItemVisibility(viewHolder) return } @@ -44,5 +40,10 @@ class HeaderWithMenuItem( val menuItemListener: OnClickListener? = menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } } viewHolder.header_menu_item.setOnClickListener(menuItemListener) + updateMenuItemVisibility(viewHolder) + } + + private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) { + viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE } } \ No newline at end of file From 5166c22ce9a8fc37b93beb1f91516cd727f1b671 Mon Sep 17 00:00:00 2001 From: Tobias Groza Date: Mon, 6 Apr 2020 21:01:40 +0200 Subject: [PATCH 11/33] Fix webalte badge and link in readme Update releases badge URL to point to releases instead of GitHub repo page See WeblateOrg/weblate#3668 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 987327ab8..50eb40594 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@

- + - +

From ff9a1ebb1bd0101c692cc99bdfc63626d6bc97f8 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Wed, 8 Apr 2020 12:08:01 -0300 Subject: [PATCH 12/33] checkstyle * drop unused methods * split blobs * make no final parameters --- .../schabi/newpipe/streams/DataReader.java | 524 ++--- .../schabi/newpipe/streams/Mp4DashReader.java | 1941 ++++++++--------- .../newpipe/streams/Mp4FromDashWriter.java | 43 +- .../newpipe/streams/OggFromWebMWriter.java | 10 +- .../schabi/newpipe/streams/WebMWriter.java | 12 +- checkstyle-suppressions.xml | 30 - 6 files changed, 1244 insertions(+), 1316 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java index 0f142ad32..01a124c9e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java @@ -1,262 +1,262 @@ -package org.schabi.newpipe.streams; - -import org.schabi.newpipe.streams.io.SharpStream; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -/** - * @author kapodamy - */ -public class DataReader { - public static final int SHORT_SIZE = 2; - public static final int LONG_SIZE = 8; - public static final int INTEGER_SIZE = 4; - public static final int FLOAT_SIZE = 4; - - private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB - - private long position = 0; - private final SharpStream stream; - - private InputStream view; - private int viewSize; - - public DataReader(final SharpStream stream) { - this.stream = stream; - this.readOffset = this.readBuffer.length; - } - - public long position() { - return position; - } - - public int read() throws IOException { - if (fillBuffer()) { - return -1; - } - - position++; - readCount--; - - return readBuffer[readOffset++] & 0xFF; - } - - public long skipBytes(long amount) throws IOException { - if (readCount < 0) { - return 0; - } else if (readCount == 0) { - amount = stream.skip(amount); - } else { - if (readCount > amount) { - readCount -= (int) amount; - readOffset += (int) amount; - } else { - amount = readCount + stream.skip(amount - readCount); - readCount = 0; - readOffset = readBuffer.length; - } - } - - position += amount; - return amount; - } - - public int readInt() throws IOException { - primitiveRead(INTEGER_SIZE); - return primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; - } - - public long readUnsignedInt() throws IOException { - long value = readInt(); - return value & 0xffffffffL; - } - - - public short readShort() throws IOException { - primitiveRead(SHORT_SIZE); - return (short) (primitive[0] << 8 | primitive[1]); - } - - public long readLong() throws IOException { - primitiveRead(LONG_SIZE); - long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; - long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; - return high << 32 | low; - } - - public int read(final byte[] buffer) throws IOException { - return read(buffer, 0, buffer.length); - } - - public int read(final byte[] buffer, int offset, int count) throws IOException { - if (readCount < 0) { - return -1; - } - int total = 0; - - if (count >= readBuffer.length) { - if (readCount > 0) { - System.arraycopy(readBuffer, readOffset, buffer, offset, readCount); - readOffset += readCount; - - offset += readCount; - count -= readCount; - - total = readCount; - readCount = 0; - } - total += Math.max(stream.read(buffer, offset, count), 0); - } else { - while (count > 0 && !fillBuffer()) { - int read = Math.min(readCount, count); - System.arraycopy(readBuffer, readOffset, buffer, offset, read); - - readOffset += read; - readCount -= read; - - offset += read; - count -= read; - - total += read; - } - } - - position += total; - return total; - } - - public boolean available() { - return readCount > 0 || stream.available() > 0; - } - - public void rewind() throws IOException { - stream.rewind(); - - if ((position - viewSize) > 0) { - viewSize = 0; // drop view - } else { - viewSize += position; - } - - position = 0; - readOffset = readBuffer.length; - readCount = 0; - } - - public boolean canRewind() { - return stream.canRewind(); - } - - /** - * Wraps this instance of {@code DataReader} into {@code InputStream} - * object. Note: Any read in the {@code DataReader} will not modify - * (decrease) the view size - * - * @param size the size of the view - * @return the view - */ - public InputStream getView(final int size) { - if (view == null) { - view = new InputStream() { - @Override - public int read() throws IOException { - if (viewSize < 1) { - return -1; - } - int res = DataReader.this.read(); - if (res > 0) { - viewSize--; - } - return res; - } - - @Override - public int read(final byte[] buffer) throws IOException { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(final byte[] buffer, final int offset, final int count) - throws IOException { - if (viewSize < 1) { - return -1; - } - - int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count)); - viewSize -= res; - - return res; - } - - @Override - public long skip(final long amount) throws IOException { - if (viewSize < 1) { - return 0; - } - int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize)); - viewSize -= res; - - return res; - } - - @Override - public int available() { - return viewSize; - } - - @Override - public void close() { - viewSize = 0; - } - - @Override - public boolean markSupported() { - return false; - } - - }; - } - viewSize = size; - - return view; - } - - private final short[] primitive = new short[LONG_SIZE]; - - private void primitiveRead(final int amount) throws IOException { - byte[] buffer = new byte[amount]; - int read = read(buffer, 0, amount); - - if (read != amount) { - throw new EOFException("Truncated stream, missing " - + String.valueOf(amount - read) + " bytes"); - } - - for (int i = 0; i < amount; i++) { - // the "byte" data type in java is signed and is very annoying - primitive[i] = (short) (buffer[i] & 0xFF); - } - } - - private final byte[] readBuffer = new byte[BUFFER_SIZE]; - private int readOffset; - private int readCount; - - private boolean fillBuffer() throws IOException { - if (readCount < 0) { - return true; - } - if (readOffset >= readBuffer.length) { - readCount = stream.read(readBuffer); - if (readCount < 1) { - readCount = -1; - return true; - } - readOffset = 0; - } - - return readCount < 1; - } -} +package org.schabi.newpipe.streams; + +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author kapodamy + */ +public class DataReader { + public static final int SHORT_SIZE = 2; + public static final int LONG_SIZE = 8; + public static final int INTEGER_SIZE = 4; + public static final int FLOAT_SIZE = 4; + + private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB + + private long position = 0; + private final SharpStream stream; + + private InputStream view; + private int viewSize; + + public DataReader(final SharpStream stream) { + this.stream = stream; + this.readOffset = this.readBuffer.length; + } + + public long position() { + return position; + } + + public int read() throws IOException { + if (fillBuffer()) { + return -1; + } + + position++; + readCount--; + + return readBuffer[readOffset++] & 0xFF; + } + + public long skipBytes(long amount) throws IOException { + if (readCount < 0) { + return 0; + } else if (readCount == 0) { + amount = stream.skip(amount); + } else { + if (readCount > amount) { + readCount -= (int) amount; + readOffset += (int) amount; + } else { + amount = readCount + stream.skip(amount - readCount); + readCount = 0; + readOffset = readBuffer.length; + } + } + + position += amount; + return amount; + } + + public int readInt() throws IOException { + primitiveRead(INTEGER_SIZE); + return primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; + } + + public long readUnsignedInt() throws IOException { + long value = readInt(); + return value & 0xffffffffL; + } + + + public short readShort() throws IOException { + primitiveRead(SHORT_SIZE); + return (short) (primitive[0] << 8 | primitive[1]); + } + + public long readLong() throws IOException { + primitiveRead(LONG_SIZE); + long high = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; + long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; + return high << 32 | low; + } + + public int read(byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + public int read(byte[] buffer, int offset, int count) throws IOException { + if (readCount < 0) { + return -1; + } + int total = 0; + + if (count >= readBuffer.length) { + if (readCount > 0) { + System.arraycopy(readBuffer, readOffset, buffer, offset, readCount); + readOffset += readCount; + + offset += readCount; + count -= readCount; + + total = readCount; + readCount = 0; + } + total += Math.max(stream.read(buffer, offset, count), 0); + } else { + while (count > 0 && !fillBuffer()) { + int read = Math.min(readCount, count); + System.arraycopy(readBuffer, readOffset, buffer, offset, read); + + readOffset += read; + readCount -= read; + + offset += read; + count -= read; + + total += read; + } + } + + position += total; + return total; + } + + public boolean available() { + return readCount > 0 || stream.available() > 0; + } + + public void rewind() throws IOException { + stream.rewind(); + + if ((position - viewSize) > 0) { + viewSize = 0; // drop view + } else { + viewSize += position; + } + + position = 0; + readOffset = readBuffer.length; + readCount = 0; + } + + public boolean canRewind() { + return stream.canRewind(); + } + + /** + * Wraps this instance of {@code DataReader} into {@code InputStream} + * object. Note: Any read in the {@code DataReader} will not modify + * (decrease) the view size + * + * @param size the size of the view + * @return the view + */ + public InputStream getView(final int size) { + if (view == null) { + view = new InputStream() { + @Override + public int read() throws IOException { + if (viewSize < 1) { + return -1; + } + int res = DataReader.this.read(); + if (res > 0) { + viewSize--; + } + return res; + } + + @Override + public int read(final byte[] buffer) throws IOException { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(final byte[] buffer, final int offset, final int count) + throws IOException { + if (viewSize < 1) { + return -1; + } + + int res = DataReader.this.read(buffer, offset, Math.min(viewSize, count)); + viewSize -= res; + + return res; + } + + @Override + public long skip(final long amount) throws IOException { + if (viewSize < 1) { + return 0; + } + int res = (int) DataReader.this.skipBytes(Math.min(amount, viewSize)); + viewSize -= res; + + return res; + } + + @Override + public int available() { + return viewSize; + } + + @Override + public void close() { + viewSize = 0; + } + + @Override + public boolean markSupported() { + return false; + } + + }; + } + viewSize = size; + + return view; + } + + private final short[] primitive = new short[LONG_SIZE]; + + private void primitiveRead(final int amount) throws IOException { + byte[] buffer = new byte[amount]; + int read = read(buffer, 0, amount); + + if (read != amount) { + throw new EOFException("Truncated stream, missing " + + String.valueOf(amount - read) + " bytes"); + } + + for (int i = 0; i < amount; i++) { + // the "byte" data type in java is signed and is very annoying + primitive[i] = (short) (buffer[i] & 0xFF); + } + } + + private final byte[] readBuffer = new byte[BUFFER_SIZE]; + private int readOffset; + private int readCount; + + private boolean fillBuffer() throws IOException { + if (readCount < 0) { + return true; + } + if (readOffset >= readBuffer.length) { + readCount = stream.read(readBuffer); + if (readCount < 1) { + readCount = -1; + return true; + } + readOffset = 0; + } + + return readCount < 1; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java index 8fad7fa7c..ff3aabd78 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4DashReader.java @@ -1,994 +1,947 @@ -package org.schabi.newpipe.streams; - -import org.schabi.newpipe.streams.io.SharpStream; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.NoSuchElementException; - -/** - * @author kapodamy - */ -public class Mp4DashReader { - private static final int ATOM_MOOF = 0x6D6F6F66; - private static final int ATOM_MFHD = 0x6D666864; - private static final int ATOM_TRAF = 0x74726166; - private static final int ATOM_TFHD = 0x74666864; - private static final int ATOM_TFDT = 0x74666474; - private static final int ATOM_TRUN = 0x7472756E; - private static final int ATOM_MDIA = 0x6D646961; - private static final int ATOM_FTYP = 0x66747970; - private static final int ATOM_SIDX = 0x73696478; - private static final int ATOM_MOOV = 0x6D6F6F76; - private static final int ATOM_MDAT = 0x6D646174; - private static final int ATOM_MVHD = 0x6D766864; - private static final int ATOM_TRAK = 0x7472616B; - private static final int ATOM_MVEX = 0x6D766578; - private static final int ATOM_TREX = 0x74726578; - private static final int ATOM_TKHD = 0x746B6864; - private static final int ATOM_MFRA = 0x6D667261; - private static final int ATOM_MDHD = 0x6D646864; - private static final int ATOM_EDTS = 0x65647473; - private static final int ATOM_ELST = 0x656C7374; - private static final int ATOM_HDLR = 0x68646C72; - private static final int ATOM_MINF = 0x6D696E66; - private static final int ATOM_DINF = 0x64696E66; - private static final int ATOM_STBL = 0x7374626C; - private static final int ATOM_STSD = 0x73747364; - private static final int ATOM_VMHD = 0x766D6864; - private static final int ATOM_SMHD = 0x736D6864; - - private static final int BRAND_DASH = 0x64617368; - private static final int BRAND_ISO5 = 0x69736F35; - - private static final int HANDLER_VIDE = 0x76696465; - private static final int HANDLER_SOUN = 0x736F756E; - private static final int HANDLER_SUBT = 0x73756274; - - private final DataReader stream; - - private Mp4Track[] tracks = null; - private int[] brands = null; - - private Box box; - private Moof moof; - - private boolean chunkZero = false; - - private int selectedTrack = -1; - private Box backupBox = null; - - public enum TrackKind { - Audio, Video, Subtitles, Other - } - - public Mp4DashReader(final SharpStream source) { - this.stream = new DataReader(source); - } - - public void parse() throws IOException, NoSuchElementException { - if (selectedTrack > -1) { - return; - } - - box = readBox(ATOM_FTYP); - brands = parseFtyp(box); - switch (brands[0]) { - case BRAND_DASH: - case BRAND_ISO5:// ¿why not? - break; - default: - throw new NoSuchElementException( - "Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is " - + boxName(brands[0]) - ); - } - - Moov moov = null; - int i; - - while (box.type != ATOM_MOOF) { - ensure(box); - box = readBox(); - - switch (box.type) { - case ATOM_MOOV: - moov = parseMoov(box); - break; - case ATOM_SIDX: - break; - case ATOM_MFRA: - break; - } - } - - if (moov == null) { - throw new IOException("The provided Mp4 doesn't have the 'moov' box"); - } - - tracks = new Mp4Track[moov.trak.length]; - - for (i = 0; i < tracks.length; i++) { - tracks[i] = new Mp4Track(); - tracks[i].trak = moov.trak[i]; - - if (moov.mvexTrex != null) { - for (Trex mvexTrex : moov.mvexTrex) { - if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) { - tracks[i].trex = mvexTrex; - } - } - } - - switch (moov.trak[i].mdia.hdlr.subType) { - case HANDLER_VIDE: - tracks[i].kind = TrackKind.Video; - break; - case HANDLER_SOUN: - tracks[i].kind = TrackKind.Audio; - break; - case HANDLER_SUBT: - tracks[i].kind = TrackKind.Subtitles; - break; - default: - tracks[i].kind = TrackKind.Other; - break; - } - } - - backupBox = box; - } - - Mp4Track selectTrack(final int index) { - selectedTrack = 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() { - if (brands == null) { - throw new IllegalStateException("Not parsed"); - } - return brands; - } - - public void rewind() throws IOException { - if (!stream.canRewind()) { - throw new IOException("The provided stream doesn't allow seek"); - } - if (box == null) { - return; - } - - box = backupBox; - chunkZero = false; - - stream.rewind(); - stream.skipBytes(backupBox.offset + (DataReader.INTEGER_SIZE * 2)); - } - - public Mp4Track[] getAvailableTracks() { - return tracks; - } - - public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException { - Mp4Track track = tracks[selectedTrack]; - - while (stream.available()) { - - if (chunkZero) { - ensure(box); - if (!stream.available()) { - break; - } - box = readBox(); - } else { - chunkZero = true; - } - - switch (box.type) { - case ATOM_MOOF: - if (moof != null) { - throw new IOException("moof found without mdat"); - } - - moof = parseMoof(box, track.trak.tkhd.trackId); - - if (moof.traf != null) { - - if (hasFlag(moof.traf.trun.bFlags, 0x0001)) { - moof.traf.trun.dataOffset -= box.size + 8; - if (moof.traf.trun.dataOffset < 0) { - throw new IOException("trun box has wrong data offset, " - + "points outside of concurrent mdat box"); - } - } - - if (moof.traf.trun.chunkSize < 1) { - if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) { - moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize - * moof.traf.trun.entryCount; - } else { - moof.traf.trun.chunkSize = (int) (box.size - 8); - } - } - if (!hasFlag(moof.traf.trun.bFlags, 0x900) - && moof.traf.trun.chunkDuration == 0) { - if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) { - moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration - * moof.traf.trun.entryCount; - } - } - } - break; - case ATOM_MDAT: - if (moof == null) { - throw new IOException("mdat found without moof"); - } - - if (moof.traf == null) { - moof = null; - continue; // find another chunk - } - - Mp4DashChunk chunk = new Mp4DashChunk(); - chunk.moof = moof; - if (!infoOnly) { - chunk.data = stream.getView(moof.traf.trun.chunkSize); - } - - moof = null; - - stream.skipBytes(chunk.moof.traf.trun.dataOffset); - return chunk; - default: - } - } - - return null; - } - - public static boolean hasFlag(final int flags, final int mask) { - return (flags & mask) == mask; - } - - private String boxName(final Box ref) { - return boxName(ref.type); - } - - private String boxName(final int type) { - try { - return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - return "0x" + Integer.toHexString(type); - } - } - - private Box readBox() throws IOException { - Box b = new Box(); - b.offset = stream.position(); - b.size = stream.readUnsignedInt(); - b.type = stream.readInt(); - - if (b.size == 1) { - b.size = stream.readLong(); - } - - return b; - } - - private Box readBox(final int expected) throws IOException { - Box b = readBox(); - if (b.type != expected) { - throw new NoSuchElementException("expected " + boxName(expected) - + " found " + boxName(b)); - } - return b; - } - - private byte[] readFullBox(final Box ref) throws IOException { - // full box reading is limited to 2 GiB, and should be enough - int size = (int) ref.size; - - ByteBuffer buffer = ByteBuffer.allocate(size); - buffer.putInt(size); - buffer.putInt(ref.type); - - int read = size - 8; - - if (stream.read(buffer.array(), 8, read) != read) { - throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s", - boxName(ref.type), ref.offset, ref.size)); - } - - return buffer.array(); - } - - private void ensure(final Box ref) throws IOException { - long skip = ref.offset + ref.size - stream.position(); - - if (skip == 0) { - return; - } else if (skip < 0) { - throw new EOFException(String.format( - "parser go beyond limits of the box. type=%s offset=%s size=%s position=%s", - boxName(ref), ref.offset, ref.size, stream.position() - )); - } - - stream.skipBytes((int) skip); - } - - private Box untilBox(final Box ref, final int... expected) throws IOException { - Box b; - while (stream.position() < (ref.offset + ref.size)) { - b = readBox(); - for (int type : expected) { - if (b.type == type) { - return b; - } - } - ensure(b); - } - - return null; - } - - private Box untilAnyBox(final Box ref) throws IOException { - if (stream.position() >= (ref.offset + ref.size)) { - return null; - } - - return readBox(); - } - - private Moof parseMoof(final Box ref, final int trackId) throws IOException { - Moof obj = new Moof(); - - Box b = readBox(ATOM_MFHD); - obj.mfhdSequenceNumber = parseMfhd(); - ensure(b); - - while ((b = untilBox(ref, ATOM_TRAF)) != null) { - obj.traf = parseTraf(b, trackId); - ensure(b); - - if (obj.traf != null) { - return obj; - } - } - - return obj; - } - - private int parseMfhd() throws IOException { - // version - // flags - stream.skipBytes(4); - - return stream.readInt(); - } - - private Traf parseTraf(final Box ref, final int trackId) throws IOException { - Traf traf = new Traf(); - - Box b = readBox(ATOM_TFHD); - traf.tfhd = parseTfhd(trackId); - ensure(b); - - if (traf.tfhd == null) { - return null; - } - - b = untilBox(ref, ATOM_TRUN, ATOM_TFDT); - - if (b.type == ATOM_TFDT) { - traf.tfdt = parseTfdt(); - ensure(b); - b = readBox(ATOM_TRUN); - } - - traf.trun = parseTrun(); - ensure(b); - - return traf; - } - - private Tfhd parseTfhd(final int trackId) throws IOException { - Tfhd obj = new Tfhd(); - - obj.bFlags = stream.readInt(); - obj.trackId = stream.readInt(); - - if (trackId != -1 && obj.trackId != trackId) { - return null; - } - - if (hasFlag(obj.bFlags, 0x01)) { - stream.skipBytes(8); - } - if (hasFlag(obj.bFlags, 0x02)) { - stream.skipBytes(4); - } - if (hasFlag(obj.bFlags, 0x08)) { - obj.defaultSampleDuration = stream.readInt(); - } - if (hasFlag(obj.bFlags, 0x10)) { - obj.defaultSampleSize = stream.readInt(); - } - if (hasFlag(obj.bFlags, 0x20)) { - obj.defaultSampleFlags = stream.readInt(); - } - - return obj; - } - - private long parseTfdt() throws IOException { - int version = stream.read(); - stream.skipBytes(3); // flags - return version == 0 ? stream.readUnsignedInt() : stream.readLong(); - } - - private Trun parseTrun() throws IOException { - Trun obj = new Trun(); - obj.bFlags = stream.readInt(); - obj.entryCount = stream.readInt(); // unsigned int - - obj.entriesRowSize = 0; - if (hasFlag(obj.bFlags, 0x0100)) { - obj.entriesRowSize += 4; - } - if (hasFlag(obj.bFlags, 0x0200)) { - obj.entriesRowSize += 4; - } - if (hasFlag(obj.bFlags, 0x0400)) { - obj.entriesRowSize += 4; - } - if (hasFlag(obj.bFlags, 0x0800)) { - obj.entriesRowSize += 4; - } - obj.bEntries = new byte[obj.entriesRowSize * obj.entryCount]; - - if (hasFlag(obj.bFlags, 0x0001)) { - obj.dataOffset = stream.readInt(); - } - if (hasFlag(obj.bFlags, 0x0004)) { - obj.bFirstSampleFlags = stream.readInt(); - } - - stream.read(obj.bEntries); - - for (int i = 0; i < obj.entryCount; i++) { - TrunEntry entry = obj.getEntry(i); - if (hasFlag(obj.bFlags, 0x0100)) { - obj.chunkDuration += entry.sampleDuration; - } - if (hasFlag(obj.bFlags, 0x0200)) { - obj.chunkSize += entry.sampleSize; - } - if (hasFlag(obj.bFlags, 0x0800)) { - if (!hasFlag(obj.bFlags, 0x0100)) { - obj.chunkDuration += entry.sampleCompositionTimeOffset; - } - } - } - - return obj; - } - - private int[] parseFtyp(final Box ref) throws IOException { - int i = 0; - int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)]; - - list[i++] = stream.readInt(); // major brand - - stream.skipBytes(4); // minor version - - for (; i < list.length; i++) { - list[i] = stream.readInt(); // compatible brands - } - - return list; - } - - private Mvhd parseMvhd() throws IOException { - int version = stream.read(); - stream.skipBytes(3); // flags - - // creation entries_time - // modification entries_time - stream.skipBytes(2 * (version == 0 ? 4 : 8)); - - Mvhd obj = new Mvhd(); - obj.timeScale = stream.readUnsignedInt(); - - // chunkDuration - stream.skipBytes(version == 0 ? 4 : 8); - - // rate - // volume - // reserved - // matrix array - // predefined - stream.skipBytes(76); - - obj.nextTrackId = stream.readUnsignedInt(); - - return obj; - } - - private Tkhd parseTkhd() throws IOException { - int version = stream.read(); - - Tkhd obj = new Tkhd(); - - // flags - // creation entries_time - // modification entries_time - stream.skipBytes(3 + (2 * (version == 0 ? 4 : 8))); - - obj.trackId = stream.readInt(); - - stream.skipBytes(4); // reserved - - obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong(); - - stream.skipBytes(2 * 4); // reserved - - obj.bLayer = stream.readShort(); - obj.bAlternateGroup = stream.readShort(); - obj.bVolume = stream.readShort(); - - stream.skipBytes(2); // reserved - - obj.matrix = new byte[9 * 4]; - stream.read(obj.matrix); - - obj.bWidth = stream.readInt(); - obj.bHeight = stream.readInt(); - - return obj; - } - - private Trak parseTrak(final Box ref) throws IOException { - Trak trak = new Trak(); - - Box b = readBox(ATOM_TKHD); - trak.tkhd = parseTkhd(); - ensure(b); - - while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) { - switch (b.type) { - case ATOM_MDIA: - trak.mdia = parseMdia(b); - break; - case ATOM_EDTS: - trak.edstElst = parseEdts(b); - break; - } - - ensure(b); - } - - return trak; - } - - private Mdia parseMdia(final Box ref) throws IOException { - Mdia obj = new Mdia(); - - Box b; - while ((b = untilBox(ref, ATOM_MDHD, ATOM_HDLR, ATOM_MINF)) != null) { - switch (b.type) { - case ATOM_MDHD: - obj.mdhd = readFullBox(b); - - // read time scale - ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd); - byte version = buffer.get(8); - buffer.position(12 + ((version == 0 ? 4 : 8) * 2)); - obj.mdhdTimeScale = buffer.getInt(); - break; - case ATOM_HDLR: - obj.hdlr = parseHdlr(b); - break; - case ATOM_MINF: - obj.minf = parseMinf(b); - break; - } - ensure(b); - } - - return obj; - } - - private Hdlr parseHdlr(final Box ref) throws IOException { - // version - // flags - stream.skipBytes(4); - - Hdlr obj = new Hdlr(); - obj.bReserved = new byte[12]; - - obj.type = stream.readInt(); - obj.subType = stream.readInt(); - stream.read(obj.bReserved); - - // component name (is a ansi/ascii string) - stream.skipBytes((ref.offset + ref.size) - stream.position()); - - return obj; - } - - private Moov parseMoov(final Box ref) throws IOException { - Box b = readBox(ATOM_MVHD); - Moov moov = new Moov(); - moov.mvhd = parseMvhd(); - ensure(b); - - ArrayList tmp = new ArrayList<>((int) moov.mvhd.nextTrackId); - while ((b = untilBox(ref, ATOM_TRAK, ATOM_MVEX)) != null) { - - switch (b.type) { - case ATOM_TRAK: - tmp.add(parseTrak(b)); - break; - case ATOM_MVEX: - moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId); - break; - } - - ensure(b); - } - - moov.trak = tmp.toArray(new Trak[0]); - - return moov; - } - - private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException { - ArrayList tmp = new ArrayList<>(possibleTrackCount); - - Box b; - while ((b = untilBox(ref, ATOM_TREX)) != null) { - tmp.add(parseTrex()); - ensure(b); - } - - return tmp.toArray(new Trex[0]); - } - - private Trex parseTrex() throws IOException { - // version - // flags - stream.skipBytes(4); - - Trex obj = new Trex(); - obj.trackId = stream.readInt(); - obj.defaultSampleDescriptionIndex = stream.readInt(); - obj.defaultSampleDuration = stream.readInt(); - obj.defaultSampleSize = stream.readInt(); - obj.defaultSampleFlags = stream.readInt(); - - return obj; - } - - private Elst parseEdts(final Box ref) throws IOException { - Box b = untilBox(ref, ATOM_ELST); - if (b == null) { - return null; - } - - Elst obj = new Elst(); - - boolean v1 = stream.read() == 1; - stream.skipBytes(3); // flags - - int entryCount = stream.readInt(); - if (entryCount < 1) { - obj.bMediaRate = 0x00010000; // default media rate (1.0) - return obj; - } - - if (v1) { - stream.skipBytes(DataReader.LONG_SIZE); // segment duration - obj.mediaTime = stream.readLong(); - // ignore all remain entries - stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2)); - } else { - stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration - obj.mediaTime = stream.readInt(); - } - - obj.bMediaRate = stream.readInt(); - - return obj; - } - - private Minf parseMinf(final Box ref) throws IOException { - Minf obj = new Minf(); - - Box b; - while ((b = untilAnyBox(ref)) != null) { - - switch (b.type) { - case ATOM_DINF: - obj.dinf = readFullBox(b); - break; - case ATOM_STBL: - obj.stblStsd = parseStbl(b); - break; - case ATOM_VMHD: - case ATOM_SMHD: - obj.mhd = readFullBox(b); - break; - - } - ensure(b); - } - - return obj; - } - - /** - * This only reads the "stsd" box inside. - * - * @param ref stbl box - * @return stsd box inside - */ - private byte[] parseStbl(final Box ref) throws IOException { - Box b = untilBox(ref, ATOM_STSD); - - if (b == null) { - return new byte[0]; // this never should happens (missing codec startup data) - } - - return readFullBox(b); - } - - class Box { - int type; - long offset; - long size; - } - - public class Moof { - int mfhdSequenceNumber; - public Traf traf; - } - - public class Traf { - public Tfhd tfhd; - long tfdt; - public Trun trun; - } - - public class Tfhd { - int bFlags; - public int trackId; - int defaultSampleDuration; - int defaultSampleSize; - int defaultSampleFlags; - } - - class TrunEntry { - int sampleDuration; - int sampleSize; - int sampleFlags; - int sampleCompositionTimeOffset; - - boolean hasCompositionTimeOffset; - boolean isKeyframe; - - } - - public class Trun { - public int chunkDuration; - public int chunkSize; - - public int bFlags; - int bFirstSampleFlags; - int dataOffset; - - public int entryCount; - byte[] bEntries; - int entriesRowSize; - - public TrunEntry getEntry(final int i) { - ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize); - TrunEntry entry = new TrunEntry(); - - if (hasFlag(bFlags, 0x0100)) { - entry.sampleDuration = buffer.getInt(); - } - if (hasFlag(bFlags, 0x0200)) { - entry.sampleSize = buffer.getInt(); - } - if (hasFlag(bFlags, 0x0400)) { - entry.sampleFlags = buffer.getInt(); - } - if (hasFlag(bFlags, 0x0800)) { - entry.sampleCompositionTimeOffset = buffer.getInt(); - } - - entry.hasCompositionTimeOffset = hasFlag(bFlags, 0x0800); - entry.isKeyframe = !hasFlag(entry.sampleFlags, 0x10000); - - return entry; - } - - public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) { - TrunEntry entry = getEntry(i); - - if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) { - entry.sampleFlags = header.defaultSampleFlags; - } - - if (!hasFlag(bFlags, 0x0200) && hasFlag(header.bFlags, 0x10)) { - entry.sampleSize = header.defaultSampleSize; - } - - if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x08)) { - entry.sampleDuration = header.defaultSampleDuration; - } - - if (i == 0 && hasFlag(bFlags, 0x0004)) { - entry.sampleFlags = bFirstSampleFlags; - } - - return entry; - } - } - - public class Tkhd { - int trackId; - long duration; - short bVolume; - int bWidth; - int bHeight; - byte[] matrix; - short bLayer; - short bAlternateGroup; - } - - public class Trak { - public Tkhd tkhd; - public Elst edstElst; - public Mdia mdia; - - } - - class Mvhd { - long timeScale; - long nextTrackId; - } - - class Moov { - Mvhd mvhd; - Trak[] trak; - Trex[] mvexTrex; - } - - public class Trex { - private int trackId; - int defaultSampleDescriptionIndex; - int defaultSampleDuration; - int defaultSampleSize; - int defaultSampleFlags; - } - - public class Elst { - public long mediaTime; - public int bMediaRate; - } - - public class Mdia { - public int mdhdTimeScale; - public byte[] mdhd; - public Hdlr hdlr; - public Minf minf; - } - - public class Hdlr { - public int type; - public int subType; - public byte[] bReserved; - } - - public class Minf { - public byte[] dinf; - public byte[] stblStsd; - public byte[] mhd; - } - - public class Mp4Track { - public TrackKind kind; - public Trak trak; - public Trex trex; - } - - public class Mp4DashChunk { - public InputStream data; - public Moof moof; - private int i = 0; - - public TrunEntry getNextSampleInfo() { - if (i >= moof.traf.trun.entryCount) { - return null; - } - return moof.traf.trun.getAbsoluteEntry(i++, moof.traf.tfhd); - } - - public Mp4DashSample getNextSample() throws IOException { - if (data == null) { - throw new IllegalStateException("This chunk has info only"); - } - if (i >= moof.traf.trun.entryCount) { - return null; - } - - Mp4DashSample sample = new Mp4DashSample(); - sample.info = moof.traf.trun.getAbsoluteEntry(i++, moof.traf.tfhd); - sample.data = new byte[sample.info.sampleSize]; - - if (data.read(sample.data) != sample.info.sampleSize) { - throw new EOFException("EOF reached while reading a sample"); - } - - return sample; - } - } - - public class Mp4DashSample { - public TrunEntry info; - public byte[] data; - } -} +package org.schabi.newpipe.streams; + +import org.schabi.newpipe.streams.io.SharpStream; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +/** + * @author kapodamy + */ +public class Mp4DashReader { + private static final int ATOM_MOOF = 0x6D6F6F66; + private static final int ATOM_MFHD = 0x6D666864; + private static final int ATOM_TRAF = 0x74726166; + private static final int ATOM_TFHD = 0x74666864; + private static final int ATOM_TFDT = 0x74666474; + private static final int ATOM_TRUN = 0x7472756E; + private static final int ATOM_MDIA = 0x6D646961; + private static final int ATOM_FTYP = 0x66747970; + private static final int ATOM_SIDX = 0x73696478; + private static final int ATOM_MOOV = 0x6D6F6F76; + private static final int ATOM_MDAT = 0x6D646174; + private static final int ATOM_MVHD = 0x6D766864; + private static final int ATOM_TRAK = 0x7472616B; + private static final int ATOM_MVEX = 0x6D766578; + private static final int ATOM_TREX = 0x74726578; + private static final int ATOM_TKHD = 0x746B6864; + private static final int ATOM_MFRA = 0x6D667261; + private static final int ATOM_MDHD = 0x6D646864; + private static final int ATOM_EDTS = 0x65647473; + private static final int ATOM_ELST = 0x656C7374; + private static final int ATOM_HDLR = 0x68646C72; + private static final int ATOM_MINF = 0x6D696E66; + private static final int ATOM_DINF = 0x64696E66; + private static final int ATOM_STBL = 0x7374626C; + private static final int ATOM_STSD = 0x73747364; + private static final int ATOM_VMHD = 0x766D6864; + private static final int ATOM_SMHD = 0x736D6864; + + private static final int BRAND_DASH = 0x64617368; + private static final int BRAND_ISO5 = 0x69736F35; + + private static final int HANDLER_VIDE = 0x76696465; + private static final int HANDLER_SOUN = 0x736F756E; + private static final int HANDLER_SUBT = 0x73756274; + + private final DataReader stream; + + private Mp4Track[] tracks = null; + private int[] brands = null; + + private Box box; + private Moof moof; + + private boolean chunkZero = false; + + private int selectedTrack = -1; + private Box backupBox = null; + + public enum TrackKind { + Audio, Video, Subtitles, Other + } + + public Mp4DashReader(final SharpStream source) { + this.stream = new DataReader(source); + } + + public void parse() throws IOException, NoSuchElementException { + if (selectedTrack > -1) { + return; + } + + box = readBox(ATOM_FTYP); + brands = parseFtyp(box); + switch (brands[0]) { + case BRAND_DASH: + case BRAND_ISO5:// ¿why not? + break; + default: + throw new NoSuchElementException( + "Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is " + + boxName(brands[0]) + ); + } + + Moov moov = null; + int i; + + while (box.type != ATOM_MOOF) { + ensure(box); + box = readBox(); + + switch (box.type) { + case ATOM_MOOV: + moov = parseMoov(box); + break; + case ATOM_SIDX: + case ATOM_MFRA: + break; + } + } + + if (moov == null) { + throw new IOException("The provided Mp4 doesn't have the 'moov' box"); + } + + tracks = new Mp4Track[moov.trak.length]; + + for (i = 0; i < tracks.length; i++) { + tracks[i] = new Mp4Track(); + tracks[i].trak = moov.trak[i]; + + if (moov.mvexTrex != null) { + for (Trex mvexTrex : moov.mvexTrex) { + if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) { + tracks[i].trex = mvexTrex; + } + } + } + + switch (moov.trak[i].mdia.hdlr.subType) { + case HANDLER_VIDE: + tracks[i].kind = TrackKind.Video; + break; + case HANDLER_SOUN: + tracks[i].kind = TrackKind.Audio; + break; + case HANDLER_SUBT: + tracks[i].kind = TrackKind.Subtitles; + break; + default: + tracks[i].kind = TrackKind.Other; + break; + } + } + + backupBox = box; + } + + Mp4Track selectTrack(final int index) { + selectedTrack = index; + return tracks[index]; + } + + public int[] getBrands() { + if (brands == null) { + throw new IllegalStateException("Not parsed"); + } + return brands; + } + + public void rewind() throws IOException { + if (!stream.canRewind()) { + throw new IOException("The provided stream doesn't allow seek"); + } + if (box == null) { + return; + } + + box = backupBox; + chunkZero = false; + + stream.rewind(); + stream.skipBytes(backupBox.offset + (DataReader.INTEGER_SIZE * 2)); + } + + public Mp4Track[] getAvailableTracks() { + return tracks; + } + + public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException { + Mp4Track track = tracks[selectedTrack]; + + while (stream.available()) { + + if (chunkZero) { + ensure(box); + if (!stream.available()) { + break; + } + box = readBox(); + } else { + chunkZero = true; + } + + switch (box.type) { + case ATOM_MOOF: + if (moof != null) { + throw new IOException("moof found without mdat"); + } + + moof = parseMoof(box, track.trak.tkhd.trackId); + + if (moof.traf != null) { + + if (hasFlag(moof.traf.trun.bFlags, 0x0001)) { + moof.traf.trun.dataOffset -= box.size + 8; + if (moof.traf.trun.dataOffset < 0) { + throw new IOException("trun box has wrong data offset, " + + "points outside of concurrent mdat box"); + } + } + + if (moof.traf.trun.chunkSize < 1) { + if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) { + moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize + * moof.traf.trun.entryCount; + } else { + moof.traf.trun.chunkSize = (int) (box.size - 8); + } + } + if (!hasFlag(moof.traf.trun.bFlags, 0x900) + && moof.traf.trun.chunkDuration == 0) { + if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) { + moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration + * moof.traf.trun.entryCount; + } + } + } + break; + case ATOM_MDAT: + if (moof == null) { + throw new IOException("mdat found without moof"); + } + + if (moof.traf == null) { + moof = null; + continue; // find another chunk + } + + Mp4DashChunk chunk = new Mp4DashChunk(); + chunk.moof = moof; + if (!infoOnly) { + chunk.data = stream.getView(moof.traf.trun.chunkSize); + } + + moof = null; + + stream.skipBytes(chunk.moof.traf.trun.dataOffset); + return chunk; + default: + } + } + + return null; + } + + public static boolean hasFlag(final int flags, final int mask) { + return (flags & mask) == mask; + } + + private String boxName(final Box ref) { + return boxName(ref.type); + } + + private String boxName(final int type) { + try { + return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + return "0x" + Integer.toHexString(type); + } + } + + private Box readBox() throws IOException { + Box b = new Box(); + b.offset = stream.position(); + b.size = stream.readUnsignedInt(); + b.type = stream.readInt(); + + if (b.size == 1) { + b.size = stream.readLong(); + } + + return b; + } + + private Box readBox(final int expected) throws IOException { + Box b = readBox(); + if (b.type != expected) { + throw new NoSuchElementException("expected " + boxName(expected) + + " found " + boxName(b)); + } + return b; + } + + private byte[] readFullBox(final Box ref) throws IOException { + // full box reading is limited to 2 GiB, and should be enough + int size = (int) ref.size; + + ByteBuffer buffer = ByteBuffer.allocate(size); + buffer.putInt(size); + buffer.putInt(ref.type); + + int read = size - 8; + + if (stream.read(buffer.array(), 8, read) != read) { + throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s", + boxName(ref.type), ref.offset, ref.size)); + } + + return buffer.array(); + } + + private void ensure(final Box ref) throws IOException { + long skip = ref.offset + ref.size - stream.position(); + + if (skip == 0) { + return; + } else if (skip < 0) { + throw new EOFException(String.format( + "parser go beyond limits of the box. type=%s offset=%s size=%s position=%s", + boxName(ref), ref.offset, ref.size, stream.position() + )); + } + + stream.skipBytes((int) skip); + } + + private Box untilBox(final Box ref, final int... expected) throws IOException { + Box b; + while (stream.position() < (ref.offset + ref.size)) { + b = readBox(); + for (int type : expected) { + if (b.type == type) { + return b; + } + } + ensure(b); + } + + return null; + } + + private Box untilAnyBox(final Box ref) throws IOException { + if (stream.position() >= (ref.offset + ref.size)) { + return null; + } + + return readBox(); + } + + private Moof parseMoof(final Box ref, final int trackId) throws IOException { + Moof obj = new Moof(); + + Box b = readBox(ATOM_MFHD); + obj.mfhdSequenceNumber = parseMfhd(); + ensure(b); + + while ((b = untilBox(ref, ATOM_TRAF)) != null) { + obj.traf = parseTraf(b, trackId); + ensure(b); + + if (obj.traf != null) { + return obj; + } + } + + return obj; + } + + private int parseMfhd() throws IOException { + // version + // flags + stream.skipBytes(4); + + return stream.readInt(); + } + + private Traf parseTraf(final Box ref, final int trackId) throws IOException { + Traf traf = new Traf(); + + Box b = readBox(ATOM_TFHD); + traf.tfhd = parseTfhd(trackId); + ensure(b); + + if (traf.tfhd == null) { + return null; + } + + b = untilBox(ref, ATOM_TRUN, ATOM_TFDT); + + if (b.type == ATOM_TFDT) { + traf.tfdt = parseTfdt(); + ensure(b); + b = readBox(ATOM_TRUN); + } + + traf.trun = parseTrun(); + ensure(b); + + return traf; + } + + private Tfhd parseTfhd(final int trackId) throws IOException { + Tfhd obj = new Tfhd(); + + obj.bFlags = stream.readInt(); + obj.trackId = stream.readInt(); + + if (trackId != -1 && obj.trackId != trackId) { + return null; + } + + if (hasFlag(obj.bFlags, 0x01)) { + stream.skipBytes(8); + } + if (hasFlag(obj.bFlags, 0x02)) { + stream.skipBytes(4); + } + if (hasFlag(obj.bFlags, 0x08)) { + obj.defaultSampleDuration = stream.readInt(); + } + if (hasFlag(obj.bFlags, 0x10)) { + obj.defaultSampleSize = stream.readInt(); + } + if (hasFlag(obj.bFlags, 0x20)) { + obj.defaultSampleFlags = stream.readInt(); + } + + return obj; + } + + private long parseTfdt() throws IOException { + int version = stream.read(); + stream.skipBytes(3); // flags + return version == 0 ? stream.readUnsignedInt() : stream.readLong(); + } + + private Trun parseTrun() throws IOException { + Trun obj = new Trun(); + obj.bFlags = stream.readInt(); + obj.entryCount = stream.readInt(); // unsigned int + + obj.entriesRowSize = 0; + if (hasFlag(obj.bFlags, 0x0100)) { + obj.entriesRowSize += 4; + } + if (hasFlag(obj.bFlags, 0x0200)) { + obj.entriesRowSize += 4; + } + if (hasFlag(obj.bFlags, 0x0400)) { + obj.entriesRowSize += 4; + } + if (hasFlag(obj.bFlags, 0x0800)) { + obj.entriesRowSize += 4; + } + obj.bEntries = new byte[obj.entriesRowSize * obj.entryCount]; + + if (hasFlag(obj.bFlags, 0x0001)) { + obj.dataOffset = stream.readInt(); + } + if (hasFlag(obj.bFlags, 0x0004)) { + obj.bFirstSampleFlags = stream.readInt(); + } + + stream.read(obj.bEntries); + + for (int i = 0; i < obj.entryCount; i++) { + TrunEntry entry = obj.getEntry(i); + if (hasFlag(obj.bFlags, 0x0100)) { + obj.chunkDuration += entry.sampleDuration; + } + if (hasFlag(obj.bFlags, 0x0200)) { + obj.chunkSize += entry.sampleSize; + } + if (hasFlag(obj.bFlags, 0x0800)) { + if (!hasFlag(obj.bFlags, 0x0100)) { + obj.chunkDuration += entry.sampleCompositionTimeOffset; + } + } + } + + return obj; + } + + private int[] parseFtyp(final Box ref) throws IOException { + int i = 0; + int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)]; + + list[i++] = stream.readInt(); // major brand + + stream.skipBytes(4); // minor version + + for (; i < list.length; i++) { + list[i] = stream.readInt(); // compatible brands + } + + return list; + } + + private Mvhd parseMvhd() throws IOException { + int version = stream.read(); + stream.skipBytes(3); // flags + + // creation entries_time + // modification entries_time + stream.skipBytes(2 * (version == 0 ? 4 : 8)); + + Mvhd obj = new Mvhd(); + obj.timeScale = stream.readUnsignedInt(); + + // chunkDuration + stream.skipBytes(version == 0 ? 4 : 8); + + // rate + // volume + // reserved + // matrix array + // predefined + stream.skipBytes(76); + + obj.nextTrackId = stream.readUnsignedInt(); + + return obj; + } + + private Tkhd parseTkhd() throws IOException { + int version = stream.read(); + + Tkhd obj = new Tkhd(); + + // flags + // creation entries_time + // modification entries_time + stream.skipBytes(3 + (2 * (version == 0 ? 4 : 8))); + + obj.trackId = stream.readInt(); + + stream.skipBytes(4); // reserved + + obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong(); + + stream.skipBytes(2 * 4); // reserved + + obj.bLayer = stream.readShort(); + obj.bAlternateGroup = stream.readShort(); + obj.bVolume = stream.readShort(); + + stream.skipBytes(2); // reserved + + obj.matrix = new byte[9 * 4]; + stream.read(obj.matrix); + + obj.bWidth = stream.readInt(); + obj.bHeight = stream.readInt(); + + return obj; + } + + private Trak parseTrak(final Box ref) throws IOException { + Trak trak = new Trak(); + + Box b = readBox(ATOM_TKHD); + trak.tkhd = parseTkhd(); + ensure(b); + + while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) { + switch (b.type) { + case ATOM_MDIA: + trak.mdia = parseMdia(b); + break; + case ATOM_EDTS: + trak.edstElst = parseEdts(b); + break; + } + + ensure(b); + } + + return trak; + } + + private Mdia parseMdia(final Box ref) throws IOException { + Mdia obj = new Mdia(); + + Box b; + while ((b = untilBox(ref, ATOM_MDHD, ATOM_HDLR, ATOM_MINF)) != null) { + switch (b.type) { + case ATOM_MDHD: + obj.mdhd = readFullBox(b); + + // read time scale + ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd); + byte version = buffer.get(8); + buffer.position(12 + ((version == 0 ? 4 : 8) * 2)); + obj.mdhdTimeScale = buffer.getInt(); + break; + case ATOM_HDLR: + obj.hdlr = parseHdlr(b); + break; + case ATOM_MINF: + obj.minf = parseMinf(b); + break; + } + ensure(b); + } + + return obj; + } + + private Hdlr parseHdlr(final Box ref) throws IOException { + // version + // flags + stream.skipBytes(4); + + Hdlr obj = new Hdlr(); + obj.bReserved = new byte[12]; + + obj.type = stream.readInt(); + obj.subType = stream.readInt(); + stream.read(obj.bReserved); + + // component name (is a ansi/ascii string) + stream.skipBytes((ref.offset + ref.size) - stream.position()); + + return obj; + } + + private Moov parseMoov(final Box ref) throws IOException { + Box b = readBox(ATOM_MVHD); + Moov moov = new Moov(); + moov.mvhd = parseMvhd(); + ensure(b); + + ArrayList tmp = new ArrayList<>((int) moov.mvhd.nextTrackId); + while ((b = untilBox(ref, ATOM_TRAK, ATOM_MVEX)) != null) { + + switch (b.type) { + case ATOM_TRAK: + tmp.add(parseTrak(b)); + break; + case ATOM_MVEX: + moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId); + break; + } + + ensure(b); + } + + moov.trak = tmp.toArray(new Trak[0]); + + return moov; + } + + private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException { + ArrayList tmp = new ArrayList<>(possibleTrackCount); + + Box b; + while ((b = untilBox(ref, ATOM_TREX)) != null) { + tmp.add(parseTrex()); + ensure(b); + } + + return tmp.toArray(new Trex[0]); + } + + private Trex parseTrex() throws IOException { + // version + // flags + stream.skipBytes(4); + + Trex obj = new Trex(); + obj.trackId = stream.readInt(); + obj.defaultSampleDescriptionIndex = stream.readInt(); + obj.defaultSampleDuration = stream.readInt(); + obj.defaultSampleSize = stream.readInt(); + obj.defaultSampleFlags = stream.readInt(); + + return obj; + } + + private Elst parseEdts(final Box ref) throws IOException { + Box b = untilBox(ref, ATOM_ELST); + if (b == null) { + return null; + } + + Elst obj = new Elst(); + + boolean v1 = stream.read() == 1; + stream.skipBytes(3); // flags + + int entryCount = stream.readInt(); + if (entryCount < 1) { + obj.bMediaRate = 0x00010000; // default media rate (1.0) + return obj; + } + + if (v1) { + stream.skipBytes(DataReader.LONG_SIZE); // segment duration + obj.mediaTime = stream.readLong(); + // ignore all remain entries + stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2)); + } else { + stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration + obj.mediaTime = stream.readInt(); + } + + obj.bMediaRate = stream.readInt(); + + return obj; + } + + private Minf parseMinf(final Box ref) throws IOException { + Minf obj = new Minf(); + + Box b; + while ((b = untilAnyBox(ref)) != null) { + + switch (b.type) { + case ATOM_DINF: + obj.dinf = readFullBox(b); + break; + case ATOM_STBL: + obj.stblStsd = parseStbl(b); + break; + case ATOM_VMHD: + case ATOM_SMHD: + obj.mhd = readFullBox(b); + break; + + } + ensure(b); + } + + return obj; + } + + /** + * This only reads the "stsd" box inside. + * + * @param ref stbl box + * @return stsd box inside + */ + private byte[] parseStbl(final Box ref) throws IOException { + Box b = untilBox(ref, ATOM_STSD); + + if (b == null) { + return new byte[0]; // this never should happens (missing codec startup data) + } + + return readFullBox(b); + } + + class Box { + int type; + long offset; + long size; + } + + public class Moof { + int mfhdSequenceNumber; + public Traf traf; + } + + public class Traf { + public Tfhd tfhd; + long tfdt; + public Trun trun; + } + + public class Tfhd { + int bFlags; + public int trackId; + int defaultSampleDuration; + int defaultSampleSize; + int defaultSampleFlags; + } + + class TrunEntry { + int sampleDuration; + int sampleSize; + int sampleFlags; + int sampleCompositionTimeOffset; + + boolean hasCompositionTimeOffset; + boolean isKeyframe; + + } + + public class Trun { + public int chunkDuration; + public int chunkSize; + + public int bFlags; + int bFirstSampleFlags; + int dataOffset; + + public int entryCount; + byte[] bEntries; + int entriesRowSize; + + public TrunEntry getEntry(final int i) { + ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize); + TrunEntry entry = new TrunEntry(); + + if (hasFlag(bFlags, 0x0100)) { + entry.sampleDuration = buffer.getInt(); + } + if (hasFlag(bFlags, 0x0200)) { + entry.sampleSize = buffer.getInt(); + } + if (hasFlag(bFlags, 0x0400)) { + entry.sampleFlags = buffer.getInt(); + } + if (hasFlag(bFlags, 0x0800)) { + entry.sampleCompositionTimeOffset = buffer.getInt(); + } + + entry.hasCompositionTimeOffset = hasFlag(bFlags, 0x0800); + entry.isKeyframe = !hasFlag(entry.sampleFlags, 0x10000); + + return entry; + } + + public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) { + TrunEntry entry = getEntry(i); + + if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) { + entry.sampleFlags = header.defaultSampleFlags; + } + + if (!hasFlag(bFlags, 0x0200) && hasFlag(header.bFlags, 0x10)) { + entry.sampleSize = header.defaultSampleSize; + } + + if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x08)) { + entry.sampleDuration = header.defaultSampleDuration; + } + + if (i == 0 && hasFlag(bFlags, 0x0004)) { + entry.sampleFlags = bFirstSampleFlags; + } + + return entry; + } + } + + public class Tkhd { + int trackId; + long duration; + short bVolume; + int bWidth; + int bHeight; + byte[] matrix; + short bLayer; + short bAlternateGroup; + } + + public class Trak { + public Tkhd tkhd; + public Elst edstElst; + public Mdia mdia; + + } + + class Mvhd { + long timeScale; + long nextTrackId; + } + + class Moov { + Mvhd mvhd; + Trak[] trak; + Trex[] mvexTrex; + } + + public class Trex { + private int trackId; + int defaultSampleDescriptionIndex; + int defaultSampleDuration; + int defaultSampleSize; + int defaultSampleFlags; + } + + public class Elst { + public long mediaTime; + public int bMediaRate; + } + + public class Mdia { + public int mdhdTimeScale; + public byte[] mdhd; + public Hdlr hdlr; + public Minf minf; + } + + public class Hdlr { + public int type; + public int subType; + public byte[] bReserved; + } + + public class Minf { + public byte[] dinf; + public byte[] stblStsd; + public byte[] mhd; + } + + public class Mp4Track { + public TrackKind kind; + public Trak trak; + public Trex trex; + } + + public class Mp4DashChunk { + public InputStream data; + public Moof moof; + private int i = 0; + + public TrunEntry getNextSampleInfo() { + if (i >= moof.traf.trun.entryCount) { + return null; + } + return moof.traf.trun.getAbsoluteEntry(i++, moof.traf.tfhd); + } + + public Mp4DashSample getNextSample() throws IOException { + if (data == null) { + throw new IllegalStateException("This chunk has info only"); + } + if (i >= moof.traf.trun.entryCount) { + return null; + } + + Mp4DashSample sample = new Mp4DashSample(); + sample.info = moof.traf.trun.getAbsoluteEntry(i++, moof.traf.tfhd); + sample.data = new byte[sample.info.sampleSize]; + + if (data.read(sample.data) != sample.info.sampleSize) { + throw new EOFException("EOF reached while reading a sample"); + } + + return sample; + } + } + + public class Mp4DashSample { + public TrunEntry info; + public byte[] data; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 2fc887896..66e9ce6f3 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -640,7 +640,7 @@ public class Mp4FromDashWriter { return size; } - private byte[] makeMdat(long refSize, final boolean is64) { + private byte[] makeMdat(long refSize, boolean is64) { if (is64) { refSize += 16; } else { @@ -674,8 +674,9 @@ public class Mp4FromDashWriter { 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values // 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, 0x00, + 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, 0x00, 0x40, 0x00, 0x00, 0x00 }); auxWrite(new byte[24]); // predefined @@ -717,12 +718,13 @@ public class Mp4FromDashWriter { // udta/meta/ilst/©too auxWrite(new byte[]{ - 0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65, 0x74, 0x61, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 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, + 0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65, + 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70, + 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x27, 0x69, 0x6C, 0x73, 0x74, 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 }); @@ -759,7 +761,8 @@ public class Mp4FromDashWriter { auxWrite(new byte[]{ 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; @@ -845,14 +848,18 @@ public class Mp4FromDashWriter { private byte[] makeHdlr(final Hdlr hdlr) { ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ 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, - // binary string "ISO Media file created in NewPipe (A libre lightweight streaming frontend for Android)." - 0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65, 0x20, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65, 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, + 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)." + 0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65, + 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65, + 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 }); diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index ee0a61492..d655b4d00 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -289,11 +289,13 @@ public class OggFromWebMWriter implements Closeable { /* // whole file duration (not implemented) 0x44,// tag string size - 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, - 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 + 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, + 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x2E, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 */ 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 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ???????? }; @@ -417,7 +419,7 @@ public class OggFromWebMWriter implements Closeable { } } - private int calcCrc32(int initialCrc, final byte[] buffer, final int size) { + private int calcCrc32(int initialCrc, byte[] buffer, int size) { for (int i = 0; i < size; i++) { int reg = (initialCrc >>> 24) & 0xff; initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java index da1e119c3..c3cd2a2e4 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMWriter.java @@ -102,10 +102,6 @@ public class WebMWriter implements Closeable { return done; } - public boolean isParsed() { - return parsed; - } - @Override public void close() { done = true; @@ -360,7 +356,7 @@ public class WebMWriter implements Closeable { Block bloq = new Block(); bloq.data = res.data; - bloq.dataSize = (int) res.dataSize; + bloq.dataSize = res.dataSize; bloq.trackNumber = internalTrackId; bloq.flags = res.flags; bloq.absoluteTimecode = res.absoluteTimeCodeNs / DEFAULT_TIMECODE_SCALE; @@ -728,7 +724,7 @@ public class WebMWriter implements Closeable { return 0; } - class KeyFrame { + static class KeyFrame { KeyFrame(final long segment, final long cluster, final long block, final long timecode) { clusterPosition = cluster - segment; relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE); @@ -740,7 +736,7 @@ public class WebMWriter implements Closeable { final long duration; } - class Block { + static class Block { InputStream data; int trackNumber; byte flags; @@ -759,7 +755,7 @@ public class WebMWriter implements Closeable { } } - class ClusterInfo { + static class ClusterInfo { long offset; int size; } diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 141522287..6b53cfc5b 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -10,34 +10,4 @@ - - - - - - - - - - - - - - From 03de4b29ea1dbbbb965e31faacc80e6e3b19a15c Mon Sep 17 00:00:00 2001 From: wb9688 Date: Thu, 9 Apr 2020 17:24:43 +0200 Subject: [PATCH 13/33] Suppress remaining Checkstyle errors --- .../schabi/newpipe/streams/DataReader.java | 4 ++-- .../newpipe/streams/Mp4FromDashWriter.java | 10 ++++++---- .../newpipe/streams/OggFromWebMWriter.java | 2 +- checkstyle-suppressions.xml | 20 +++++++++++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java index 01a124c9e..96f78ac0e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java @@ -86,11 +86,11 @@ public class DataReader { return high << 32 | low; } - public int read(byte[] buffer) throws IOException { + public int read(final byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } - public int read(byte[] buffer, int offset, int count) throws IOException { + public int read(final byte[] buffer, int offset, int count) throws IOException { if (readCount < 0) { return -1; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 66e9ce6f3..41a2331ba 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -640,7 +640,7 @@ public class Mp4FromDashWriter { return size; } - private byte[] makeMdat(long refSize, boolean is64) { + private byte[] makeMdat(long refSize, final boolean is64) { if (is64) { refSize += 16; } else { @@ -736,8 +736,10 @@ public class Mp4FromDashWriter { int start = auxOffset(); auxWrite(new byte[]{ - 0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B, // trak header - 0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header + // trak 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); @@ -857,7 +859,7 @@ public class Mp4FromDashWriter { 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65, 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, + 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 diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index d655b4d00..e24464dc0 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -419,7 +419,7 @@ public class OggFromWebMWriter implements Closeable { } } - private int calcCrc32(int initialCrc, byte[] buffer, int size) { + private int calcCrc32(int initialCrc, final byte[] buffer, final int size) { for (int i = 0; i < size; i++) { int reg = (initialCrc >>> 24) & 0xff; initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)]; diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 6b53cfc5b..d015a9e03 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -10,4 +10,24 @@ + + + + + + + + + + From a1b9892c7739a1b8aa78922a075f0b18f52717b8 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 28 Mar 2020 20:08:42 -0300 Subject: [PATCH 14/33] Move exception utils to a separate class and add tests for it --- app/src/main/java/org/schabi/newpipe/App.java | 6 +- .../newpipe/fragments/BaseStateFragment.java | 4 +- .../fragments/list/search/SearchFragment.java | 3 +- .../org/schabi/newpipe/util/ExceptionUtils.kt | 82 +++++++++++++++++++ .../schabi/newpipe/util/ExtractorHelper.java | 82 ------------------- .../schabi/newpipe/util/ExceptionUtilsTest.kt | 69 ++++++++++++++++ 6 files changed, 158 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/ExceptionUtils.kt create mode 100644 app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index f9b3abfb1..7e9df0bed 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -27,7 +27,7 @@ import org.schabi.newpipe.report.AcraReportSenderFactory; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; 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.ServiceHelper; import org.schabi.newpipe.util.StateSaver; @@ -173,7 +173,7 @@ public class App extends Application { private boolean isThrowableIgnored(@NonNull final Throwable throwable) { // Don't crash the application over a simple network problem - return ExtractorHelper.hasAssignableCauseThrowable(throwable, + return ExceptionUtils.hasAssignableCause(throwable, // network api cancellation IOException.class, SocketException.class, // blocking code disposed @@ -182,7 +182,7 @@ public class App extends Application { private boolean isThrowableCritical(@NonNull final Throwable throwable) { // Though these exceptions cannot be ignored - return ExtractorHelper.hasAssignableCauseThrowable(throwable, + return ExceptionUtils.hasAssignableCause(throwable, NullPointerException.class, IllegalArgumentException.class, // bug in app OnErrorNotImplementedException.class, MissingBackpressureException.class, IllegalStateException.class); // bug in operator diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 861dc2c60..3167c4632 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -23,7 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.report.ErrorActivity; 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 java.io.IOException; @@ -200,7 +200,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC return true; } - if (ExtractorHelper.isInterruptedCaused(exception)) { + if (ExceptionUtils.isInterruptedCaused(exception)) { if (DEBUG) { Log.w(TAG, "onError() isInterruptedCaused! = [" + exception + "]"); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 718865f10..b3c7ae189 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -41,6 +41,7 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchInfo; +import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -764,7 +765,7 @@ public class SearchFragment extends BaseListFragment): 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 9c6ab1898..95e2b65d3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -52,7 +52,6 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import java.io.IOException; -import java.io.InterruptedIOException; import java.util.Collections; import java.util.List; @@ -306,85 +305,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); - } } diff --git a/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt new file mode 100644 index 000000000..fc0e9dcbd --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/util/ExceptionUtilsTest.kt @@ -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)) + } +} \ No newline at end of file From 913796ff0fe0c4488648e679075ca662cf367dcd Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Sat, 28 Mar 2020 20:10:48 -0300 Subject: [PATCH 15/33] Use exception utils in network error detection throughout the app --- .../schabi/newpipe/fragments/BaseStateFragment.java | 3 +-- .../newpipe/fragments/list/search/SearchFragment.java | 11 +---------- .../newpipe/local/feed/service/FeedLoadService.kt | 8 +++++--- .../services/BaseImportExportService.java | 4 ++-- .../services/SubscriptionsImportService.java | 5 ++++- .../java/org/schabi/newpipe/util/ExtractorHelper.java | 3 +-- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 3167c4632..d71b933c2 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -26,7 +26,6 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.InfoCache; -import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -213,7 +212,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC } else if (exception instanceof ContentNotAvailableException) { showError(getString(R.string.content_not_available), false); return true; - } else if (exception instanceof IOException) { + } else if (ExceptionUtils.isNetworkRelated(exception)) { showError(getString(R.string.network_error), true); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index b3c7ae189..e49126757 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -41,7 +41,6 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchInfo; -import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; @@ -54,9 +53,6 @@ import org.schabi.newpipe.util.FireTvUtils; import org.schabi.newpipe.util.NavigationHelper; 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.Arrays; import java.util.HashMap; @@ -764,12 +760,7 @@ public class SearchFragment extends BaseListFragment throw error - cause is IOException -> throw cause - error is ReCaptchaException -> throw error cause is ReCaptchaException -> throw cause + + error is IOException -> throw error + cause is IOException -> throw cause + ExceptionUtils.isNetworkRelated(error) -> throw IOException(error) } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 28bacaadd..f485844ea 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -38,9 +38,9 @@ import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.ExceptionUtils; import java.io.FileNotFoundException; -import java.io.IOException; import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -227,7 +227,7 @@ public abstract class BaseImportExportService extends Service { message = getString(R.string.invalid_source); } else if (error instanceof FileNotFoundException) { message = getString(R.string.invalid_file); - } else if (error instanceof IOException) { + } else if (ExceptionUtils.isNetworkRelated(error)) { message = getString(R.string.network_error); } return message; diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java index 70d061d7e..06ba55106 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java @@ -35,6 +35,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.subscription.SubscriptionItem; import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.ExceptionUtils; import org.schabi.newpipe.util.ExtractorHelper; import java.io.File; @@ -245,8 +246,10 @@ public class SubscriptionsImportService extends BaseImportExportService { final Throwable cause = error.getCause(); if (error instanceof IOException) { throw (IOException) error; - } else if (cause != null && cause instanceof IOException) { + } else if (cause instanceof IOException) { throw (IOException) cause; + } else if (ExceptionUtils.isNetworkRelated(error)) { + throw new IOException(error); } eventListener.onItemCompleted(""); diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 95e2b65d3..813b1f6b9 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -51,7 +51,6 @@ import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; -import java.io.IOException; import java.util.Collections; import java.util.List; @@ -288,7 +287,7 @@ public final class ExtractorHelper { Intent intent = new Intent(context, ReCaptchaActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); - } else if (exception instanceof IOException) { + } else if (ExceptionUtils.isNetworkRelated(exception)) { Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); } else if (exception instanceof ContentNotAvailableException) { Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); From cc7a25d9ce8a4546e981d438e6b32125f3eac160 Mon Sep 17 00:00:00 2001 From: bopol Date: Mon, 30 Mar 2020 12:38:42 +0200 Subject: [PATCH 16/33] handle ContentNotSupportedException in BaseStateFragment thus not supported soundcloud streams (e.g. hls streams) don't crash anyore --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/fragments/BaseStateFragment.java | 4 ++++ app/src/main/res/values-eo/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index baa11bd20..6ad29f89f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:69e0624e3' + implementation 'com.github.B0pol:NewPipeExtractor:cf24e2935cbb46117ab7bd2705d5e86f68f649e1' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 861dc2c60..cf49cf302 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -20,6 +20,7 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; 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.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; @@ -216,6 +217,9 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC } else if (exception instanceof IOException) { showError(getString(R.string.network_error), true); return true; + } else if (exception instanceof ContentNotSupportedException) { + showError(getString(R.string.content_not_supported), false); + return true; } return false; diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 797ee4bf4..f985931e5 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -586,4 +586,5 @@ Mutigi Malmutigi Helpo + Tio enhavo ne estas ankoraŭ subtenata per NewPipe.\n\nĜi espereble estos en sekvanta versio. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 14556f1c3..b35e60b45 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -598,4 +598,5 @@ \n \n Donc le choix vous revient : Préferez-vous la vitesse ou des informations précises \? Aide + Ce contenu n\'est pas encore supporté par NewPipe.\n\nIl le sera peut-être dans une version future. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef09be9f4..ea2202c58 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -637,4 +637,5 @@ Enable fast mode Disable fast mode 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. + This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. \ No newline at end of file From c392804f47e9fe2ac0c3f86ac9726fe63ed51751 Mon Sep 17 00:00:00 2001 From: bopol Date: Tue, 7 Apr 2020 14:54:49 +0200 Subject: [PATCH 17/33] handle ContentNotSupportedException in ExtractorHelper.handleGeneralException() --- app/build.gradle | 2 +- app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 6ad29f89f..c11a55216 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.B0pol:NewPipeExtractor:cf24e2935cbb46117ab7bd2705d5e86f68f649e1' + implementation 'com.github.B0pol:NewPipeExtractor:9a7c6b7ab00c0f5b8337232fc66d3d9b538c229f' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 9c6ab1898..701acddad 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -37,6 +37,7 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.comments.CommentsInfo; 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.ReCaptchaException; import org.schabi.newpipe.extractor.feed.FeedExtractor; @@ -293,6 +294,8 @@ public final class ExtractorHelper { Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); } else if (exception instanceof ContentNotAvailableException) { 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 { int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error From 4c128d837cbf778fcd5f1f7e1154e3f95da0aa4c Mon Sep 17 00:00:00 2001 From: bopol Date: Tue, 7 Apr 2020 15:39:06 +0200 Subject: [PATCH 18/33] handle ContentNotSupportedException for Channel Fragment (when an user has no video tab) --- app/build.gradle | 2 +- .../list/channel/ChannelFragment.java | 43 +++++++++++++++++-- app/src/main/res/layout/fragment_channel.xml | 11 +++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c11a55216..b714faa84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.B0pol:NewPipeExtractor:9a7c6b7ab00c0f5b8337232fc66d3d9b538c229f' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:a5155fb' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 8c93ee293..fbe3e34c7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; 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.NewPipe; 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.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; @@ -45,6 +47,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ShareUtils; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; @@ -83,6 +86,9 @@ public class ChannelFragment extends BaseListInfoFragment { private LinearLayout headerPopupButton; private LinearLayout headerBackgroundButton; private MenuItem menuRssButton; + private TextView contentNotSupportedTextView; + private TextView kaomojiTextView; + private TextView noVideosTextView; public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { @@ -118,6 +124,14 @@ public class ChannelFragment extends BaseListInfoFragment { 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 public void onDestroy() { super.onDestroy(); @@ -234,7 +248,7 @@ public class ChannelFragment extends BaseListInfoFragment { .debounce(100, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe((List subscriptionEntities) -> - updateSubscribeButton(!subscriptionEntities.isEmpty()), onError)); + updateSubscribeButton(!subscriptionEntities.isEmpty()), onError)); } @@ -417,9 +431,23 @@ public class ChannelFragment extends BaseListInfoFragment { playlistCtrl.setVisibility(View.VISIBLE); - if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, - NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + List errors = new ArrayList<>(result.getErrors()); + 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 it = errors.iterator(); it.hasNext();) { + Throwable throwable = it.next(); + if (throwable instanceof ContentNotSupportedException) { + showContentNotSupported(); + it.remove(); + } + } + + if (!errors.isEmpty()) { + showSnackBarError(errors, UserAction.REQUESTED_CHANNEL, + NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + } } if (disposables != null) { @@ -439,6 +467,13 @@ public class ChannelFragment extends BaseListInfoFragment { .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() { return getPlayQueue(0); } diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index f6f8afaa3..898440db6 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -32,6 +32,7 @@ tools:visibility="visible"> + + From 2710d9de5bf8b23acf30ea4aa0bdeb34e7fd88c9 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 17 Mar 2020 18:41:39 +0100 Subject: [PATCH 19/33] Add support for INFINITE_ and MORE_THAN_100_ITEMS in playlists --- app/build.gradle | 2 +- .../fragments/list/playlist/PlaylistFragment.java | 12 ++++++++++-- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b714faa84..981a5b4f1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:a5155fb' + implementation 'com.github.XiangRongLin:NewPipeExtractor:6a23efa8d41a05f37b8f523aa08af7e0a9d6c4e1' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index e3eac27ca..7c21c073d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -27,6 +27,7 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; @@ -302,8 +303,15 @@ public class PlaylistFragment extends BaseListInfoFragment { IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, - (int) result.getStreamCount(), (int) result.getStreamCount())); + + int streamCount = (int) result.getStreamCount(); + if (streamCount == PlaylistExtractor.MORE_THAN_100_ITEMS) { + headerStreamCount.setText(getResources().getString(R.string.playlist_more_than_100_items)); + } else if (streamCount == PlaylistExtractor.INFINITE_ITEMS) { + headerStreamCount.setText(getResources().getString(R.string.playlist_infinite_items)); + } else { + headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, streamCount, streamCount)); + } if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea2202c58..d8641cc0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -458,6 +458,8 @@ Playlist thumbnail changed. Could not delete playlist. Auto-generated (no uploader found) + 100+ videos + ∞ videos No Captions Fit From 625419a7db7e93cec83633cd6b0bee9e3aabf797 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 20 Mar 2020 20:57:56 +0100 Subject: [PATCH 20/33] Detect ITEM_COUNT_* in localizeStreamCount() ITEM_COUNT_INFINITE and ITEM_COUNT_MORE_THAN_100. Use localizeStreamCount in PlaylistFragment and PlaylistItemHolder --- app/build.gradle | 2 +- .../fragments/list/playlist/PlaylistFragment.java | 13 +++---------- .../holder/PlaylistMiniInfoItemHolder.java | 4 +++- .../java/org/schabi/newpipe/util/Localization.java | 13 +++++++++++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 981a5b4f1..3357b4b31 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.XiangRongLin:NewPipeExtractor:6a23efa8d41a05f37b8f523aa08af7e0a9d6c4e1' + implementation 'com.github.XiangRongLin:NewPipeExtractor:f9a084f8f9611a87d0f8d6f309a40429304b2ac9' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 7c21c073d..93df98c97 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -27,7 +27,6 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.playlist.PlaylistExtractor; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; @@ -40,6 +39,7 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.StreamDialogEntry; @@ -303,15 +303,8 @@ public class PlaylistFragment extends BaseListInfoFragment { IMAGE_LOADER.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - - int streamCount = (int) result.getStreamCount(); - if (streamCount == PlaylistExtractor.MORE_THAN_100_ITEMS) { - headerStreamCount.setText(getResources().getString(R.string.playlist_more_than_100_items)); - } else if (streamCount == PlaylistExtractor.INFINITE_ITEMS) { - headerStreamCount.setText(getResources().getString(R.string.playlist_infinite_items)); - } else { - headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, streamCount, streamCount)); - } + headerStreamCount.setText(Localization + .localizeStreamCount(getContext(), result.getStreamCount())); if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index c6e637881..2b0c2ef28 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -10,6 +10,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; @@ -41,7 +42,8 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; itemTitleView.setText(item.getName()); - itemStreamCountView.setText(String.valueOf(item.getStreamCount())); + itemStreamCountView.setText(Localization + .localizeStreamCount(itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); itemBuilder.getImageLoader() diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 0b81df07d..c7d10438f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -6,6 +6,8 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.preference.PreferenceManager; + +import org.schabi.newpipe.extractor.ListExtractor; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -151,8 +153,15 @@ public final class Localization { } public static String localizeStreamCount(final Context context, final long streamCount) { - return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, - localizeNumber(context, streamCount)); + switch ((int) streamCount) { + case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: + return context.getResources().getString(R.string.playlist_more_than_100_items); + case (int) ListExtractor.ITEM_COUNT_INFINITE: + return context.getResources().getString(R.string.playlist_infinite_items); + default: + return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, + localizeNumber(context, streamCount)); + } } public static String localizeWatchingCount(final Context context, final long watchingCount) { From 2b47a1b06ab7378f0c28838cdce78e2108ce3b67 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 20 Mar 2020 21:22:09 +0100 Subject: [PATCH 21/33] Also use localizeStreamCount() in local items --- .../schabi/newpipe/local/holder/LocalPlaylistItemHolder.java | 4 +++- .../schabi/newpipe/local/holder/RemotePlaylistItemHolder.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index 3ff4f707a..a3d72094d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -8,6 +8,7 @@ import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; import java.text.DateFormat; @@ -31,7 +32,8 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; itemTitleView.setText(item.name); - itemStreamCountView.setText(String.valueOf(item.streamCount)); + itemStreamCountView.setText(Localization.localizeStreamCount(itemStreamCountView.getContext(), + item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index c6d387fd4..015173842 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -34,7 +34,8 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; itemTitleView.setText(item.getName()); - itemStreamCountView.setText(String.valueOf(item.getStreamCount())); + itemStreamCountView.setText(Localization.localizeStreamCount(itemStreamCountView.getContext(), + item.getStreamCount())); // Here is where the uploader name is set in the bookmarked playlists library if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), From 914d3c4a66ae5d32b4badd9e49c3e2961ca40ddc Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 20 Mar 2020 21:43:17 +0100 Subject: [PATCH 22/33] Use "mini" stream count alternatives for info items Note: more_than_100_videos_mini and infinite_videos_mini are untranslatable --- .../holder/PlaylistMiniInfoItemHolder.java | 2 +- .../local/holder/LocalPlaylistItemHolder.java | 4 ++-- .../local/holder/RemotePlaylistItemHolder.java | 4 ++-- .../org/schabi/newpipe/util/Localization.java | 16 ++++++++++++++-- app/src/main/res/values/strings.xml | 6 ++++-- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index 2b0c2ef28..d4af63062 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -43,7 +43,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { itemTitleView.setText(item.getName()); itemStreamCountView.setText(Localization - .localizeStreamCount(itemStreamCountView.getContext(), item.getStreamCount())); + .localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount())); itemUploaderView.setText(item.getUploaderName()); itemBuilder.getImageLoader() diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java index a3d72094d..458b3c30e 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/LocalPlaylistItemHolder.java @@ -32,8 +32,8 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; itemTitleView.setText(item.name); - itemStreamCountView.setText(Localization.localizeStreamCount(itemStreamCountView.getContext(), - item.streamCount)); + itemStreamCountView.setText(Localization.localizeStreamCountMini( + itemStreamCountView.getContext(), item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, diff --git a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java index 015173842..a47d61d2f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/local/holder/RemotePlaylistItemHolder.java @@ -34,8 +34,8 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; itemTitleView.setText(item.getName()); - itemStreamCountView.setText(Localization.localizeStreamCount(itemStreamCountView.getContext(), - item.getStreamCount())); + itemStreamCountView.setText(Localization.localizeStreamCountMini( + itemStreamCountView.getContext(), item.getStreamCount())); // Here is where the uploader name is set in the bookmarked playlists library if (!TextUtils.isEmpty(item.getUploader())) { itemUploaderView.setText(Localization.concatenateStrings(item.getUploader(), diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index c7d10438f..b8431adff 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -8,6 +8,7 @@ import android.content.res.Resources; import android.preference.PreferenceManager; import org.schabi.newpipe.extractor.ListExtractor; + import android.text.TextUtils; import android.util.DisplayMetrics; @@ -155,15 +156,26 @@ public final class Localization { public static String localizeStreamCount(final Context context, final long streamCount) { switch ((int) streamCount) { case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: - return context.getResources().getString(R.string.playlist_more_than_100_items); + return context.getResources().getString(R.string.more_than_100_videos); case (int) ListExtractor.ITEM_COUNT_INFINITE: - return context.getResources().getString(R.string.playlist_infinite_items); + return context.getResources().getString(R.string.infinite_videos); default: return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount)); } } + public static String localizeStreamCountMini(final Context context, final long streamCount) { + switch ((int) streamCount) { + case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: + return context.getResources().getString(R.string.more_than_100_videos_mini); + case (int) ListExtractor.ITEM_COUNT_INFINITE: + return context.getResources().getString(R.string.infinite_videos_mini); + default: + return String.valueOf(streamCount); + } + } + public static String localizeWatchingCount(final Context context, final long watchingCount) { return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, localizeNumber(context, watchingCount)); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8641cc0f..930974668 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -284,6 +284,10 @@ %s listeners No videos + 100+ videos + ∞ videos + 100+ + %s video %s videos @@ -458,8 +462,6 @@ Playlist thumbnail changed. Could not delete playlist. Auto-generated (no uploader found) - 100+ videos - ∞ videos No Captions Fit From 63e489f1346fd675c5040e18e8292755dc5a52ee Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 20 Mar 2020 22:01:56 +0100 Subject: [PATCH 23/33] Use ITEM_COUNT_UNKNOWN --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/util/Localization.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3357b4b31..056602f9c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.XiangRongLin:NewPipeExtractor:f9a084f8f9611a87d0f8d6f309a40429304b2ac9' + implementation 'com.github.XiangRongLin:NewPipeExtractor:34073527498a22e1f7c1feb18132bf62fb544825' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index b8431adff..c799c44c8 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -155,10 +155,12 @@ public final class Localization { public static String localizeStreamCount(final Context context, final long streamCount) { switch ((int) streamCount) { - case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: - return context.getResources().getString(R.string.more_than_100_videos); + case (int) ListExtractor.ITEM_COUNT_UNKNOWN: + return ""; case (int) ListExtractor.ITEM_COUNT_INFINITE: return context.getResources().getString(R.string.infinite_videos); + case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: + return context.getResources().getString(R.string.more_than_100_videos); default: return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount)); @@ -167,10 +169,12 @@ public final class Localization { public static String localizeStreamCountMini(final Context context, final long streamCount) { switch ((int) streamCount) { - case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: - return context.getResources().getString(R.string.more_than_100_videos_mini); + 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); } From 41a100613f2d40510ba1a1bc4bf0f46faa77c03d Mon Sep 17 00:00:00 2001 From: wb9688 Date: Tue, 17 Mar 2020 11:39:01 +0100 Subject: [PATCH 24/33] Add ability to translate YouTube Music search options --- app/build.gradle | 2 +- .../java/org/schabi/newpipe/util/ServiceHelper.java | 10 ++++++++++ app/src/main/res/values-nl/strings.xml | 7 ++++++- app/src/main/res/values/strings.xml | 5 +++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 056602f9c..75cbf8fba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.XiangRongLin:NewPipeExtractor:34073527498a22e1f7c1feb18132bf62fb544825' + implementation 'com.github.wb9688:NewPipeExtractor:3b4fca8133456cc8839c465341cf185eb2d276be' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index cea624663..369627041 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -61,6 +61,16 @@ public final class ServiceHelper { return c.getString(R.string.conferences); case "events": return c.getString(R.string.events); + case "music_songs": + return c.getString(R.string.music_songs); + case "music_videos": + return c.getString(R.string.music_videos); + case "music_albums": + return c.getString(R.string.music_albums); + case "music_playlists": + return c.getString(R.string.music_playlists); + case "music_artists": + return c.getString(R.string.music_artists); default: return filter; } diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 88c012ef5..6f72b3226 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -596,4 +596,9 @@ Toggle service, momenteel geselecteerd: Meest geliked NewPipe werd gesloten terwijl het bezig was met het bestand - \ No newline at end of file + Music/Nummers + Music/Video\'s + Music/Albums + Music/Afspeellijsten + Music/Artiesten + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 930974668..76e018eeb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -150,6 +150,11 @@ Tracks Users Events + Music/Songs + Music/Videos + Music/Albums + Music/Playlists + Music/Artists Yes Later Disabled From 2852815e1ab5553f22403ca31cb5e422d56de1aa Mon Sep 17 00:00:00 2001 From: wb9688 Date: Sat, 21 Mar 2020 20:53:30 +0100 Subject: [PATCH 25/33] Use suggested layout for search filters --- app/build.gradle | 2 +- .../fragments/list/search/SearchFragment.java | 7 +++++++ .../java/org/schabi/newpipe/util/Localization.java | 4 +--- .../java/org/schabi/newpipe/util/ServiceHelper.java | 12 +++++------- app/src/main/res/values-nl/strings.xml | 8 +++----- app/src/main/res/values/strings.xml | 8 +++----- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 75cbf8fba..e6a37527d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.wb9688:NewPipeExtractor:3b4fca8133456cc8839c465341cf185eb2d276be' + implementation 'com.github.wb9688:NewPipeExtractor:c1cfdb3356a1024953c506a1849c64d3595f41f6' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 718865f10..6dd52c59e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -416,6 +416,13 @@ public class SearchFragment extends BaseListFragmentToggle service, momenteel geselecteerd: Meest geliked NewPipe werd gesloten terwijl het bezig was met het bestand - Music/Nummers - Music/Video\'s - Music/Albums - Music/Afspeellijsten - Music/Artiesten + Nummers + Albums + Artiesten diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76e018eeb..4af6149b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -150,11 +150,9 @@ Tracks Users Events - Music/Songs - Music/Videos - Music/Albums - Music/Playlists - Music/Artists + Songs + Albums + Artists Yes Later Disabled From 9bbd03c14e9a734d97e05420f072d1881d4889ba Mon Sep 17 00:00:00 2001 From: wb9688 Date: Sat, 4 Apr 2020 16:55:26 +0200 Subject: [PATCH 26/33] Bump NewPipeExtractor version --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e6a37527d..6db015b7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.wb9688:NewPipeExtractor:c1cfdb3356a1024953c506a1849c64d3595f41f6' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:a5155fb562ca99ca4a9c8caa2fd60f2f0a305eb0' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' From 90f9819cbd1d10ffd2d649291873ed2eb01b7d95 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Fri, 10 Apr 2020 14:25:00 +0200 Subject: [PATCH 27/33] Show error when video has age limit and setting is disabled --- .../fragments/detail/VideoDetailFragment.java | 19 +++++++++++++------ app/src/main/res/values/strings.xml | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 9ad734cb5..4ebe4b8f8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -103,6 +103,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; import static org.schabi.newpipe.util.AnimationUtils.animateView; public class VideoDetailFragment extends BaseStateFragment @@ -806,19 +807,25 @@ public class VideoDetailFragment extends BaseStateFragment currentWorker.dispose(); } + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + currentWorker = ExtractorHelper.getStreamInfo(serviceId, url, forceLoad) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe((@NonNull StreamInfo result) -> { + .subscribe((@NonNull final StreamInfo result) -> { isLoading.set(false); - currentInfo = result; - handleResult(result); - showContent(); - }, (@NonNull Throwable throwable) -> { + if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean( + getString(R.string.show_age_restricted_content), false)) { + showError(getString(R.string.restricted_video), false); + } else { + currentInfo = result; + handleResult(result); + showContent(); + } + }, (@NonNull final Throwable throwable) -> { isLoading.set(false); onError(throwable); }); - } private void initTabs() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea2202c58..48205d8f4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -136,8 +136,9 @@ Play Content Age restricted content - Live Show age restricted video. Future changes are possible from the settings. + This video is age restricted.\n\nIf you want to view it, enable \"Age restricted content\" in the settings. + Live Downloads Downloads Error report @@ -638,4 +639,4 @@ Disable fast mode 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. This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. - \ No newline at end of file + From 506d1dc1f23438a06c0d0df426bbeaccc55b1256 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Fri, 10 Apr 2020 10:31:28 -0300 Subject: [PATCH 28/33] Improve size handling of the drawer header title Some devices, specially with custom fonts that changed the font width, weren't being correctly adjusted before. --- .../java/org/schabi/newpipe/MainActivity.java | 15 +++++++++++++++ app/src/main/res/layout/drawer_header.xml | 9 ++++----- app/src/main/res/values/dimens.xml | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index c004eae4a..e6269dd5f 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -33,6 +33,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.AdapterView; @@ -301,6 +302,20 @@ public class MainActivity extends AppCompatActivity { headerServiceView = hView.findViewById(R.id.drawer_header_service_view); toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button); 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() { diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml index 4abf20c44..a2f447cdf 100644 --- a/app/src/main/res/layout/drawer_header.xml +++ b/app/src/main/res/layout/drawer_header.xml @@ -45,22 +45,21 @@ + tools:ignore="UnusedAttribute" + tools:text="NewPipe" /> 16dp 48dp 12dp - 120dp - 220dp + 130dp + 200dp 18sp 32sp 16dp From 9487b5367d4f3cf1a78ffca34ca9024ef55c8f71 Mon Sep 17 00:00:00 2001 From: Harshal Lele Date: Tue, 11 Feb 2020 11:07:16 +0530 Subject: [PATCH 29/33] changed popup resizing --- .../newpipe/player/PopupVideoPlayer.java | 85 +++++++++++++++---- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 008aaaf9b..0cb1fe6c9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -41,6 +41,7 @@ import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; @@ -1010,6 +1011,14 @@ public final class PopupVideoPlayer extends Service { private boolean isMoving; 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 public boolean onDoubleTap(final MotionEvent e) { if (DEBUG) { @@ -1201,6 +1210,17 @@ public final class PopupVideoPlayer extends Service { playerImpl.hideControls(0, 0); animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 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 + float xDiff = event.getX(0) - event.getX(1); + float yDiff = event.getY(0) - event.getY(1); + initPointerDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff); + isResizing = true; } @@ -1224,6 +1244,10 @@ public final class PopupVideoPlayer extends Service { if (isResizing) { isResizing = false; + + initPointerDistance = -1; + initFirstPointerX = initFirstPointerY = initSecPointerX = initSecPointerY = -1; + animateView(playerImpl.getResizingIndicator(), false, 100, 0); playerImpl.changeState(playerImpl.getCurrentState()); } @@ -1242,25 +1266,52 @@ public final class PopupVideoPlayer extends Service { return false; } - final float firstPointerX = event.getX(0); - final float secondPointerX = event.getX(1); + if(initPointerDistance != -1){ + + //get the movements of the fingers + float firstPointerMoveX = event.getX(0) - initFirstPointerX; + float firstPointerMoveY = event.getY(0) - initFirstPointerY; + float secPointerMoveX = event.getX(1) - initSecPointerX; + float secPointerMoveY = event.getY(1) - initSecPointerY; + //minimum threshold beyond which pinch gesture will work + int scaledTouchSlop = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop(); + + if(firstPointerMoveX > scaledTouchSlop ||firstPointerMoveY > scaledTouchSlop || + secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop){ + + double newWidth = popupWidth; + + //calculate current distance between the pointers + float currentXDiff = event.getX(0) - event.getX(1); + float currentYDiff = event.getY(0) - event.getY(1); + double currentPointerDistance = Math.sqrt(currentXDiff * currentXDiff + currentYDiff * currentYDiff); + + //scale popup width + double scale = 1 + (currentPointerDistance - initPointerDistance)/ initPointerDistance; + + newWidth = (popupWidth * scale); + + //change co-ordinates of popup so the center stays at the same position + if(currentPointerDistance > initPointerDistance){ + popupLayoutParams.x -= (newWidth - popupWidth)/2; + } + else{ + popupLayoutParams.x += (popupWidth - newWidth)/2; + } + + initPointerDistance = currentPointerDistance; + + checkPopupPositionBounds(); + updateScreenSize(); + + final int width = (int) Math.min(screenWidth, newWidth); + updatePopupSize(width, -1); + + return true; + } - final float diff = Math.abs(firstPointerX - secondPointerX); - if (firstPointerX > secondPointerX) { - // second pointer is the anchor (the leftmost pointer) - popupLayoutParams.x = (int) (event.getRawX() - diff); - } else { - // first pointer is the anchor - popupLayoutParams.x = (int) event.getRawX(); } - - checkPopupPositionBounds(); - updateScreenSize(); - - final int width = (int) Math.min(screenWidth, diff); - updatePopupSize(width, -1); - - return true; + return false; } /*////////////////////////////////////////////////////////////////////////// From fe1889653e98b7fb55d24b03f9d4903e26b82278 Mon Sep 17 00:00:00 2001 From: Harshal Lele Date: Sun, 8 Mar 2020 12:10:06 +0530 Subject: [PATCH 30/33] made formatting changes --- .../newpipe/player/PopupVideoPlayer.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 0cb1fe6c9..e46aa94d6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -1217,9 +1217,8 @@ public final class PopupVideoPlayer extends Service { initSecPointerX = event.getX(1); initSecPointerY = event.getY(1); //record distance between fingers - float xDiff = event.getX(0) - event.getX(1); - float yDiff = event.getY(0) - event.getY(1); - initPointerDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff); + initPointerDistance = Math.hypot(event.getX(0) - event.getX(1), + event.getY(0) - event.getY(1)); isResizing = true; } @@ -1277,27 +1276,22 @@ public final class PopupVideoPlayer extends Service { int scaledTouchSlop = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop(); if(firstPointerMoveX > scaledTouchSlop ||firstPointerMoveY > scaledTouchSlop || - secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop){ + secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop) { double newWidth = popupWidth; //calculate current distance between the pointers - float currentXDiff = event.getX(0) - event.getX(1); - float currentYDiff = event.getY(0) - event.getY(1); - double currentPointerDistance = Math.sqrt(currentXDiff * currentXDiff + currentYDiff * currentYDiff); + double currentPointerDistance = Math.hypot(event.getX(0) - event.getX(1), + event.getY(0) - event.getY(1)); //scale popup width - double scale = 1 + (currentPointerDistance - initPointerDistance)/ initPointerDistance; + double scale = currentPointerDistance / initPointerDistance; newWidth = (popupWidth * scale); //change co-ordinates of popup so the center stays at the same position - if(currentPointerDistance > initPointerDistance){ - popupLayoutParams.x -= (newWidth - popupWidth)/2; - } - else{ - popupLayoutParams.x += (popupWidth - newWidth)/2; - } + popupLayoutParams.x += (popupWidth - newWidth)/2; + initPointerDistance = currentPointerDistance; From 46918ee9079f22e19b32f119fb6d454043f4efcc Mon Sep 17 00:00:00 2001 From: Harshal Lele Date: Mon, 9 Mar 2020 23:26:35 +0530 Subject: [PATCH 31/33] formatting changes --- .../java/org/schabi/newpipe/player/PopupVideoPlayer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index e46aa94d6..71e0feac8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -1265,7 +1265,7 @@ public final class PopupVideoPlayer extends Service { return false; } - if(initPointerDistance != -1){ + if (initPointerDistance != -1) { //get the movements of the fingers float firstPointerMoveX = event.getX(0) - initFirstPointerX; @@ -1275,8 +1275,8 @@ public final class PopupVideoPlayer extends Service { //minimum threshold beyond which pinch gesture will work int scaledTouchSlop = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop(); - if(firstPointerMoveX > scaledTouchSlop ||firstPointerMoveY > scaledTouchSlop || - secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop) { + if (firstPointerMoveX > scaledTouchSlop ||firstPointerMoveY > scaledTouchSlop + || secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop) { double newWidth = popupWidth; From a50e430cd96ae508a66755678f21b318d00127f8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 10 Apr 2020 22:12:45 +0200 Subject: [PATCH 32/33] Fix checkstyle issues and improve code formatting Also calculate differently the moved distance of a pointer: use euclidean and not manhattan geometry --- .../newpipe/player/PopupVideoPlayer.java | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 71e0feac8..de9e9b746 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -1217,8 +1217,8 @@ public final class PopupVideoPlayer extends Service { initSecPointerX = event.getX(1); initSecPointerY = event.getY(1); //record distance between fingers - initPointerDistance = Math.hypot(event.getX(0) - event.getX(1), - event.getY(0) - event.getY(1)); + initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX, + initFirstPointerY - initSecPointerY); isResizing = true; } @@ -1245,7 +1245,10 @@ public final class PopupVideoPlayer extends Service { isResizing = false; initPointerDistance = -1; - initFirstPointerX = initFirstPointerY = initSecPointerX = initSecPointerY = -1; + initFirstPointerX = -1; + initFirstPointerY = -1; + initSecPointerX = -1; + initSecPointerY = -1; animateView(playerImpl.getResizingIndicator(), false, 100, 0); playerImpl.changeState(playerImpl.getCurrentState()); @@ -1261,49 +1264,33 @@ public final class PopupVideoPlayer extends Service { } private boolean handleMultiDrag(final MotionEvent event) { - if (event.getPointerCount() != 2) { - return false; - } + if (initPointerDistance != -1 && event.getPointerCount() == 2) { + // 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); - if (initPointerDistance != -1) { - - //get the movements of the fingers - float firstPointerMoveX = event.getX(0) - initFirstPointerX; - float firstPointerMoveY = event.getY(0) - initFirstPointerY; - float secPointerMoveX = event.getX(1) - initSecPointerX; - float secPointerMoveY = event.getY(1) - initSecPointerY; - //minimum threshold beyond which pinch gesture will work - int scaledTouchSlop = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop(); - - if (firstPointerMoveX > scaledTouchSlop ||firstPointerMoveY > scaledTouchSlop - || secPointerMoveX > scaledTouchSlop || secPointerMoveY > scaledTouchSlop) { - - double newWidth = popupWidth; - - //calculate current distance between the pointers - double currentPointerDistance = Math.hypot(event.getX(0) - event.getX(1), - event.getY(0) - event.getY(1)); - - //scale popup width - double scale = currentPointerDistance / initPointerDistance; - - newWidth = (popupWidth * scale); - - //change co-ordinates of popup so the center stays at the same position - popupLayoutParams.x += (popupWidth - newWidth)/2; + // minimum threshold beyond which pinch gesture will work + int minimumMove = ViewConfiguration.get(PopupVideoPlayer.this).getScaledTouchSlop(); + if (Math.max(firstPointerMove, secPointerMove) > minimumMove) { + // calculate current distance between the pointers + double currentPointerDistance = + Math.hypot(event.getX(0) - event.getX(1), + event.getY(0) - event.getY(1)); + // change co-ordinates of popup so the center stays at the same position + double newWidth = (popupWidth * currentPointerDistance / initPointerDistance); initPointerDistance = currentPointerDistance; + popupLayoutParams.x += (popupWidth - newWidth) / 2; checkPopupPositionBounds(); updateScreenSize(); - final int width = (int) Math.min(screenWidth, newWidth); - updatePopupSize(width, -1); - + updatePopupSize((int) Math.min(screenWidth, newWidth), -1); return true; } - } return false; } From 70ede70ea8d157d78bed59463e25fa244a546511 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Sat, 11 Apr 2020 09:30:12 +0200 Subject: [PATCH 33/33] Hide tabs when a video is age restricted --- .../fragments/detail/VideoDetailFragment.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 4ebe4b8f8..0441e1ba1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -816,7 +816,7 @@ public class VideoDetailFragment extends BaseStateFragment isLoading.set(false); if (result.getAgeLimit() != NO_AGE_LIMIT && !prefs.getBoolean( getString(R.string.show_age_restricted_content), false)) { - showError(getString(R.string.restricted_video), false); + hideAgeRestrictedContent(); } else { currentInfo = result; handleResult(result); @@ -1239,6 +1239,16 @@ public class VideoDetailFragment extends BaseStateFragment } } + 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() { try {