Skip to content

chanphiromsok/react-native-hls-cache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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-modules

Bare React Native

cd ios && pod install

Expo

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 disk

All 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

About

HLS segment caching for React Native via a local TCP proxy server.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors