Recipes
<VideoThumbnail> — the drop-in tile
A ready-made, fully customizable thumbnail tile is shipped with the package:
import { VideoThumbnail } from 'react-native-nitro-thumbnail';
<VideoThumbnail
videoUrl={item.videoUrl}
serverThumbnail={item.thumbnailUrl} // shown directly if present
isLoading={item.isFetching} // your API loading state
onPress={() => openPlayer(item.videoUrl)}
/>;It handles the whole lifecycle:
serverThumbnail provided? ──yes──▶ show it (createThumbnail never runs)
│ no
▼
generate it ──▶ createThumbnail({ url }) ──▶ cache → show + play button- Server-first — if
serverThumbnailis set, it’s shown and nothing is generated. - Shimmer while loading — built-in
Animatedsweep (iOS + Android + Web), or pass your ownrenderLoading. Also driven by yourisLoadingprop. - Play button overlay — customizable color/size, or fully custom via
renderPlayButton. - Graceful failure — a broken video shows an empty/placeholder state, never a crash.
- Zero extra dependencies.
⚠️
“Play” is a callback, not a bundled player. onPress fires when the tile is tapped —
you open your video player / navigate. The library never depends on a video-player
package, so it stays small and unopinionated.
Props
All props are optional except a source (videoUrl and/or serverThumbnail).
| Prop | Type | Default | Description |
|---|---|---|---|
videoUrl | string | null | — | Video to generate from when there’s no serverThumbnail. |
serverThumbnail | string | null | — | A thumbnail URL from your API. If set, shown directly — no generation. |
timeStamp | number | 1000 | Frame to capture (ms) when generating. |
format / maxWidth / maxHeight / headers / cacheName | — | — | Passed straight to createThumbnail. |
isLoading | boolean | false | Force the loading (shimmer) state, e.g. while your API fetches. |
renderLoading | () => ReactNode | — | Replace the built-in shimmer. |
shimmerColors | [string, string] | ['#e2e8f0','#f8fafc'] | [base, highlight] for the built-in shimmer. |
shimmerDuration | number | 1200 | Shimmer sweep duration (ms). |
showPlayButton | boolean | true | Show the play overlay (requires onPress). |
onPress | () => void | — | Tile pressed — open your player here. |
renderPlayButton | () => ReactNode | — | Replace the built-in play button. |
playButtonColor / playButtonBackgroundColor / playButtonSize | string / string / number | #fff / rgba(0,0,0,0.45) / 56 | Theme the default play button. |
width / height / borderRadius / resizeMode / backgroundColor | — | —/—/12/'cover'/#e2e8f0 | Appearance. |
style / imageStyle | StyleProp | — | Escape hatches for full styling. |
renderError | () => ReactNode | — | Custom error state. |
onThumbnail / onError | callbacks | — | Fired when a thumbnail is generated / fails. |
In a list
import { FlatList } from 'react-native';
import { VideoThumbnail } from 'react-native-nitro-thumbnail';
<FlatList
data={videos}
keyExtractor={(v) => v.id}
renderItem={({ item }) => (
<VideoThumbnail
width={160}
height={90}
videoUrl={item.videoUrl}
serverThumbnail={item.thumbnailUrl}
onPress={() => navigation.navigate('Player', { url: item.videoUrl })}
/>
)}
/>;Customizing it
// Dark shimmer + branded play button
<VideoThumbnail
videoUrl={url}
shimmerColors={['#1f2937', '#374151']}
playButtonColor="#e0218a"
playButtonSize={64}
borderRadius={16}
onPress={open}
/>
// Bring your own everything
<VideoThumbnail
videoUrl={url}
renderLoading={() => <MyGradientShimmer />}
renderPlayButton={() => <MyPlayIcon />}
onPress={open}
/>A runnable version of all four cases (server / remote / local / failure) plus the shimmer toggle is in the example app.
Doing it yourself (headless)
If you’d rather build your own UI, the rule is the same — call createThumbnail only
when there’s no server thumbnail:
const uri = serverThumbnail ?? generated;
React.useEffect(() => {
if (serverThumbnail || !videoUrl) return; // server has one → do nothing
createThumbnail({ url: videoUrl, cacheName: `vt-${videoUrl}` }).then((t) =>
setGenerated(t.path)
);
}, [videoUrl, serverThumbnail]);