Building cross-platform media players is a common requirement in mobile app development. Whether you're creating a music streaming app, a video-on-demand service, or a simple player for user-generated content, a consistent, high‑performance audio or video experience across iOS and Android is critical. React Native, with its “learn once, write anywhere” philosophy, offers an efficient path to achieve this. By leveraging native platform capabilities through JavaScript, you can build rich media players that feel native on both platforms while maintaining a single codebase. This article provides an in‑depth guide to creating robust, production‑ready audio and video players using React Native, covering library selection, implementation, performance optimization, and advanced features.

Why React Native for Media Players?

React Native is particularly well‑suited for media applications for several reasons:

  • Code reuse across platforms: The vast majority of your business logic and UI code can be shared between iOS and Android. This reduces development time and the cost of maintaining two separate native apps.
  • Rich ecosystem of media libraries: The community has produced mature, actively maintained libraries such as react‑native‑video, expo‑av, and react‑native‑audio‑toolkit. These libraries expose native media APIs (AVPlayer on iOS, ExoPlayer on Android) while providing a unified JavaScript interface.
  • Native performance: Media playback is delegated to platform‑specific native engines. This means full support for hardware acceleration, background audio, and advanced codecs like H.265 or AAC without sacrificing performance.
  • Easy integration with other React Native modules: You can seamlessly combine your player with push notifications, background tasks, file system access, or streaming APIs, all within the same JavaScript environment.

For apps that demand low‑latency, adaptive streaming (HLS or DASH), or custom DRM schemes, React Native’s bridge to native code allows you to drop down into Swift/Kotlin/Java when necessary while still benefiting from the cross‑platform foundation.

Choosing the Right Media Libraries

For Video Playback: react-native-video

react-native-video is the most widely used video player for React Native. It wraps AVPlayer on iOS and ExoPlayer on Android, giving you full access to playback controls, progress tracking, and advanced features like custom DRM, audio tracks, and subtitle support.

Key strengths:

  • Active community with frequent updates (currently at v6.x as of 2024).
  • Supports progressive download, HLS, DASH, and local file playback.
  • Built‑in support for buffer management, seek, rate control, and picture‑in‑picture (iOS, Android).
  • Extensive props and event callbacks for building custom UIs.

Installation:

npm install react-native-video
npx pod-install  # needed for iOS

After linking, you can import and use the Video component directly.

For Audio Playback: react-native-audio-toolkit and expo-av

react-native-audio-toolkit is a dedicated audio library that includes playlist management, recording, and extensive playback controls. It uses AVAudioPlayer on iOS and MediaPlayer on Android.

However, many developers prefer expo-av because it works both in Expo managed and bare workflows. It provides a unified API for audio and video, including background audio support, metering, and looping. expo-av is particularly straightforward if you are already using Expo, but it can also be installed in a bare React Native project via expo install expo-av.

Comparison table:

Library Best for Background audio DRM Recording
react-native-video Video (audio via video tracks) Yes (iOS only) Yes (FairPlay, Widevine) No
expo-av Audio & video (simpler use cases) Yes Limited (HLS with basic encryption) Yes
react-native-audio-toolkit Audio‑only apps with playlists Yes No Yes

Implementing a Basic Video Player

Let’s walk through a production‑grade video player using react‑native‑video. Start by creating a new component VideoPlayer.tsx.

Step 1: Import and Basic Setup

import React, { useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import Video, { VideoRef, OnLoadData } from 'react-native-video';

We’ll use a ref to access the player instance for programmatic control.

Step 2: Core Component

const VideoPlayer = () => {
  const videoRef = useRef<VideoRef>(null);
  const [duration, setDuration] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);
  const [isPaused, setIsPaused] = useState(true);

  return (
    <View style={styles.container}>
      <Video
        ref={videoRef}
        source={{ uri: 'https://www.w3schools.com/html/mov_bbb.mp4' }}
        style={styles.video}
        paused={isPaused}
        controls={false} // we will build custom controls
        resizeMode="contain"
        onLoad={(data: OnLoadData) => setDuration(data.duration)}
        onProgress={(data) => setCurrentTime(data.currentTime)}
      />
      { /* custom controls */ }
    </View>
  );
};

Step 3: Custom Controls (Play/Pause, Progress, Fullscreen)

const togglePlayback = () => setIsPaused(!isPaused);

const seekTo = (time: number) => videoRef.current?.seek(time);

const enterFullscreen = () => {
  videoRef.current?.presentFullscreenPlayer();
};

return (
  <View style={styles.container}>
    <Video ... />
    <View style={styles.controls}>
      <TouchableOpacity onPress={togglePlayback}>
        <Text>{isPaused ? 'Play' : 'Pause'}</Text>
      </TouchableOpacity>
      <Slider
        style={{ flex: 1, marginHorizontal: 10 }}
        value={currentTime}
        maximumValue={duration}
        onSlidingComplete={seekTo}
        minimumTrackTintColor="#1EB1FC"
        thumbTintColor="#1EB1FC"
      />
      <TouchableOpacity onPress={enterFullscreen}>
        <Text>Fullscreen</Text>
      </TouchableOpacity>
    </View>
  </View>
);

Important: React Native does not include a Slider component by default on newer versions. You can use @react-native-community/slider or a custom slider. For a complete player, also handle orientation changes and fullscreen lock.

Implementing an Audio Player

For audio‑only playback we’ll use expo‑av, which gives us a simple yet powerful API. It supports background audio, but you need to configure the audio mode first.

Step 1: Configure Audio Mode

import { Audio } from 'expo-av';

await Audio.setAudioModeAsync({
  allowsRecordingIOS: false,
  playsInSilentModeIOS: true,
  staysActiveInBackground: true,
});

This call ensures audio continues when the app is backgrounded and that it plays even when the device’s silent switch is on.

Step 2: Load and Play Audio

const soundObject = new Audio.Sound();

try {
  await soundObject.loadAsync(
    { uri: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3' },
    { shouldPlay: true }
  );
} catch (error) {
  console.error('Failed to load audio', error);
}

Step 3: UI Controls

const [isPlaying, setIsPlaying] = useState(false);
const [position, setPosition] = useState(0);
const [duration, setDuration] = useState(0);

const playPause = async () => {
  if (isPlaying) {
    await soundObject.pauseAsync();
  } else {
    await soundObject.playAsync();
  }
  setIsPlaying(!isPlaying);
};

soundObject.setOnPlaybackStatusUpdate((status) => {
  if (status.isLoaded) {
    setPosition(status.positionMillis);
    setDuration(status.durationMillis || 0);
    setIsPlaying(status.isPlaying);
  }
});

Use the same slider pattern as the video player for seeking. expo‑av supports setPositionAsync for seeking.

Enhancing User Experience

Custom Overlay Controls with Gestures

Instead of simple buttons, implement touch‑based controls: tap to show/hide controls, double‑tap to skip forward/backward, swipe for brightness/volume. Libraries like react-native-gesture-handler and react-native-reanimated make this smooth and performant.

Buffering Indicators

Both react‑native‑video and expo‑av provide events for buffering. Show an ActivityIndicator when the player is buffering:

onBuffer={({ isBuffering }) => setBuffering(isBuffering)}
// Then conditionally render a loader overlay.

Picture‑in‑Picture (PiP) on iOS and Android

react‑native‑video supports PiP on iOS natively. On Android (API 26+), you can use android.hardware.picture_in_picture feature. Both require special configuration in the native side. For iOS, set `restoresAfterScreenLightScale` and implement the delegate methods. The library’s documentation provides clear steps.

Background Audio for Video (iOS)

For audio playback from a video while the app is backgrounded, you need to enable the “Audio, AirPlay, and Picture in Picture” capability in Xcode and configure the audio session. react‑native‑video does this automatically if you set audioOnly prop, but for video with audio you must handle the background task manually.

Performance Optimization

Media players are performance‑sensitive – especially when handling high‑resolution video or streaming. Follow these best practices:

  • Use resizeMode wisely: Prefer contain over stretch to avoid upscaling artefacts, and cover for full‑screen video.
  • Manage memory for large files: React Native holds video buffers in memory. For long videos, enable streaming (HLS/DASH) so that only a segment is loaded at a time.
  • Background audio: Use the platform’s native background modes correctly. expo‑av’s staysActiveInBackground is a simple flag; for more control on iOS you may need to add the “Audio” background mode.
  • Scrolling performance: If your video is inside a FlatList, consider using react-native-video’s paused prop to stop playback of off‑screen items. Alternatively, use react-native-video in a View outside the scrollable list.
  • Hardware acceleration: All mentioned libraries use the platform’s hardware decoders by default. On Android, ExoPlayer automatically chooses between hardware and software decoders.

Advanced Topics

DRM and Protected Content

React‑native‑video supports FairPlay (iOS) and Widevine (Android). You need to provide the license URL and the certificate (for FairPlay) or a license token. Example for FairPlay:

source={{
  uri: 'https://...m3u8',
  drm: {
    type: 'fairplay',
    certificateUrl: 'https://...cert',
    licenseUrl: 'https://...license',
    contentId: 'yourContentId',
  }
}}

This is a managed solution; you still need to implement the native delegates for certificate fetching.

Playlists and Queues

For audio players, react‑native‑audio‑toolkit has a built‑in playlist API. With expo‑av, you must manage the queue manually: load the next track when the current one ends, pre‑load the next sound object, and use setOnPlaybackStatusUpdate to detect completion.

Live Streaming and Low Latency

For live HLS streams, react‑native‑video supports liveBuffer and timeupdateFrequency props. For ultra‑low latency (sub‑second), consider using WebRTC with react-native-webrtc and a media server – but that adds complexity.

Accessibility

Add accessibilityLabel and accessibilityRole to all controls. For video, consider offering closed captions – react‑native‑video supports embedded text tracks via the textTracks prop.

Conclusion

React Native offers a mature, battle‑tested ecosystem for building cross‑platform audio and video players. By carefully selecting the right library (react‑native‑video for video, expo‑av for audio, or react‑native‑audio‑toolkit for playlists) and following best practices around performance, background playback, and custom UX, you can deliver a seamless media experience on both iOS and Android. Start with the basic implementations outlined here, then iterate by adding gesture controls, fullscreen support, and DRM integration as your app’s requirements grow.

Further resources: