Skip to content

[Bug]: onError never fires for a source that fails to load asynchronously (e.g. 404) - only onStatusChange('error') does #5083

Description

What happened?

onError never fires for a source that fails to load asynchronously (e.g. a 404 / unreachable URL), even though the docs describe it as the way to catch playback errors and show it wired up right next to player.play() with no caveat.

Root cause, traced in the core Nitro player:

  • onError is dispatched only from VideoPlayer.ts's private throwError(), called only when a promise from initialize()/preload()/replaceSourceAsync() rejects (VideoPlayer.ts:86-98, :254-256).
  • On iOS, a bad source does not reject initialize() - it resolves optimistically, and the failure surfaces later via AVPlayerItem's async status observer, which sets status = .error (HybridVideoPlayer+Events.swift:118-120). The status setter (HybridVideoPlayer.swift:175-184) emits onStatusChange('error') - a separate "shared" event path, while onError is explicitly "JS-only" (VideoPlayerEvents.native.ts:14-19).

Net effect: for the most common real-world error (bad URL/network failure at load), onError is silent; only onStatusChange('error') fires. Compounded by a second gotcha: with default initializeOnCreation: true, useVideoPlayer's setup callback (where listeners attach) is deferred until onLoadStart, which a failing source never reaches - so listeners never even get registered unless you set initializeOnCreation: false and call initialize() manually.

Found while building an E2E smoke-test harness and manually verifying a broken-source scenario on iOS Simulator - reproducible every run.

Expected: onError fires for a source that fails to load, matching the docs - or the docs/JSDoc should explicitly say onStatusChange('error') is the canonical signal instead.

Steps to reproduce

  1. Create a player with a source that will fail to load (e.g. a 404 URL), and attach both onError and onStatusChange before calling play():

    const player = useVideoPlayer(
    { uri: 'https://example.com/does-not-exist.mp4', initializeOnCreation: false },
    (p) => {
    p.addEventListener('onError', (e) => console.log('onError fired:', e));
    p.addEventListener('onStatusChange', (s) => console.log('onStatusChange fired:', s));
    p.initialize().then(() => p.play());
    }
    );

  2. Render and watch the console/logs.

  3. Observe the logs.

Reproduction repository

No response

react-native-video version

7.0.0-beta.11

react-native version

0.77.0

react-native-nitro-modules version

0.35.0

Platforms

iOS

OS version

iOS 26.5

Device

Simulator / Emulator

Architecture

New Architecture

Expo

No (bare / React Native CLI)

Last working version

No response

Media / source type

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions