A vertical video feed in the style of TikTok built with React Native Video v7 and @legendapp/list using Expo: smooth scroll, one-video snap, preloading, and iOS/Android optimizations.
Supported by TheWidlarzGroup – the software agency with focus on audio and video in React Native. Maintainers of react-native-video. Builders of a PRO player. If you like this project, give it a star ⭐
TWG provides commercial support for this project.
Need a custom use case, an extra feature, or want to speed up the feed even more? We can help with that too – reach out for consulting or implementation.
See FEED_EXTENSIONS.md for possible enhancements (DAI/video ads, user tracking, UI customization, mixed content, navigation).
Stay up to date with news – follow us on Twitter or LinkedIn.
- HLS: Sources are HLS streams (
/v0/index.m3u8…/v9/index.m3u8). - Start at 50% visibility: Active video starts when 50% visible in the viewport.
- One video per swipe:
snapToInterval+disableIntervalMomentum={true}– one swipe advances or goes back one video. - Tap to pause/play: Tap on video to pause/resume; centered play button shows when paused.
- Overlay: Like, comment, share icons and animated play button.
- Preload window: 1 behind, 5 ahead on both platforms (plus immediate neighbors stay preloaded). Placeholders outside the window to reduce active players.
- Virtualization: LegendList with fixed item size and draw distance 2× item height.
- Source loading only in window:
shouldPreloadgates when a player is hydrated. - Scroll feel:
decelerationRate0.98, paging enabled;disableIntervalMomentum
- TTFF (load): from
replaceSourceAsync/preload()(or when status is alreadyloading) toreadyToPlay. - Perceived TTFF: from when the video becomes visible (active) to
readyToPlay; ~0 ms when preload works well. - FPS: number of
requestAnimationFramecallbacks per second (≈ display refresh rate). - Scroll lag: delay from scroll start to first frame (requestAnimationFrame).
- Enabled in
__DEV__; view via 📊 button (PerformanceMonitor).
-
Production-ready patterns – Asymmetric preload (1 behind, 3 ahead), viewability-based play, and source loading only in the preload window. Copy these patterns into your app instead of figuring them out from scratch.
-
React Native Video v7 + Legend List – See how
useVideoPlayer,replaceSourceAsync,preload()and a virtualized list work together for a TikTok-style feed on both iOS and Android with one codebase. -
Performance from day one – Virtualization, platform-tuned preload counts, and optional dev metrics (TTFF, perceived TTFF, FPS, scroll lag) so you can measure and improve before launch.
-
HLS-ready – Built for real HLS streams (
.m3u8); preload and playback are tuned for manifest-based sources, so you can plug in your own CDN or ad-stitched URLs without reworking the player logic. -
TikTok-style UX – One video per swipe, play on 30% visibility, tap to pause and overlay controls (like, comment, share). The scroll and snap behaviour are tuned per platform so the feed feels native on both iOS and Android.
Configuration:
viewabilityConfig.itemVisiblePercentThreshold: 30PRELOAD_AHEAD: Android 3, iOS 5 |PRELOAD_BEHIND: 1DRAW_DISTANCE_MULTIPLIER: Android 2, iOS 3SCROLL_EVENT_THROTTLE: Android 32 ms, iOS 16 mssnapToInterval={itemHeight},snapToAlignment="start"DECELERATION_RATE: Android 0.98, iOS 0.95disableIntervalMomentum={true}– one video per swipe
- Player:
useVideoPlayer(video.url); loop, muted. - Hydration: Player only rendered for items in the preload/active window (placeholders elsewhere).
- Play/pause: Play when
isActive && !userPaused && AppState === "active"; pause otherwise; resetcurrentTimewhen becoming active. - Height:
itemHeightfrom list (measured layout on Android).
- Video list from
SOURCES(10 HLS URLs);CYCLE_COUNT: Android 10, iOS 20 (100 vs 200 videos). - Returns
videos,loading,error,refetch.
In VideoFeedList.tsx:
const PRELOAD_AHEAD = 3;
const PRELOAD_BEHIND = 1;In useVideoFeed.ts:
const CYCLE_COUNT = Platform.OS === "android" ? 10 : 20;- Paging enabled on both platforms,
snapToIntervalper item height. disableIntervalMomentumon Android for one-video snap.DECELERATION_RATE: 0.98 on both platforms.
bun install
bun run ios
bun run android- react-native-video@7.0.0-beta.2 –
useVideoPlayer,VideoView,useEvent,preload(),replaceSourceAsync() - @legendapp/list – list with virtualization and fixed item size
- expo ~52
- react 18.3.1, react-native 0.76.9
