diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6075acaa..23f0da2b 100755 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + - + + { Log.e("OpenIPCDecoder", "Uncaught exception", throwable); - Intent intent = new Intent(getApplicationContext(), Crash.class); - intent.putExtra("error", Log.getStackTraceString(throwable)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - // Process will terminate naturally with the crash activity visible + try { + Intent intent = new Intent(getApplicationContext(), Crash.class); + intent.putExtra("error", Log.getStackTraceString(throwable)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } finally { + // The app state is undefined after an uncaught exception (and the main looper + // may be dead), so end this process; Crash runs in its own ":crash" process. + Process.killProcess(Process.myPid()); + System.exit(10); + } }); } } diff --git a/android/app/src/main/java/com/openipc/decoder/Decoder.java b/android/app/src/main/java/com/openipc/decoder/Decoder.java index 15e2dce5..ff7ae467 100755 --- a/android/app/src/main/java/com/openipc/decoder/Decoder.java +++ b/android/app/src/main/java/com/openipc/decoder/Decoder.java @@ -391,9 +391,40 @@ private void saveSettings() { // ====================== Status overlay ====================== + // Delayed overlay updates after "connected"; cancelled on every status change + private final Runnable statusDetails = this::showConnectedDetails; + private final Runnable statusHide = () -> { + if (statusText != null) statusText.setVisibility(View.GONE); + }; + + private void showConnectedDetails() { + if (statusText == null || statusText.getVisibility() != View.VISIBLE) return; + String videoCodec = codecH265 ? "H.265" : "H.264"; + String aCodec; + switch (audioCodec) { + case AudioConfig.AAC: aCodec = " • AAC"; break; + case AudioConfig.PCMU: + case AudioConfig.PCMA: aCodec = " • G.711"; break; + case AudioConfig.PCM: aCodec = " • PCM"; break; + default: aCodec = ""; break; + } + long elapsedNs = System.nanoTime() - statusBitrateNs; + String bps = ""; + long bytes = rtspClient != null ? rtspClient.getStreamBytes() : 0; + if (elapsedNs > 500_000_000 && bytes > 0) { + double mbps = (bytes * 8.0 / 1_000_000) + / (elapsedNs / 1_000_000_000.0); + bps = String.format(Locale.US, " • %.1f Mbps", mbps); + } + statusText.setText("Connected • " + videoCodec + aCodec + bps); + } + private void updateStatus(String status) { runOnUiThread(() -> { if (statusText == null) return; + // A stale timer from an earlier "connected" must not overwrite or hide this status + statusText.removeCallbacks(statusDetails); + statusText.removeCallbacks(statusHide); switch (status) { case "connecting": @@ -405,34 +436,9 @@ private void updateStatus(String status) { statusText.setVisibility(View.VISIBLE); statusStreamBytes = rtspClient != null ? rtspClient.getStreamBytes() : 0; statusBitrateNs = rtspClient != null ? rtspClient.getBitrateMarkNs() : System.nanoTime(); - // After 1 second, refresh with codec + bitrate info - statusText.postDelayed(() -> { - if (statusText == null - || statusText.getVisibility() != View.VISIBLE) return; - String videoCodec = codecH265 ? "H.265" : "H.264"; - String aCodec; - switch (audioCodec) { - case AudioConfig.AAC: aCodec = " • AAC"; break; - case AudioConfig.PCMU: - case AudioConfig.PCMA: aCodec = " • G.711"; break; - case AudioConfig.PCM: aCodec = " • PCM"; break; - default: aCodec = ""; break; - } - long elapsedNs = System.nanoTime() - statusBitrateNs; - String bps = ""; - long bytes = rtspClient != null ? rtspClient.getStreamBytes() : 0; - if (elapsedNs > 500_000_000 && bytes > 0) { - double mbps = (bytes * 8.0 / 1_000_000) - / (elapsedNs / 1_000_000_000.0); - bps = String.format(Locale.US, " • %.1f Mbps", mbps); - } - statusText.setText("Connected • " + videoCodec + aCodec + bps); - }, 1000); - statusText.postDelayed(() -> { - if (statusText != null) { - statusText.setVisibility(View.GONE); - } - }, 5000); + // After 1 second, refresh with codec + bitrate info; hide after 5 seconds + statusText.postDelayed(statusDetails, 1000); + statusText.postDelayed(statusHide, 5000); break; case "disconnected": statusText.setText(getString(R.string.status_disconnected)); diff --git a/android/app/src/main/java/com/openipc/decoder/MenuManager.java b/android/app/src/main/java/com/openipc/decoder/MenuManager.java index fa85657c..8aab8d6d 100644 --- a/android/app/src/main/java/com/openipc/decoder/MenuManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MenuManager.java @@ -337,7 +337,8 @@ void startBrowser(String mHost) { public void onReceivedHttpAuthRequest( WebView v, HttpAuthHandler handler, String h, String realm) { String userInfo = finalUri.getUserInfo(); - if (userInfo != null) { + // Only the camera itself gets its credentials, never a host reached by a redirect or link + if (userInfo != null && link.equalsIgnoreCase(h)) { String[] parts = userInfo.split(":", 2); if (parts.length == 2) { handler.proceed(parts[0], parts[1]);