engineering-design-and-analysis
Building Cross-platform Audio and Video Players with React Native
Table of Contents
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
resizeModewisely: Prefercontainoverstretchto avoid upscaling artefacts, andcoverfor 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
staysActiveInBackgroundis 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’spausedprop to stop playback of off‑screen items. Alternatively, usereact-native-videoin aViewoutside 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: