Playing Audio in React Native with react-native-track-player

Learn how to easily integrate audio playback into your React Native apps using the react-native-track-player package. Includes code snippets you can use in your project.

Friday April 4th 2025 | 8 min read

Playing Audio in React Native with react-native-track-player blog post header image

Audio playback is a crucial feature in many React Native applications, from meditation apps to social platforms. While implementing robust audio features might seem daunting, we've recently tackled this challenge head-on in our latest healthcare app and want to break down the barriers for other looking to play audio in their React native mobile apps on both iOS and Android.

Following our series of React Native guides on Live Activities, Internationalisation, and Essential Tools for 2025, this post dives deep into creating a reliable audio playback system using react-native-track-player.

Our production example

We recently built a React Native app to help people experiencing Post-Exertional Malaise (PEM) episodes associated with ME/CFS or Long COVID to keep a track of their condition. The app is called PEM Dairy and is free to download on the App Store.

A common symptom of a PEM episode is poor quality of sleep so a key feature was the ability to play calming audio to soothe users during difficult moments and aid in restful sleep. After researching audio playback options in React Native, we settled on the react-native-track-player package. In this post, I'll share how we integrated it and some tips we learnt along the way.

Why react-native-track-player?

There are several audio playback packages available for React Native, but react-native-track-player stood out for a few key reasons:

  1. Strong support for background audio playback on both iOS and Android
  2. Simple, promise-based API
  3. Queue management for easily creating playlists
  4. Event-based tracking of playback state
  5. Good documentation with clear examples

It ticked all the boxes for our use case of not just playing audio, but also having a quick and easy setup. This allowed us to have rapid development time and get the app into the hands of real users to gather feedback that we could quickly iterate on and improve.

Business case for react-native-track-player

Before diving into implementation details, let's consider why investing in a proper audio solution matters:

  • Enhanced user experience → Background playback keeps audio running when users switch apps
  • Battery optimisation → Native audio APIs are more power-efficient than JavaScript-based solutions
  • Faster time to market → Ready-to-use solution saves development time
  • Platform consistency → Works reliably across iOS and Android

Setting up react-native-track-player

Installing the package is straightforward using npm or yarn:

npm install --save react-native-track-player

or

yarn add react-native-track-player

For iOS, ensure you update your pods:

cd ios && pod install

Then you can create your trackPlayerService and call this in index.js or wherever you register your app component to ensure it is fully set up.

Background mode

This works out of the box for Android. However, one change we opted to make was to ensure all audio was stopped when the app was killed. This requires an additional bit of setup.

In our trackPlayerSetup file, we added the following option:

await TrackPlayer.setupPlayer({
  android: {
    appKilledPlaybackBehaviour:
      AppKilledPlaybackBehavior.StopPlaybackAndRemoveNotification,
  },
})

The "additional" setup to support this on iOS is extremely easy. Just open up Xcode, navigate to Signing & Capabilities, add the Background Modes capability, and ensure you have ticked Audio, AirPlay, and Picture in Picture.

There you have it—background audio support is set up and ready to go. The native audio controls on the lock screen also work seamlessly, and you get all of the features you expect: play, pause, skip, and scrub.

Reusability and maintainability

One of the key development principles we follow at Add Jam is to take a DRY approach when writing code.

With this in mind, we opted to use a React Hook approach when loading, playing, and interacting with the app's audio. This means we could implement the Sleep Aid audio feature and have the benefit of easily using more audio in the future. An instance of this would be improving user feedback with the use of audio for unlocking achievements, interaction feedback, etc.

Below is a simplified version of how you can set up your own hook:

import { useState, useEffect } from "react"
import TrackPlayer, {
  usePlaybackState,
  State,
  useProgress,
  RepeatMode,
  useTrackPlayerEvents,
  Event,
} from "react-native-track-player"
import { setupPlayer } from "../utils/trackPlayerSetup"

// Track player setup state
let isSetupDone = false
let setupPromise = null

export default function useAudioPlayer(track, options = { autoPlay: false }) {
  // State
  const [isPlayerReady, setIsPlayerReady] = useState(false)
  const [currentTrack, setCurrentTrack] = useState(0)
  const playbackState = usePlaybackState()
  const progress = useProgress()

  const { autoPlay = false } = options
  const loop = !Array.isArray(track) && track.loop ? track.loop : false

  // Initialise player
  useEffect(() => {
    const setupAudioPlayer = async () => {
      try {
        // Setup player if not already done
        if (!isSetupDone) {
          if (!setupPromise) {
            setupPromise = setupPlayer().then(result => {
              isSetupDone = result
              return result
            })
          }
          await setupPromise
        }

        // Reset player and add tracks
        await TrackPlayer.reset()
        await TrackPlayer.add(Array.isArray(track) ? track : [track])

        // Set repeat mode based on loop option
        await TrackPlayer.setRepeatMode(
          loop ? RepeatMode.Queue : RepeatMode.Off
        )

        // Auto play if enabled
        if (autoPlay && !Array.isArray(track)) {
          await TrackPlayer.play()
        }

        setIsPlayerReady(true)
      } catch (error) {
        console.error("Error setting up audio player:", error)
      }
    }

    setupAudioPlayer()

    // Cleanup on unmount
    return () => {
      TrackPlayer.pause()
      TrackPlayer.reset()
    }
  }, [track, autoPlay, loop])

  // Update loop mode when it changes
  useEffect(() => {
    if (isPlayerReady) {
      TrackPlayer.setRepeatMode(loop ? RepeatMode.Queue : RepeatMode.Off)
    }
  }, [loop, isPlayerReady])

  // Track change event listener
  useTrackPlayerEvents([Event.PlaybackTrackChanged], event => {
    if (
      event.type === Event.PlaybackTrackChanged &&
      event.nextTrack !== undefined
    ) {
      setCurrentTrack(event.nextTrack)
    }
  })

  // Player controls
  const togglePlayback = async () => {
    const currentState = playbackState.state
    if (currentState === State.Playing) {
      await TrackPlayer.pause()
    } else {
      await TrackPlayer.play()
    }
  }

  const skipToNext = async () => {
    await TrackPlayer.skipToNext()
  }

  const skipToPrevious = async () => {
    await TrackPlayer.skipToPrevious()
  }

  const seekTo = async position => {
    await TrackPlayer.seekTo(position)
  }

  return {
    isPlayerReady,
    playbackState,
    progress,
    currentTrack,
    controls: {
      togglePlayback,
      skipToNext,
      skipToPrevious,
      seekTo,
    },
  }
}

