From e632fab4d0036545cbd1a407dd6268a07dd6bd2b Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 18 Sep 2021 19:47:24 +0200 Subject: [PATCH 01/11] Added option to report player errors * Added a new setting so that player errors are reported (under Video and Audio > Player) * Moved the player error logic to separate class specially created for this purpose --- .../org/schabi/newpipe/player/Player.java | 60 ++++--------- .../playererror/PlayerErrorHandler.java | 86 +++++++++++++++++++ app/src/main/res/values/settings_keys.xml | 2 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/video_audio_settings.xml | 9 ++ 5 files changed, 116 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java 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 ac1b76270..2d8c1a830 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -97,7 +97,6 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -166,6 +165,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.playback.SurfaceHolderCallback; +import org.schabi.newpipe.player.playererror.PlayerErrorHandler; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -268,7 +268,7 @@ public final class Player implements @Nullable private MediaSourceTag currentMetadata; @Nullable private Bitmap currentThumbnail; - @Nullable private Toast errorToast; + @NonNull private PlayerErrorHandler playerErrorHandler; /*////////////////////////////////////////////////////////////////////////// // Player @@ -413,6 +413,8 @@ public final class Player implements videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + playerErrorHandler = new PlayerErrorHandler(context); + windowManager = ContextCompat.getSystemService(context, WindowManager.class); } @@ -2512,30 +2514,33 @@ public final class Player implements */ @Override public void onPlayerError(@NonNull final ExoPlaybackException error) { - if (DEBUG) { - Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]"); - } - if (errorToast != null) { - errorToast.cancel(); - errorToast = null; - } + Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); saveStreamProgressState(); switch (error.type) { case ExoPlaybackException.TYPE_SOURCE: processSourceError(error.getSourceException()); - showStreamError(error); + playerErrorHandler.showPlayerError( + error, + currentMetadata.getMetadata(), + R.string.player_stream_failure); break; case ExoPlaybackException.TYPE_UNEXPECTED: - showRecoverableError(error); + playerErrorHandler.showPlayerError( + error, + currentMetadata.getMetadata(), + R.string.player_recoverable_failure); setRecovery(); reloadPlayQueueManager(); break; case ExoPlaybackException.TYPE_REMOTE: case ExoPlaybackException.TYPE_RENDERER: default: - showUnrecoverableError(error); + playerErrorHandler.showPlayerError( + error, + currentMetadata.getMetadata(), + R.string.player_unrecoverable_failure); onPlaybackShutdown(); break; } @@ -2557,37 +2562,6 @@ public final class Player implements playQueue.error(); } } - - private void showStreamError(final Exception exception) { - exception.printStackTrace(); - - if (errorToast == null) { - errorToast = Toast - .makeText(context, R.string.player_stream_failure, Toast.LENGTH_SHORT); - errorToast.show(); - } - } - - private void showRecoverableError(final Exception exception) { - exception.printStackTrace(); - - if (errorToast == null) { - errorToast = Toast - .makeText(context, R.string.player_recoverable_failure, Toast.LENGTH_SHORT); - errorToast.show(); - } - } - - private void showUnrecoverableError(final Exception exception) { - exception.printStackTrace(); - - if (errorToast != null) { - errorToast.cancel(); - } - errorToast = Toast - .makeText(context, R.string.player_unrecoverable_failure, Toast.LENGTH_SHORT); - errorToast.show(); - } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java new file mode 100644 index 000000000..8e9ad9d15 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java @@ -0,0 +1,86 @@ +package org.schabi.newpipe.player.playererror; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.preference.PreferenceManager; + +import com.google.android.exoplayer2.ExoPlaybackException; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.error.ErrorActivity; +import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.Info; + +/** + * Handles (exoplayer)errors that occur in the player. + */ +public class PlayerErrorHandler { + // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25) + // or it fails with an IllegalArgumentException + // https://stackoverflow.com/a/54744028 + private static final String TAG = "PlayerErrorHandler"; + + @Nullable + private Toast errorToast; + + @NonNull + private final Context context; + + public PlayerErrorHandler(@NonNull final Context context) { + this.context = context; + } + + public void showPlayerError( + @NonNull final ExoPlaybackException exception, + @NonNull final Info info, + @StringRes final int textResId) { + // Hide existing toast message + if (errorToast != null) { + Log.d(TAG, "Trying to cancel previous player error error toast"); + errorToast.cancel(); + errorToast = null; + } + + if (shouldReportError()) { + try { + reportError(exception, info); + // When a report pops up we need no toast + return; + } catch (final Exception ex) { + Log.w(TAG, "Unable to report error:", ex); + } + } + + Log.d(TAG, "Showing player error toast"); + errorToast = Toast.makeText(context, textResId, Toast.LENGTH_SHORT); + errorToast.show(); + } + + private void reportError(@NonNull final ExoPlaybackException exception, + @NonNull final Info info) { + ErrorActivity.reportError( + context, + new ErrorInfo( + exception, + UserAction.PLAY_STREAM, + "Player error[type=" + exception.type + "] occurred while playing: " + + info.getUrl(), + info + ) + ); + } + + private boolean shouldReportError() { + return PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean( + context.getString(R.string.report_player_errors_key), + false); + } +} diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 9db147deb..eda0a3ad0 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -89,6 +89,8 @@ @string/never + report_player_errors_key + seekbar_preview_thumbnail_key seekbar_preview_thumbnail_high_quality seekbar_preview_thumbnail_low_quality diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c8fd98ab..89434a2cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,8 @@ org.xbmc.kore Show \"Play with Kodi\" option Display an option to play a video via Kodi media center + Report player errors + Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems) Scale thumbnail to 1:1 aspect ratio Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions) First action button diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index f605fbe17..0bd6b47d5 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -89,6 +89,7 @@ android:title="@string/show_play_with_kodi_title" app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + + Date: Tue, 21 Sep 2021 21:01:10 +0200 Subject: [PATCH 02/11] Added a "Crash the player" debug option --- .../fragments/detail/VideoDetailFragment.java | 33 +++- .../detail/VideoDetailPlayerCrasher.java | 165 ++++++++++++++++++ .../main/res/layout/fragment_video_detail.xml | 18 +- app/src/main/res/values/settings_keys.xml | 1 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/debug_settings.xml | 9 + 6 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java 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 5552cf73f..96aa43e7b 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 @@ -205,6 +205,9 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); + @Nullable + private VideoDetailPlayerCrasher videoDetailPlayerCrasher = null; + /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ @@ -594,6 +597,18 @@ public final class VideoDetailFragment // Init //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + + if (DEBUG) { + this.videoDetailPlayerCrasher = new VideoDetailPlayerCrasher( + () -> this.getContext(), + () -> this.getLayoutInflater() + ); + } + } + @Override // called from onViewCreated in {@link BaseFragment#onViewCreated} protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); @@ -604,6 +619,18 @@ public final class VideoDetailFragment binding.detailThumbnailRootLayout.requestFocus(); + binding.detailControlsPlayWithKodi.setVisibility( + KoreUtils.shouldShowPlayWithKodi(requireContext(), serviceId) + ? View.VISIBLE + : View.GONE + ); + binding.detailControlsCrashThePlayer.setVisibility( + DEBUG && PreferenceManager.getDefaultSharedPreferences(getContext()) + .getBoolean(getString(R.string.show_crash_the_player_key), false) + ? View.VISIBLE + : View.GONE + ); + if (DeviceUtils.isTv(getContext())) { // remove ripple effects from detail controls final int transparent = ContextCompat.getColor(requireContext(), @@ -638,8 +665,10 @@ public final class VideoDetailFragment binding.detailControlsShare.setOnClickListener(this); binding.detailControlsOpenInBrowser.setOnClickListener(this); binding.detailControlsPlayWithKodi.setOnClickListener(this); - binding.detailControlsPlayWithKodi.setVisibility(KoreUtils.shouldShowPlayWithKodi( - requireContext(), serviceId) ? View.VISIBLE : View.GONE); + if (DEBUG) { + binding.detailControlsCrashThePlayer.setOnClickListener( + v -> videoDetailPlayerCrasher.onCrashThePlayer(this.player)); + } binding.overlayThumbnail.setOnClickListener(this); binding.overlayThumbnail.setOnLongClickListener(this); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java new file mode 100644 index 000000000..fc2377657 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -0,0 +1,165 @@ +package org.schabi.newpipe.fragments.detail; + +import android.content.Context; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.RendererCapabilities; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.ListRadioIconItemBinding; +import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.util.ThemeHelper; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +public class VideoDetailPlayerCrasher { + + private static final String TAG = "VideoDetPlayerCrasher"; + + @NonNull + private final Supplier contextSupplier; + @NonNull + private final Supplier layoutInflaterSupplier; + + public VideoDetailPlayerCrasher( + @NonNull final Supplier contextSupplier, + @NonNull final Supplier layoutInflaterSupplier + ) { + this.contextSupplier = contextSupplier; + this.layoutInflaterSupplier = layoutInflaterSupplier; + } + + private static Map> getExceptionTypes() { + final String defaultMsg = "Dummy"; + final Map> exceptionTypes = new LinkedHashMap<>(); + exceptionTypes.put( + "Source", + () -> ExoPlaybackException.createForSource( + new IOException(defaultMsg) + ) + ); + exceptionTypes.put( + "Renderer", + () -> ExoPlaybackException.createForRenderer( + new Exception(defaultMsg), + "Dummy renderer", + 0, + null, + RendererCapabilities.FORMAT_HANDLED + ) + ); + exceptionTypes.put( + "Unexpected", + () -> ExoPlaybackException.createForUnexpected( + new RuntimeException(defaultMsg) + ) + ); + exceptionTypes.put( + "Remote", + () -> ExoPlaybackException.createForRemote(defaultMsg) + ); + exceptionTypes.put( + "Timeout", + () -> ExoPlaybackException.createForTimeout( + new TimeoutException(defaultMsg), + ExoPlaybackException.TIMEOUT_OPERATION_UNDEFINED + ) + ); + + return exceptionTypes; + } + + private Context getContext() { + return this.contextSupplier.get(); + } + + private LayoutInflater getLayoutInflater() { + return this.layoutInflaterSupplier.get(); + } + + private Context getThemeWrapperContext() { + return new ContextThemeWrapper( + getContext(), + ThemeHelper.isLightThemeSelected(getContext()) + ? R.style.LightTheme + : R.style.DarkTheme); + } + + public void onCrashThePlayer(final Player player) { + if (!isPlayerAvailable(player)) { + Log.d(TAG, "Player is not available"); + Toast.makeText(getContext(), "Player is not available", Toast.LENGTH_SHORT) + .show(); + + return; + } + + final Context themeWrapperContext = getThemeWrapperContext(); + + final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); + final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(getLayoutInflater()) + .list; + + final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext()) + .setTitle("Choose an exception") + .setView(radioGroup) + .setCancelable(true) + .setNegativeButton(R.string.cancel, null) + .create(); + + for (final Map.Entry> entry + : getExceptionTypes().entrySet()) { + final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); + radioButton.setText(entry.getKey()); + radioButton.setChecked(false); + radioButton.setLayoutParams( + new RadioGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + radioButton.setOnClickListener(v -> { + tryCrashPlayerWith(player, entry.getValue().get()); + if (alertDialog != null) { + alertDialog.cancel(); + } + }); + radioGroup.addView(radioButton); + } + + alertDialog.show(); + } + + private void tryCrashPlayerWith( + @NonNull final Player player, + @NonNull final ExoPlaybackException exception + ) { + Log.d(TAG, "Crashing the player using player.onPlayerError(ex)"); + try { + player.onPlayerError(exception); + } catch (final Exception exPlayer) { + Log.e(TAG, + "Run into an exception while crashing the player:", + exPlayer); + } + } + + private boolean isPlayerAvailable(final Player player) { + return player != null; + } +} diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 3d9daa156..b7d97cac5 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -213,7 +213,7 @@ android:layout_below="@id/detail_title_root_layout" android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" android:visibility="gone" - tools:visibility="visible" /> + tools:visibility="gone" /> + + disable_media_tunneling_key crash_the_app_key show_image_indicators_key + show_crash_the_player_key theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89434a2cf..a8bb4c788 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ org.xbmc.kore Show \"Play with Kodi\" option Display an option to play a video via Kodi media center + Crash the player Report player errors Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems) Scale thumbnail to 1:1 aspect ratio @@ -475,6 +476,8 @@ Show image indicators Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory Crash the app + Show \"crash the player\" + Shows a crash option when using the player Import Import from diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index 22abebcae..df1559c37 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -54,4 +54,13 @@ android:title="@string/crash_the_app" app:singleLineTitle="false" app:iconSpaceReserved="false" /> + + From e5c00a7ef4957e2a605d384170316d5ab0c2fc0e Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:18:54 +0200 Subject: [PATCH 03/11] Added some doc --- .../newpipe/fragments/detail/VideoDetailPlayerCrasher.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index fc2377657..d8a4a99d1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -145,6 +145,12 @@ public class VideoDetailPlayerCrasher { alertDialog.show(); } + /** + * Note that this method does not crash the underlying exoplayer directly (it's not possible). + * It simply supplies a Exception to {@link Player#onPlayerError(ExoPlaybackException)}. + * @param player + * @param exception + */ private void tryCrashPlayerWith( @NonNull final Player player, @NonNull final ExoPlaybackException exception From c3f1478fdec874f9b71e7ee513c340109dc7b8b1 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:33:55 +0200 Subject: [PATCH 04/11] Support for debug option "Crash the player" on TVs --- .../fragment_video_detail.xml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index fe12776e1..98b24f511 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -225,7 +225,7 @@ android:layout_below="@id/detail_title_root_layout" android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" android:visibility="gone" - tools:visibility="visible" /> + tools:visibility="gone" /> + + Date: Tue, 21 Sep 2021 21:36:05 +0200 Subject: [PATCH 05/11] PlayerErrorHandler refactor + docs --- .../schabi/newpipe/player/playererror/PlayerErrorHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java index 8e9ad9d15..4c8d9bc5f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java +++ b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java @@ -39,7 +39,8 @@ public class PlayerErrorHandler { public void showPlayerError( @NonNull final ExoPlaybackException exception, @NonNull final Info info, - @StringRes final int textResId) { + @StringRes final int textResId + ) { // Hide existing toast message if (errorToast != null) { Log.d(TAG, "Trying to cancel previous player error error toast"); @@ -54,6 +55,7 @@ public class PlayerErrorHandler { return; } catch (final Exception ex) { Log.w(TAG, "Unable to report error:", ex); + // This will show the toast as fallback } } From f18ee8e83d048ac3fb062c120387c546e47e887a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 1 Oct 2021 18:44:47 +0200 Subject: [PATCH 06/11] Added a bit more documentation --- .../fragments/detail/VideoDetailPlayerCrasher.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index d8a4a99d1..a7c382f33 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -27,8 +27,14 @@ import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; +/** + * Outsourced logic for crashing the player in the {@link VideoDetailFragment}. + */ public class VideoDetailPlayerCrasher { + // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25) + // or it fails with an IllegalArgumentException + // https://stackoverflow.com/a/54744028 private static final String TAG = "VideoDetPlayerCrasher"; @NonNull @@ -109,6 +115,8 @@ public class VideoDetailPlayerCrasher { return; } + // -- Build the dialog/UI -- + final Context themeWrapperContext = getThemeWrapperContext(); final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); From aa28a85747008976a9ca7ae3d976af036858b9cd Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 24 Oct 2021 21:09:47 +0200 Subject: [PATCH 07/11] Added a workaround for not serializable exceptions --- .../error/EnsureExceptionSerializable.java | 103 ++++++++++++++++++ .../schabi/newpipe/error/ErrorActivity.java | 10 ++ .../playererror/PlayerErrorHandler.java | 3 +- 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java diff --git a/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java b/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java new file mode 100644 index 000000000..db94de5e5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/EnsureExceptionSerializable.java @@ -0,0 +1,103 @@ +package org.schabi.newpipe.error; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Ensures that a Exception is serializable. + * This is + */ +public final class EnsureExceptionSerializable { + private static final String TAG = "EnsureExSerializable"; + + private EnsureExceptionSerializable() { + // No instance + } + + /** + * Ensures that an exception is serializable. + *
+ * If that is not the case a {@link WorkaroundNotSerializableException} is created. + * + * @param exception + * @return if an exception is not serializable a new {@link WorkaroundNotSerializableException} + * otherwise the exception from the parameter + */ + public static Exception ensureSerializable(@NonNull final Exception exception) { + return checkIfSerializable(exception) + ? exception + : WorkaroundNotSerializableException.create(exception); + } + + public static boolean checkIfSerializable(@NonNull final Exception exception) { + try { + // Check by creating a new ObjectOutputStream which does the serialization + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos) + ) { + oos.writeObject(exception); + oos.flush(); + + bos.toByteArray(); + } + + return true; + } catch (final IOException ex) { + Log.d(TAG, "Exception is not serializable", ex); + return false; + } + } + + public static class WorkaroundNotSerializableException extends Exception { + protected WorkaroundNotSerializableException( + final Throwable notSerializableException, + final Throwable cause) { + super(notSerializableException.toString(), cause); + setStackTrace(notSerializableException.getStackTrace()); + } + + protected WorkaroundNotSerializableException(final Throwable notSerializableException) { + super(notSerializableException.toString()); + setStackTrace(notSerializableException.getStackTrace()); + } + + public static WorkaroundNotSerializableException create( + @NonNull final Exception notSerializableException + ) { + // Build a list of the exception + all causes + final List throwableList = new ArrayList<>(); + + int pos = 0; + Throwable throwableToProcess = notSerializableException; + + while (throwableToProcess != null) { + throwableList.add(throwableToProcess); + + pos++; + throwableToProcess = throwableToProcess.getCause(); + } + + // Reverse list so that it starts with the last one + Collections.reverse(throwableList); + + // Build exception stack + WorkaroundNotSerializableException cause = null; + for (final Throwable t : throwableList) { + cause = cause == null + ? new WorkaroundNotSerializableException(t) + : new WorkaroundNotSerializableException(t, cause); + } + + return cause; + } + + } +} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index c0d88c8ec..db3a92d4f 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -77,6 +77,16 @@ public class ErrorActivity extends AppCompatActivity { private ActivityErrorBinding activityErrorBinding; + /** + * Reports a new error by starting a new activity. + *
+ * Ensure that the data within errorInfo is serializable otherwise + * an exception will be thrown!
+ * {@link EnsureExceptionSerializable} might help. + * + * @param context + * @param errorInfo + */ public static void reportError(final Context context, final ErrorInfo errorInfo) { final Intent intent = new Intent(context, ErrorActivity.class); intent.putExtra(ERROR_INFO, errorInfo); diff --git a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java index 4c8d9bc5f..626200ae1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java +++ b/app/src/main/java/org/schabi/newpipe/player/playererror/PlayerErrorHandler.java @@ -12,6 +12,7 @@ import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.ExoPlaybackException; import org.schabi.newpipe.R; +import org.schabi.newpipe.error.EnsureExceptionSerializable; import org.schabi.newpipe.error.ErrorActivity; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.UserAction; @@ -69,7 +70,7 @@ public class PlayerErrorHandler { ErrorActivity.reportError( context, new ErrorInfo( - exception, + EnsureExceptionSerializable.ensureSerializable(exception), UserAction.PLAY_STREAM, "Player error[type=" + exception.type + "] occurred while playing: " + info.getUrl(), From 0d51eefbb9385883d2ebd3739fad38e72db26866 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 23 Nov 2021 20:11:36 +0100 Subject: [PATCH 08/11] Refactoring --- .../fragments/detail/VideoDetailFragment.java | 13 +---- .../detail/VideoDetailPlayerCrasher.java | 50 +++++++------------ 2 files changed, 21 insertions(+), 42 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 96aa43e7b..b54a04d6f 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 @@ -205,9 +205,6 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); - @Nullable - private VideoDetailPlayerCrasher videoDetailPlayerCrasher = null; - /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ @@ -600,13 +597,6 @@ public final class VideoDetailFragment @Override public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); - - if (DEBUG) { - this.videoDetailPlayerCrasher = new VideoDetailPlayerCrasher( - () -> this.getContext(), - () -> this.getLayoutInflater() - ); - } } @Override // called from onViewCreated in {@link BaseFragment#onViewCreated} @@ -667,7 +657,8 @@ public final class VideoDetailFragment binding.detailControlsPlayWithKodi.setOnClickListener(this); if (DEBUG) { binding.detailControlsCrashThePlayer.setOnClickListener( - v -> videoDetailPlayerCrasher.onCrashThePlayer(this.player)); + v -> VideoDetailPlayerCrasher.onCrashThePlayer(this.player, getLayoutInflater()) + ); } binding.overlayThumbnail.setOnClickListener(this); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index a7c382f33..773a77f50 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -22,6 +22,7 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.ThemeHelper; import java.io.IOException; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeoutException; @@ -30,24 +31,18 @@ import java.util.function.Supplier; /** * Outsourced logic for crashing the player in the {@link VideoDetailFragment}. */ -public class VideoDetailPlayerCrasher { +public final class VideoDetailPlayerCrasher { // This has to be <= 23 chars on devices running Android 7 or lower (API <= 25) // or it fails with an IllegalArgumentException // https://stackoverflow.com/a/54744028 private static final String TAG = "VideoDetPlayerCrasher"; - @NonNull - private final Supplier contextSupplier; - @NonNull - private final Supplier layoutInflaterSupplier; + private static final Map> AVAILABLE_EXCEPTION_TYPES = + getExceptionTypes(); - public VideoDetailPlayerCrasher( - @NonNull final Supplier contextSupplier, - @NonNull final Supplier layoutInflaterSupplier - ) { - this.contextSupplier = contextSupplier; - this.layoutInflaterSupplier = layoutInflaterSupplier; + private VideoDetailPlayerCrasher() { + // No impls } private static Map> getExceptionTypes() { @@ -87,29 +82,22 @@ public class VideoDetailPlayerCrasher { ) ); - return exceptionTypes; + return Collections.unmodifiableMap(exceptionTypes); } - private Context getContext() { - return this.contextSupplier.get(); - } - - private LayoutInflater getLayoutInflater() { - return this.layoutInflaterSupplier.get(); - } - - private Context getThemeWrapperContext() { + private static Context getThemeWrapperContext(final Context context) { return new ContextThemeWrapper( - getContext(), - ThemeHelper.isLightThemeSelected(getContext()) + context, + ThemeHelper.isLightThemeSelected(context) ? R.style.LightTheme : R.style.DarkTheme); } - public void onCrashThePlayer(final Player player) { + public static void onCrashThePlayer(final Player player, final LayoutInflater layoutInflater) { + final Context context = player.getContext(); if (!isPlayerAvailable(player)) { Log.d(TAG, "Player is not available"); - Toast.makeText(getContext(), "Player is not available", Toast.LENGTH_SHORT) + Toast.makeText(context, "Player is not available", Toast.LENGTH_SHORT) .show(); return; @@ -117,13 +105,13 @@ public class VideoDetailPlayerCrasher { // -- Build the dialog/UI -- - final Context themeWrapperContext = getThemeWrapperContext(); + final Context themeWrapperContext = getThemeWrapperContext(context); final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); - final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(getLayoutInflater()) + final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(layoutInflater) .list; - final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext()) + final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext(context)) .setTitle("Choose an exception") .setView(radioGroup) .setCancelable(true) @@ -131,7 +119,7 @@ public class VideoDetailPlayerCrasher { .create(); for (final Map.Entry> entry - : getExceptionTypes().entrySet()) { + : AVAILABLE_EXCEPTION_TYPES.entrySet()) { final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); radioButton.setText(entry.getKey()); radioButton.setChecked(false); @@ -159,7 +147,7 @@ public class VideoDetailPlayerCrasher { * @param player * @param exception */ - private void tryCrashPlayerWith( + private static void tryCrashPlayerWith( @NonNull final Player player, @NonNull final ExoPlaybackException exception ) { @@ -173,7 +161,7 @@ public class VideoDetailPlayerCrasher { } } - private boolean isPlayerAvailable(final Player player) { + private static boolean isPlayerAvailable(final Player player) { return player != null; } } From 6992b2c308c18bcee526c2f3f0136abb84378945 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 23 Nov 2021 20:16:01 +0100 Subject: [PATCH 09/11] Moved report_player_errors to debug --- app/src/main/res/xml/debug_settings.xml | 8 ++++++++ app/src/main/res/xml/video_audio_settings.xml | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml index df1559c37..5e2cc28ed 100644 --- a/app/src/main/res/xml/debug_settings.xml +++ b/app/src/main/res/xml/debug_settings.xml @@ -49,6 +49,14 @@ android:title="@string/show_image_indicators_title" app:iconSpaceReserved="false" /> + + - -
Date: Tue, 23 Nov 2021 20:21:59 +0100 Subject: [PATCH 10/11] Fixed build --- .../fragments/detail/VideoDetailPlayerCrasher.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index 773a77f50..9ddad9855 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -12,8 +12,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.RendererCapabilities; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; @@ -25,7 +25,6 @@ import java.io.IOException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.TimeoutException; import java.util.function.Supplier; /** @@ -61,7 +60,7 @@ public final class VideoDetailPlayerCrasher { "Dummy renderer", 0, null, - RendererCapabilities.FORMAT_HANDLED + C.FORMAT_HANDLED ) ); exceptionTypes.put( @@ -74,13 +73,6 @@ public final class VideoDetailPlayerCrasher { "Remote", () -> ExoPlaybackException.createForRemote(defaultMsg) ); - exceptionTypes.put( - "Timeout", - () -> ExoPlaybackException.createForTimeout( - new TimeoutException(defaultMsg), - ExoPlaybackException.TIMEOUT_OPERATION_UNDEFINED - ) - ); return Collections.unmodifiableMap(exceptionTypes); } From 4c8dca53009cd6f8571f41f8a467117102623a4c Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 28 Nov 2021 13:42:26 +0100 Subject: [PATCH 11/11] Fixed NPE + Problems with context --- .../fragments/detail/VideoDetailFragment.java | 5 ++++- .../fragments/detail/VideoDetailPlayerCrasher.java | 14 +++++++------- 2 files changed, 11 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 b54a04d6f..8c6e01537 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 @@ -657,7 +657,10 @@ public final class VideoDetailFragment binding.detailControlsPlayWithKodi.setOnClickListener(this); if (DEBUG) { binding.detailControlsCrashThePlayer.setOnClickListener( - v -> VideoDetailPlayerCrasher.onCrashThePlayer(this.player, getLayoutInflater()) + v -> VideoDetailPlayerCrasher.onCrashThePlayer( + this.getContext(), + this.player, + getLayoutInflater()) ); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index 9ddad9855..9309a8a49 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -10,6 +10,7 @@ import android.widget.RadioGroup; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import com.google.android.exoplayer2.C; @@ -85,9 +86,12 @@ public final class VideoDetailPlayerCrasher { : R.style.DarkTheme); } - public static void onCrashThePlayer(final Player player, final LayoutInflater layoutInflater) { - final Context context = player.getContext(); - if (!isPlayerAvailable(player)) { + public static void onCrashThePlayer( + @NonNull final Context context, + @Nullable final Player player, + @NonNull final LayoutInflater layoutInflater + ) { + if (player == null) { Log.d(TAG, "Player is not available"); Toast.makeText(context, "Player is not available", Toast.LENGTH_SHORT) .show(); @@ -152,8 +156,4 @@ public final class VideoDetailPlayerCrasher { exPlayer); } } - - private static boolean isPlayerAvailable(final Player player) { - return player != null; - } }