For AI agents: the complete documentation index is available at /react-native-arrangement-view/v0.5/llms.txt, the full documentation bundle is available at /react-native-arrangement-view/v0.5/llms-full.txt, and this page is available as Markdown at /react-native-arrangement-view/v0.5/examples/video-player.md.

Video player

A full-screen video player. In tabletop posture, media sits at the top, while tappable controls live on a stable base at the bottom. The full example is in the example app.

Arrangement

Use arrangement="overlay", with the controls as the primary pane. Overlay keeps both panes at full size, the primary in front, until a half-open hinge separates them. It then puts the primary after the hinge: below it in tabletop, or on the trailing side.

import { StyleSheet, View } from 'react-native';
import { VideoView, useVideoPlayer } from 'expo-video';
import { ArrangementView } from 'react-native-arrangement-view';

export function VideoPlayer() {
  const player = useVideoPlayer(source, (player) => player.play());

  return (
    <View style={styles.screen}>
      <ArrangementView style={styles.fill} arrangement="overlay">
        <ArrangementView.Primary>
          <Controls player={player} />
        </ArrangementView.Primary>
        <ArrangementView.Secondary>
          <VideoView
            style={styles.fill}
            player={player}
            nativeControls={false}
            contentFit="contain"
          />
        </ArrangementView.Secondary>
      </ArrangementView>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, backgroundColor: 'black' },
  fill: { flex: 1 },
});

Moving between postures doesn't unmount either pane, so the video keeps playing.

Controls

Inside the controls, useArrangementLayout tells the two layouts apart: isOverlapping is true while the controls are in front of the video. When overlapping, the controls hide a few seconds after the last touch while it plays. When in their own region, they stay always visible.

import { useArrangementLayout } from 'react-native-arrangement-view';

function Controls({ player }: { player: VideoPlayer }) {
  const layout = useArrangementLayout();
  const separated = layout !== null && !layout.isOverlapping;

  const [shown, setShown] = useState(true);
  const visible = separated || shown;
  useEffect(() => {
    if (!visible || separated) return;
    const timer = setTimeout(() => setShown(false), 3000);
    return () => clearTimeout(timer);
  }, [visible, separated]);

  if (separated) return <Remote player={player} />;
  return (
    <Pressable style={{ flex: 1 }} onPress={() => setShown(!shown)}>
      {visible && <OverlayControls player={player} />}
    </Pressable>
  );
}

Full screen

The example's screen hides the stack header and the status bar, and turns off the back gesture, which on iOS 26 and later works anywhere on the screen and would take over drags on the seek bar:

<Stack.Screen
  name="video-player"
  component={VideoPlayer}
  options={{
    headerShown: false,
    statusBarHidden: true,
    gestureEnabled: false,
    autoHideHomeIndicator: true,
  }}
/>

statusBarHidden needs UIViewControllerBasedStatusBarAppearance set to true in Info.plist.

Screenshots

PostureLayout
Closed, or open flatFull-screen video, with controls over it that hide while it plays and return on tap
Half-open, tabletopThe video on the upright half, and controls filling the half on the table
Half-open, held like a bookThe video on the leading half, and controls on the trailing half
Closed
iOS, closediOS
Android, closedAndroid
Open
iOS, openiOS
Android, openAndroid
Tabletop
iOS, tabletopiOS
Android, tabletopAndroid
Book
iOS, bookiOS
Android, bookAndroid