DEV Community

Cover image for How to create a music streaming app - Expo(RN)
C I R L O R M ⚡
C I R L O R M ⚡

Posted on

How to create a music streaming app - Expo(RN)

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

  1. Expo (React native)
  2. 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)

Collapse
 
aungmyintthein profile image
Aung Myint Thein

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!