How to Build a Music Streaming App with React Native
Introduction
Daily, most of us use various music streaming apps. Without a doubt, the most popular application is Spotify and YouTube music. In this article, I will present a React Native library which we can use to create our own music app. We will discuss its most important functions and finally, I will demonstrate how to build a music streaming app with React Native.
The library we will be talking about is React Native Track Player. It is currently the best audio playback library. It is characterized primarily by ease of implementation, simple configuration and functions that will make your music streaming application look fully professional on digital platforms and 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. And so it’s the right place for both music lovers and mobile app developers!
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? In short, we can say that by using it 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.
So let’s discuss the most important features of React Native Track Player:
- Optimized Resource Usage: Tailored to use minimal device resources efficiently, ensuring optimal performance for your app’s needs.
- Cross-platform compatibility: Works harmoniously across iOS and Android platforms, aligning with the native design principles of music applications.
- Media Control Accessibility: Enables control of the application through various avenues like Bluetooth devices, lock screens, notifications, smartwatches, and car systems.
- Caching Capability: Allows for the caching of media files, enabling seamless playback even in scenarios where the internet connection is disrupted.
- Background Playback Support: Provides the capability to continue audio playback even when the application operates in the background.
- Extensive Customization: Offers comprehensive customization options, including customizable notification icons, enabling tailored own app’s UI experiences as desired by developers.
Native modules
React Native Track Player, known for its cross-platform compatibility, seamlessly operates on both iOS and Android devices. Underlying this compatibility are two pivotal native modules: SwiftAudioEx and KotlinAudio.
SwiftAudioEx is dedicated to empowering seamless operations tailored explicitly for iOS devices. On the other hand, KotlinAudio is meticulously designed for Android, ensuring an optimized and tailored experience on this platform.
These native modules play a critical role in boosting performance by leveraging platform-specific functionalities. Specifically, they enable the storage of playlists in native data structures, contributing significantly to the app’s efficiency and performance. By employing SwiftAudioEx and KotlinAudio, React Native Track Player strives to deliver an exceptional user experience, optimizing the library’s functionalities to the core features of each platform.
Why React Native Track Player?
React Native Track Player stands out as an optimal choice for music app development due to its streamlined integration process and cross-platform compatibility across iOS and Android.
With a rich feature set encompassing audio playback controls, background mode support, and seamless integration with external devices, it offers efficient and optimized performance, utilizing minimal device resources.
Leveraging native modules for platform-specific functionalities ensures high performance, while its flexibility allows for easy customization of the app’s UI and functionalities. Supported by extensive documentation and an active community, React Native Track Player proves to be a reliable and robust 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
Don’t have time to build a music app on your own?
Platform Support
As we gear up to craft our very own music streaming app, understanding the compatibility across different platforms is key. In this section, we’re delving deep into a comparison that highlights the supported streaming types and casting features on both iOS and Android platforms.
Stream Types
Casting
Miscellaneous
React Native Track Player functions
In this chapter, I will discuss some of the most basic functions provided by the library. Some of them we will use later while building your own music streaming 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) – I refer you to the documentation.
Player
Feature | Description |
---|---|
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
Function | Description |
---|---|
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 a Music Streaming App with React Native Track Player
Now that you’re familiar with React Native Track Player and its capabilities, let’s start with the development of our music app! Here’s a glimpse of what our application will feature:
- A fully functional music player showcasing a sample playlist of diverse music tracks.
- Core functionalities including play and pause controls for the active song.
- An interactive progress bar illustrating the playback status of the currently selected track.
The application’s interface will resemble the following structure:
Let’s write our simple music streaming application
I assume you already have a new React Native project created. If not, I refer you to the Expo documentation – https://docs.expo.dev/get-started/create-a-project/.
In the next chapters, we will focus on installing the necessary packages and preparing the basic configuration of our streaming app.
To begin, we’ll initiate the installation process by adding the React Native Track Player library:
yarn add react-native-track-player
Following that, we’ll proceed by installing the progress bar package. For this scenario, we’ve opted for ‘@miblanchard/react-native-slider’ due to its extensive functionality and ease of 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. So if you don’t have it, create it or do it according to your own convention. Above the code I will add the paths to the files I used.
To enable background mode functionality in RN Track Player, we’re required to configure the player to sustain audio playback even when the application is in the background.
Let’s create a file named ‘RNTP-service.js’ 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, which 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 to be created is the component called player. This component will encompass the following elements:
- 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'
Subsequently, 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 that’s all. Now you should see the player presented at the beginning of this chapter.
I recommend that you 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, we managed to create a very simple music player that supports a playlist. 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 a thorough analysis of 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.
Read more
Why React Native updates are important?
Publishing Expo React Native apps
Best React Native Tech Stack in 2024
Need someone to build a music streaming app for you?
Sources: