Skip to content

fix(example): auto-recover stream after lock/unlock#18

Merged
gdelataillade merged 1 commit into
mainfrom
fix/example-stream-auto-recover-after-lock
Jul 1, 2026
Merged

fix(example): auto-recover stream after lock/unlock#18
gdelataillade merged 1 commit into
mainfrom
fix/example-stream-auto-recover-after-lock

Conversation

@gdelataillade

Copy link
Copy Markdown
Collaborator

Problem

When streaming from the glasses camera (without background streaming enabled), locking the iPhone and then unlocking it back to the stream screen intermittently shows a red "Video streaming encountered an error" banner and the stream does not resume — you have to manually Stop then Start to get it back. Reproduction rate ~2/5.

flutter: [MetaWearablesDAT] streamSessionError → videoStreamingError: Video streaming encountered an error.

Root cause

Locking the phone suspends the app. Intermittently the DAT SDK's stream pipeline breaks and emits StreamError.videoStreamingError. Per the SDK's own docs (doc/ios/MWDATCamera.swift):

"The session automatically stops when an error occurs."

So that error transitions the Stream to .stopped — it's dead. There is no pause()/resume() API; recovery requires a fresh addStream() + start(). On unlock the plugin flips isInBackground = false and waits for frames that never arrive. The example app just surfaced the error and gave up, so the only recourse was a manual stop + start.

Fix

StreamSessionProvider now transparently restarts the session on transient stream errors instead of dead-ending:

  • Recoverable codes: videoStreamingError, timeout, internalError, deviceNotConnected, deviceNotFound.
  • Up to 3 attempts with backoff + a 10s watchdog; success is confirmed when the state stream reaches streaming.
  • Gated on user intent (_streamingIntended) — an explicit Stop cancels any in-flight recovery.
  • The stream screen shows a calm "Reconnecting…" overlay/label while recovering; the red banner only appears if all attempts are exhausted.

Deliberate exclusions: hingesClosed (Meta's no-auto-resume-on-doff design) and thermal/battery/power errors are not auto-retried.

Why the example app, not the plugin

Recovery re-invokes startStreamSession() — the proven manual stop→start path (native already tears down the stale .stopped stream and recreates it). It lives in the app rather than the plugin because retry policy is app-specific and startStreamSession() mints a new texture ID each call, so recovery must flow through Dart to rebind the Texture widget; a silent plugin-internal restart would strand the widget on an unregistered texture.

Testing

  • dart analyze clean on the whole example/lib.
  • ⚠️ The underlying bug is hardware-only (needs the glasses + a physical lock/unlock), so this was validated by static analysis and logic tracing, not a live device run. Worth a real-device check: stream → lock → unlock a few times and confirm it reconnects (with a brief "Reconnecting…") instead of the red banner.

Notes

  • No plugin (lib/) or native changes — example app only. CI's analyze/format jobs don't cover example/, and the file's existing (short) formatting style is preserved.
  • Possible follow-ups (not in this PR): document in the plugin's streamSessionErrorStream() API that these codes are transient so consumer apps handle them too; keep a Stop/Cancel button visible during the brief pre-texture reconnect window.

🤖 Generated with Claude Code

Locking the iPhone while streaming suspends the app; the DAT SDK's stream pipeline can break and emit `videoStreamingError`, which per the SDK docs auto-stops the stream ("The session automatically stops when an error occurs"). There is no pause/resume — recovery needs a fresh addStream+start. The example app previously dead-ended on a red error banner, forcing a manual stop + start.

StreamSessionProvider now transparently restarts the session on transient stream errors (videoStreamingError, timeout, internalError, deviceNotConnected, deviceNotFound): up to 3 attempts with backoff and a 10s watchdog, gated on user intent so an explicit Stop cancels recovery. Success is confirmed when the state stream reaches streaming. The stream screen shows a calm "Reconnecting…" overlay/label instead of the red banner while recovery is in flight; the banner only appears if all attempts are exhausted.

hingesClosed (Meta's no-auto-resume-on-doff design) and thermal/battery/power errors are intentionally excluded. Recovery lives in the example app rather than the plugin because startStreamSession mints a new texture ID each call, so recovery must flow through Dart to rebind the Texture widget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gdelataillade gdelataillade merged commit 92df7d1 into main Jul 1, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant