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.
| 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 |
yarn add @react-native-aviation/core react-native-nitro-modules
yarn add @react-native-aviation/videoOptional packages:
yarn add @react-native-aviation/cast
yarn add @react-native-aviation/ima-adsFor iOS:
cd ios && pod installimport {
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);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.
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.
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'. |
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();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: truestarts playback atstartIndex, or at the first item whenstartIndexis omitted.autoPlay: false(default) with astartIndexpositions the queue at that item without starting playback; the item loads and settles inready.- With neither option, the queue contents are replaced and nothing is loaded.
| 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 |
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 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 |
<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.
import { CastPlugin } from '@react-native-aviation/cast';
const player = await aviation.createPlayer({
plugins: [
AudioPlugin,
VideoPlugin,
CastPlugin({ appId: 'YOUR_CAST_APP_ID' }),
],
});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" />- 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
Aviationinstance, while playback state remains player-owned.
| Requirement | Version |
|---|---|
| iOS | 16.0+ |
| Android | API 24+ |
| React Native | >= 0.76.0 |
| Nitro Modules | ^0.33.9 |
MIT