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