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" /> + +