diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index d4df26ac..791c29e8 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -10,6 +10,7 @@ import com.limelight.binding.input.TouchContext; import com.limelight.binding.input.driver.UsbDriverService; import com.limelight.binding.input.evdev.EvdevListener; import com.limelight.binding.input.virtual_controller.VirtualController; +import com.limelight.binding.video.CrashListener; import com.limelight.binding.video.MediaCodecDecoderRenderer; import com.limelight.binding.video.MediaCodecHelper; import com.limelight.nvstream.NvConnection; @@ -34,6 +35,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.graphics.Point; import android.hardware.input.InputManager; import android.media.AudioManager; @@ -81,6 +83,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, private VirtualController virtualController; private PreferenceConfiguration prefConfig; + private SharedPreferences tombstonePrefs; private NvConnection conn; private SpinnerDialog spinner; @@ -165,6 +168,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Read the stream preferences prefConfig = PreferenceConfiguration.readPreferences(this); + tombstonePrefs = Game.this.getSharedPreferences("DecoderTombstone", 0); + // Listen for events on the game surface streamView = findViewById(R.id.surfaceView); @@ -214,7 +219,19 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Initialize the MediaCodec helper before creating the decoder MediaCodecHelper.initializeWithContext(this); - decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat, prefConfig.bitrate, prefConfig.batterySaver); + decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat, + prefConfig.bitrate, + prefConfig.batterySaver, + new CrashListener() { + @Override + public void notifyCrash(Exception e) { + // The MediaCodec instance is going down due to a crash + // let's tell the user something when they open the app again + + // We must use commit because the app will crash when we return from this function + tombstonePrefs.edit().putInt("CrashCount", tombstonePrefs.getInt("CrashCount", 0) + 1).commit(); + } + }); // Display a message to the user if H.265 was forced on but we still didn't find a decoder if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) { @@ -448,6 +465,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Destroy the capture provider inputCaptureProvider.destroy(); + + // Clear the tombstone count + if (tombstonePrefs.getInt("CrashCount", 0) != 0) { + tombstonePrefs.edit().putInt("CrashCount", 0).apply(); + } } @Override diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index 1f934cc3..53709eb4 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -31,6 +31,7 @@ import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; @@ -163,6 +164,23 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { PreferenceConfiguration.readPreferences(this).smallIconMode); initializeViews(); + + SharedPreferences prefs = getSharedPreferences("DecoderTombstone", 0); + int crashCount = prefs.getInt("CrashCount", 0); + if (crashCount == 3) { + // At 3 consecutive crashes, we'll forcefully reset their settings + PreferenceConfiguration.resetStreamingSettings(this); + Dialog.displayDialog(this, + getResources().getString(R.string.title_decoding_reset), + getResources().getString(R.string.message_decoding_reset), + false); + } + else if (crashCount >= 1) { + Dialog.displayDialog(this, + getResources().getString(R.string.title_decoding_error), + getResources().getString(R.string.message_decoding_error), + false); + } } private void startComputerUpdates() { diff --git a/app/src/main/java/com/limelight/binding/video/CrashListener.java b/app/src/main/java/com/limelight/binding/video/CrashListener.java new file mode 100644 index 00000000..5023da5e --- /dev/null +++ b/app/src/main/java/com/limelight/binding/video/CrashListener.java @@ -0,0 +1,5 @@ +package com.limelight.binding.video; + +public interface CrashListener { + void notifyCrash(Exception e); +} diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java index 603272e0..418bbc05 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -12,6 +12,7 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer; import com.limelight.nvstream.jni.MoonBridge; import com.limelight.preferences.PreferenceConfiguration; +import android.content.SharedPreferences; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; @@ -47,6 +48,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer { private int videoFormat; private SurfaceHolder renderTarget; private volatile boolean stopping; + private CrashListener crashListener; private boolean needsBaselineSpsHack; private SeqParameterSet savedSps; @@ -109,10 +111,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer { this.renderTarget = renderTarget; } - public MediaCodecDecoderRenderer(int videoFormat, int bitrate, boolean batterySaver) { + public MediaCodecDecoderRenderer(int videoFormat, int bitrate, boolean batterySaver, CrashListener crashListener) { //dumpDecoders(); this.bitrate = bitrate; + this.crashListener = crashListener; // Disable spinner threads in battery saver mode if (batterySaver) { @@ -317,6 +320,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer { // This isn't the first time we've had an exception processing video if (System.currentTimeMillis() - initialExceptionTimestamp >= EXCEPTION_REPORT_DELAY_MS) { // It's been over 3 seconds and we're still getting exceptions. Throw the original now. + crashListener.notifyCrash(initialException); throw initialException; } } @@ -911,6 +915,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer { str += "Format: "+renderer.videoFormat+"\n"; str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n"; str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n"; + str += "Build fingerprint: "+Build.FINGERPRINT+"\n"; str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n"; str += "FPS target: "+renderer.refreshRate+"\n"; str += "Bitrate: "+renderer.bitrate+" Mbps \n"; diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index e6b53c44..5c7b151e 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -130,6 +130,16 @@ public class PreferenceConfiguration { } } + public static void resetStreamingSettings(Context context) { + // We consider resolution, FPS, bitrate, and video format as "streaming settings" here + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit() + .remove(BITRATE_PREF_STRING) + .remove(RES_FPS_PREF_STRING) + .remove(VIDEO_FORMAT_PREF_STRING) + .apply(); + } + public static PreferenceConfiguration readPreferences(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); PreferenceConfiguration config = new PreferenceConfiguration(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49a71a2f..6daa76d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,10 @@ GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. Try rebooting your machine or reinstalling GFE. + Video Decoder Crashed + Moonlight has crashed due to a issue with this device\'s video decoder. Try adjusting the streaming settings if the crashes continue. + Video Settings Reset + Your device\'s video decoder continues to crash at your selected streaming settings. Your streaming settings have been reset to default. Establishing Connection