mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	Merge pull request #7142 from litetex/better-player-error-handling
Better player error handling
This commit is contained in:
		| @@ -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. | ||||||
|  |      * <br/> | ||||||
|  |      * 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<Throwable> 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; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -77,6 +77,16 @@ public class ErrorActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|     private ActivityErrorBinding activityErrorBinding; |     private ActivityErrorBinding activityErrorBinding; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Reports a new error by starting a new activity. | ||||||
|  |      * <br/> | ||||||
|  |      * Ensure that the data within errorInfo is serializable otherwise | ||||||
|  |      * an exception will be thrown!<br/> | ||||||
|  |      * {@link EnsureExceptionSerializable} might help. | ||||||
|  |      * | ||||||
|  |      * @param context | ||||||
|  |      * @param errorInfo | ||||||
|  |      */ | ||||||
|     public static void reportError(final Context context, final ErrorInfo errorInfo) { |     public static void reportError(final Context context, final ErrorInfo errorInfo) { | ||||||
|         final Intent intent = new Intent(context, ErrorActivity.class); |         final Intent intent = new Intent(context, ErrorActivity.class); | ||||||
|         intent.putExtra(ERROR_INFO, errorInfo); |         intent.putExtra(ERROR_INFO, errorInfo); | ||||||
|   | |||||||
| @@ -594,6 +594,11 @@ public final class VideoDetailFragment | |||||||
|     // Init |     // Init | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { | ||||||
|  |         super.onViewCreated(rootView, savedInstanceState); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override // called from onViewCreated in {@link BaseFragment#onViewCreated} |     @Override // called from onViewCreated in {@link BaseFragment#onViewCreated} | ||||||
|     protected void initViews(final View rootView, final Bundle savedInstanceState) { |     protected void initViews(final View rootView, final Bundle savedInstanceState) { | ||||||
|         super.initViews(rootView, savedInstanceState); |         super.initViews(rootView, savedInstanceState); | ||||||
| @@ -604,6 +609,18 @@ public final class VideoDetailFragment | |||||||
|  |  | ||||||
|         binding.detailThumbnailRootLayout.requestFocus(); |         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())) { |         if (DeviceUtils.isTv(getContext())) { | ||||||
|             // remove ripple effects from detail controls |             // remove ripple effects from detail controls | ||||||
|             final int transparent = ContextCompat.getColor(requireContext(), |             final int transparent = ContextCompat.getColor(requireContext(), | ||||||
| @@ -638,8 +655,14 @@ public final class VideoDetailFragment | |||||||
|         binding.detailControlsShare.setOnClickListener(this); |         binding.detailControlsShare.setOnClickListener(this); | ||||||
|         binding.detailControlsOpenInBrowser.setOnClickListener(this); |         binding.detailControlsOpenInBrowser.setOnClickListener(this); | ||||||
|         binding.detailControlsPlayWithKodi.setOnClickListener(this); |         binding.detailControlsPlayWithKodi.setOnClickListener(this); | ||||||
|         binding.detailControlsPlayWithKodi.setVisibility(KoreUtils.shouldShowPlayWithKodi( |         if (DEBUG) { | ||||||
|                 requireContext(), serviceId) ? View.VISIBLE : View.GONE); |             binding.detailControlsCrashThePlayer.setOnClickListener( | ||||||
|  |                     v -> VideoDetailPlayerCrasher.onCrashThePlayer( | ||||||
|  |                             this.getContext(), | ||||||
|  |                             this.player, | ||||||
|  |                             getLayoutInflater()) | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         binding.overlayThumbnail.setOnClickListener(this); |         binding.overlayThumbnail.setOnClickListener(this); | ||||||
|         binding.overlayThumbnail.setOnLongClickListener(this); |         binding.overlayThumbnail.setOnLongClickListener(this); | ||||||
|   | |||||||
| @@ -0,0 +1,159 @@ | |||||||
|  | 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.annotation.Nullable; | ||||||
|  | import androidx.appcompat.app.AlertDialog; | ||||||
|  |  | ||||||
|  | import com.google.android.exoplayer2.C; | ||||||
|  | import com.google.android.exoplayer2.ExoPlaybackException; | ||||||
|  |  | ||||||
|  | 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.Collections; | ||||||
|  | import java.util.LinkedHashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Outsourced logic for crashing the player in the {@link VideoDetailFragment}. | ||||||
|  |  */ | ||||||
|  | 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"; | ||||||
|  |  | ||||||
|  |     private static final Map<String, Supplier<ExoPlaybackException>> AVAILABLE_EXCEPTION_TYPES = | ||||||
|  |             getExceptionTypes(); | ||||||
|  |  | ||||||
|  |     private VideoDetailPlayerCrasher() { | ||||||
|  |         // No impls | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static Map<String, Supplier<ExoPlaybackException>> getExceptionTypes() { | ||||||
|  |         final String defaultMsg = "Dummy"; | ||||||
|  |         final Map<String, Supplier<ExoPlaybackException>> exceptionTypes = new LinkedHashMap<>(); | ||||||
|  |         exceptionTypes.put( | ||||||
|  |                 "Source", | ||||||
|  |                 () -> ExoPlaybackException.createForSource( | ||||||
|  |                         new IOException(defaultMsg) | ||||||
|  |                 ) | ||||||
|  |         ); | ||||||
|  |         exceptionTypes.put( | ||||||
|  |                 "Renderer", | ||||||
|  |                 () -> ExoPlaybackException.createForRenderer( | ||||||
|  |                         new Exception(defaultMsg), | ||||||
|  |                         "Dummy renderer", | ||||||
|  |                         0, | ||||||
|  |                         null, | ||||||
|  |                         C.FORMAT_HANDLED | ||||||
|  |                 ) | ||||||
|  |         ); | ||||||
|  |         exceptionTypes.put( | ||||||
|  |                 "Unexpected", | ||||||
|  |                 () -> ExoPlaybackException.createForUnexpected( | ||||||
|  |                         new RuntimeException(defaultMsg) | ||||||
|  |                 ) | ||||||
|  |         ); | ||||||
|  |         exceptionTypes.put( | ||||||
|  |                 "Remote", | ||||||
|  |                 () -> ExoPlaybackException.createForRemote(defaultMsg) | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return Collections.unmodifiableMap(exceptionTypes); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static Context getThemeWrapperContext(final Context context) { | ||||||
|  |         return new ContextThemeWrapper( | ||||||
|  |                 context, | ||||||
|  |                 ThemeHelper.isLightThemeSelected(context) | ||||||
|  |                         ? R.style.LightTheme | ||||||
|  |                         : R.style.DarkTheme); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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(); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // -- Build the dialog/UI -- | ||||||
|  |  | ||||||
|  |         final Context themeWrapperContext = getThemeWrapperContext(context); | ||||||
|  |  | ||||||
|  |         final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext); | ||||||
|  |         final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(layoutInflater) | ||||||
|  |                 .list; | ||||||
|  |  | ||||||
|  |         final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext(context)) | ||||||
|  |                 .setTitle("Choose an exception") | ||||||
|  |                 .setView(radioGroup) | ||||||
|  |                 .setCancelable(true) | ||||||
|  |                 .setNegativeButton(R.string.cancel, null) | ||||||
|  |                 .create(); | ||||||
|  |  | ||||||
|  |         for (final Map.Entry<String, Supplier<ExoPlaybackException>> entry | ||||||
|  |                 : AVAILABLE_EXCEPTION_TYPES.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(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 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 static 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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -97,7 +97,6 @@ import android.widget.ProgressBar; | |||||||
| import android.widget.RelativeLayout; | import android.widget.RelativeLayout; | ||||||
| import android.widget.SeekBar; | import android.widget.SeekBar; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| import android.widget.Toast; |  | ||||||
|  |  | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | 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.PlaybackListener; | ||||||
| import org.schabi.newpipe.player.playback.PlayerMediaSession; | import org.schabi.newpipe.player.playback.PlayerMediaSession; | ||||||
| import org.schabi.newpipe.player.playback.SurfaceHolderCallback; | 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.PlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; | import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueueItem; | import org.schabi.newpipe.player.playqueue.PlayQueueItem; | ||||||
| @@ -268,7 +268,7 @@ public final class Player implements | |||||||
|     @Nullable private MediaSourceTag currentMetadata; |     @Nullable private MediaSourceTag currentMetadata; | ||||||
|     @Nullable private Bitmap currentThumbnail; |     @Nullable private Bitmap currentThumbnail; | ||||||
|  |  | ||||||
|     @Nullable private Toast errorToast; |     @NonNull private PlayerErrorHandler playerErrorHandler; | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Player |     // Player | ||||||
| @@ -413,6 +413,8 @@ public final class Player implements | |||||||
|         videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); |         videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); | ||||||
|         audioResolver = new AudioPlaybackResolver(context, dataSource); |         audioResolver = new AudioPlaybackResolver(context, dataSource); | ||||||
|  |  | ||||||
|  |         playerErrorHandler = new PlayerErrorHandler(context); | ||||||
|  |  | ||||||
|         windowManager = ContextCompat.getSystemService(context, WindowManager.class); |         windowManager = ContextCompat.getSystemService(context, WindowManager.class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -2512,30 +2514,33 @@ public final class Player implements | |||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     public void onPlayerError(@NonNull final ExoPlaybackException error) { |     public void onPlayerError(@NonNull final ExoPlaybackException error) { | ||||||
|         if (DEBUG) { |         Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); | ||||||
|             Log.d(TAG, "ExoPlayer - onPlayerError() called with: " + "error = [" + error + "]"); |  | ||||||
|         } |  | ||||||
|         if (errorToast != null) { |  | ||||||
|             errorToast.cancel(); |  | ||||||
|             errorToast = null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         saveStreamProgressState(); |         saveStreamProgressState(); | ||||||
|  |  | ||||||
|         switch (error.type) { |         switch (error.type) { | ||||||
|             case ExoPlaybackException.TYPE_SOURCE: |             case ExoPlaybackException.TYPE_SOURCE: | ||||||
|                 processSourceError(error.getSourceException()); |                 processSourceError(error.getSourceException()); | ||||||
|                 showStreamError(error); |                 playerErrorHandler.showPlayerError( | ||||||
|  |                         error, | ||||||
|  |                         currentMetadata.getMetadata(), | ||||||
|  |                         R.string.player_stream_failure); | ||||||
|                 break; |                 break; | ||||||
|             case ExoPlaybackException.TYPE_UNEXPECTED: |             case ExoPlaybackException.TYPE_UNEXPECTED: | ||||||
|                 showRecoverableError(error); |                 playerErrorHandler.showPlayerError( | ||||||
|  |                         error, | ||||||
|  |                         currentMetadata.getMetadata(), | ||||||
|  |                         R.string.player_recoverable_failure); | ||||||
|                 setRecovery(); |                 setRecovery(); | ||||||
|                 reloadPlayQueueManager(); |                 reloadPlayQueueManager(); | ||||||
|                 break; |                 break; | ||||||
|             case ExoPlaybackException.TYPE_REMOTE: |             case ExoPlaybackException.TYPE_REMOTE: | ||||||
|             case ExoPlaybackException.TYPE_RENDERER: |             case ExoPlaybackException.TYPE_RENDERER: | ||||||
|             default: |             default: | ||||||
|                 showUnrecoverableError(error); |                 playerErrorHandler.showPlayerError( | ||||||
|  |                         error, | ||||||
|  |                         currentMetadata.getMetadata(), | ||||||
|  |                         R.string.player_unrecoverable_failure); | ||||||
|                 onPlaybackShutdown(); |                 onPlaybackShutdown(); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
| @@ -2557,37 +2562,6 @@ public final class Player implements | |||||||
|             playQueue.error(); |             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 |     //endregion | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,89 @@ | |||||||
|  | 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.EnsureExceptionSerializable; | ||||||
|  | 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); | ||||||
|  |                 // This will show the toast as fallback | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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( | ||||||
|  |                         EnsureExceptionSerializable.ensureSerializable(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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -225,7 +225,7 @@ | |||||||
|                         android:layout_below="@id/detail_title_root_layout" |                         android:layout_below="@id/detail_title_root_layout" | ||||||
|                         android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" |                         android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" | ||||||
|                         android:visibility="gone" |                         android:visibility="gone" | ||||||
|                         tools:visibility="visible" /> |                         tools:visibility="gone" /> | ||||||
|  |  | ||||||
|                     <!--HIDING ROOT--> |                     <!--HIDING ROOT--> | ||||||
|                     <LinearLayout |                     <LinearLayout | ||||||
| @@ -563,6 +563,22 @@ | |||||||
|                                 android:textSize="@dimen/detail_control_text_size" |                                 android:textSize="@dimen/detail_control_text_size" | ||||||
|                                 app:drawableTopCompat="@drawable/ic_cast" /> |                                 app:drawableTopCompat="@drawable/ic_cast" /> | ||||||
|  |  | ||||||
|  |                             <TextView | ||||||
|  |                                 android:id="@+id/detail_controls_crash_the_player" | ||||||
|  |                                 android:layout_width="@dimen/detail_control_width" | ||||||
|  |                                 android:layout_height="@dimen/detail_control_height" | ||||||
|  |                                 android:layout_gravity="center_vertical" | ||||||
|  |                                 android:layout_weight="1" | ||||||
|  |                                 android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|  |                                 android:clickable="true" | ||||||
|  |                                 android:contentDescription="@string/crash_the_player" | ||||||
|  |                                 android:focusable="true" | ||||||
|  |                                 android:gravity="center" | ||||||
|  |                                 android:paddingVertical="@dimen/detail_control_padding" | ||||||
|  |                                 android:text="@string/crash_the_player" | ||||||
|  |                                 android:textSize="@dimen/detail_control_text_size" | ||||||
|  |                                 app:drawableTopCompat="@drawable/ic_bug_report" /> | ||||||
|  |  | ||||||
|                         </LinearLayout> |                         </LinearLayout> | ||||||
|  |  | ||||||
|                         <View |                         <View | ||||||
|   | |||||||
| @@ -213,7 +213,7 @@ | |||||||
|                     android:layout_below="@id/detail_title_root_layout" |                     android:layout_below="@id/detail_title_root_layout" | ||||||
|                     android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" |                     android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" | ||||||
|                     android:visibility="gone" |                     android:visibility="gone" | ||||||
|                     tools:visibility="visible" /> |                     tools:visibility="gone" /> | ||||||
|  |  | ||||||
|                 <!--HIDING ROOT--> |                 <!--HIDING ROOT--> | ||||||
|                 <LinearLayout |                 <LinearLayout | ||||||
| @@ -547,6 +547,22 @@ | |||||||
|                             android:textSize="@dimen/detail_control_text_size" |                             android:textSize="@dimen/detail_control_text_size" | ||||||
|                             app:drawableTopCompat="@drawable/ic_cast" /> |                             app:drawableTopCompat="@drawable/ic_cast" /> | ||||||
|  |  | ||||||
|  |                         <TextView | ||||||
|  |                             android:id="@+id/detail_controls_crash_the_player" | ||||||
|  |                             android:layout_width="@dimen/detail_control_width" | ||||||
|  |                             android:layout_height="@dimen/detail_control_height" | ||||||
|  |                             android:layout_gravity="center_vertical" | ||||||
|  |                             android:layout_weight="1" | ||||||
|  |                             android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|  |                             android:clickable="true" | ||||||
|  |                             android:contentDescription="@string/crash_the_player" | ||||||
|  |                             android:focusable="true" | ||||||
|  |                             android:gravity="center" | ||||||
|  |                             android:paddingVertical="@dimen/detail_control_padding" | ||||||
|  |                             android:text="@string/crash_the_player" | ||||||
|  |                             android:textSize="@dimen/detail_control_text_size" | ||||||
|  |                             app:drawableTopCompat="@drawable/ic_bug_report" /> | ||||||
|  |  | ||||||
|                     </LinearLayout> |                     </LinearLayout> | ||||||
|  |  | ||||||
|                     <View |                     <View | ||||||
|   | |||||||
| @@ -89,6 +89,8 @@ | |||||||
|         <item>@string/never</item> |         <item>@string/never</item> | ||||||
|     </string-array> |     </string-array> | ||||||
|  |  | ||||||
|  |     <string name="report_player_errors_key" translatable="false">report_player_errors_key</string> | ||||||
|  |  | ||||||
|     <string name="seekbar_preview_thumbnail_key" translatable="false">seekbar_preview_thumbnail_key</string> |     <string name="seekbar_preview_thumbnail_key" translatable="false">seekbar_preview_thumbnail_key</string> | ||||||
|     <string name="seekbar_preview_thumbnail_high_quality" translatable="false">seekbar_preview_thumbnail_high_quality</string> |     <string name="seekbar_preview_thumbnail_high_quality" translatable="false">seekbar_preview_thumbnail_high_quality</string> | ||||||
|     <string name="seekbar_preview_thumbnail_low_quality" translatable="false">seekbar_preview_thumbnail_low_quality</string> |     <string name="seekbar_preview_thumbnail_low_quality" translatable="false">seekbar_preview_thumbnail_low_quality</string> | ||||||
| @@ -188,6 +190,7 @@ | |||||||
|     <string name="disable_media_tunneling_key" translatable="false">disable_media_tunneling_key</string> |     <string name="disable_media_tunneling_key" translatable="false">disable_media_tunneling_key</string> | ||||||
|     <string name="crash_the_app_key" translatable="false">crash_the_app_key</string> |     <string name="crash_the_app_key" translatable="false">crash_the_app_key</string> | ||||||
|     <string name="show_image_indicators_key" translatable="false">show_image_indicators_key</string> |     <string name="show_image_indicators_key" translatable="false">show_image_indicators_key</string> | ||||||
|  |     <string name="show_crash_the_player_key" translatable="false">show_crash_the_player_key</string> | ||||||
|  |  | ||||||
|     <!-- THEMES --> |     <!-- THEMES --> | ||||||
|     <string name="theme_key" translatable="false">theme</string> |     <string name="theme_key" translatable="false">theme</string> | ||||||
|   | |||||||
| @@ -52,6 +52,9 @@ | |||||||
|     <string name="kore_package" translatable="false">org.xbmc.kore</string> |     <string name="kore_package" translatable="false">org.xbmc.kore</string> | ||||||
|     <string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string> |     <string name="show_play_with_kodi_title">Show \"Play with Kodi\" option</string> | ||||||
|     <string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string> |     <string name="show_play_with_kodi_summary">Display an option to play a video via Kodi media center</string> | ||||||
|  |     <string name="crash_the_player">Crash the player</string> | ||||||
|  |     <string name="report_player_errors_title">Report player errors</string> | ||||||
|  |     <string name="report_player_errors_summary">Reports player errors in full detail instead of showing a short-lived toast message (useful for diagnosing problems)</string> | ||||||
|     <string name="notification_scale_to_square_image_title">Scale thumbnail to 1:1 aspect ratio</string> |     <string name="notification_scale_to_square_image_title">Scale thumbnail to 1:1 aspect ratio</string> | ||||||
|     <string name="notification_scale_to_square_image_summary">Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions)</string> |     <string name="notification_scale_to_square_image_summary">Scale the video thumbnail shown in the notification from 16:9 to 1:1 aspect ratio (may introduce distortions)</string> | ||||||
|     <string name="notification_action_0_title">First action button</string> |     <string name="notification_action_0_title">First action button</string> | ||||||
| @@ -473,6 +476,8 @@ | |||||||
|     <string name="show_image_indicators_title">Show image indicators</string> |     <string name="show_image_indicators_title">Show image indicators</string> | ||||||
|     <string name="show_image_indicators_summary">Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory</string> |     <string name="show_image_indicators_summary">Show Picasso colored ribbons on top of images indicating their source: red for network, blue for disk and green for memory</string> | ||||||
|     <string name="crash_the_app">Crash the app</string> |     <string name="crash_the_app">Crash the app</string> | ||||||
|  |     <string name="show_crash_the_player_title">Show \"crash the player\"</string> | ||||||
|  |     <string name="show_crash_the_player_summary">Shows a crash option when using the player</string> | ||||||
|     <!-- Subscriptions import/export --> |     <!-- Subscriptions import/export --> | ||||||
|     <string name="import_title">Import</string> |     <string name="import_title">Import</string> | ||||||
|     <string name="import_from">Import from</string> |     <string name="import_from">Import from</string> | ||||||
|   | |||||||
| @@ -49,9 +49,26 @@ | |||||||
|         android:title="@string/show_image_indicators_title" |         android:title="@string/show_image_indicators_title" | ||||||
|         app:iconSpaceReserved="false" /> |         app:iconSpaceReserved="false" /> | ||||||
|  |  | ||||||
|  |     <SwitchPreferenceCompat | ||||||
|  |         android:defaultValue="false" | ||||||
|  |         android:key="@string/report_player_errors_key" | ||||||
|  |         android:summary="@string/report_player_errors_summary" | ||||||
|  |         android:title="@string/report_player_errors_title" | ||||||
|  |         app:singleLineTitle="false" | ||||||
|  |         app:iconSpaceReserved="false" /> | ||||||
|  |  | ||||||
|     <Preference |     <Preference | ||||||
|         android:key="@string/crash_the_app_key" |         android:key="@string/crash_the_app_key" | ||||||
|         android:title="@string/crash_the_app" |         android:title="@string/crash_the_app" | ||||||
|         app:singleLineTitle="false" |         app:singleLineTitle="false" | ||||||
|         app:iconSpaceReserved="false" /> |         app:iconSpaceReserved="false" /> | ||||||
|  |  | ||||||
|  |     <SwitchPreferenceCompat | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:defaultValue="false" | ||||||
|  |         android:key="@string/show_crash_the_player_key" | ||||||
|  |         android:summary="@string/show_crash_the_player_summary" | ||||||
|  |         android:title="@string/show_crash_the_player_title" | ||||||
|  |         app:iconSpaceReserved="false" /> | ||||||
| </PreferenceScreen> | </PreferenceScreen> | ||||||
|   | |||||||
| @@ -89,6 +89,7 @@ | |||||||
|             android:title="@string/show_play_with_kodi_title" |             android:title="@string/show_play_with_kodi_title" | ||||||
|             app:singleLineTitle="false" |             app:singleLineTitle="false" | ||||||
|             app:iconSpaceReserved="false" /> |             app:iconSpaceReserved="false" /> | ||||||
|  |  | ||||||
|         <ListPreference |         <ListPreference | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stypox
					Stypox