Implementing react-native-track-player with Expo, including lock screen (Part 2: Android)

Gionata Brunel
5 min readAug 27, 2023

--

The purpose of this article is to provide all the information you need to display a music player on the lock screen. The article is divided in two parts, the first is dedicated to iOS (https://medium.com/@gionata.brunel/implementing-react-native-track-player-with-expo-including-lock-screen-part-1-ios-9552fea5178c), and the second to Android.

Simple implementation for Android

Initialize your project with Expo (the name of the project used in this article is ‘miniplayerandroid’ and the Expo version is 49.0.8 — the latest available at this time).

npx create-expo-app miniplayerandroid

You can start your App once with ‘npx expo start’ (and then kill it) to be sure that there’s no issues (but it’s not really necessary).

Then install EAS globally and the development client:

cd miniplayerandroid
npm install -g eas-cli
npx expo install expo-dev-client

Now, before creating the build, you need to tell EAS what are the packages you will use. That because EAS will look into package.json and create all the necessary resources for you.

npm install --save react-native-track-player
npx expo install @react-native-community/slider

Note: Probably the following points could be done after creating the build, but I prefer do it now.

Edit package.json and delete the line “main”. After it should look like that:

{
"name": "miniplayerandroid",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~49.0.8",
"expo-dev-client": "~2.4.8",
"expo-status-bar": "~1.6.0",
"react": "18.2.0",
"react-native": "0.72.4",
"react-native-track-player": "^3.2.0",
"@react-native-community/slider": "4.4.2"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}

Create service.js and add:

import TrackPlayer from 'react-native-track-player';

module.exports = async function () {

TrackPlayer.addEventListener('remote-play', () => TrackPlayer.play());
TrackPlayer.addEventListener('remote-pause', () => TrackPlayer.pause());
TrackPlayer.addEventListener('remote-next', () => TrackPlayer.skipToNext());
TrackPlayer.addEventListener('remote-previous', () => TrackPlayer.skipToPrevious());

};

Create index.js and add:

import { registerRootComponent } from 'expo';
import TrackPlayer from 'react-native-track-player';
import App from './App';

registerRootComponent(App);
TrackPlayer.registerPlaybackService(() => require('./service'));

And finally create eas.json and copy the following:

{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {}
}
}

Now you are ready to create a build for an Android device. If you prefer using a simulator, please refer to the documentation (https://docs.expo.dev/development/create-development-builds/).

Let’s create the build following the interactive dialog provided by EAS:

eas build --profile development --platform android

After the build is complete, from the log (or the Expo builds page) find the URL (or use the QR code) and open it with your device to download and install the .apk.

Now it’s time to code your app.

The App.js is pretty simple, basically it’s just for displaying the MusicPlayer screen.

import React from 'react';
import {View, StatusBar, StyleSheet } from 'react-native';
import MusicPlayer from './MusicPlayer';

const App = () => {

return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<MusicPlayer />
</View>
);
};

export default App;

const styles = StyleSheet.create({
container: {
flex: 1,
},
});

And herewith the MusicPlayer.js where you will find a simple implementation of the react-native-track-player working on lock screen. For more details and explainations please refer to the react-native-track-player documentation (https://react-native-track-player.js.org/). The whole prototype is here: https://github.com/JonnaryMotors/miniplayerios (yes, this is the iOS version, but it’s pretty much the same: the only relevant difference is the eas.json file — please refer to the example above).

import React, {useEffect, useState} from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
Dimensions,
TouchableOpacity,
Image,
} from 'react-native';
import TrackPlayer, {
Capability,
State,
Event,
usePlaybackState,
useProgress,
useTrackPlayerEvents,
} from 'react-native-track-player';
import Slider from '@react-native-community/slider';
import Ionicons from 'react-native-vector-icons/Ionicons';
import podcasts from './assets/data';

function MusicPlayer() {

const podcastsCount = podcasts.length;
const [trackIndex, setTrackIndex] = useState(0);
const [trackTitle, setTrackTitle] = useState();
const [trackArtist, setTrackArtist] = useState();
const [trackArtwork, setTrackArtwork] = useState();

const playBackState = usePlaybackState();
const progress = useProgress();

const setupPlayer = async () => {
try {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
capabilities: [
Capability.Play,
Capability.Pause,
Capability.SkipToNext,
Capability.SkipToPrevious
],
});
await TrackPlayer.add(podcasts);
await gettrackdata();
await TrackPlayer.play();
} catch (error) { console.log(error); }
};

useTrackPlayerEvents([Event.PlaybackTrackChanged], async event => {
if (event.type === Event.PlaybackTrackChanged && event.nextTrack !== null) {
const track = await TrackPlayer.getTrack(event.nextTrack);
const {title, artwork, artist} = track;
console.log(event.nextTrack);
setTrackIndex(event.nextTrack);
setTrackTitle(title);
setTrackArtist(artist);
setTrackArtwork(artwork);
}
});

const gettrackdata = async () => {
let trackIndex = await TrackPlayer.getCurrentTrack();
let trackObject = await TrackPlayer.getTrack(trackIndex);
console.log(trackIndex);
setTrackIndex(trackIndex);
setTrackTitle(trackObject.title);
setTrackArtist(trackObject.artist);
setTrackArtwork(trackObject.artwork);
};

const togglePlayBack = async playBackState => {
const currentTrack = await TrackPlayer.getCurrentTrack();
if (currentTrack != null) {
if ((playBackState == State.Paused) | (playBackState == State.Ready)) {
await TrackPlayer.play();
} else {
await TrackPlayer.pause();
}
}
};

const nexttrack = async () => {
if (trackIndex < podcastsCount-1) {
await TrackPlayer.skipToNext();
gettrackdata();
};
};

const previoustrack = async () => {
if (trackIndex > 0) {
await TrackPlayer.skipToPrevious();
gettrackdata();
};
};

useEffect(() => {
setupPlayer();
}, []);

return (
<SafeAreaView style={styles.container}>
<View style={styles.mainContainer}>
<View style={styles.mainWrapper}>
<Image source={trackArtwork} style={styles.imageWrapper} />
</View>
<View style={styles.songText}>
<Text style={[styles.songContent, styles.songTitle]} numberOfLines={3}>{trackTitle}</Text>
<Text style={[styles.songContent, styles.songArtist]} numberOfLines={2}>{trackArtist}</Text>
</View>
<View>
<Slider
style={styles.progressBar}
value={progress.position}
minimumValue={0}
maximumValue={progress.duration}
thumbTintColor="#FFD369"
minimumTrackTintColor="#FFD369"
maximumTrackTintColor="#fff"
onSlidingComplete={async value => await TrackPlayer.seekTo(value) }
/>
<View style={styles.progressLevelDuraiton}>
<Text style={styles.progressLabelText}>
{new Date(progress.position * 1000)
.toLocaleTimeString()
.substring(3)}
</Text>
<Text style={styles.progressLabelText}>
{new Date((progress.duration - progress.position) * 1000)
.toLocaleTimeString()
.substring(3)}
</Text>
</View>
</View>
<View style={styles.musicControlsContainer}>
<TouchableOpacity onPress={previoustrack}>
<Ionicons
name="play-skip-back-outline"
size={35}
color="#FFD369"
/>
</TouchableOpacity>
<TouchableOpacity onPress={() => togglePlayBack(playBackState) }>
<Ionicons
name={
playBackState === State.Playing
? 'ios-pause-circle'
: playBackState === State.Connecting
? 'ios-caret-down-circle'
: 'ios-play-circle'
}
size={75}
color="#FFD369"
/>
</TouchableOpacity>
<TouchableOpacity onPress={nexttrack}>
<Ionicons
name="play-skip-forward-outline"
size={35}
color="#FFD369"
/>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};

export default MusicPlayer;

const {width, height} = Dimensions.get('window');

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#222831',
},
mainContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
mainWrapper: {
width: width,
height: width,
justifyContent: 'center',
alignItems: 'center',
},
imageWrapper: {
alignSelf: "center",
width: '90%',
height: '90%',
borderRadius: 15,
},
songText: {
marginTop:2,
height: 70
},
songContent: {
textAlign: 'center',
color: '#EEEEEE',
},
songTitle: {
fontSize: 18,
fontWeight: '600',
},
songArtist: {
fontSize: 16,
fontWeight: '300',
},
progressBar: {
alignSelf: "stretch",
marginTop: 40,
marginLeft:5,
marginRight:5
},
progressLevelDuraiton: {
width: width,
padding: 5,
flexDirection: 'row',
justifyContent: 'space-between',
},
progressLabelText: {
color: '#FFF',
},
musicControlsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: 20,
marginBottom: 20,
width: '60%',
},
});

To start your App you need to use:

npx expo start --dev-client

Then start the build you installed in your device.

--

--