Implementing the audio player component

With your custom hook in place, creating a reusable audio player component becomes straightforward:

import React from "react"
import {
  Pressable,
  StyleSheet,
  View,
  Text,
  ActivityIndicator,
} from "react-native"
import { State } from "react-native-track-player"
import useAudioPlayer from "../hooks/useAudioPlayer"

const PlayIcon = () => <Text style={styles.icon}>▶️</Text>
const PauseIcon = () => <Text style={styles.icon}>⏸️</Text>

const SimpleAudioPlayer = ({ track, options = { autoPlay: false } }) => {
  const { isPlayerReady, playbackState, controls } = useAudioPlayer(
    track,
    options
  )

  if (!isPlayerReady) {
    return (
      <View style={styles.container}>
        <ActivityIndicator size="large" colour="#007AFF" />
      </View>
    )
  }

  const isPlaying = playbackState.state === State.Playing

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{track.title}</Text>
      <Pressable
        style={({ pressed }) => [
          styles.playButton,
          pressed && styles.buttonPressed,
        ]}
        onPress={controls.togglePlayback}
      >
        {isPlaying ? <PauseIcon /> : <PlayIcon />}
      </Pressable>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    padding: 16,
    backgroundColor: "white",
    borderRadius: 12,
    shadowColour: "#000",
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 2,
    elevation: 3,
    alignItems: "center",
  },
  title: {
    fontSize: 18,
    fontWeight: "bold",
    textAlign: "center",
    marginBottom: 16,
  },
  playButton: {
    width: 80,
    height: 80,
    borderRadius: 40,
    backgroundColor: "#f0f0f0",
    justifyContent: "center",
    alignItems: "center",
    margin: 20,
  },
  buttonPressed: {
    backgroundColor: "#e0e0e0",
    transform: [{ scale: 0.98 }],
  },
  icon: {
    fontSize: 32,
  },
})

export default SimpleAudioPlayer

Common pitfalls and considerations

When implementing audio in your React Native app, be aware of these potential issues:

  1. Audio interruptions - Handle phone calls and other audio interruptions gracefully
  2. Memory management - Release audio resources when they're no longer needed
  3. Battery consumption - Monitor and optimise for power usage, especially with background playback
  4. File formats - Different platforms support different audio formats (MP3 works well cross-platform)
  5. Offline support - Consider how your app will handle audio when offline
  6. Accessibility - Ensure your audio player is accessible to all users with proper accessibility features

Remember that just because you have added a feature to one app, duplicating it in another may not always be the best approach. Always consider the specific needs of your users and the context of your application.

Testing your audio implementation

To ensure your audio player works reliably, we recommend adding tests. Jest snapshot tests can be particularly useful for verifying that your audio player components render correctly. For more on how we approach testing React Native components at Add Jam, check out our guide on improving React Native testing with Jest snapshots.

Basic tests might include verifying that:

  • The player renders in all states (loading, playing, paused)
  • Controls respond correctly to user interactions
  • Audio state is managed properly through component lifecycle events

Conclusion

Audio playback is a powerful feature that can significantly enhance user experience in the right context. With react-native-track-player, implementing robust audio capabilities in your React Native app becomes a straightforward process that works reliably across platforms.

If you need help with adding new features to an existing app or just knowing if you are adding true value for your users, get in touch with our team - we'd love to help you create an exceptional mobile experience.


Looking for expert React Native developers? Add Jam specialises in creating robust, scalable mobile applications using React Native. From initial concept to deployment and beyond, we're here to help. Contact us to discuss your project.

For more insights into React Native development, check out our other React Native blog posts such as React Native Modals in 2025 and Essential Tools for React Native Apps in 2025.

Daniel Taylor's avatar

Daniel Taylor

Software Engineer

Recent case studies

Here's a look at some of products we've brought to market recently

PEM Diary - ME/CFS Crash Log

PEM Diary - ME/CFS Crash Log

PEM Diary is a React Native mobile app designed to help individuals with ME/CFS track and document PEM episodes. Built from personal experience, this app serves as a handy tool to understand your condition

Educational Intelligence: Money Matters

Educational Intelligence: Money Matters

How Add Jam partnered with Educational Intelligence to create Money Matters, a digital platform addressing the UK's financial literacy crisis where 12.9 million adults struggle with money management.

Great Glasgow Coffee Run

Great Glasgow Coffee Run

Celebrating Glasgow's vibrant coffee culture and running community through an interactive digital experience that maps out the perfect coffee-fuelled running route through the city.

We take products from an idea to revenue

Add Jam is your plug in team of web and mobile developers, designers and product managers. We work with you to create, ship and scale digital products that people use and love.

Hello, let's chat 👋