I started working on a music streaming app a week ago and it wasn't easy as i thought. Expo provides tools needed to build the app but putting them together and making them worked as one is the hard part.
In this article i am going to go through the basics of streaming music in expo app.
Prerequisites
- Expo (React native)
- Firebase
We are going to use firebase to host the songs.
The Playlist
In our App.js
import React from 'react';
import { Text, FlatList, Image, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { Audio } from 'expo-av';
import firebase from 'firebase';
import { FontAwesome } from '@expo/vector-icons';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
allSongs: [],
currentSongData: {},
playingStatus: 'nosound',
paused: false
};
}
componentDidMount() {
firebase
.database()
.ref('trending/')
.on('value', snapshot => {
let array = [];
snapshot.forEach(child => {
array.push(child.toJSON());
});
this.setState({
allSongs: array
});
});
}
render() {
return (
<React.Fragment>
{/* THE PLAYLIST */}
<Text>Playlist</Text>
{this.state.allSongs && (
<FlatList
contentContainerStyle={styles.containerContent}
data={this.state.allSongs}
keyExtractor={itemObj => itemObj.id.toString()}
renderItem={itemObj => {
const { item } = itemObj;
return (
<TouchableOpacity
onPress={() => {
this.setState({
currentSongData: item
});
}}
>
<Text>{item.songTitle}</Text>
<Text>{item.artist}</Text>
</TouchableOpacity>
);
}}
/>
{/* THE PLAYER - player code goes here */}
)}
</React.Fragment>
);
}
}
We set our inital states for data
constructor(props) {
super(props);
this.state = {
allSongs: [],
currentSongData: {}
};
}
Then make a firebase call when the component mounts
componentDidMount() {
firebase
.database()
.ref('trending/')
.on('value', snapshot => {
let array = [];
snapshot.forEach(child => {
array.push(child.toJSON());
});
this.setState({
allSongs: array
});
});
}
We user the forEach method so that we can make the values and push them to an empty array.
After, we update our state with the new array
this.setState({
allSongs: array
});
In our render method, we use a FlatList to return each value in the array.
{this.state.allSongs && (
<FlatList
contentContainerStyle={styles.containerContent}
data={this.state.allSongs}
keyExtractor={itemObj => itemObj.id.toString()}
renderItem={itemObj => {
const { item } = itemObj;
return (
<TouchableOpacity
onPress={() => {
this.setState({
currentSongData: item
});
}}
>
<Text>{item.songTitle}</Text>
<Text>{item.artist}</Text>
</TouchableOpacity>
);
}}
/>
)}
The Player
let song = this.state.currenSongData
<View>
<TouchableOpacity>
<View>
<Image
style={{
height: 40,
width: 40,
borderRadius: 3,
backgroundColor: 'gray'
}}
source={{ uri: song.image }}
/>
</View>
{song && (
<View>
<View>
<Text>{`${song.songTitle} · `}</Text>
<Text>{song.artist}</Text>
</View>
<View>
<FontAwesome
color={purple}
name="play"
size={14}
/>
<Text style={styles.device}>NOW PLAYING</Text>
</View>
</View>
)}
<TouchableOpacity
onPress={() => togglePlay()}
>
<FontAwesome color={colors.white} name={iconPlay} size={28} />
</TouchableOpacity>
</TouchableOpacity>
</View>
This is the interface, we display the cover art of the song, song title and artist.
Playing the song
funtions
togglePlay()
let togglePlay = async () => {
this.setState(prev => ({
paused: !prev.paused
}));
this._playAndPause();
};
this.playAndPause()
_playAndPause = () => {
switch (this.state.playingStatus) {
case 'nosound':
this._playRecording();
break;
case 'donepause':
case 'playing':
this._pauseAndPlayRecording();
break;
}
};
this.playRecording()
async _playRecording() {
// alert('playing it');
const { sound } = await Audio.Sound.createAsync(
{ uri: this.state.currentSongData.songLink },
{
shouldPlay: true,
isLooping: false
},
this._updateScreenForSoundStatus
);
this.sound = sound;
this.setState({
playingStatus: 'playing'
});
}
_pauseAndPlayRecording()
async _pauseAndPlayRecording() {
if (this.sound != null) {
if (this.state.playingStatus == 'playing') {
console.log('pausing...');
await this.sound.pauseAsync();
console.log('paused!');
this.setState({
playingStatus: 'donepause'
});
} else {
console.log('playing...');
await this.sound.playAsync();
console.log('playing!');
this.setState({
playingStatus: 'playing'
});
}
}
}
_updateScreenForSoundStatus()
_updateScreenForSoundStatus = status => {
if (status.isPlaying && this.state.playingStatus !== 'playing') {
this.setState({ playingStatus: 'playing' });
} else if (!status.isPlaying && this.state.playingStatus === 'playing') {
this.setState({ playingStatus: 'donepause' });
}
};
_syncPauseAndPlayRecording()
_syncPauseAndPlayRecording() {
if (this.sound != null) {
if (this.state.playingStatus == 'playing') {
this.sound.pauseAsync();
} else {
this.sound.playAsync();
}
}
}
This is a technical writing and you need knowledge in expo and react native to understand.
However you can send me a message on twitter full for source code (github repo)
Twitter : @cirlorm_io
Photo by Nadine Shaabana on Unsplash
Photo by NeONBRAND on Unsplash
Top comments (1)
Would be nice if you can create this tutorial in Hook 🙂
We are using hook to create new pages and want to use expo-av to play audio :D Thanks!