How to Build a Music Streaming App with React Native

PageproPagepro
14 min read

Introduction

Most of us use different music streaming apps daily. Spotify and YouTube Music are the most popular options, but did you know you can easily build a music streaming app with React Native?

In this article, I will introduce you to React Native Track Player, a React Native library that we can use to create a music app. It’s easy to implement, has a simple configuration, and functions that will make your music streaming application look fully professional on digital platforms.

We will discuss its most important functions, and finally, I will demonstrate how to build a music streaming app with React Native. With a little work, in terms of functionality, it will not differ from the top music mobile apps that you can find on Google Play and the App Store.

React Native Track Player

As I mentioned earlier, the article will be devoted to the React Native Track Player package. So it’s worth explaining what it is at the very beginning. As the welcome text in the documentation says:

A fully fledged audio module created for music apps. Provides audio playback, external media controls, background mode and more!

Sounds good, right? By using i,t we will create a fully functional music application that we can control from the application level and externally.

Since we’re talking about the React Native library, we don’t have to build separate native apps for iOS and Android, but one music streaming service for both of them using the same code.

Features of React Native Track Player

  • Optimized Resource Usage: Designed to use minimal resources for optimal performance for app performance.

  • Cross-platform compatibility: Works across iOS and Android platforms, aligning with the native design rules.

  • Media Control Accessibility: Let users control the application through Bluetooth devices, lock screens, notifications, smartwatches, and car systems.

  • Caching Capability: Allows for the caching of media files, enabling playback when the internet connection is disrupted.

  • Background Playback Support: Continues audio playback when the app is in the background.

  • Extensive Customization: Offers customization options like customizable notification icons and lets devs tailor the UI as they want.

Native Modules

React Native Track Player operates on both iOS and Android devices. Underlying this compatibility are two native modules: SwiftAudioEx and KotlinAudio.

SwiftAudioEx is dedicated to operations for iOS devices. KotlinAudio works for Android to guarantee an optimized experience on this platform.

These native modules play a critical role in improving performance by using platform-specific functionalities. Specifically, they enable the storage of playlists in native data structures, which affects the app’s efficiency and performance.

With SwiftAudioEx and KotlinAudio, React Native Track Player delivers great UX, optimizing the library’s functionalities to the core features of each platform.

Why Choose React Native Track Player?

React Native Track Player is an optimal choice for a music app because of its streamlined integration process and cross-platform compatibility across iOS and Android.

The newest version of React Native Track Player, 4.1.2, was released in August 2025. Using native modules for platform-specific functionalities means high performance, and the library’s flexibility allows for easy customization of the app’s UI and functionalities.

A rich feature set, including audio playback controls, background mode support, and easy integration with external devices, offers efficient and optimized performance while saving resources**.**

Supported by extensive documentation and an active community, React Native Track Player is a reliable solution for creating music streaming apps in React Native.

“Working with React Native Track Player has truly simplified my approach to building music streaming apps. Its efficiency and user-friendly design make it a standout choice for seamless cross-platform development. From synchronizing audio playback controls to creating immersive user experiences, this library has transformed the way I work with the apps.”


Michal Moroz, React Native Developer at Pagepro

Don’t have time to build a music app on your own? Use our experience.

Platform Support

Before making your very own music streaming app, you have to understand that compatibility across different platforms is key. That’s why in this section, we’ll compare the supported streaming types and casting features on both iOS and Android platforms.

Stream Types

Comparison chart showing streaming format support for iOS and Android when you build a music streaming app with React Native. Both platforms support regular streams. Android supports DASH and SmoothStreaming, while iOS does not. iOS supports HLS (HTTP Live Streaming), while Android also supports it.

Casting

Casting feature comparison for iOS and Android when you build a music streaming app with React Native. Android supports Google Cast, while iOS does not. Both platforms lack support for Miracast/DLNA and AirPlay within this setup.

Miscellaneous

Miscellaneous feature comparison for iOS and Android when you build a music streaming app with React Native. Both platforms support media controls and background mode. Android supports caching, while iOS does not.

React Native Track Player functions

Now we can discuss the most basic functions provided by the library, which we will use later while building the app.

If you are interested in all the events and functions provided by the library (and there are a lot of them’s quite a lot of them), be sure to check the official documentation.

Player

FeatureDescription
setupPlayer(options)Setup player with options. Sample options: minBuffer, maxBuffer, maxCacheSize.
play()Plays/resumes current track
pause()Pause track
stop()Stops playback
retry()Replays the current track if it was stopped due to an error
seekBy(offset)Seeks by a relative time offset in the current track
seekTo(seconds)Seeks current track to specified time position
setVolume(volume)Sets the volume
getVolume()Gets player volume
setRate(rate)Sets the playback rate
getProgress()Gets the playback progress – position, duration
getPlaybackState()Gets the PlaybackState – e.g.ready, playing, paused, stopped.
getPlayWhenReady()Gets the state of playWhenReady

Queue

FunctionDescription
add(tracks, insertBeforeIndex)Adds tracks to the queue
remove(tracks)Clears the current queue and adds tracks to the empty queue
skip(index, initialPosition)Skips to a track in the queue
skipToNext(initialPosition)Skips to the next track
skipToPrevious(initialPosition)Skips to the previous track
reset()Resets the player and clear the queue
getTrack(index)Gets a track object
getActiveTrack()Gets active track object
getQueue()Gets a queue
removeUpcomingTracks()Clears upcoming tracks from the queue
setRepeatMode(mode)Sets the repeat mode – Loops the current track / Repeats the whole queue / Does Not repeat

Building React Native Spotify

Since you’re familiar with React Native Track Player and its capabilities, let’s start with the development of our music app!

Features of Your App

  • A fully functional music player showing a sample playlist of music tracks.

  • Core functionalities include play and pause controls for the active song.

  • An interactive progress bar illustrating the playback status of the selected track.

The application’s interface will look like this:

Creating A Simple Music Streaming APp

I assume you already have a new React Native project created. If not, read the official Expo documentation.

To begin, start the installation process by adding the React Native Track Player library:

yarn add react-native-track-player

Next, we’ll proceed by installing the progress bar package. For this scenario, we’ve opted for @miblanchard/react-native-slider because of the extensive functionality and easy styling:

yarn add @miblanchard/react-native-slider

Add Service

Next, let’s incorporate the playback service.

It’s crucial to register the playback service immediately after registering the primary React Native application component, typically found in the index.js file located in the project’s root directory:

IMPORTANT NOTE: The configuration and components will be stored in the src directory. If you don’t have one, create it or do it according to your own convention. I will add the paths to the files I used above the code.

To enable background mode functionality in RN Track Player, we need to configure the player to sustain audio playback even when the application is in the background.

Create RNTP-service.js file to manage this configuration.

src/setup/RNTP-service.js

import TrackPlayer, { Event } from 'react-native-track-player'

export const RNTPService = async () => {
 TrackPlayer.addEventListener(Event.RemotePlay, () => TrackPlayer.play())
 TrackPlayer.addEventListener(Event.RemotePause, () => TrackPlayer.pause())
}

Then, in the project root, create an index.js file and import the service and App there.

index.js

import { registerRootComponent } from 'expo'
import TrackPlayer from 'react-native-track-player'

import { RNTPService } from './src/setup/rntp-service'

import App from './App'

registerRootComponent(App)

TrackPlayer.registerPlaybackService(() => RNTPService)

Then, we need to set up the player. In App.js file add setupPlayer.

App.js

import { SafeAreaView, StyleSheet, Text, View } from 'react-native'
import { useEffect } from 'react'
import TrackPlayer, {
 AppKilledPlaybackBehavior,
 Capability,
} from 'react-native-track-player'

export default function App() {
 useEffect(() => {
   const setupPlayer = async () => {
     await TrackPlayer.setupPlayer()

     await TrackPlayer.updateOptions({
       android: {
         appKilledPlaybackBehavior:
           AppKilledPlaybackBehavior.StopPlaybackAndRemoveNotification,
         alwaysPauseOnInterruption: true,
       },
       capabilities: [
         Capability.Play,
         Capability.Pause,
         Capability.SkipToNext,
         Capability.SkipToPrevious,
         Capability.SeekTo,
       ],
       compactCapabilities: [Capability.Play, Capability.Pause],
       progressUpdateEventInterval: 1,
     })
   }

   setupPlayer()
 }, [])

 return (
   <View style={styles.container}>
     <SafeAreaView />
     <Text style={styles.header}>MUSIC APP</Text>
   </View>
 )
}

