Skip to content

Troubleshoot the NovaVMS mobile app

This page is the recovery flowchart for problems that surface inside the mobile app. Each section is one symptom.

Symptom: splash screen never disappears

The app’s splash holds until the cold-launch session restore (SecureStore → access token + refresh) completes OR an 8-second watchdog fires. The watchdog was introduced in v1.0.3 specifically because the SecureStore + /auth/refresh call could hang silently on locked iOS Keychains or congested networks.

Recovery:

  1. Wait 8 seconds. The watchdog will time out and route you to the sign-in screen.
  2. If splash still holds longer than 10 seconds, kill the app (swipe up from app switcher) and reopen. The next cold-launch boots with a clean watchdog.
  3. If the problem repeats every launch, sign out from another device that’s still authenticated (web Account → Active sessions → revoke this device), then sign back in fresh on mobile. SecureStore’s refresh token will be replaced.

Symptom: live tile shows black or “Reconnecting…” forever

The mobile live transport is codec-split: H.264 cameras use WebRTC/WHEP; H.265 cameras use LL-HLS. The recovery differs by camera codec.

For H.264 (WebRTC) cameras

A black tile that never starts streaming usually means WHEP negotiation failed. Causes:

  • TURN credentials expired (rare; the cloud refreshes hourly)
  • The cloud could not reach the gateway (gateway offline)
  • The carrier filters WebRTC (uncommon)

Recovery:

  1. Confirm the camera is online on the web Cameras page. If it shows Offline there, the mobile tile cannot recover until the gateway/camera issue is fixed.
  2. Toggle Wi-Fi off and back on. This forces a fresh ICE negotiation.
  3. Kill and reopen the app — a fresh prewarm-pool of PeerConnections gets allocated at app mount.

For H.265 (LL-HLS) cameras

A tile stuck on “Reconnecting…” for 20+ seconds on cold start means the cloud LL-HLS writer is cold. The cloud needs to produce the init segment and the first few CMAF parts before AVPlayer / ExoPlayer can play.

Recovery:

  1. Background the app and bring it back to the foreground. The pre-warm fires on every active transition — by the time you reopen, the writer should be warm.
  2. If still cold after foreground re-warm, sign out and sign in. The login response triggers prewarmH265Writers('login') which warms every H.265 camera you can see.
  3. If the problem affects every H.265 camera (not just one), the cloud-side LL-HLS service may be unhealthy. Check your team’s status channel; the web Live view will be similarly affected.

Symptom: live tile shows a static thumbnail and never goes live

The decoder lease is full. The phone’s MediaCodec / VideoToolbox can hold a finite number of simultaneous hardware decoders (Android emulator: 4, Android real device: 8, iOS: 8). Tiles beyond the cap render the last thumbnail rather than mounting a transport — better than the alternative (the OS reclaiming an older decoder and the tile cycling forever).

Recovery:

  1. Tap the thumbnail tile. Expanding it shifts the decoder lease to the foreground player.
  2. Switch to a layout with fewer cameras. A 2x2 grid (4 tiles) is comfortably under the cap on every supported device.
  3. Background then foreground the app. Backgrounding pauses every tile and releases the leases; foregrounding re-acquires them in FIFO order from the top of the grid.

Symptom: push notifications are silent

Push requires three conditions to all hold. Walk through them in order:

  1. OS notification permission is granted. Check More → Notification settings. The row below “Push notifications” shows the current state (“Permission granted”, “Permission denied”, etc.).
  2. The in-app Push toggle is ON. Same screen.
  3. Your assigned alert rules are firing. Confirm an alert exists on the web Alerts page; if no alert was fired, no push could have been sent.

Recovery for permission:

  • If the row says “Permission denied in system settings”, tap Open System Settings (the CTA below the toggle), grant Notifications for NovaVMS, and return to the app. The screen re-checks permission on every focus return and the CTA disappears.
  • If the row says “Permission denied — toggle to grant”, toggling the row will re-show the OS prompt.

Recovery for Android channel routing:

Android 8+ routes notifications through channels. NovaVMS registers the alerts channel at boot with HIGH importance, but a user (or another app via a “do not disturb” config) can override the channel to silent.

  1. Open Android Settings → Apps → NovaVMS → Notifications.
  2. Make sure Allow notifications is on and the Alerts channel is set to anything other than “Silent”.

