npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@orca-runtime/orca-video-player

v0.2.3

Published

React Native video player with disk caching, preloading, and multi-URI support

Readme

Orca Video Player

@orca-runtime/orca-video-player

React Native video player with disk caching, preloading, and multi-URI support. Published on npm under the @orca-runtime scope. Built on Nitro Modules.

Installation

npm install @orca-runtime/orca-video-player react-native-nitro-modules
yarn add @orca-runtime/orca-video-player react-native-nitro-modules
pnpm add @orca-runtime/orca-video-player react-native-nitro-modules

react-native-nitro-modules is a required peer dependency.

After installing or changing native code, run:

yarn nitrogen   # first time / after *.nitro.ts changes
cd ios && pod install

Package exports

All public APIs are imported from @orca-runtime/orca-video-player:

import {
  OrcaVideoPlayer,
  OrcaVideoPlayerCacheApi,
  useVideoCache,
  getVideoUris,
  resolveVideoSource,
} from '@orca-runtime/orca-video-player';

import type {
  OrcaVideoPlayerProps,
  OrcaVideoPlayerHandle,
  VideoSource,
  ResizeMode,
  ResolvedVideoSource,
  UseVideoCacheOptions,
  UseVideoCacheResult,
} from '@orca-runtime/orca-video-player';

Quick start

import { OrcaVideoPlayer } from '@orca-runtime/orca-video-player';

<OrcaVideoPlayer
  source={{ uri: 'https://example.com/video.mp4' }}
  autoplay
  muted
  controls
  resizeMode="cover"
  onProgress={(time) => console.log(time)}
  onEnd={() => console.log('ended')}
/>;

Looping

Replay the same source indefinitely in a single player instance. When loop is enabled, onEnd is not called:

<OrcaVideoPlayer
  source={{ uri: 'https://example.com/video.mp4' }}
  loop
  autoplay
  muted
/>

Imperative control

Attach a ref to control playback from your own UI — useful when controls={false} and you need custom Play / Pause / Seek buttons:

import { useRef } from 'react';
import { Pressable, Text } from 'react-native';
import {
  OrcaVideoPlayer,
  type OrcaVideoPlayerHandle,
} from '@orca-runtime/orca-video-player';

const playerRef = useRef<OrcaVideoPlayerHandle>(null);

<>
  <OrcaVideoPlayer
    ref={playerRef}
    source={{ uri: 'https://example.com/video.mp4' }}
    controls={false}
    resizeMode="cover"
  />

  <Pressable onPress={() => playerRef.current?.play()}>
    <Text>Play</Text>
  </Pressable>
  <Pressable onPress={() => playerRef.current?.pause()}>
    <Text>Pause</Text>
  </Pressable>
  <Pressable onPress={() => playerRef.current?.seekTo(10)}>
    <Text>Seek to 10s</Text>
  </Pressable>
  <Pressable onPress={() => playerRef.current?.enterPictureInPicture()}>
    <Text>PiP</Text>
  </Pressable>
</>;

Ref methods

| Method | Description | | ------------------------- | ----------------------------- | | play() | Start or resume playback | | pause() | Pause playback | | seekTo(seconds) | Seek to a position in seconds | | enterPictureInPicture() | Enter Picture-in-Picture mode | | exitPictureInPicture() | Exit Picture-in-Picture mode |

interface OrcaVideoPlayerHandle {
  play(): void;
  pause(): void;
  seekTo(seconds: number): void;
  enterPictureInPicture(): void;
  exitPictureInPicture(): void;
}

Ref methods are available on iOS, Android, and web. On web, play() may be blocked by the browser until the user interacts with the page.

Enable Picture-in-Picture

Enable PiP on a player and optionally enter it automatically when the app moves to the background:

const playerRef = useRef<OrcaVideoPlayerHandle>(null);

<OrcaVideoPlayer
  ref={playerRef}
  source={{ uri: 'https://example.com/video.mp4' }}
  allowsPictureInPicture
  autoEnterPictureInPicture
  onPictureInPictureChange={(active) => console.log('PiP active:', active)}
/>

<Pressable onPress={() => playerRef.current?.enterPictureInPicture()}>
  <Text>Enter PiP</Text>
</Pressable>

Platform setup

iOS — add background audio mode to your app's Info.plist:

<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

When controls={true} and allowsPictureInPicture is enabled, the system controls may also show a PiP button.

Android — PiP requires host app configuration:

  1. Enable PiP on your main activity in AndroidManifest.xml:
<activity
  android:name=".MainActivity"
  android:supportsPictureInPicture="true"
  ...
/>
  1. Forward lifecycle hooks from MainActivity:
