Skip to content

Repository files navigation

react-native-aviation

Unified React Native audio/video playback built around explicit player instances.

Aviation has no global playback singleton. You create an Aviation instance, create one or more players from it, and pass those players to views, hooks, and plugins. Each player owns its playback engine, store, plugin wiring, ads/cast controllers, and native callback lifecycle.

Packages

Package Purpose
@react-native-aviation/core Aviation, players, hooks, audio session, plugins
@react-native-aviation/video VideoView, AirPlayButton, video plugin
@react-native-aviation/cast Chromecast controller and plugin
@react-native-aviation/ima-ads Google IMA ads controller and plugin

Installation

yarn add @react-native-aviation/core react-native-nitro-modules
yarn add @react-native-aviation/video

Optional packages:

yarn add @react-native-aviation/cast
yarn add @react-native-aviation/ima-ads

For iOS:

cd ios && pod install

Quick Start

import {
  createAviation,
  AviationProvider,
  AudioPlugin,
  usePlayerActions,
} from '@react-native-aviation/core';
import { VideoPlugin, VideoView } from '@react-native-aviation/video';

const aviation = createAviation();

const player = await aviation.createPlayer({
  id: 'main',
  audio: { category: 'playback' },
  plugins: [AudioPlugin, VideoPlugin],
});

function App() {
  return (
    <AviationProvider player={player}>
      <PlayerScreen />
    </AviationProvider>
  );
}

function PlayerScreen() {
  const actions = usePlayerActions();

  return (
    <>
      <VideoView
        source={{
          uri: 'https://example.com/video.m3u8',
          mediaType: 'videoOnDemand',
        }}
        style={{ width: '100%', aspectRatio: 16 / 9 }}
      />
      <Button title="Play" onPress={() => actions.play()} />
    </>
  );
}

You can also avoid React context and pass the player explicitly:

<VideoView player={player} source={source} />

Hooks support both forms:

const playback = usePlayback();        // nearest AviationProvider
const playback = usePlayback(player);  // explicit player

const actions = usePlayerActions();
const actions = usePlayerActions(player);

Example App

The repository includes a runnable Spotify-style React Native example in example/. It ships with Android and iOS native shells and exercises mixed audio/video playback, queue controls, video presentation, audio session behavior, Cast, AirPlay, IMA ads, cache, preload, buffering, remote commands, now playing metadata, and playback metrics.

See example/README.md for install, run, and native validation commands.

Core Model

Aviation
  -> creates AviationPlayer instances
  -> owns process-wide policy such as audio focus
  -> disposes all live players

AviationPlayer
  -> owns one PlaybackEngine
  -> owns one AudioSession
  -> owns one reactive store
  -> owns plugins, ads, cast, cache, preload, remote commands

Audio focus is coordinated by the Aviation instance. The internal audio focus handle is not part of the public API.

Creating Aviation

const aviation = createAviation({
  audioFocusPolicy: 'exclusive',
  logLevel: 'none',
});

Options:

Option Default Description
audioFocusPolicy 'exclusive' 'exclusive' deactivates the previous active player when another player requests audio focus. 'mixWithOthers' allows multiple active sessions.
logLevel 'none' One of 'none', 'info', 'debug', or 'verbose'.
logger console logger Custom logger implementation used when logLevel is not 'none'.

Creating Players

const player = await aviation.createPlayer({
  id: 'main',
  plugins: [AudioPlugin, VideoPlugin],
  audio: { category: 'playback' },
  deferActivation: true,
  autoDeactivateOnQueueEnd: true,
});

Player options:

Option Description
id Optional stable id. Generated when omitted. Duplicate live ids are rejected.
plugins Player-scoped plugins to set up.
audio Native audio session configuration.
deferActivation Defaults to true; waits to activate audio until playback starts.
enabledCommands Remote commands enabled for lock screen / media notification.
compactCommands Android compact notification commands.
skipIntervalMs Skip-forward/backward interval. Defaults to 15000.
autoDeactivateOnQueueEnd Defaults to true; releases audio focus when the queue ends.
queue Initial queue applied during player setup.

Dispose one player or the whole Aviation instance:

await aviation.disposePlayer('main');
await aviation.dispose();

Queue

Set the initial queue during player creation:

const player = await aviation.createPlayer({
  plugins: [AudioPlugin],
  queue: {
    items: [
      {
        uri: 'https://example.com/track.mp3',
        mediaType: 'audioOnDemand',
        title: 'Track',
      },
    ],
    startIndex: 0,
    autoPlay: false,
  },
});

Replace it later through the player:

await player.setQueue({
  items: nextItems,
  startIndex: 0,
  autoPlay: true,
});

Queue options:

  • autoPlay: true starts playback at startIndex, or at the first item when startIndex is omitted.
  • autoPlay: false (default) with a startIndex positions the queue at that item without starting playback; the item loads and settles in ready.
  • With neither option, the queue contents are replaced and nothing is loaded.

Hooks

Hook Description
usePlayer() Combined player state, position, engine, and actions
usePlayerActions() Imperative actions: load, play, pause, stop, seek, skip
usePlayback() Playback state, current item, item source, error
usePlaybackFor(item) Playback state scoped to one item
useMediaPosition() Position, duration, progress, live-edge data
useQueueState() Queue items, index, revision
useAdState() Ad break info, current ad, ad playback state
useCastState() Cast session state and device
useAviationReady() Readiness for the resolved player

Player API

export interface Aviation {
  createPlayer(options?: CreatePlayerOptions): Promise<AviationPlayer>;
  getPlayer(id: string): AviationPlayer | undefined;
  disposePlayer(id: string): Promise<void>;
  dispose(): Promise<void>;
}

export interface AviationPlayer {
  readonly id: string;
  readonly engine: PlaybackEngineInstance;
  readonly session: AudioSessionInstance;
  readonly store: AviationStore;
  readonly state: PlayerLifecycleState;

  onReady(): Promise<void>;
  hasPlugin(name: string): boolean;
  setQueue(options: SetQueueOptions): Promise<void>;
  activateSession(): Promise<void>;
  deactivateSession(): Promise<void>;
  dispose(): Promise<void>;

  clearCache(): Promise<void>;
  getCacheSize(): number;
  isCached(uri: string): boolean;
  setCacheEnabled(enabled: boolean): void;
  preload(items: MediaItemInstance[]): void;
  setPreloadEnabled(enabled: boolean): void;
  setBufferConfig(config: BufferConfig): void;
}

Ads, cast, and remote-command APIs are also player-scoped. Advanced native access remains available through player.engine.

Plugins

Plugins are scoped to the player that installs them:

export interface MediaPlugin {
  readonly name: string;
  setup(context: PluginContext): void | Promise<void>;
  teardown(context: PluginContext): void | Promise<void>;
}

export interface PluginContext {
  readonly player: PluginPlayer;
  readonly engine: PlaybackEngineInstance;
  readonly store: AviationStore;
}

Core plugins:

Plugin Package Purpose
AudioPlugin @react-native-aviation/core Headless audio playback (no rendering surface)
VideoPlugin @react-native-aviation/video Declares the player video-capable; VideoView warns in dev without it
CachePlugin(config) @react-native-aviation/core Disk cache
PreloadPlugin(config) @react-native-aviation/core Preload/warm pool
BufferPlugin(config) @react-native-aviation/core Buffer tuning
CastPlugin(config) @react-native-aviation/cast Chromecast
IMAdsPlugin(config) @react-native-aviation/ima-ads Google IMA ads

Video

<VideoView
  player={player}
  source={{
    uri: 'https://example.com/video.m3u8',
    mediaType: 'videoOnDemand',
  }}
  autoPlay
  resizeMode="contain"
  pipEnabled
/>

If player is omitted, VideoView uses the nearest AviationProvider. Unmounting a VideoView detaches the surface; it does not dispose the player.

Cast

import { CastPlugin } from '@react-native-aviation/cast';

const player = await aviation.createPlayer({
  plugins: [
    AudioPlugin,
    VideoPlugin,
    CastPlugin({ appId: 'YOUR_CAST_APP_ID' }),
  ],
});

IMA Ads

import { IMAdsPlugin } from '@react-native-aviation/ima-ads';

const player = await aviation.createPlayer({
  plugins: [
    AudioPlugin,
    VideoPlugin,
    IMAdsPlugin({
      adTagUrl: 'https://example.com/vast.xml',
    }),
  ],
});

For pre-roll on a video surface:

<VideoView player={player} source={source} adTagUrl="https://example.com/vast.xml" />

Design Notes

  • No global Aviation.setup().
  • No implicit default player.
  • No module-level playback store.
  • Hooks, views, plugins, ads, and cast receive or resolve a specific player.
  • Native callbacks are scoped to the owning player and ignored after disposal.
  • Process-wide audio focus is coordinated by the Aviation instance, while playback state remains player-owned.

Platform Requirements

Requirement Version
iOS 16.0+
Android API 24+
React Native >= 0.76.0
Nitro Modules ^0.33.9

License

MIT

About

Unified React Native Audio/Video Library. Mirror of https://codeberg.org/adsellor/aviation

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages