The component is in the tree, the props look right, nothing throws, and the screen shows a black rectangle. Or nothing at all. react-native-video fails quietly by design. The native player it wraps reports problems through callbacks rather than exceptions, and an unhandled callback is a silent one. Five different faults produce the same blank result, and they're separable in about a minute if you check them in the right order.
TL;DR
Work down five checks in cost order. Open the .m3u8 in a browser first, because a wrong playback ID or a still-encoding media rules out React Native entirely. Then give the style a temporary backgroundColor: no rectangle means the view has zero height, which is the most common cause. Then add onError and onLoad, since the native player reports failures through callbacks rather than by throwing. Then confirm you rebuilt rather than just reloaded, because native modules live in the binary. Finally check the URL is HTTPS, as Android blocks cleartext before any player code runs.
Check the HLS stream URL outside the app first
This is the cheapest test and it eliminates the most expensive investigation. Open the stream URL in a browser:
https://stream.fastpix.com/{PLAYBACK_ID}.m3u8If that doesn't play, nothing about React Native is involved. Either the playback ID is wrong, or the media is still encoding and has no manifest yet. Upload success isn't the same event as playback readiness. A video page rendered immediately after upload is the usual way people arrive here.
| What the browser does | What it means |
|---|---|
| Plays | The stream is fine. Continue down this list. |
| 404 or error | Wrong playback ID, or the media is still processing. |
| Plays only after a wait | You are rendering before video.media.ready arrives. |
That readiness event, and how to verify it, is covered in the video.media.ready webhook. Need a stream you know is good? The free plan covers 10 videos and 100K streaming minutes a month, no credit card.
What you need before you start
react-native-videoinstalled, with pods run on iOS if the project is bare.- A FastPix playback ID from a media asset that has finished encoding.
- A development build.
react-native-videodoesn't run in Expo Go.
Fault one: the react-native-video component has no size
This is the most common cause and the least satisfying, because the code is correct and the layout isn't.
<Video> fills the space it's given. Given none, it renders at zero height and you see nothing, or you see the parent's background where you expected a player. React Native doesn't warn about this, because a zero-height view is a legal view.
<Video
source={{ uri: streamUrl }}
style={{ width: "100%", aspectRatio: 16 / 9 }} // not just flex: 1
controls
resizeMode="contain"
/>aspectRatio is safer than flex: 1 here, because flex: 1 only produces a size when the parent has one. Inside a ScrollView, or inside a View with no explicit height, flex: 1 resolves to nothing at all.
The diagnostic is to give the style a temporary backgroundColor: "red". A red rectangle means the view has a size and the problem is further down this list. No red rectangle means you found it.
Fault two: react-native-video was never linked natively
react-native-video contains native code, so installing the JavaScript package is half of an install.
In a bare project, cd ios && pod install is required, and running it isn't enough on its own. An app already on the simulator doesn't pick up new native modules. Rebuild it with npx expo run:ios, or from Xcode. Installing pods then reloading the JS bundle changes nothing, which is why this fault survives several attempts at fixing it.
In an Expo project, this library needs a development build. Expo Go ships a fixed set of native modules and react-native-video isn't among them. The component either throws about a missing native module, or renders nothing.
The same constraint applies to uploads for the same reason, covered in uploading video from Expo Go doesn't work.
Fault three: no onError handler, so the failure is silent
The player is probably telling you what is wrong. Nothing is catching it.
onError is where the native layer reports a failed load, an unsupported format or a network refusal. Without it, a hard failure looks identical to a video that has simply not started:
<Video
source={{ uri: streamUrl }}
style={{ width: "100%", aspectRatio: 16 / 9 }}
onError={(e) => console.log("player error", JSON.stringify(e))}
onLoad={(meta) => console.log("loaded", meta.duration)}
onBuffer={({ isBuffering }) => console.log("buffering", isBuffering)}
/>Adding those three converts a silent failure into a readable one. onLoad firing with a duration means the manifest parsed and the problem is visual, which sends you back to fault one. onError firing means you now have an actual message to search for.
Fault four: Android blocked the HLS stream URL
Android blocks cleartext HTTP by default. A stream on http:// is declined before any player code runs. The rejection surfaces as a load failure rather than as a security warning.
FastPix serves streams over HTTPS, so following the documented URL format avoids this entirely. It bites when a local test server or a proxy is introduced during development and the scheme quietly changes.
The related iOS constraint is App Transport Security, which enforces the same rule from the other direction. Both platforms want HTTPS, and https://stream.fastpix.com satisfies both without configuration.
Why react-native-video shows no controls until you ask
A player that plays but shows no play button, no scrubber and no fullscreen control isn't broken.
react-native-video renders no UI unless you ask for it. Pass controls for the native control set, or build your own against the player's callbacks. This is last on the list because it produces a video that visibly works. It rarely gets confused with the silent failures above.
expo-video behaves the other way round. Its equivalent prop is nativeControls, and it defaults to true, so that player shows controls until you switch them off. Worth knowing if you're moving between the two, because the same screen gains or loses its controls on the same code. The wider comparison is in expo-video vs react-native-video in 2026.
React Native video debugging order, condensed
| Check | Cost | What it rules out |
|---|---|---|
| Open the .m3u8 in a browser | Seconds | Bad playback ID, media still encoding |
| Add backgroundColor to the style | Seconds | Zero-height layout |
| Add onError and onLoad | A minute | Silent native failures |
| Confirm the build, not just the install | Minutes | Unlinked native modules, Expo Go |
| Confirm the URL scheme is HTTPS | Seconds | Android cleartext blocking |
Work down that list rather than across the props. Three of the five checks cost seconds, and they eliminate most of the search space before you touch the component's configuration.
Get a FastPix stream playing on a device
Build the stream URL as https://stream.fastpix.com/{PLAYBACK_ID}.m3u8, give the view a real aspectRatio, and wire onError before you wire anything else. Those three steps close every fault on the triage list except the native build. The free plan covers 10 videos and 100K streaming minutes a month, no credit card. That gives you a known-good stream to test against while you rule the layout out.
Frequently Asked Questions (FAQs)
Why is react-native-video showing a black screen?
Most often the view has no height. <Video> fills the space it is given, and flex: 1 inside a parent with no size resolves to zero. Set an explicit aspectRatio or height, and confirm it by adding a temporary backgroundColor to the style. If a colored rectangle appears, the layout is fine and the fault is the stream, the native build or an unhandled error.
Does react-native-video work in Expo Go?
No. It contains native code, and Expo Go ships a fixed set of native modules that does not include it. Create a development build with npx expo run:ios or npx expo run:android, or use EAS Build. expo-video does run in Expo Go for basic playback, which is why the two behave differently on the same project.
How do I debug a react-native-video player that does not play?
Add onError, onLoad and onBuffer and read the console. The native player reports failures through callbacks rather than by throwing. An unhandled callback makes a hard failure look like a video that simply has not started. onLoad firing with a duration means the manifest parsed and the remaining problem is visual.
Why does my HLS stream fail only on Android?
Almost always because the URL is http:// rather than https://. Android blocks cleartext traffic by default and rejects the request before any player code runs. FastPix streams are served over HTTPS at https://stream.fastpix.com/{PLAYBACK_ID}.m3u8, so this usually appears only when a local server or proxy is introduced during development.
Why are there no play or fullscreen controls?
Because react-native-video renders no UI unless asked. Pass the controls prop for the native control set, or build your own from the player's callbacks. This is a configuration gap rather than a fault, and it produces a video that otherwise plays correctly.