import com.margelo.nitro.orcavideoplayer.OrcaVideoPlayerPipHelper

override fun onUserLeaveHint() {
  OrcaVideoPlayerPipHelper.onUserLeaveHint(this)
  super.onUserLeaveHint()
}

override fun onPictureInPictureModeChanged(
  isInPictureInPictureMode: Boolean,
  newConfig: Configuration,
) {
  super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
  OrcaVideoPlayerPipHelper.onPictureInPictureModeChanged(isInPictureInPictureMode)
}

autoEnterPictureInPicture uses onUserLeaveHint to enter PiP when the user leaves the app while video is playing.

Web — uses the Document Picture-in-Picture API. Auto PiP on tab hide works only in browsers that support it; otherwise it is a no-op. User interaction may be required before enterPictureInPicture() succeeds.

PiP is best tested on a physical iOS device. The iOS Simulator has limited PiP support.

OrcaVideoPlayer props

| Prop | Type | Default | Description | | --------------------------- | ----------------------------------- | ----------- | ---------------------------------------------------------------------------------------- | | source | VideoSource | required | Video source (single or multiple URIs) | | uriIndex | number | 0 | Index into source.uri when it is an array | | autoplay | boolean | false | Start playback automatically | | muted | boolean | false | Mute audio | | controls | boolean | false | Show native playback controls | | resizeMode | 'cover' \| 'contain' \| 'stretch' | 'contain' | How video fills the view | | preload | boolean | false | Buffer the video in the player without starting playback (unless autoplay is also set) | | loop | boolean | false | Replay the same source indefinitely. When true, onEnd is not called | | allowsPictureInPicture | boolean | false | Enable Picture-in-Picture support | | autoEnterPictureInPicture | boolean | false | Enter PiP automatically when the app moves to the background (requires platform setup) | | onProgress | (time: number) => void | — | Called with current time in seconds | | onEnd | () => void | — | Called when playback finishes (not called when loop is true) | | onPictureInPictureChange | (active: boolean) => void | — | Called when PiP mode starts or stops | | style | StyleProp<ViewStyle> | — | Container style |

VideoSource

interface VideoSource {
  uri: string | string[];
  headers?: Record<string, string>;
  type?: string;
}

Multiple URIs

Pass an array of remote URLs and select which one to play with uriIndex:

import { useState } from 'react';
import { OrcaVideoPlayer } from '@orca-runtime/orca-video-player';

const VIDEOS = [
  'https://example.com/video-a.mp4',
  'https://example.com/video-b.mp4',
];

const [index, setIndex] = useState(0);

<OrcaVideoPlayer
  source={{ uri: VIDEOS }}
  uriIndex={index}
  controls
  resizeMode="cover"
/>;

Player preload vs disk cache

There are two different preload mechanisms:

| Mechanism | API | Scope | Use case | | ------------------ | ------------------------------------------- | --------------------------------- | -------------------------------------------- | | Player preload | OrcaVideoPlayer preload prop | In-memory, tied to mounted player | Buffer early on the same screen | | Disk cache | OrcaVideoPlayerCacheApi / useVideoCache | Persistent on device, global | Preload at app start, play on another screen |

Player preload (in-memory)

Prepares the native player and buffers media without starting playback:

<OrcaVideoPlayer
  source={{ uri: 'https://example.com/video.mp4' }}
  preload
  autoplay={false}
  controls
/>

When the player unmounts, this buffer is released. For cross-screen preloading, use the disk cache API below.

Compare preload modes side by side

<OrcaVideoPlayer
  source={{ uri: 'https://example.com/video.mp4' }}
  preload
  autoplay={false}
  controls
/>

<OrcaVideoPlayer
  source={{ uri: 'https://example.com/video.mp4' }}
  preload={false}
  autoplay={false}
  controls
/>;

Disk cache API

OrcaVideoPlayerCacheApi is a global native singleton. It downloads videos to the device cache directory and resolves them to a local file:// URI.

import { OrcaVideoPlayerCacheApi } from '@orca-runtime/orca-video-player';

// Download one video
await OrcaVideoPlayerCacheApi.preload({
  uri: 'https://example.com/video.mp4',
});

// Download multiple videos
await OrcaVideoPlayerCacheApi.preload({
  uri: ['https://example.com/video-a.mp4', 'https://example.com/video-b.mp4'],
});

// Check cache status
OrcaVideoPlayerCacheApi.isCached('https://example.com/video.mp4'); // boolean

// Get local file URI (null if not cached yet)
const localUri = OrcaVideoPlayerCacheApi.getCachedUri(
  'https://example.com/video.mp4'
);

// Clear cache
await OrcaVideoPlayerCacheApi.clearCache('https://example.com/video.mp4');
await OrcaVideoPlayerCacheApi.clearAllCache();

Play from cache

const remoteUri = 'https://example.com/video.mp4';
const cachedUri = OrcaVideoPlayerCacheApi.getCachedUri(remoteUri);

<OrcaVideoPlayer source={{ uri: cachedUri ?? remoteUri }} autoplay controls />;

useVideoCache hook

React hook that wraps the disk cache API and returns a resolved source for OrcaVideoPlayer.

import {
  OrcaVideoPlayer,
  useVideoCache,
} from '@orca-runtime/orca-video-player';

const { source, isCached, isPreloading, preload, clearCache } = useVideoCache(
  { uri: 'https://example.com/video.mp4' },
  {
    autoPreload: false, // download on mount
    preferCache: true, // use local file URI when available
    uriIndex: 0, // active URI when uri is an array
  }
);

<OrcaVideoPlayer source={source} controls />;

Hook options

| Option | Type | Default | Description | | ------------- | --------- | ------- | ------------------------------------------ | | autoPreload | boolean | false | Download to disk when the hook mounts | | preferCache | boolean | true | Resolve source.uri to a cached local URI | | uriIndex | number | 0 | Active URI when source.uri is an array |

Hook return value

| Field | Type | Description | | -------------- | ------------------------- | --------------------------------------- | | source | VideoSource | Resolved source (local URI when cached) | | uris | string[] | All remote URIs | | activeUri | string | Currently selected remote URI | | isCached | boolean | true when all URIs are cached | | cachedByUri | Record<string, boolean> | Per-URI cache status | | isPreloading | boolean | Download in progress | | error | Error \| null | Last preload error | | preload | () => Promise<void> | Manually trigger download | | clearCache | () => Promise<void> | Clear cache for all URIs in the source |

Multiple URIs with the hook

import { useState } from 'react';
import {
  OrcaVideoPlayer,
  useVideoCache,
} from '@orca-runtime/orca-video-player';

const [uriIndex, setUriIndex] = useState(0);

const { source, cachedByUri, preload } = useVideoCache(
  { uri: ['https://example.com/a.mp4', 'https://example.com/b.mp4'] },
  { uriIndex, preferCache: true }
);

// cachedByUri['https://example.com/a.mp4'] → boolean

<OrcaVideoPlayer source={source} uriIndex={uriIndex} controls />;

Auto-preload on mount

const { source } = useVideoCache(
  { uri: 'https://example.com/video.mp4' },
  { autoPreload: true, preferCache: true }
);

Preload at app start, play on another screen

Recommended pattern for feed / detail flows:

// Root layout / app bootstrap
import { useEffect } from 'react';
import { OrcaVideoPlayerCacheApi } from '@orca-runtime/orca-video-player';

const FEED_VIDEOS = [
  'https://example.com/video-a.mp4',
  'https://example.com/video-b.mp4',
];

export function AppBootstrap() {
  useEffect(() => {
    OrcaVideoPlayerCacheApi.preload({ uri: FEED_VIDEOS });
  }, []);

  return <Navigation />;
}
// Player screen (different route)
import {
  OrcaVideoPlayer,
  useVideoCache,
} from '@orca-runtime/orca-video-player';

export function PlayerScreen({ remoteUri }: { remoteUri: string }) {
  const { source } = useVideoCache({ uri: remoteUri }, { preferCache: true });

  return <OrcaVideoPlayer source={source} autoplay controls />;
}

If the user opens the player before preload finishes, preferCache falls back to the remote URL automatically.

Utility helpers

import {
  getVideoUris,
  resolveVideoSource,
} from '@orca-runtime/orca-video-player';

getVideoUris(['https://a.mp4', 'https://b.mp4']);
// → ['https://a.mp4', 'https://b.mp4']

getVideoUris('https://a.mp4');
// → ['https://a.mp4']

resolveVideoSource({ uri: ['https://a.mp4', 'https://b.mp4'] }, 1);
// → { uri: 'https://b.mp4' }

Platform notes

  • iOS: Cache stored in Caches/orca-video-cache/. Player preload uses AVPlayer buffering.
  • Android: Cache stored in cacheDir/orca-video-cache/. Player preload uses ExoPlayer prepare().
  • Web: Cache uses in-memory blob URLs (not persisted across page reloads).
  • Cache may be evicted by the OS at any time. Always fall back to the remote URI.
  • Authenticated URLs (headers) are not yet supported by the native disk cache downloader.

Support

If you find this library useful, you can support Orca Runtime on Patreon.

Contributing

License

MIT


Made with create-react-native-library