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

react-native-ino-player

v0.1.1

Published

A full-featured media(audio/video) player for React Native ecosystem. Supports Android, iOS, Web, and Windows (New Architecture).

Readme

React Native Ino Player

A full-featured audio/video media player for React Native — built on the New Architecture (TurboModule + Bridgeless, RN ≥ 0.74).

npm version CI Android Build iOS Build License: MIT TypeScript


Table of Contents


Platform Support

| Device / Platform | Supported | Engine | Min version | |---|:---:|---|---| | Android phone, tablet, foldable | ✅ | Media3 ExoPlayer 1.10.0 | API 26 (Android 8.0) | | iOS iPhone, iPad | ✅ | AVFoundation | iOS 13.0 | | Mac Catalyst | ✅ | AVFoundation | macOS 10.15 | | Web (React Native Web) | ✅ | HTMLAudioElement + MediaSession API | Chrome 73 / Firefox 82 / Safari 14 | | Windows (RN Windows) | ✅ | WinRT Windows.Media.Playback.MediaPlayer | Windows 10 build 19041 | | Android Auto (old + new head units) | ✅ | MediaLibraryService | 2016+ | | Android Automotive OS | ✅ | CarAppService | AAOS API 1–6 | | Android TV | ✅ | MediaLibrarySession | API 26+ | | Wear OS 2 / 3 / 4 | ✅ | Wearable Data Layer | OS 2+ | | CarPlay | ✅ | MPNowPlayingInfoCenter | iOS 13+ | | Chromecast | ✅ | media3-cast CastPlayer | Receiver 3.x–6.x | | AirPlay 1 & 2 | ✅ | AVAudioSession (automatic) | All iOS | | macOS native (AppKit) | ❌ | — | Use Mac Catalyst instead | | watchOS | ❌ | — | Uses WKAudioFileQueuePlayer | | tvOS | ❌ | — | Separate Apple TV target needed |


Installation

npm install react-native-ino-player
# or
yarn add react-native-ino-player

New Architecture (react-native ≥ 0.79)

This library requires the New Architecture. In your android/gradle.properties:

newArchEnabled=true

In ios/Podfile:

ENV['RCT_NEW_ARCH_ENABLED'] = '1'

Android

No extra steps — the library's AndroidManifest.xml declares all permissions and services.

Ensure your app's build.gradle has minSdkVersion = 26.

For Chromecast support, add to your app's AndroidManifest.xml:

<meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.inoplayer.cast.InoCastOptionsProvider" />

This uses Google's default Cast receiver (CC1AD845), which works for standard audio/video playback without any extra setup.

Using a custom Cast receiver app ID

If you have your own Cast receiver app, subclass InoCastOptionsProvider in your app's Android source and override getReceiverApplicationId():

// android/app/src/main/java/com/yourapp/MyOptionsProvider.kt
package com.yourapp
 
import com.inoplayer.cast.InoCastOptionsProvider
 
class MyOptionsProvider : InoCastOptionsProvider() {
    override fun getReceiverApplicationId(): String = "YOUR_CUSTOM_APP_ID"
}

Then point the manifest meta-data at your subclass instead:

<meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.yourapp.MyOptionsProvider" />

If you need more than just a different app ID (e.g. custom namespaces, launch options), override getCastOptions() directly:

import android.content.Context
import com.google.android.gms.cast.framework.CastOptions
import com.inoplayer.cast.InoCastOptionsProvider
 
class MyOptionsProvider : InoCastOptionsProvider() {
    override fun getCastOptions(context: Context) =
        CastOptions.Builder()
            .setReceiverApplicationId("YOUR_CUSTOM_APP_ID")
            .setEnableReconnectionService(true)
            // any other CastOptions here
            .build()
}

For Android Auto, also add to your manifest:

<meta-data
  android:name="com.google.android.gms.car.application"
  android:resource="@xml/automotive_app_desc" />

iOS / Mac Catalyst