const styles = StyleSheet.create({
 container: {
   flex: 1,
   paddingHorizontal: 16,
   backgroundColor: '#f53340',
 },
 header: {
   fontSize: 28,
   fontWeight: '700',
   textAlign: 'center',
   color: '#FFF',
   marginTop: 20,
   marginBottom: 48,
 },
})

Once you’ve completed these steps, you’ll have the groundwork laid for a fully functional music player.

Add Player Playlist

Before creating the first component, create an array of songs that the music player will use.

For each song, ensure to include a URL pointing to the audio file. It could be either a file path or a direct URL.

App.js

export const tracks = [
  {
    id: 1,
    url: YOUR_URL,
    title: '01. Track 1 sample',
    playlistName: 'Playlist sample',
    playlistId: 1,
  },
  {
    id: 2,
    url: YOUR_URL,    
    title: '02. Track 2 sample',
    playlistName: 'Playlist sample',
    playlistId: 1,
  },
  {
    id: 3,
    url: YOUR_URL,    
    title: '03. Track 3 sample',
    playlistName: 'Playlist sample',
    playlistId: 1,
  },
  {
    id: 4,
    url: YOUR_URL,    
    title: '04. Track 4 sample',
    playlistName: 'Playlist sample',
    playlistId: 1,
  },
  {
    id: 5,
    url: YOUR_URL,    
    title: '05. Track 5 sample',
    playlistName: 'Playlist sample',
    playlistId: 1,
  },
]

Create Track List

In the components directory, the initial addition is the TrackList component. This component serves as a clickable list of songs. Upon selecting a song, it activates the track and generates the music queue.

Pay special attention to the PlaybackActiveTrackChanged event and the onTrackPress function.

App.js

import { FlatList, Pressable, StyleSheet, View, Text } from 'react-native'
import TrackPlayer, {
 Event,
 useTrackPlayerEvents,
} from 'react-native-track-player'
import { useState } from 'react'

const TrackList = ({ items }) => {
 const [activeTrackIndex, setActiveTrackIndex] = useState(0)

 useTrackPlayerEvents([Event.PlaybackActiveTrackChanged], async (event) => {
   if (event.index === undefined) {
     setActiveTrackIndex(0)
     return
   }

   if (
     event.type === Event.PlaybackActiveTrackChanged &&
     event.index != null
   ) {
     const activeTrackItem = await TrackPlayer.getActiveTrack()

     if (!activeTrackItem) {
       return
     }

     setActiveTrackIndex(activeTrackItem.id)
   }
 })

 useTrackPlayerEvents([Event.PlaybackQueueEnded], async () => {
   const repeatMode = await TrackPlayer.getRepeatMode()
   TrackPlayer.seekTo(0)

   if (repeatMode === 0) {
     await TrackPlayer.pause()
   } else {
     await TrackPlayer.play()
   }
 })

 const onTrackPress = async (id) => {
   const currentTrackIndex = items.findIndex((item) => item.id === id)
   const trackPlayerQueue = await TrackPlayer.getQueue()
   const sameTrackFromQueue = trackPlayerQueue[currentTrackIndex]
   const currentTrack = items[currentTrackIndex]

   if (currentTrack.id !== sameTrackFromQueue?.id) {
     const newQueue = items.map((item) => ({
       id: item.id,
       url: item.url,
       title: item.title,
       headers: {
         playlist: item.playlistName,
       },
       playlistId: item.playlistId,
     }))

     await TrackPlayer.reset()
     await TrackPlayer.add(newQueue[currentTrackIndex])
     await TrackPlayer.add(newQueue)
     await TrackPlayer.skip(currentTrackIndex + 1)
     await TrackPlayer.play()
     await TrackPlayer.remove(0)
   } else {
     await TrackPlayer.skip(currentTrackIndex)
     await TrackPlayer.play()
   }
 }

 return (
   <View style={styles.container}>
     <FlatList
       data={items}
       renderItem={({
         item: { id, url, title, playlistName, playlistId },
       }) => {
         const isActiveTrack = activeTrackIndex === id

         return (
           <View
             key={id}
             style={[styles.listItem, isActiveTrack && styles.listItemActive]}
           >
             <Pressable
               onPress={() =>
                 onTrackPress(id)
               }
             >
               <Text
                 style={[styles.title, isActiveTrack && styles.textActive]}
               >
                 {title}
               </Text>
               <Text
                 style={[
                   styles.description,
                   isActiveTrack && styles.textActive,
                 ]}
               >
                 {playlistName}
               </Text>
             </Pressable>
           </View>
         )
       }}
       alwaysBounceVertical={false}
       keyExtractor={({ id }) => id.toString()}
     />
   </View>
 )
}

