fix(example): auto-recover stream after lock/unlock#18
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
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):So that error transitions the
Streamto.stopped— it's dead. There is nopause()/resume()API; recovery requires a freshaddStream()+start(). On unlock the plugin flipsisInBackground = falseand 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
StreamSessionProvidernow transparently restarts the session on transient stream errors instead of dead-ending:videoStreamingError,timeout,internalError,deviceNotConnected,deviceNotFound.streaming._streamingIntended) — an explicit Stop cancels any in-flight recovery.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.stoppedstream and recreates it). It lives in the app rather than the plugin because retry policy is app-specific andstartStreamSession()mints a new texture ID each call, so recovery must flow through Dart to rebind theTexturewidget; a silent plugin-internal restart would strand the widget on an unregistered texture.Testing
dart analyzeclean on the wholeexample/lib.Notes
lib/) or native changes — example app only. CI's analyze/format jobs don't coverexample/, and the file's existing (short) formatting style is preserved.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