cd ios && pod install

Add to your Info.plist:

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

Web

Works out of the box with React Native Web. Metro resolves the .web.ts files automatically — no extra configuration needed.

// Web usage is identical to native
import InoPlayer from 'react-native-ino-player';
await InoPlayer.setupPlayer();

Web-specific Notes

  • Background audio: Pauses when tab is hidden (browser restriction). navigator.mediaSession keeps lock-screen controls alive.
  • AirPlay: Automatic in Safari. No extra code.
  • Chromecast: Requires Google Cast SDK for Web separately.
  • Caching: Uses Cache API when available (HTTPS + Service Worker), falls back to <audio preload="auto">.

Windows (React Native Windows)

See windows/README.md for Visual Studio setup.

// In your App.cpp
#include <winrt/RNInoPlayer.h>
PackageProviders().Append(winrt::make<winrt::RNInoPlayer::ReactPackageProvider>());

Windows-specific Notes

  • Background audio: MediaPlayerAudioCategory.Media keeps audio alive when minimised.
  • Lock screen: SystemMediaTransportControls shows Now Playing in taskbar/lock screen.
  • HTTP headers: Not supported by MediaSource.CreateFromUri() — use signed URLs for authenticated streams.
  • Chromecast/AirPlay: Not applicable on Windows.

Checking Platform Support at Runtime

On unsupported platforms ( macOS native AppKit, watchOS, tvOS ), every InoPlayer.* call throws UnsupportedPlatformError. Guard your setup:

import InoPlayer, { isPlatformSupported } from 'react-native-ino-player';

useEffect(() => {
  if (!isPlatformSupported()) return; 
  // macOS native AppKit, watchOS, tvOS — skip gracefully

  InoPlayer.setupPlayer().then(() => {
    // Ready
  });
}, []);

or

import InoPlayer, { isPlatformSupported } from 'react-native-ino-player';

if (!isPlatformSupported()) {
  // macOS native AppKit, watchOS, tvOS — skip gracefully
  return;
}

await InoPlayer.setupPlayer(); // Ready

See docs/ARCHITECTURE.md for the full per-platform compatibility breakdown.


Quick Start

import InoPlayer, { Capability, usePlaybackState, useProgress, useActiveTrack, isPlatformSupported, } from 'react-native-ino-player';

// Guard for unsupported platforms (macOS native, watchOS, tvOS)
if (!isPlatformSupported()) return;

// 1. Initialize once at app startup (before any other API call)
await InoPlayer.setupPlayer({
  maxCacheSize: 512 * 1024 * 1024, // 512 MB
  preloadWindowSize: 3,
  android: { wakeMode: 'network' },
  ios: { audioCategory: 'playback' },
});

// 2. Configure notification / lock-screen controls
await InoPlayer.updateOptions({
  capabilities: [
    Capability.Play,
    Capability.Pause,
    Capability.SkipToNext,
    Capability.SkipToPrevious,
    Capability.SeekTo,
    Capability.Shuffle,
    Capability.Repeat,
  ],
  compactCapabilities: [Capability.Play, Capability.SkipToNext],
  progressUpdateEventInterval: 1,
});

// 3. Load tracks and play
await InoPlayer.setQueue([
  {
    id: '1',
    url: 'https://example.com/track1.mp3',
    title: 'Track One',
    artist: 'Artist Name',
    artwork: 'https://example.com/cover.jpg',
    duration: 240,
  },
]);

await InoPlayer.play();

Playback Controls

await InoPlayer.play();
await InoPlayer.pause();
await InoPlayer.stop();

// Absolute seek
await InoPlayer.seekTo(90);          // jump to 1:30

// Relative seek
await InoPlayer.seekBy(30);          // forward 30 s
await InoPlayer.seekBy(-10);         // backward 10 s

// Convenience wrappers (wrap to prev/next track at boundaries)
await InoPlayer.skipForward(30);
await InoPlayer.skipBackward(15);

// Speed (0.25 – 4.0)
await InoPlayer.setRate(1.5);

// Volume (0.0 – 1.0)
await InoPlayer.setVolume(0.8);

// fade to silence over 3 s
await InoPlayer.fadeVolumeTo(0, 3000);

Queue Management

// Replace entire queue
await InoPlayer.setQueue(tracks, /* initialIndex */ 0);

// Append
await InoPlayer.add(newTrack); // Default insert at the end
await InoPlayer.add(newTrack, 2); // Insert before index

await InoPlayer.add([track1, track2]); // Default insert at the end
await InoPlayer.add([track1, track2], 2); // Insert before index

// Navigate
await InoPlayer.skip(3);             // jump to queue index 3
await InoPlayer.skip(3, 30);         // jump to index 3, start at 30s
await InoPlayer.skipToNext();
await InoPlayer.skipToPrevious();    // seeks to start if > 3s in

// Modify
await InoPlayer.remove(1);
await InoPlayer.move(2, 0);          // move index 2 to top
await InoPlayer.updateMetadataForTrack(0, { title: 'Live — Updated Title' });
await InoPlayer.clearQueue();

Repeat Modes

| Mode | Behaviour | |---|---| | RepeatMode.Off | Queue plays once, then stops. Default. | | RepeatMode.Track | Current track loops forever. | | RepeatMode.TrackOnce | Current track plays one additional time, then queue continues normally ("repeat once"). | | RepeatMode.Queue | Whole queue repeats indefinitely. |

import { RepeatMode } from 'react-native-ino-player';

await InoPlayer.setRepeatMode(RepeatMode.Queue);
await InoPlayer.setRepeatMode(RepeatMode.TrackOnce);   // "repeat once"

Shuffle

await InoPlayer.setShuffle(true);
await InoPlayer.setShuffle(false);

// Or use the hook:
const { shuffle, toggleShuffle, setShuffle } = useShuffle();

When shuffle is enabled the player follows a randomised order while still respecting the active repeat mode.


Progress Bar & Seeking

import { useProgress } from 'react-native-ino-player';
import Slider from '@react-native-community/slider';

function ProgressBar() {
  const { position, duration, buffered } = useProgress(500); // poll every 500 ms

  return (
    <Slider
      minimumValue={0}
      maximumValue={duration || 1}
      value={position}
      onSlidingComplete={val => InoPlayer.seekTo(val)}
    />
  );
}

Sleep Timer

// Stop in 30 minutes with a 10-second volume fade
await InoPlayer.setSleepTimer({
  duration: 1800,
  fadeOut: true,
  fadeDuration: 10,
});

// Stop when the current track finishes (no fade)
await InoPlayer.setSleepTimer({ mode: 'end-of-track', fadeOut: false });

// Cancel
await InoPlayer.cancelSleepTimer();
const { remaining, active, cancel } = useSleepTimer();
<Text onPress={cancel}>{active ? `Stops in ${Math.ceil(remaining)}s` : 'No timer'}
</Text>

Volume Fade

// Fade to silence over 3 seconds
await InoPlayer.fadeVolumeTo(0, 3_000);

// Fade back up
await InoPlayer.fadeVolumeTo(1, 2_000);

Custom Remote Actions

Register any number of fully configurable notification / lock-screen buttons:

await InoPlayer.setCustomActions([
  {
    id: 'bookmark',
    title: 'Bookmark',
    icon: 'ic_bookmark',     // Android: drawable resource name
                             // iOS: SF Symbol name (e.g. 'bookmark')
    showIn: 'both',          // 'notification' | 'lockscreen' | 'both'
  },
  {
    id: 'speed_1_5x',
    title: '1.5×',
    icon: 'ic_speed',
    showIn: 'notification',
  },
]);

Respond to taps:

// With hook (recommended)
useRemoteCustomAction('bookmark', () => {
  saveBookmark(activeTrack, position);
});

// With raw event listener
import { addEventListener, Event } from 'react-native-ino-player';

const sub = addEventListener(Event.RemoteCustomAction, ({ id }) => {
  if (id === 'speed_1_5x') InoPlayer.setRate(1.5);
});
// cleanup: sub.remove();

Preloading & Caching

The library automatically preloads the next preloadWindowSize (default: 3) tracks in the background — no configuration needed.

For manual pre-fetching (e.g. next episode, album tracks):

// Prime the cache for a specific URL
await InoPlayer.preloadTrack('https://cdn.example.com/episode-5.mp3');

// With auth headers
await InoPlayer.preloadTrack(url, { Authorization: 'Bearer token' });

// Inspect / manage cache
const bytes = await InoPlayer.getCacheSize();
console.log(`Cache: ${(bytes / 1024 / 1024).toFixed(1)} MB`);

await InoPlayer.clearCache();

Offline playback — set the localUri field on a Track to use a local file instead of the remote URL:

{
  id: '1',
  url: 'https://cdn.example.com/track.mp3',   // fallback
  localUri: 'file:///path/to/downloaded.mp3', // used when present
  title: 'Offline Track',
}

AirPlay & Chromecast

import { Platform, Text, TouchableOpacity } from 'react-native';
import { useCastState, CastState } from 'react-native-ino-player';

function CastButton() {
  const { state, deviceName } = useCastState();

  const handlePress = () => {
    if (Platform.OS === 'ios') {
      InoPlayer.showAirPlayPicker();        // shows native AirPlay popover
    } else {
      InoPlayer.showCastDialog();           // shows Chromecast device list
    }
  };

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>
        {state === CastState.Connected
          ? `▶ ${deviceName}`
          : state === CastState.Connecting
          ? 'Connecting…'
          : 'Cast'}
      </Text>
    </TouchableOpacity>
  );
}

Android Auto & CarPlay

Register a browse-tree loader that the OS calls whenever the car head unit needs content:

InoPlayer.setCarBrowseTreeLoader(async (parentId) => {
  // parentId === null → root level
  if (parentId === null) {
    return [
      { id: 'playlists', title: 'Playlists', browsable: true,  playable: false },
      { id: 'albums',    title: 'Albums',    browsable: true,  playable: false },
      { id: 'recent',    title: 'Recent',    browsable: false, playable: true  },
    ];
  }

  // Return children for the selected item
  if (parentId === 'playlists') return await fetchPlaylists();
  if (parentId === 'albums')    return await fetchAlbums();
  return [];
});

Each CarMediaItem:

| Field | Type | Description | |---|---|---| | id | string | Stable identifier | | title | string | Display name | | subtitle | string? | Secondary line | | artworkUri | string? | Remote or local URI | | playable | boolean | Triggers playback when selected | | browsable | boolean | Opens a child list when selected |


React Hooks Reference

| Hook | Returns | Description | |---|---|---| | usePlaybackState() | { state: State, error? } | Current player state | | useIsPlaying() | { playing, bufferingDuringPlay } | Quick play/buffer check | | useProgress(ms?) | { position, duration, buffered } | Live progress (seconds) | | useActiveTrack() | Track \| undefined | Currently playing track | | useQueue() | Track[] | Full playback queue | | useShuffle() | { shuffle, toggleShuffle, setShuffle } | Shuffle state + controls | | useRepeatMode() | { repeatMode, setRepeatMode, cycleRepeatMode } | Repeat state + controls | | useSleepTimer() | { remaining, active, cancel } | Sleep timer countdown | | useCastState() | { state: CastState, deviceName? } | AirPlay / Chromecast state | | useRemoteCustomAction(id, fn) | void | Subscribe to a custom action tap | | usePlaybackError() | PlaybackError \| null | Most recent error |


Full API Reference

InoPlayer.setupPlayer(options?)

Must be called once, from the foreground, before any other API.

| Option | Type | Default | Description | |---|---|---|---| | minBufferMs | number | 2500 | Buffer before playback starts (ms) | | maxBufferMs | number | 50000 | Max buffer ahead (ms) | | backBufferMs | number | 2500 | Buffer behind playhead (ms) | | maxCacheSize | number | 1073741824 | Disk cache limit in bytes (0 = off) | | preloadWindowSize | number | 3 | Upcoming tracks to preload | | backgroundAudio | boolean | true | Keep audio alive when backgrounded | | handleAudioBecomingNoisy | boolean | true | Pause on headphone unplug | | android.notificationChannelName | string | 'Media Playback' | Notification channel name | | android.wakeMode | 'none' \| 'local' \| 'network' | 'network' | CPU wake-lock strategy | | android.smallIcon | string | app icon | Drawable resource for notification icon | | ios.audioCategory | 'playback' \| 'ambient' \| 'soloAmbient' | 'playback' | AVAudioSession category | | ios.audioMode | string | 'default' | AVAudioSession mode |

State enum

| Value | Description | |---|---| | State.None | Not initialized | | State.Ready | Initialized, no item | | State.Playing | Actively playing | | State.Paused | Paused | | State.Buffering | Buffer empty, waiting | | State.Loading | Initial track load | | State.Ended | Queue finished | | State.Error | Fatal error |

Event enum

| Event | Payload | Description | |---|---|---| | PlaybackState | { state, error? } | State changed | | PlaybackError | { code, message } | Fatal error | | PlaybackActiveTrackChanged | { index, track, lastIndex, lastTrack, lastPosition } | Track changed | | PlaybackQueueEnded | { index, position } | Queue finished | | PlaybackProgressUpdated | { position, duration, buffered, track } | Progress tick | | SleepTimerFired | {} | Sleep timer triggered | | SleepTimerTick | { remaining } | Countdown second | | RemotePlay | {} | Lock-screen / notification play | | RemotePause | {} | Lock-screen / notification pause | | RemoteStop | {} | Lock-screen stop | | RemoteNext | {} | Next button tapped | | RemotePrevious | {} | Previous button tapped | | RemoteSeek | { position } | Scrubber dragged | | RemoteJumpForward | { interval } | Jump-forward tapped | | RemoteJumpBackward | { interval } | Jump-backward tapped | | RemoteShuffle | {} | Shuffle button tapped | | RemoteRepeat | {} | Repeat button tapped | | RemoteCustomAction | { id } | Custom button tapped | | CastStateChanged | { state, deviceName? } | Cast connection changed | | CarBrowseItemSelected | { id, parentId } | Car head unit browse request |


Error Handling

Every playback error fires Event.PlaybackError and transitions to State.Error.

import { usePlaybackState, State } from 'react-native-ino-player';

function PlayerErrorHandler() {
  const { state, error } = usePlaybackState();

  useEffect(() => {
    if (state === State.Error) {
      console.error(`[${error?.code}] ${error?.message}`);
      // Auto-skip to next track on error
      InoPlayer.skipToNext().catch(() => {});
    }
  }, [state, error]);

  return null;
}

Common error codes on Android (ExoPlayer):

| Code | Cause | |---|---| | ERROR_CODE_IO_NETWORK_CONNECTION_FAILED | Network unreachable | | ERROR_CODE_IO_FILE_NOT_FOUND | 404 / bad URL | | ERROR_CODE_PARSING_CONTAINER_MALFORMED | Corrupt media file | | ERROR_CODE_DECODER_INIT_FAILED | Codec not supported on device |

Common error codes on Web:

| Code | Cause | |---|---| | MEDIA_ERR_NETWORK| Network unreachable | | MEDIA_ERR_DECODE| Corrupt media file | | MEDIA_ERR_SRC_NOT_SUPPORTED| Unsupported format/Bad URL | | AUTOPLAY_BLOCKED|


Contributing

See CONTRIBUTING.md for the full development guide.


License

MIT © Pritam Nanda