Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/afkcodes/react-native-audio-pro/llms.txt

Use this file to discover all available pages before exploring further.

useAudioPro is a React hook that connects your components to the audio player’s internal Zustand store. Every field updates automatically as playback progresses — no manual event listeners or useEffect wiring required. The hook ships in two overloads: call it with no arguments to get the full state object, or pass a selector function to subscribe to only the slice your component actually needs.
import { useAudioPro } from 'react-native-audio-pro';

Overloads

Full state — useAudioPro()

useAudioPro(): UseAudioProReturn
Subscribes to all player state. The component re-renders whenever any field changes. Use this for player UIs that display multiple pieces of state simultaneously.
const { state, position, duration, playingTrack } = useAudioPro();

Selector — useAudioPro(selector, equalityFn?)

useAudioPro<T>(
  selector: (state: AudioProStore) => T,
  equalityFn?: (left: T, right: T) => boolean,
): T
Subscribe to a derived slice of state. The component only re-renders when the value returned by selector changes (using the optional equalityFn, or strict equality by default).
selector
(state: AudioProStore) => T
required
A function that receives the full store and returns the piece of state you care about.
equalityFn
(left: T, right: T) => boolean
Optional custom equality function. When it returns true, the component skips a re-render even if the selector ran. Useful for number fields where small changes don’t need to trigger a render (e.g. position with a tolerance).
Returns: T — the result of your selector.
// Only re-renders when the playing track changes
const track = useAudioPro((s) => s.trackPlaying);

Return Fields

When called without a selector, useAudioPro() returns a UseAudioProReturn object with the following fields:
state
AudioProState
Current playback state. One of 'IDLE', 'STOPPED', 'LOADING', 'PLAYING', 'PAUSED', or 'ERROR'.
position
number
Current playback position in milliseconds. Updated at the interval configured via AudioPro.configure({ progressIntervalMs }). Defaults to 0.
duration
number
Total duration of the current track in milliseconds. 0 until the track is loaded. Defaults to 0.
bufferedPosition
number
How far ahead the player has buffered, in milliseconds. Useful for rendering a buffer indicator on a progress bar. Defaults to 0.
playingTrack
AudioProTrack | null
The track object that is currently loaded and playing. null when the queue is empty or the player is idle.
activeTrackIndex
number
Zero-based index of the active track within the queue. -1 when no track is active.
queueSize
number
Total number of tracks currently in the queue. 0 when the queue is empty.
playbackSpeed
number
Current playback speed multiplier. Range: 0.252.0. Normal speed is 1.0.
volume
number
Current player volume. Range: 0.0 (mute) – 1.0 (full). Defaults to 1.0.
error
AudioProPlaybackErrorPayload | null
The most recent playback error, or null if the last operation succeeded. Cleared automatically when the state leaves ERROR. Contains error (message string), errorCode, recoverable, cause, and index.

Examples

Basic usage

Destructure all state fields in a single call. The component re-renders whenever any field changes.
import { useAudioPro } from 'react-native-audio-pro';
import { AudioPro } from 'react-native-audio-pro';
import { View, Text, Button } from 'react-native';

export function PlayerStatus() {
  const {
    state,
    position,
    duration,
    playingTrack,
    playbackSpeed,
    volume,
    error,
  } = useAudioPro();

  const isPlaying = state === 'PLAYING';

  return (
    <View>
      <Text>{playingTrack?.title ?? 'Nothing playing'}</Text>
      <Text>{playingTrack?.artist}</Text>
      <Text>
        {Math.floor(position / 1000)}s / {Math.floor(duration / 1000)}s
      </Text>
      <Text>State: {state}</Text>
      <Button
        title={isPlaying ? 'Pause' : 'Play'}
        onPress={() => (isPlaying ? AudioPro.pause() : AudioPro.play())}
      />
      {error && <Text>Error: {error.error}</Text>}
    </View>
  );
}

Selector for performance

Pass a selector to subscribe to only one field. The component below only re-renders when the currently playing track changes — position ticks do not cause re-renders.
import { useAudioPro } from 'react-native-audio-pro';
import { Text, Image, View } from 'react-native';

export function NowPlayingTitle() {
  // Re-renders only when the track object changes
  const track = useAudioPro((s) => s.trackPlaying);

  if (!track) return <Text>Nothing playing</Text>;

  return (
    <View>
      <Image source={{ uri: track.artwork }} style={{ width: 56, height: 56 }} />
      <Text>{track.title}</Text>
      <Text>{track.artist}</Text>
    </View>
  );
}

Selector with tolerance equality