export default TrackList

const styles = StyleSheet.create({
 container: {
   flex: 1,
 },
 listItem: {
   paddingVertical: 4,
   backgroundColor: '#E21515',
   marginBottom: 8,
   borderRadius: 8,
   paddingHorizontal: 8,
   paddingVertical: 12,
 },
 listItemActive: {
   backgroundColor: '#FFF',
 },
 header: {
   marginBottom: 8,
   color: '#FFF',
   fontWeight: '700',
 },
 title: {
   fontSize: 14,
   color: '#FFF',
 },
 description: {
   fontSize: 12,
   color: '#FFF',
 },
 textActive: {
   color: '#E21515',
   fontWeight: '700',
 },
})

Create Player

The final element you need is the component called player. This component will include the following:

  • Current song display

  • Song timer

  • Clickable progress bar (utilizing the @miblanchard/react-native-slider package)

  • Play/pause button functionality

import { StyleSheet, View, Text, Pressable } from 'react-native'
import { Slider } from '@miblanchard/react-native-slider'
import TrackPlayer, {
 useProgress,
 usePlaybackState,
 State,
 Event,
 useTrackPlayerEvents,
} from 'react-native-track-player'
import { Dimensions } from 'react-native'
import { useCallback, useState, useEffect } from 'react'

const AudioPlayer = () => {
 const { position, duration } = useProgress(100)
 const { state: playbackState } = usePlaybackState()

 const [isPlaying, setIsPlaying] = useState(playbackState === State.Playing)
 const [activeTrackData, setActiveTrackData] = useState()
 const [isPlayButtonDisabled, setIsPlayButtonDisabled] = useState(true)

 // Util for time format
 const formatTime = (timeInSeconds) => {
   const minutes = Math.floor(timeInSeconds / 60)
     .toString()
     .padStart(1, '0')
   const seconds = (Math.trunc(timeInSeconds) % 60).toString().padStart(2, '0')

   return `${minutes}:${seconds}`
 }

 const play = useCallback(async () => {
   await TrackPlayer.play()
   setIsPlayButtonDisabled(false)
 }, [])

 const pause = useCallback(async () => {
   await TrackPlayer.pause()
 }, [])

 const trackTimeProgress = formatTime(position)
 const trackTimeLeft = formatTime(duration - position)

 useEffect(() => {
   if (playbackState === State.Playing && !isPlaying) {
     setIsPlaying(true)
   }

   if (
     playbackState === State.Paused ||
     (playbackState === State.Stopped && isPlaying)
   ) {
     setIsPlaying(false)
   }
 }, [isPlaying, playbackState])

 useTrackPlayerEvents([Event.PlaybackActiveTrackChanged], async (event) => {
   if (
     event.type === Event.PlaybackActiveTrackChanged &&
     event.index != null
   ) {
     const activeTrackItem = await TrackPlayer.getActiveTrack()

     if (!activeTrackItem) {
       return
     }

     setIsPlayButtonDisabled(false)

     setActiveTrackData({
       title: activeTrackItem.title,
       playlist: activeTrackItem.headers.playlist,
     })
   }
 })

 return (
   <View style={styles.container}>
     {activeTrackData && (
       <View style={styles.activeTrack}>
         <Text style={styles.activeTrackTitle}>{activeTrackData.title}</Text>
         <Text style={styles.activeTrackDescription}>
           {activeTrackData.playlist}
         </Text>
       </View>
     )}
     <Slider
       style={styles.progressBar}
       trackStyle={{
         height: 6,
         width: Dimensions.get('window').width - 32,
       }}
       thumbTouchSize={{ width: 4, height: 4 }}
       animationType="timing"
       trackClickable
       value={position}
       minimumValue={0}
       maximumValue={duration}
       thumbTintColor={'#fff'}
       minimumTrackTintColor={'#fff'}
       maximumTrackTintColor={'#fff'}
       onSlidingComplete={async (value) => {
         TrackPlayer.seekTo(Number(value))
       }}
     />
     <Text style={[styles.text, styles.timer]}>
       {trackTimeProgress} / {trackTimeLeft}
     </Text>
     <View style={styles.buttons}>
       <Pressable
         disabled={isPlayButtonDisabled}
         style={[styles.button, isPlayButtonDisabled && styles.buttonDisabled]}
         onPress={isPlaying ? pause : play}
       >
         <Text style={styles.buttonText}>{isPlaying ? 'PAUSE' : 'PLAY'}</Text>
       </Pressable>
     </View>
   </View>
 )
}

export default AudioPlayer

const styles = StyleSheet.create({
 container: {
   justifyContent: 'flex-end',
   width: '100%',
   backgroundColor: '#f53340',
   paddingVertical: 32,
 },
 activeTrack: {
   marginBottom: 32,
 },
 activeTrackTitle: {
   fontSize: 28,
   fontWeight: '700',
   color: '#fff',
 },
 activeTrackDescription: { fontSize: 20, fontWeight: '700', color: '#fff' },
 buttons: {
   flexDirection: 'row',
   justifyContent: 'flex-end',
   alignItems: 'flex-end',
   marginBottom: 12,
   width: '100%',
 },
 button: {
   backgroundColor: '#fff',
   paddingVertical: 8,
   paddingHorizontal: 16,
   marginLeft: 16,
   borderRadius: 8,
 },
 buttonText: {
   fontWeight: '700',
   textAlign: 'center',
   color: '#f53340',
   fontSize: 18,
 },
 buttonDisabled: {
   opacity: 0,
 },
 progressBar: {
   alignItems: 'center',
   flex: 1,
   width: '100%',
 },
 timer: {
   fontSize: 18,
   marginTop: 8,
   marginBottom: 20,
   fontWeight: '700',
 },
 text: {
   color: '#FFF',
 },
})

Getting it all together

Now that we have the components ready, all that remains is to put everything together.

In the App.js file we import the list and player components. In addition to the components, we need to import a const containing a list of songs.

App.js

import AudioPlayer from './src/components/AudioPlayer'
import TrackList from './src/components/TrackList'

import { tracks } from './src/consts/index'

Next we add the components and pass the tracks array as props to the list component.

 return (
   <View style={styles.container}>
     <SafeAreaView />
     <Text style={styles.header}>MUSIC APP</Text>
     <TrackList items={tracks} />
     <AudioPlayer />
   </View>
 )

And now you have your own music streaming app!

Play around with the player’s functionalities and then add additional functions such as scrolling to the next/previous song, volume control or support for more than one playlist.

Summary

In this short tutorial, you created a simple music player. As you have noticed, the functions and events provided by React Native Track Player make our work much easier, so we can focus more on how the functionality should work rather than how to create it. And this is only a fraction of what this music library provides us.

After analysing the API, you will realize that you can create a React Native track player that supports several playlists with full control over the song being played. Offline support will allow you to download a few tracks to your device and play them offline.

Of course, you can always use the expertise of a mobile app development company to help you build a music streaming app that will work anywhere and that you will be able to monetize.

Need someone to build a music streaming app for you? Use our help.

FAQ

Is React Native good for building apps like Spotify or Apple Music?

Yes, React Native is a solid choice for building apps similar to Spotify or Apple Music. It supports cross-platform development, integrates with audio libraries like React Native Track Player, and allows you to implement features like streaming, playlists, and background playback on both iOS and Android.

What is the best library to build a music streaming app in React Native?

The most widely recommended library is React Native Track Player. It offers cross-platform support, background playback, lock-screen controls, and now includes features like adaptive streaming (HLS, DASH) and caching for offline use. It’s actively maintained and well-suited for building production-grade music apps.

Does React Native Track Player support offline playback?

Yes, React Native Track Player supports offline playback through its caching feature. Developers can allow users to download tracks for later listening, making it possible to enjoy music even without an internet connection.

How do I add background audio playback in React Native?

Background audio playback can be added using React Native Track Player. After installing and configuring the library, you register a service that keeps playback running when the app is minimized. This setup enables media controls on the lock screen, notification area, and external devices like headphones.

Read more

Sources

0
Subscribe to my newsletter

Read articles from Pagepro directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pagepro
Pagepro

Next.js, Expo & Sanity developers for hire! At Pagepro, we take over the technical duties by providing Next.js, Expo & Sanity development teams, so you can focus on the strategic goals of your business, and stop worrying about the delivery so much.