Recovery for iOS:

  1. Open iOS Settings → NovaVMS → Notifications.
  2. Make sure Allow Notifications, Sounds, Badges, Banners (Persistent or Temporary) are all on.

Symptom: talkdown button is silent (camera speaker stays quiet)

The talkdown button visual reaches Transmitting (red, with mic icon) but the camera’s speaker stays quiet. Two likely causes:

  1. The camera’s backchannel speaker accepts only PCMU/PCMA, not Opus. The Opus→PCMU transcode is partial in v1; some legacy cameras may not receive audio. Test against a Tapo C200 or LC-TA3R confirmed in production; if those work, the failing camera is the unsupported codec class.
  2. The camera’s audio_enabled is off. Open Camera settings → Audio. Toggle Audio enabled on (operator+ only).
  3. The speaker is physically muted. Some cameras have a hardware switch or a vendor-app-level mute that NovaVMS cannot see. Verify in the vendor app.

If the button never reaches Transmitting at all:

  • Error state on first press: microphone permission was denied. Re-enable in OS Settings → NovaVMS → Microphone.
  • HTTP 403 in the network trace: your role is not operator or higher. Talkdown is restricted to operator+ roles.
  • HTTP 404 in the trace: the cloud could not route to a gateway for this camera (gateway offline).

Symptom: biometric login flipped off without you turning it off

NovaVMS automatically disables biometric unlock when the OS biometric enrollment security level changes — all biometrics were removed, or the security level dropped from Class 3 / Face ID-strong to Class 2 / Touch ID-weak. This is the validateEnrollment check in mobile/src/libraries/auth/biometric.ts. Note: adding a new biometric at the same security level (e.g., a second fingerprint while Class 3 stays Class 3) does NOT trigger auto-disable — the check compares levels, not individual enrollments. This is a deliberate trade-off documented under OWASP MASVS-AUTH-2 guidance.

The app surfaces a one-time toast on the next visit to Account:

Biometric login was turned off

The biometrics enrolled on this device changed, so we disabled biometric sign-in for safety. Re-enable it below if you like.

Recovery: sign in with your password, open More → Account → Security → Biometric login, toggle it on. The toggle runs a fresh biometric ceremony to confirm you can authenticate, then re-snapshots the new enrollment level.

If the toggle is disabled (greyed out), it means the OS reports no biometrics are enrolled. Enroll a fingerprint or set up Face ID in OS Settings first, then return.

Symptom: sign-in fails with “Invalid email or password”

If you’re sure the email and password are correct (try them on the web to confirm), check:

  • Soft keyboard dropped characters: Android’s IME can drop characters under high CPU load. Watch the password field as you type to confirm every character appeared.
  • Capslock / autocorrect: the email field auto-disables capitalization and autocorrect; the password field auto-disables capitalization. If your password contains a leading uppercase letter, type it manually.
  • Account locked / disabled: if web sign-in also fails for the same email, your admin may have disabled the account.

Symptom: share or download fails with “Could not save the clip”

The Event-detail Share + Download path downloads the presigned MP4 to local storage before opening the share sheet. If the download fails, the share never opens.

Causes:

  • Presigned URL expired. The cloud’s presigned URLs are short-lived. If you opened the event 10+ minutes ago and never tapped Share until now, the URL has expired. Tap Back then Forward to the event — the screen refetches with a fresh URL.
  • Disk full. The app uses the document directory for the local file. If your device is out of space, the download fails. Free space.
  • Network failed mid-download. The error alert names the failing step (download vs. share). Retry on a stable connection.

Symptom: app crashes / one screen renders blank with “Something went wrong”

The app wraps every top-level screen in a Sentry error boundary. A render-phase crash in (e.g.) Event detail does not take down the tab bar; you see a “Something went wrong” fallback on that screen with a Retry button.

Recovery:

  1. Tap Retry. The boundary remounts the screen with fresh state.
  2. If Retry doesn’t fix it, go back and try a different event / camera. Sentry captures the crash with stack trace for your admin to triage.
  3. Background the app and reopen. A fresh JS engine + native module reset clears most stuck states.

If the entire app crashes back to the OS, that’s a native-level crash (much rarer). Reopen the app and report to your admin so they can look up the crash in Sentry.

Symptom: app shows a “Session expired” sheet mid-session

Your access token expired AND the refresh attempt failed — typically after long idle periods, or after your admin revoked your sessions from the web.

Recovery: tap Sign in again. If biometric unlock is enabled, the unlock button on LoginScreen completes the re-auth without typing a password.