Skip to content

Fix Android app crash when leaving the video player#268

Merged
nyomanjyotisa merged 3 commits into
mainfrom
fix/android-video-back-crash
Jun 11, 2026
Merged

Fix Android app crash when leaving the video player#268
nyomanjyotisa merged 3 commits into
mainfrom
fix/android-video-back-crash

Conversation

@nyomanjyotisa

@nyomanjyotisa nyomanjyotisa commented Jun 11, 2026

Copy link
Copy Markdown
Member

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:

useEffect(() => () => player.pause(), [player]);

During back navigation, react-native-screens freezes/detaches the screen and expo-video's useReleasingSharedObject releases the native player. The subsequent player.pause() then runs against an already-released shared object and throws:

Call to function 'VideoPlayer.pause' has been rejected.
→ Caused by: Cannot use shared object that was already released
   code: ERR_USING_RELEASED_SHARED_OBJECT

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 the AppState background/foreground handler — in a guard that swallows ERR_USING_RELEASED_SHARED_OBJECT and 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.

  • Before (unguarded): back from the video player → ERR_USING_RELEASED_SHARED_OBJECT from player.pause() (red box + logcat); in production this closes the app.
  • After (this PR): repeated open → back cycles with both system back and the header back arrow (including the playback-error state) → returns to the previous screen, app stays alive, no exception.

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 in tests/app/video-player.test.tsx that assert teardown/background no longer crash when the player is already released. Both fail when the guard is reverted.

Test Suites: 29 passed, 29 total
Tests:       214 passed, 214 total

tsc --noEmit and expo lint both clean.


AI disclosure

Model: Claude Opus 4.8 (via Claude Code).

Prompts given by the author:

  1. "github.com/Android: back from a video closes the entire app instead of returning to the previous screen #267 — debug and fix this, create PR and assign to me"
  2. "are you 100% sure it will fix the issue?"
  3. "do verification on real app on simulator, run the app either iOS or Android and verify the fix, and test before/after. update the env first to use production API"
  4. "so does the PR fix the issue?"
  5. "check reviewer comments, and address them if correct"

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 / withReleasedPlayerGuard to swallow ERR_USING_RELEASED_SHARED_OBJECT, ERR_NATIVE_SHARED_OBJECT_NOT_FOUND, and matching message patterns, while rethrowing other errors. Wraps the unmount player.pause() cleanup and the full AppState background/foreground handler so pause, seek, and resume no longer throw when the shared object is gone.

Adds regression tests in video-player.test.tsx for unmount and background paths when pause throws 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.

@nyomanjyotisa nyomanjyotisa self-assigned this Jun 11, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread app/video-player.tsx Outdated
@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR guards player.pause() and the AppState change handler inside withReleasedPlayerGuard so that ERR_USING_RELEASED_SHARED_OBJECT (and its iOS equivalent) no longer crashes the app when navigating back from the video player on Android.

  • Guard helpers addedisReleasedPlayerError matches both the Android error code and an iOS message-based variant; withReleasedPlayerGuard wraps a void operation, swallowing only those errors and rethrowing everything else.
  • Two call-sites wrapped — the unmount useEffect cleanup (player.pause()) and the AppState "background/inactive" handler, matching the two crash paths described in the issue.
  • Four regression tests added — cover Android ERR_USING_RELEASED_SHARED_OBJECT and iOS message-match paths for both unmount and background transitions; they fail when the guard is reverted.

Confidence Score: 4/5

Safe 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

Filename Overview
app/video-player.tsx Adds isReleasedPlayerError / withReleasedPlayerGuard helpers and wraps the unmount pause() and the AppState change handler in the guard; the periodic player.currentTime read inside setInterval is left unguarded.
tests/app/video-player.test.tsx Adds four regression tests covering Android (ERR_USING_RELEASED_SHARED_OBJECT) and iOS message-based guard paths for both unmount and background transitions; all existing tests preserved.

Sequence Diagram

sequenceDiagram
    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
Loading

Comments Outside Diff (1)

  1. app/video-player.tsx, line 160-171 (link)

    P1 The periodic setInterval callback reads player.currentTime directly without any guard. Because this interval runs every 5 seconds and is only cleared on unmount, there is a window between when react-native-screens detaches the screen (releasing the native player) and when React actually runs the cleanup. During that window the interval can fire and throw ERR_USING_RELEASED_SHARED_OBJECT (or the iOS equivalent) on the player.currentTime property access — the same class of error the rest of this PR guards against.

Reviews (3): Last reviewed commit: "Also guard the iOS released-player teard..." | Re-trigger Greptile

Comment thread app/video-player.tsx Outdated
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>
@nyomanjyotisa nyomanjyotisa force-pushed the fix/android-video-back-crash branch from 6485945 to 797aa95 Compare June 11, 2026 04:17
nyomanjyotisa and others added 2 commits June 11, 2026 12:26
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>
@nyomanjyotisa nyomanjyotisa merged commit e8ede60 into main Jun 11, 2026
2 checks passed
@nyomanjyotisa nyomanjyotisa deleted the fix/android-video-back-crash branch June 11, 2026 07:23
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.

Android: back from a video closes the entire app instead of returning to the previous screen

1 participant