Picking a React Native video player in 2026 comes down to two libraries, and the choice between them is mostly decided by one question: are you on Expo? This guide compares expo-video (58.0.13, the default for new apps) with react-native-video (6.19.3 stable, v7 in beta on Nitro Modules), shows working code for the common jobs (a single player, events, Picture in Picture, background audio, caching, a vertical feed, HLS and DRM), and covers the cases where you should not use a player library at all.
Versions checked on npm on November 9, 2026. If you are reading this a few releases later, the APIs below are stable but check the changelogs before you copy a config plugin.
The short answer
- New app, Expo or bare with Expo modules: use
expo-video. It has the cleaner API, about 3.9 million downloads a week, and it is maintained by the same team that ships the SDK you are already on. - Existing app on react-native-video v6: stay on 6.19.x. It works on the New Architecture through the interop layer. Plan the v7 migration when v7 leaves beta, not before.
- You need DRM, offline downloads or ads and want commercial support: react-native-video, because The Widlarz Group sells exactly those plugins and support packages on top of it.
- You only embed YouTube: use
react-native-youtube-iframeor a WebView. Neither library above plays YouTube URLs, and YouTube’s terms want their player anyway. - You want live video calls: that is not a player problem. Skip to the last section.
React Native video player libraries compared
| Library | Version (Nov 2026) | Downloads/week | Architecture | Best for |
|---|---|---|---|---|
| expo-video | 58.0.13 | ~3.86M | Expo Modules, New Architecture | Most apps, feeds, PiP, caching |
| react-native-video | 6.19.3 (v7.0.0-beta.12) | ~673K | v6 via interop layer, v7 on Nitro Modules | DRM, existing v6 apps, commercial add-ons |
| expo-av | 16.0.8 | ~548K | Legacy | Nothing new. Migrate off it |
| react-native-youtube-iframe | 2.4.1 | ~250K | WebView based | YouTube embeds only |
| react-native-vlc-media-player | 1.0.98 | ~6K | Native VLC | RTSP cameras, exotic codecs |
Two notes on that table. expo-av still gets downloaded because half a million apps never moved, but it has not had a release since December 2025 and Expo’s docs point you at expo-video and expo-audio. And the download gap between the top two is partly an Expo effect: every new create-expo-app project that plays video pulls expo-video. It is not proof that react-native-video is worse. It is proof of where the defaults are.
expo-video: install and a first player
npx expo install expo-video
The API splits the player (state, playback, source) from the view (pixels on screen). You create the player with a hook and hand it to a VideoView:
import { useEvent } from 'expo';
import { useVideoPlayer, VideoView } from 'expo-video';
import { Button, StyleSheet, View } from 'react-native';
const source = 'https://example.com/videos/intro.mp4';
export default function IntroVideo() {
const player = useVideoPlayer(source, (p) => {
p.loop = false;
});
const { isPlaying } = useEvent(player, 'playingChange', {
isPlaying: player.playing,
});
return (
<View style={styles.container}>
<VideoView
player={player}
style={styles.video}
contentFit="contain"
allowsPictureInPicture
/>
<Button
title={isPlaying ? 'Pause' : 'Play'}
onPress={() => (isPlaying ? player.pause() : player.play())}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center' },
video: { width: '100%', aspectRatio: 16 / 9 },
});
The split is the thing to internalise. A player can exist without a view, which is how preloading works, and you can move one player between views. The hook disposes the player when the component unmounts, so you rarely manage its lifecycle yourself.
Listening to events
Use useEvent when you want a value in render, and useEventListener when you want a side effect:
import { useEventListener } from 'expo';
useEventListener(player, 'statusChange', ({ status, error }) => {
if (status === 'error') {
console.warn('Playback failed', error?.message);
}
});
Wire the error status to your crash reporter from day one. Video failures are almost always network or codec problems on specific devices, and you will not reproduce them on your own phone. Our Sentry guide covers the reporting side.
Picture in Picture and background playback
Both need native configuration, so they go in the config plugin and require a new build (not Expo Go):
{
"expo": {
"plugins": [
[
"expo-video",
{
"supportsBackgroundPlayback": true,
"supportsPictureInPicture": true
}
]
]
}
}
On iOS this adds the audio background mode to Info.plist; on Android it enables supportsPictureInPicture and, for background playback, a foreground service. Then set allowsPictureInPicture on the view, and tell the player it may keep playing in the background with player.staysActiveInBackground = true. App Store review asks why you need the audio background mode, so have an honest answer (a lecture app, a podcast with video) before you submit.
Caching
If users replay the same clips, turn on caching per source:
const player = useVideoPlayer({
uri: 'https://example.com/videos/onboarding.mp4',
useCaching: true,
});
The cache is least recently used, 1 GB by default (change it with setVideoCacheSizeAsync, clear it with clearVideoCacheAsync), works offline for whatever was cached, and the OS may purge it when storage is low. Two limits from the docs that bite people: HLS sources cannot be cached on iOS, and DRM protected video is not cached on either platform. So caching helps onboarding clips and short MP4s, not your streaming catalogue.
A vertical video feed that does not melt the phone
The TikTok-style feed is where most video bugs live. The rule I give every team: a handful of players alive at once, never one per item. Mobile hardware decoders are a finite resource, and on mid-range Android phones you hit the limit sooner than you expect. Render the feed as a paged list with a small window, give each mounted item its own player, and play only the active one:
import { useEffect, useRef, useState } from 'react';
import { FlatList, useWindowDimensions, ViewToken } from 'react-native';
import { useVideoPlayer, VideoView } from 'expo-video';
type Clip = { id: string; uri: string };
function FeedItem({ clip, active, height }: { clip: Clip; active: boolean; height: number }) {
const player = useVideoPlayer({ uri: clip.uri, useCaching: true }, (p) => {
p.loop = true;
p.muted = true;
});
useEffect(() => {
if (active) player.play();
else player.pause();
}, [active, player]);
return (
<VideoView
player={player}
style={{ width: '100%', height }}
contentFit="cover"
nativeControls={false}
/>
);
}
export function Feed({ clips }: { clips: Clip[] }) {
const { height } = useWindowDimensions();
const [activeId, setActiveId] = useState(clips[0]?.id);
const onViewable = useRef(({ viewableItems }: { viewableItems: ViewToken[] }) => {
if (viewableItems[0]) setActiveId(viewableItems[0].item.id);
}).current;
return (
<FlatList
data={clips}
keyExtractor={(c) => c.id}
renderItem={({ item }) => (
<FeedItem clip={item} active={item.id === activeId} height={height} />
)}
pagingEnabled
windowSize={3}
initialNumToRender={1}
maxToRenderPerBatch={2}
onViewableItemsChanged={onViewable}
viewabilityConfig={{ itemVisiblePercentThreshold: 80 }}
/>
);
}
windowSize={3} keeps roughly the current screen plus one either side mounted, which gives you the next clip pre-buffered (a player fills its buffer even before it plays) without stacking ten decoders. If you need more control, keep a small pool of players and swap their sources with player.replaceAsync() as the user scrolls; expo-video’s docs show the same idea for preloading. Measure on a three-year-old Android phone, not a new iPhone. Thumbnails in the gaps are an image problem, covered in our image handling guide.
HLS, DASH and DRM
Both libraries play HLS on both platforms because they sit on AVPlayer and ExoPlayer (Media3). DASH works on Android; on iOS, AVPlayer does not speak DASH, so serve HLS to Apple devices. For paid content you need DRM, Widevine on Android and FairPlay on iOS. In expo-video the DRM config sits on the source:
const player = useVideoPlayer({
uri: 'https://cdn.example.com/movie/manifest.mpd',
drm: {
type: 'widevine',
licenseServer: 'https://license.example.com/widevine',
headers: { Authorization: `Bearer ${token}` },
},
});
FairPlay additionally needs a certificate URL and usually a custom content id step; read the DRM section of whichever library you choose and test on a real device, because simulators do not do FairPlay. This is also the point where teams with a big catalogue tend to choose react-native-video, because The Widlarz Group publishes a free DRM test stream generator and sells offline download support for protected streams.
react-native-video: v6 today, v7 tomorrow
If your app already uses react-native-video, the component API is familiar:
import { useRef } from 'react';
import Video, { VideoRef } from 'react-native-video';
export function Lecture({ uri }: { uri: string }) {
const ref = useRef<VideoRef>(null);
return (
<Video
ref={ref}
source={{ uri }}
style={{ width: '100%', aspectRatio: 16 / 9 }}
resizeMode="contain"
controls
onError={(e) => console.warn('video error', e.error)}
onEnd={() => ref.current?.seek(0)}
/>
);
}
v6 is the maintained line and runs on the New Architecture through the interop layer. v7 is a rewrite on Nitro Modules with full New Architecture support, and it adopts the same player and view split as expo-video:
import { useVideoPlayer, VideoView } from 'react-native-video';
const player = useVideoPlayer('https://example.com/video.mp4', (p) => p.play());
// <VideoView player={player} style={{ width: '100%', aspectRatio: 16 / 9 }} controls />
v7 needs React Native 0.75+ and react-native-nitro-modules, and its README still says to expect breaking changes and missing features (fine-grained track control is listed as in development, TV and visionOS as to do). The last beta, 7.0.0-beta.12, shipped on October 6. My advice: do not migrate a production app to a beta player. Do read the v7 API, because once it is stable, moving between react-native-video and expo-video becomes a mostly mechanical change, which is good for everyone.
When not to use a player library
- YouTube and Vimeo: embed their players.
react-native-youtube-iframewraps the YouTube IFrame API in a WebView; for anything else, a plain WebView with the provider’s embed URL. See our WebView patterns post for the pitfalls (autoplay policies, inline playback on iOS). - Recording or processing video: that is the camera’s job. Use VisionCamera, covered in our VisionCamera guide, then play the file back with expo-video.
- RTSP security cameras or odd codecs: the VLC wrapper still earns its place here, at the cost of app size.
Video calls are a different problem
A surprising number of “react native video” searches are really about live calls. Players are built for buffered, one-way media; a call needs two-way real-time media with sub-second latency, which means WebRTC (react-native-webrtc, about 510K downloads a week) or a hosted SFU with an SDK such as LiveKit’s, plus signalling, TURN servers and CallKit on iOS. None of the libraries above help. Disclosure: we build Ethora, an open-source chat SDK whose React Native package ships 1:1 and group calls next to messaging, and the honest lesson from building it is that the calling layer is far more work than the player ever was. If calls are your goal, start from our chat SDK comparison and the WebSockets guide for the signalling side.
FAQ
What is the best video player for React Native?
For most new apps, expo-video: it is the default in the Expo SDK, supports PiP, background playback, caching and DRM, and has the larger user base. react-native-video is the better choice if you already use it or need its commercial DRM and offline add-ons.
Does expo-video work without Expo?
Yes, in a bare React Native app with Expo Modules installed (npx install-expo-modules). You then configure PiP and background playback by hand instead of through the config plugin.
Is react-native-video compatible with the New Architecture?
v6 runs on the New Architecture through the interop layer. v7, currently in beta, is built on Nitro Modules with full New Architecture support.
Can React Native play HLS streams?
Yes. Both expo-video and react-native-video play HLS on iOS and Android. DASH plays on Android only, so serve HLS to iOS.
How do I play YouTube videos in React Native?
Use react-native-youtube-iframe or a WebView with the YouTube embed URL. Native player libraries cannot play YouTube page URLs.