For high-frequency numeric fields like position, combine a selector with a custom equality function to throttle re-renders to meaningful visual changes.
import { useAudioPro } from 'react-native-audio-pro';
import { Slider } from '@react-native-community/slider';
import { AudioPro } from 'react-native-audio-pro';

// Only re-render when position changes by more than 500 ms
const POSITION_TOLERANCE_MS = 500;

export function ProgressSlider() {
  const position = useAudioPro(
    (s) => s.position,
    (prev, next) => Math.abs(next - prev) < POSITION_TOLERANCE_MS,
  );
  const duration = useAudioPro((s) => s.duration);

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

Complete player component

A self-contained component that combines track info, a progress bar, play/pause, next/previous, and error handling — all reactive with no manual subscription management.
import { AudioPro, useAudioPro } from 'react-native-audio-pro';
import { View, Text, Button, StyleSheet } from 'react-native';

export function AudioPlayer() {
  const {
    state,
    position,
    duration,
    bufferedPosition,
    playingTrack,
    activeTrackIndex,
    queueSize,
    error,
  } = useAudioPro();

  const isPlaying = state === 'PLAYING';
  const isLoading = state === 'LOADING';
  const progress = duration > 0 ? position / duration : 0;
  const buffered = duration > 0 ? bufferedPosition / duration : 0;

  const formatTime = (ms: number) => {
    const s = Math.floor(ms / 1000);
    return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
  };

  return (
    <View style={styles.container}>
      {/* Track info */}
      <Text style={styles.title}>{playingTrack?.title ?? '—'}</Text>
      <Text style={styles.artist}>{playingTrack?.artist}</Text>

      {/* Progress */}
      <Text>{formatTime(position)} / {formatTime(duration)}</Text>
      <Text style={styles.queue}>
        Track {activeTrackIndex + 1} of {queueSize}
      </Text>

      {/* Controls */}
      <View style={styles.controls}>
        <Button title="Prev" onPress={() => AudioPro.seekToPreviousMediaItem()} />
        <Button
          title={isLoading ? '…' : isPlaying ? 'Pause' : 'Play'}
          onPress={() => (isPlaying ? AudioPro.pause() : AudioPro.play())}
        />
        <Button title="Next" onPress={() => AudioPro.seekToNextMediaItem()} />
      </View>

      {/* Error */}
      {error && (
        <Text style={styles.error}>
          {error.error} (code {error.errorCode ?? 'unknown'})
        </Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16, gap: 8 },
  title: { fontSize: 18, fontWeight: '600' },
  artist: { fontSize: 14, color: '#888' },
  queue: { fontSize: 12, color: '#aaa' },
  controls: { flexDirection: 'row', gap: 16, justifyContent: 'center' },
  error: { color: 'red', fontSize: 12 },
});

Subscription cleanup

useAudioPro uses Zustand internally and manages its own subscriptions — there is no need to wrap calls in useEffect or call any cleanup function. The subscription is automatically removed when the component unmounts.
// ✅ Correct — no cleanup needed
function TrackTitle() {
  const title = useAudioPro((s) => s.trackPlaying?.title);
  return <Text>{title ?? '—'}</Text>;
}

// ❌ Unnecessary — useAudioPro already handles this
function TrackTitleVerbose() {
  const [title, setTitle] = useState<string | undefined>();

  useEffect(() => {
    const sub = AudioPro.addEventListener((event) => {
      if (event.type === 'TRACK_CHANGED') {
        setTitle(event.track?.title);
      }
    });
    return () => sub.remove();
  }, []);

  return <Text>{title ?? '—'}</Text>;
}

Performance Tips

Prefer selectors for leaf components. Components deep in the tree that only display a single field (track title, volume badge, queue size) should always use the selector overload so they don’t re-render on every position tick.
Combine related fields in one selector. If a component needs both position and duration together, select them as an object. Zustand’s shallow comparison will handle the rest: useAudioPro((s) => ({ position: s.position, duration: s.duration })). Without a custom equality function, use useShallow from zustand/shallow or provide your own comparator.
Use a tolerance equality function for sliders. Progress bars and sliders that map position to a visual pixel value don’t need sub-millisecond precision. A tolerance of 250 – 500 ms significantly reduces render frequency without any noticeable visual lag.
// Efficient pattern: select only what you render, skip trivial changes
const position = useAudioPro(
  (s) => s.position,
  (a, b) => Math.abs(a - b) < 250, // skip re-render if change < 250 ms
);
The no-argument overload useAudioPro() uses Zustand’s useShallow internally to prevent reference-equality false positives on the returned object. You do not need to wrap it in useShallow yourself.

Build docs developers (and LLMs) love