Fix Android app crash when leaving the video player#268
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6485945. Configure here.
Greptile SummaryThis PR guards
Confidence Score: 4/5Safe to merge for the guarded paths; one unguarded call site remains that can throw the same released-player error. The unmount and AppState paths are correctly guarded and tested. However, the setInterval callback that runs every 5 seconds reads player.currentTime and calls updateMediaLocation without any guard — it can throw the same released-player error in the window between native-player release and interval cleanup, leaving that crash path open. app/video-player.tsx — the setInterval block around line 160 needs the same guard applied to the unmount and AppState paths. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant RNScreens as react-native-screens
participant React
participant Guard as withReleasedPlayerGuard
participant Player as expo-video Player
User->>RNScreens: Press Back
RNScreens->>Player: useReleasingSharedObject releases native player
RNScreens->>React: Freeze/detach screen
React->>Guard: unmount cleanup → player.pause()
alt Player already released
Player-->>Guard: throws ERR_USING_RELEASED_SHARED_OBJECT
Guard-->>React: swallows error (returns)
else Player still alive
Player-->>Guard: pause() succeeds
Guard-->>React: returns normally
end
React-->>User: Previous screen shown, app alive
Note over AppState,Guard: AppState path (background/inactive)
participant AppState
AppState->>Guard: background → player.pause()
alt Player already released
Player-->>Guard: throws ERR_USING_RELEASED_SHARED_OBJECT
Guard-->>AppState: swallowed
else Player still alive
Player-->>Guard: pause() succeeds
end
|
On Android, opening a video then pressing back closed the entire app
instead of returning to the previous screen.
Root cause (confirmed on a device): tearing down the video screen runs the
cleanup `useEffect(() => () => player.pause(), [player])`, but during the
back-navigation teardown react-native-screens freezes/detaches the screen
and expo-video's `useReleasingSharedObject` releases the player first. The
subsequent `player.pause()` then hits an already-released shared object and
throws `ERR_USING_RELEASED_SHARED_OBJECT` ("Cannot use shared object that
was already released"). In dev this is a red box; in production it is an
unhandled JS exception that closes the app — matching the report and the
high-volume `player.*` expo-video Sentry crash group. The race is timing
dependent, which is why it can't be reproduced reliably in-house yet hits
many users in the wild.
Wrap the player calls that can run during teardown (the unmount pause and
the AppState background/foreground handler) in a guard that swallows
`ERR_USING_RELEASED_SHARED_OBJECT` and rethrows anything else, so a
released player can no longer crash the app regardless of teardown ordering.
Verified on an Android emulator against the production API: reproduced the
released-object crash on the unguarded code, then confirmed repeated
open -> back cycles (system back and the header back arrow, including the
playback-error state) return to the previous screen with the app staying
alive and no exception.
Fixes #267
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6485945 to
797aa95
Compare
Cover the two sites guarded by withReleasedPlayerGuard: the unmount pause and the AppState background handler. Both assert that a player whose native shared object has already been released (throwing ERR_USING_RELEASED_SHARED_OBJECT) no longer crashes the screen. They fail when the guard is reverted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The teardown guard only swallowed ERR_USING_RELEASED_SHARED_OBJECT (the Android code). On iOS the same race surfaces as a FunctionCallException (code ERR_FUNCTION_CALL) wrapping NativeSharedObjectNotFoundException, so the guard rethrew it and the app still crashed leaving the video player. Broaden the guard to also match the iOS not-found code and the released/ not-found shared-object message signature, and add regression tests for the iOS variant on both the unmount and AppState paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

What
On Android, opening a video and pressing back closed the entire app instead of returning to the previous screen (Fixes #267). The "app close" is a fatal, unhandled JS exception on the back path. This guards the player calls that run during teardown so they can no longer crash the app.
Why
Tearing down the video screen runs this cleanup:
During back navigation,
react-native-screensfreezes/detaches the screen and expo-video'suseReleasingSharedObjectreleases the native player. The subsequentplayer.pause()then runs against an already-released shared object and throws:In dev this is a red box; in production it is an uncaught exception that closes the app — matching the report and the high-volume
player.*expo-video Sentry crash group. The race is timing-dependent, which is why it can't be reproduced reliably in-house yet hits many users in the wild.Approach chosen: wrap the player calls that can run during teardown — the unmount
pause()and theAppStatebackground/foreground handler — in a guard that swallowsERR_USING_RELEASED_SHARED_OBJECTand rethrows everything else. This neutralizes the exact failing call regardless of teardown ordering, with no behavior change on the happy path. Alternatives considered and rejected: deferring player creation to avoid the null-source recreation (verified on-device to not prevent this crash); removing the manual pause (relies on release timing and still leaves the AppState path exposed).Before/After
Verified on an Android emulator (API 36) against the production API.
ERR_USING_RELEASED_SHARED_OBJECTfromplayer.pause()(red box + logcat); in production this closes the app.Walkthrough video attached as a comment below (GitHub media can only be uploaded through the web UI).
Test Results
jest— 29 suites, 214 tests passing, including two new regression tests intests/app/video-player.test.tsxthat assert teardown/background no longer crash when the player is already released. Both fail when the guard is reverted.tsc --noEmitandexpo lintboth clean.AI disclosure
Model: Claude Opus 4.8 (via Claude Code).
Prompts given by the author:
Note
Low Risk
Narrow defensive change around teardown timing; happy-path playback is unchanged and other errors still propagate.
Overview
Fixes a fatal crash when leaving the video player on Android by guarding expo-video player calls that can run after the native player is already released during screen teardown.
Adds
isReleasedPlayerError/withReleasedPlayerGuardto swallowERR_USING_RELEASED_SHARED_OBJECT,ERR_NATIVE_SHARED_OBJECT_NOT_FOUND, and matching message patterns, while rethrowing other errors. Wraps the unmountplayer.pause()cleanup and the full AppState background/foreground handler sopause, seek, and resume no longer throw when the shared object is gone.Adds regression tests in
video-player.test.tsxfor unmount and background paths whenpausethrows released-player errors (Android) and iOS “native shared object not found” failures.Reviewed by Cursor Bugbot for commit 860c657. Bugbot is set up for automated code reviews on this repo. Configure here.