Skip to content

Video player fails with NotAllowedError + $state crash on feed navigation (Next.js 16 + Swiper) #1753

@floatrx

Description

@floatrx

Environment

Package Version
next 16.1.6 (Turbopack)
@vidstack/react ^1.12.13
react / react-dom 19.2.4
swiper ^12.1.2
Browser Chrome (desktop & mobile)

Description

The vertical video feed occasionally fails to play videos. Two errors appear in sequence:

1. NotAllowedError: play() failed because the user didn't interact with the document first

The browser autoplay policy blocks play() when the page loads or after Swiper slide transitions — even though <MediaPlayer> has muted set initially.

Current workaround: catch the error and retry with forced mute:

player.play().catch((err: unknown) => {
  if ((err as { name?: string })?.name === 'NotAllowedError') {
    player.muted = true;
    setMuted(true);
    player.play().catch(() => {});
  }
});

This works most of the time but not always (e.g., when the tab regains focus or on certain mobile browsers).

2. Uncaught TypeError: this.$state[prop2] is not a function

This occurs after the NotAllowedError, suggesting that Vidstack's internal Maverick signal store gets into a broken state. It happens when:

  • Swiper rapidly destroys/remounts <MediaPlayer> components during fast swiping
  • The src prop is cleared (set to "") — destroying the signal store
  • The player component unmounts while an async play() promise is still pending

Related: #1549 — similar signal store crash on navigation/remount with React Router.

Steps to Reproduce

  1. Open the video feed page
  2. Quickly swipe through several videos (vertical Swiper)
  3. Observe console errors — NotAllowedError followed by $state[prop2] is not a function
  4. The affected slide shows a black screen; the player is unrecoverable without remount

Current Mitigations

src is never cleared — preload="none" used for far slides to keep the signal store alive:

/*
 * Memory management by distance:
 *  0 (active)   — full preload, play with sound
 *  1 (adjacent) — full preload, paused — ready to play instantly on swipe
 *  2+ (far)     — src kept but preload="none" — browser won't buffer,
 *                 Vidstack signal store stays alive → no $state errors
 *
 * NOTE: src is NEVER cleared — setting src="" destroys Vidstack's internal
 * Maverick signal system causing "this.$state[prop2] is not a function".
 */
const src = episode.videoUrl ?? '';
const preload = distance <= 1 ? 'auto' : 'none';
  • NotAllowedError is caught and retried with forced mute (see above)
  • player.pause() is wrapped in try/catch to prevent cascading errors

Expected Behavior

  • Videos should play reliably on slide activation without console errors
  • Player state should not crash when components are rapidly mounted/unmounted by Swiper

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions