react-native-hls-cache
v1.4.0
Published
HSL
Readme
react-native-hls-cache
HLS segment caching for React Native via a local TCP proxy server.
| Platform | Support | |----------|---------| | iOS | Full proxy — manifests + segments cached to disk | | Android | No-op — ExoPlayer/Media3 handles caching natively |
Powered by Nitro Modules for zero-overhead FFI.
Installation
npm install react-native-hls-cache react-native-nitro-modulesBare React Native
cd ios && pod installExpo
expo prebuild regenerates ios/Podfile from scratch, wiping manual edits. Use the config plugin to inject the Cache pod automatically:
// app.json
{
"expo": {
"plugins": ["react-native-hls-cache"]
}
}How it works
AVPlayer
→ http://127.0.0.1:9000/proxy?url=<encoded>
→ Local proxy (NWListener)
├── Cache hit → serve from disk instantly
└── Cache miss → fetch from origin, stream to player + write to diskAll HLS manifest URLs are rewritten to route through the proxy. Subsequent plays of the same video are served entirely from disk — zero network traffic.
Quick start
import { useEffect } from 'react';
import { startServer, convertUrl, precacheBatch, cancelPrecache } from 'react-native-hls-cache';
import Video from 'react-native-video';
// 1. Start the proxy once on app mount
useEffect(() => {
startServer(9000, 1_073_741_824, false, (e) => {
if (e.type === 'ready') console.log('Proxy ready on port 9000');
});
}, []);
// 2. Warm upcoming videos before the user scrolls to them
useEffect(() => {
const sessionId = precacheBatch(upcomingUrls, 3);
return () => cancelPrecache(sessionId);
}, [upcomingUrls]);
// 3. Route playback through the proxy
const url = convertUrl('https://cdn.example.com/video.m3u8');
<Video source={{ uri: url }} />;API
startServer(port?, maxCacheSize?, headOnlyCache?, onCacheEvent?)
Starts the local proxy server. Call once on app mount.
startServer(
9000, // port (default: 9000)
1_073_741_824, // max disk cache in bytes (default: 1 GB)
false, // headOnlyCache: only cache the first 3 segments (default: false)
(event) => {
// event.type: 'ready' | 'hit' | 'miss' | 'download' | 'error'
// event.url: origin URL (empty when not applicable)
// event.bytes: bytes transferred (0 when not applicable)
// event.error: error message (empty when not applicable)
console.log(event);
}
);headOnlyCache is optimised for vertical video feeds — it caches just enough to ensure the first play is fast without filling disk on content the user might never finish watching.
convertUrl(url, isCacheable?)
Rewrites a remote HLS URL to route through the proxy. Returns the original URL unchanged on Android or when the server is not running.
const proxyUrl = convertUrl('https://cdn.example.com/video.m3u8');
// → "http://127.0.0.1:9000/proxy?url=https%3A%2F%2Fcdn..."
// Bypass the proxy for a specific video:
const directUrl = convertUrl(url, false);Pass proxyUrl to your video player instead of the original URL.
clearCache()
Deletes all cached files from disk.
await clearCache();precache(url, segmentCount?)
Downloads the manifest and first N segments of a stream to disk before the user presses play. Subsequent playback will be instant cache hits.
// Cache the first 5 segments (≈ first 30s at 6s/segment)
await precache('https://cdn.example.com/video.m3u8', 5);
// Cache the entire stream:
await precache('https://cdn.example.com/video.m3u8', 0);precacheBatch(urls, segmentCount?)
Concurrently precaches multiple streams. Returns a session ID for cancellation.
const sessionId = precacheBatch(
['https://cdn.example.com/v1.m3u8', 'https://cdn.example.com/v2.m3u8'],
3 // segments per stream (default: 3)
);
// Cancel all in-flight downloads (e.g. user navigates away):
cancelPrecache(sessionId);cancelPrecache(sessionId)
Cancels all in-flight downloads for a session returned by precacheBatch.
cancelPrecache(sessionId);getVariants(url)
Fetches an HLS master playlist and returns all variant renditions sorted by bandwidth (lowest → highest). Useful for picking which quality to precache.
const variants = await getVariants('https://cdn.example.com/master.m3u8');
// [
// { url, bandwidth: 365811, resolution: '270x480', codecs: '...', frameRate: 30 },
// { url, bandwidth: 613626, resolution: '480x854', ... },
// { url, bandwidth: 1096138, resolution: '720x1280', ... },
// ]
// Precache only the lowest quality:
await precache(variants[0]!.url, 5);Returns [] if the URL is a media playlist (no #EXT-X-STREAM-INF) or if the fetch fails.
Cache behaviour
| Detail | Value |
|--------|-------|
| Storage location | ~/Library/Caches/HLSVideoCache/ |
| Cache key | SHA-256 of URL (+ byte range for fMP4 segments) |
| Eviction | LRU — oldest files pruned on each write until under maxCacheSize |
| Manifest rewriting | EXT-X-MAP, EXT-X-MEDIA, EXT-X-PART, EXT-X-PRELOAD-HINT URIs all rewritten to proxy URLs |
| Range requests | Supported — fMP4 byte-range requests cached per range, with fallback slice from full cached file |
License
MIT
