Skip to content

[IOS][v7.0.0-beta-9] iOS video briefly plays, then snaps back to the first frame after mounting #4919

Description

@robgraeber

What happened?

On iOS, videos sometimes begin playback normally, then after roughly a few hundred
milliseconds the visible frame jumps back to the first frame and playback continues
from the beginning. Only observed in production builds.

ScreenRecording_06-15-2026.12-09-46_1.mov

Symptoms

  • Video starts playing; audio starts normally.
  • Around the initial startup window, the rendered video jumps back to the first frame
    (brief restart / flash / visual snapback).
  • Not purely asset encoding: the same video can work in one mounted layout and fail in another.

What we ruled out

  • Not strictly caused by too many background videos (still happened in low-motion mode without autoplay).
  • Not a specific video file (the same video could work in one screen and snap back in another).
  • Not fixed by native-side experiments (forcing a different layer path, preroll/preload timing, fixed play delay).
  • Not fixed by waiting on readyToPlay alone - it means the player item is playable, not that the
    native view/layer has a stable size and presentation path.

Relevant observation
Stable screens naturally delayed playback until after layout was established. Unstable cases were more
likely to create the player, mount the native view, and issue play() during the same early
render/layout window. This suggests the issue is related to starting playback before the native
VideoView / AVPlayerLayer has a stable mounted layout, rather than a simple JS state bug.

Workaround applied
Gate playback on layout readiness: wait for onLayout to report non-zero width/height, then one
requestAnimationFrame, and only then allow play() (when player.status === 'readyToPlay').

const [isLayoutReadyForPlayback, setIsLayoutReadyForPlayback] = useState(false);
const layoutSizeRef = useRef({ width: 0, height: 0 });

function onContainerLayout(event) {
  const { width, height } = event.nativeEvent.layout;
  layoutSizeRef.current = { width, height };

  if (width <= 0 || height <= 0) {
    setIsLayoutReadyForPlayback(false);
    return;
  }

  requestAnimationFrame(() => {
    const latest = layoutSizeRef.current;
    if (latest.width > 0 && latest.height > 0) {
      setIsLayoutReadyForPlayback(true);
    }
  });
}

useEffect(() => {
  if (!playing) return;
  if (!isLayoutReadyForPlayback) return;
  if (player.status !== 'readyToPlay') return;

  player.play();
}, [playing, isLayoutReadyForPlayback, player]);

Desired upstream behavior
Ideally react-native-video would either avoid starting playback until the native video layer is
attached and has a stable non-zero presentation size, or expose a reliable event indicating the player
is safe to start without risking an initial visual reset/snapback. onReadyToDisplay is close
conceptually, but in practice could not be relied on as the only gate, because it may require
playback/display progression first - a chicken-and-egg problem for autoplay.

Steps to reproduce

  1. Mount a VideoView and create/attach its player during the initial render/layout window.
  2. Autoplay: call play() immediately, before the native view has a stable non-zero layout.
  3. In a production build on iOS, observe playback start, then snap back to the first frame after a few hundred ms.

Most reproducible in gallery/modal layouts that mount the view and start playback in the same early layout pass.

Reproduction repository

No response

react-native-video version

7.0.0-beta.9

react-native version

No response

react-native-nitro-modules version

No response

Platforms

iOS

OS version

No response

Device

No response

Architecture

No response

Expo

No response

Last working version

No response

Media / source type

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    To Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions