diff --git a/app/build.gradle b/app/build.gradle index 07d2752c9..1c193ff34 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { resValue "string", "app_name", "NewPipe" minSdkVersion 19 targetSdkVersion 29 - versionCode 975 - versionName "0.21.9" + versionCode 976 + versionName "0.21.10" multiDexEnabled true @@ -189,7 +189,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:62b87552f51022be76804f3bed65447aeb9fce9b' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.10' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" @@ -259,6 +259,9 @@ dependencies { // Crash reporting implementation "ch.acra:acra-core:5.7.0" + // Properly restarting + implementation 'com.jakewharton:process-phoenix:2.1.2' + // Reactive extensions for Java VM implementation "io.reactivex.rxjava3:rxjava:3.0.7" implementation "io.reactivex.rxjava3:rxandroid:3.0.0" diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 9f4b6d550..766ebe834 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -11,6 +11,8 @@ import androidx.core.app.NotificationManagerCompat; import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; +import com.jakewharton.processphoenix.ProcessPhoenix; + import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; @@ -86,6 +88,12 @@ public class App extends MultiDexApplication { app = this; + if (ProcessPhoenix.isPhoenixProcess(this)) { + Log.i(TAG, "This is a phoenix process! " + + "Aborting initialization of App[onCreate]"); + return; + } + // Initialize settings first because others inits can use its values NewPipeSettings.initSettings(this); diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index c8b6969c6..0be427648 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -16,7 +16,7 @@ import leakcanary.AppWatcher; public abstract class BaseFragment extends Fragment { protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); - protected final boolean DEBUG = MainActivity.DEBUG; + protected static final boolean DEBUG = MainActivity.DEBUG; protected AppCompatActivity activity; //These values are used for controlling fragments when they are part of the frontpage @State diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt index a18d15af3..a8fdcae26 100644 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt @@ -170,6 +170,10 @@ class AboutActivity : AppCompatActivity() { "PrettyTime", "2012 - 2020", "Lincoln Baxter, III", "https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2 ), + SoftwareComponent( + "ProcessPhoenix", "2015", "Jake Wharton", + "https://github.com/JakeWharton/ProcessPhoenix", StandardLicenses.APACHE2 + ), SoftwareComponent( "RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2 diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt index 66d5e6831..228c17f8c 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorPanelHelper.kt @@ -121,27 +121,14 @@ class ErrorPanelHelper( ErrorActivity.reportError(context, errorInfo) } - errorTextView.setText( - when (errorInfo.throwable) { - is AgeRestrictedContentException -> R.string.restricted_video_no_stream - is GeographicRestrictionException -> R.string.georestricted_content - is PaidContentException -> R.string.paid_content - is PrivateContentException -> R.string.private_content - is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content - is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content - is ContentNotAvailableException -> R.string.content_not_available - is ContentNotSupportedException -> R.string.content_not_supported - else -> { - // show retry button only for content which is not unavailable or unsupported - errorRetryButton.isVisible = true - if (errorInfo.throwable != null && errorInfo.throwable!!.isNetworkRelated) { - R.string.network_error - } else { - R.string.error_snackbar_message - } - } - } - ) + errorTextView.setText(getExceptionDescription(errorInfo.throwable)) + + if (errorInfo.throwable !is ContentNotAvailableException && + errorInfo.throwable !is ContentNotSupportedException + ) { + // show retry button only for content which is not unavailable or unsupported + errorRetryButton.isVisible = true + } } setRootVisible() @@ -189,5 +176,27 @@ class ErrorPanelHelper( companion object { val TAG: String = ErrorPanelHelper::class.simpleName!! val DEBUG: Boolean = MainActivity.DEBUG + + @StringRes + public fun getExceptionDescription(throwable: Throwable?): Int { + return when (throwable) { + is AgeRestrictedContentException -> R.string.restricted_video_no_stream + is GeographicRestrictionException -> R.string.georestricted_content + is PaidContentException -> R.string.paid_content + is PrivateContentException -> R.string.private_content + is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content + is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content + is ContentNotAvailableException -> R.string.content_not_available + is ContentNotSupportedException -> R.string.content_not_supported + else -> { + // show retry button only for content which is not unavailable or unsupported + if (throwable != null && throwable.isNetworkRelated) { + R.string.network_error + } else { + R.string.error_snackbar_message + } + } + } + } } } diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index cd6a882ae..555dd709b 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.error; +import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; @@ -66,6 +67,7 @@ public class ReCaptchaActivity extends AppCompatActivity { private ActivityRecaptchaBinding recaptchaBinding; private String foundCookies = ""; + @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(final Bundle savedInstanceState) { ThemeHelper.setTheme(this); 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 f206bb00a..5a30ea0f3 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 @@ -424,7 +424,7 @@ public final class VideoDetailFragment showRelatedItems = sharedPreferences.getBoolean(key, true); tabSettingsChanged = true; } else if (key.equals(getString(R.string.show_description_key))) { - showComments = sharedPreferences.getBoolean(key, true); + showDescription = sharedPreferences.getBoolean(key, true); tabSettingsChanged = true; } } @@ -743,20 +743,19 @@ public final class VideoDetailFragment && player.getPlayQueue() != null && player.videoPlayerSelected() && player.getPlayQueue().previous()) { - return true; + return true; // no code here, as previous() was used in the if } + // That means that we are on the start of the stack, - // return false to let the MainActivity handle the onBack if (stack.size() <= 1) { restoreDefaultOrientation(); - - return false; + return false; // let MainActivity handle the onBack (e.g. to minimize the mini player) } + // Remove top stack.pop(); // Get stack item from the new top - assert stack.peek() != null; - setupFromHistoryItem(stack.peek()); + setupFromHistoryItem(Objects.requireNonNull(stack.peek())); return true; } @@ -1433,17 +1432,15 @@ public final class VideoDetailFragment //////////////////////////////////////////////////////////////////////////*/ private void restoreDefaultOrientation() { - if (!isPlayerAvailable() || !player.videoPlayerSelected() || activity == null) { - return; + if (isPlayerAvailable() && player.videoPlayerSelected()) { + toggleFullscreenIfInFullscreenMode(); } - toggleFullscreenIfInFullscreenMode(); - // This will show systemUI and pause the player. // User can tap on Play button and video will be in fullscreen mode again // Note for tablet: trying to avoid orientation changes since it's not easy // to physically rotate the tablet every time - if (!DeviceUtils.isTablet(activity)) { + if (activity != null && !DeviceUtils.isTablet(activity)) { activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } } 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 5ff2f4788..7de212383 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 @@ -215,6 +215,7 @@ public class SearchFragment extends BaseListFragment { - if (listNotification.isOnNext()) { - if (listNotification.getValue() != null) { - handleSuggestions(listNotification.getValue()); - } - } else if (listNotification.isOnError() - && listNotification.getError() != null - && !ExceptionUtils.isInterruptedCaused(listNotification.getError())) { - showSnackBarError(new ErrorInfo(listNotification.getError(), - UserAction.GET_SUGGESTIONS, searchString, serviceId)); - } - }); + .subscribe( + listNotification -> { + if (listNotification.isOnNext()) { + if (listNotification.getValue() != null) { + handleSuggestions(listNotification.getValue()); + } + } else if (listNotification.isOnError() + && listNotification.getError() != null + && !ExceptionUtils.isInterruptedCaused( + listNotification.getError())) { + showSnackBarError(new ErrorInfo(listNotification.getError(), + UserAction.GET_SUGGESTIONS, searchString, serviceId)); + } + }, throwable -> showSnackBarError(new ErrorInfo( + throwable, UserAction.GET_SUGGESTIONS, searchString, serviceId))); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index cb6481011..5ad855e78 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -238,7 +238,7 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds - public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 500; // 500 millis + public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 1000; // 1 second public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds @@ -611,7 +611,6 @@ public final class Player implements // Resolve enqueue intents if (intent.getBooleanExtra(ENQUEUE, false) && playQueue != null) { playQueue.append(newQueue.getStreams()); - return; // Resolve enqueue next intents @@ -619,7 +618,6 @@ public final class Player implements final int currentIndex = playQueue.getIndex(); playQueue.append(newQueue.getStreams()); playQueue.move(playQueue.size() - 1, currentIndex + 1); - return; } @@ -2328,7 +2326,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + "repeatMode = [" + repeatMode + "]"); } - setRepeatModeButton(((AppCompatImageButton) binding.repeatButton), repeatMode); + setRepeatModeButton(binding.repeatButton, repeatMode); onShuffleOrRepeatModeChanged(); } @@ -3191,7 +3189,7 @@ public final class Player implements private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { return (item, seconds) -> { segmentAdapter.selectSegment(item); - seekTo(seconds * 1000); + seekTo(seconds * 1000L); triggerProgressUpdate(); }; } @@ -3201,7 +3199,7 @@ public final class Player implements final List segments = currentMetadata.getMetadata().getStreamSegments(); for (int i = 0; i < segments.size(); i++) { - if (segments.get(i).getStartTimeSeconds() * 1000 > playbackPosition) { + if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { break; } nearestPosition++; 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 fcdf0172d..067c315ee 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -60,6 +60,8 @@ import java.util.ArrayList; import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp; +import com.jakewharton.processphoenix.ProcessPhoenix; + public final class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; @@ -348,7 +350,7 @@ public final class NavigationHelper { autoPlay = false; } - final RunnableWithVideoDetailFragment onVideoDetailFragmentReady = (detailFragment) -> { + final RunnableWithVideoDetailFragment onVideoDetailFragmentReady = detailFragment -> { expandMainPlayer(detailFragment.requireActivity()); detailFragment.setAutoPlay(autoPlay); if (switchingPlayers) { @@ -595,8 +597,7 @@ public final class NavigationHelper { */ public static void restartApp(final Activity activity) { NewPipeDatabase.close(); - activity.finishAffinity(); - final Intent intent = new Intent(activity, MainActivity.class); - activity.startActivity(intent); + + ProcessPhoenix.triggerRebirth(activity.getApplicationContext()); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index c64631b72..160eb59cd 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -119,7 +119,7 @@ public final class PermissionHelper { public static boolean isPopupEnabled(final Context context) { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M - || PermissionHelper.checkSystemAlertWindowPermission(context); + || checkSystemAlertWindowPermission(context); } public static void showPopupEnablementToast(final Context context) { diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java index 39ec51ce4..240341ab0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java @@ -1,9 +1,14 @@ package org.schabi.newpipe.util.external_communication; import android.content.Context; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorPanelHelper; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -24,6 +29,9 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; public final class InternalUrlsHandler { + private static final String TAG = InternalUrlsHandler.class.getSimpleName(); + private static final boolean DEBUG = MainActivity.DEBUG; + private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)"); private static final Pattern HASHTAG_TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)"); @@ -93,7 +101,12 @@ public final class InternalUrlsHandler { return false; } final String matchedUrl = matcher.group(1); - final int seconds = Integer.parseInt(matcher.group(2)); + final int seconds; + if (matcher.group(2) == null) { + seconds = -1; + } else { + seconds = Integer.parseInt(matcher.group(2)); + } final StreamingService service; final StreamingService.LinkType linkType; @@ -146,8 +159,18 @@ public final class InternalUrlsHandler { .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { final PlayQueue playQueue - = new SinglePlayQueue(info, seconds * 1000); + = new SinglePlayQueue(info, seconds * 1000L); NavigationHelper.playOnPopupPlayer(context, playQueue, false); + }, throwable -> { + if (DEBUG) { + Log.e(TAG, "Could not play on popup: " + url, throwable); + } + new AlertDialog.Builder(context) + .setTitle(R.string.player_stream_failure) + .setMessage( + ErrorPanelHelper.Companion.getExceptionDescription(throwable)) + .setPositiveButton(R.string.ok, (v, b) -> { }) + .show(); })); return true; } diff --git a/app/src/main/res/values-night-v21/styles.xml b/app/src/main/res/values-night-v21/styles.xml new file mode 100644 index 000000000..eb39dee38 --- /dev/null +++ b/app/src/main/res/values-night-v21/styles.xml @@ -0,0 +1,9 @@ + + + + + + +