React Native WebView Track Player is a headless audio player for building universal audio apps in React Native. It supports Android, iOS, Web, and Expo Go out-of-the-box. Using a WebView bridge, it aims to achieve similar functionality to react-native-track-player without requiring custom native code (see comparison table below).
Check out the example app for a full-fledged music player sample.
react-native-track-player | expo-av | react-native-webview-track-player | |
---|---|---|---|
Expo/Expo Go compatibility | ❌ | ✅ | ✅ |
Web compatibility | ❌ | ✅ | ✅ |
Background audio play | ✅ | ❌ (not without a build flag) | ✅ |
Lock-screen controls | ✅ | ❌ | ✅* (iOS & Web, no Android due to lack of MediaSession API) |
Built-in activity | ❌ | ❌ | ✅ |
True native bridge | ✅ | ✅ | ❌ |
From my experience, adopting a WebView bridge made audio playback a far more stable and consistent experience on iOS and especially Android. Plus, we gain seamless Web compatibility instead of the bundle splitting/conditional imports required when using react-native-track-player
with React Native Web. That being said, a WebView bridge brings its own shortcomings (CORS with non-public URLs, HTML5 <audio>
filetypes, etc.), so weigh in your own project needs carefully.
- Minimal core library, 1 file, ~600 SLOC.
- 1 peer dependency,
react-native-webview
. - Expo Go, iOS Simulator, Web all work out-of-the-box with background audio and screen locking.
- Render prop pattern over manually-exposed hooks (core manages all of the state/hooks for you, so you only need to provide the UI).
- Track activity, plays, pauses, percentages listened, etc. are all automatically managed. A simple
onActivity
callback helps build analytics easily. By default, nothing is stored or transmitted.
Using your desired package manager (npm install, yarn add, bun add, etc.), add react-native-webview-track-player
and its one required peer dependency react-native-webview
to your project. Eg. with npm:
npm install react-native-webview-track-player react-native-webview --save
If you added react-native-webview
for the first time, refer to its getting started guide if needed.
No assumptions are made on React/React Native versions or Expo SDKs. The latest of both should work without issue. As of current, the sample app is running on Expo SDK 49
, React 18.2.0
, React Native 0.72.6
, and React Native WebView 13.6.3
.
Here is a minimal working example:
import React from "react";
import { SafeAreaView, Text, Pressable } from "react-native";
import TrackPlayer, {
type TrackPlayerRenderProps
} from "react-native-webview-track-player";
const playlist = [
{
id: "e8DPlI",
title: "Modern",
artist: "JM Galiè",
audio: {
url: "https://raw.githubusercontent.com/harismh/react-native-webview-track-player/main/.github/audio/sample.mp3"
}
}
];
const renderPlayer = ({ playerState, playPause }: TrackPlayerRenderProps) => (
<SafeAreaView>
<Pressable onPress={playPause}>
<Text>{playerState.playing ? "Pause" : "Play"}</Text>
</Pressable>
<Text>Player State: {JSON.stringify(playerState)}</Text>
</SafeAreaView>
);
const MinimalPlayer = () => (
<TrackPlayer playlist={playlist} render={renderPlayer} />
);
export default MinimalPlayer;
There are many more props you can destructure or pass in to customize it further. See the example app in the repo for a fuller example.
All type definitions are located in index.d.ts
. The major types are:
Type | Defn. | Optional? | Notes | |
---|---|---|---|---|
TrackPlayerProps.playlist |
Array of PlaylistTrack |
no | A minimal PlaylistTrack is {id: string, title: string, audio: { url: string }} . |
|
TrackPlayerProps.render |
Main render function | no | Callback receives TrackPlayerRenderProps . |
|
TrackPlayerProps.activity |
Map of track ids to PlaylistActivity |
yes | Used to bootstrap and restore activity. Typically is used in conjunction with a back-end to serialize/restore user player state. | |
TrackPlayerProps.onActivity |
Callback for any activity in the player | yes | Can be used to build analytics/reporting. Passed-in arg to callback is Activity . |
|
TrackPlayerProps.config |
TrackPlayerConfig |
yes | Any config keys not passed-in will fallback to defaults. | |
TrackPlayerConfig |
{audioElementId: string, playerIndexKey: string, webViewPostMessageIntervalMs: number, activityIntervalMs: number, finishedTrackPercentageThreshold: number} |
yes | Defaults: { audioElementId: 'rnwv_audio', playerIndexKey: 'rnwv_player_index', webViewPostMessageIntervalMs: 1000, activityIntervalMs: 20000, finishedTrackPercentageThreshold: 90 } . audioElementId or playerIndexKey should be changed only if there are any conflicts with the defaults. webViewPostMessageIntervalMs is the setInterval time between RN and the WebView. Times <=1 second work best for rendering. activityIntervalMs sets up how often onActivity gets called (activityState from render props gets updated continuously. onActivity is meant to be used as auto-debounced async/reporting calls. If you need no delay, reference activityState directly.)finishedTrackPercentageThreshold is a number between 1-99 and is used to decide whether to send the finishedTrack activity to onActivity . |
|
TrackPlayerRenderProps.PlayerState |
{ playing: ?boolean, currentTime: ?number, duration: ?number, trackIndex: number, playbackRate: 1 | 1.5 | 2 | 0.5 } |
-- | <TrackPlayer/> loads each audio in the playlist lazily to reduce bandwidth. Prior to loading audio, playing , currentTime , duration are all null. So, your UI will need to guard against them. |
|
TrackPlayerRenderProps.ActivityState |
?{ [trackId: string]: ?{ lastPositionSecs: number, totalListenedSecs: number, percentageListened: number, secToListened?: { [sec: number]: true/false }}} |
-- | If a track is never played, activity map will be undefined until first played. secToListened is largely a bookkeeping prop used to create scrub-proof percentages. onActivity always receives secToListened as undefined to reduce prop size. |
|
TrackPlayerRenderProps.playPause |
() => void; | -- | Sets current track to pause/play. Calls onActivity if set. |
|
TrackPlayerRenderProps.next |
() => void; | -- | Skips to next track. Guards against index out of bounds automatically. | |
TrackPlayerRenderProps.previous |
() => void; | -- | Skips to previous track. Guards against index out of bounds automatically. | |
TrackPlayerRenderProps.seekTrack |
(trackIndex: number) => void; | -- | Skips to the index provided. Does not guard currently. | |
TrackPlayerRenderProps.scrubTo |
(seconds: number) => void; | -- | Skips to time provided in current track. Does not guard currently. | |
TrackPlayerRenderProps.setPlaybackRate |
(playbackSpeed: 1 | 0.5 | 1.5 | 2 ) => void; | -- | Sets the provided playback speed to the current track. |
I consider the library as feature-complete and stable as it fulfills my own needs. Pull requests and forks are welcome, though. Refer to CONTRIBUTING.md for development commands.
Initial template bootstrapped with create-react-native-library.