React Native Cookbook
上QQ阅读APP看书,第一时间看更新

How to do it...

  1. Let's start by opening up App.js and adding the dependencies we'll need:
import React, { Component } from 'react';
import { Audio } from 'expo';
import { Feather } from '@expo/vector-icons';
import {
StyleSheet,
Text,
TouchableOpacity,
View,
Dimensions
} from 'react-native';
  1. An audio player needs audio to play. We'll create a playlist array to hold the audio tracks. Each track is represented by an object with a titleartistalbum, and uri:
const playlist = [
{
title: 'People Watching',
artist: 'Keller Williams',
album: 'Keller Williams Live at The Westcott Theater on 2012-09-22',
uri: 'https://ia800308.us.archive.org/7/items/kwilliams2012-09-22.at853.flac16/kwilliams2012-09-22at853.t16.mp3'
},
{
title: 'Hunted By A Freak',
artist: 'Mogwai',
album: 'Mogwai Live at Ancienne Belgique on 2017-10-20',
uri: 'https://ia601509.us.archive.org/17/items/mogwai2017-10-20.brussels.fm/Mogwai2017-10-20Brussels-07.mp3'
},
{
title: 'Nervous Tic Motion of the Head to the Left',
artist: 'Andrew Bird',
album: 'Andrew Bird Live at Rio Theater on 2011-01-28',
uri: 'https://ia800503.us.archive.org/8/items/andrewbird2011-01-28.early.dr7.flac16/andrewbird2011-01-28.early.t07.mp3'
}
];
  1. Next, we'll define our App class and initial state object with four properties:
  • isPlaying for defining whether the player is playing or paused
  • playbackInstance to hold the Audio instance
  • volume and currentTrackIndex for the currently playing track
  • isBuffering to display a Buffering... message while the track is buffering at the beginning of playback

As shown in following code:

export default class App extends Component {
state = {
isPlaying: false,
playbackInstance: null,
volume: 1.0,
currentTrackIndex: 0,
isBuffering: false,
}

// Defined in following steps
}
  1. Let's define the componentDidMount life cycle hook next. We'll use this method to configure the Audio component via the setAudioModeAsync method, passing in an options object with a few recommended settings. These will be discussed more in the How it works... section at the end of the recipe. After this, we'll load the audio with loadAudio, defined in the next step:
 async componentDidMount() {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
playThroughEarpieceAndroid: true,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid:
Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
});
this.loadAudio();
}
  1. The loadAudio function will handle loading the audio for our player. First, we'll create a new instance of Audio.Sound. We'll then call the setOnPlaybackStatusUpdate method on our new Audio instance, passing in a handler that will be called whenever the state of playback within the instance has changed. Finally, we call loadAsync on the instance, passing it a source from the playlist array, as well as a status object with the volume and a shouldPlay property set to the isPlaying value of state. The third parameter dictates whether we want to wait for the file to finish downloading before it is played, so we pass in false:
async loadAudio() {
const playbackInstance = new Audio.Sound();
const source = {
uri: playlist[this.state.currentTrackIndex].uri
}
const status = {
shouldPlay: this.state.isPlaying,
volume: this.state.volume,
};
playbackInstance
.setOnPlaybackStatusUpdate(
this.onPlaybackStatusUpdate
);
await playbackInstance.loadAsync(source, status, false);
this.setState({
playbackInstance
});
}
  1. We still need to define the callback for handling status updates. All we need to do in this function is set the value of isBuffering on state to the isBuffering value on the status parameter that was passed in from the setOnPlaybackStatusUpdate function call:
  onPlaybackStatusUpdate = (status) => {
this.setState({
isBuffering: status.isBuffering
});
}
  1. Our app now knows how to load an audio file from the playlist array and update state with the current buffering status of the loaded audio file, which we'll use later in the render function to display a message to the user. All that's left is to add the behavior for the player itself. First, we'll handle the play/pause state. The handlePlayPause method checks the value of this.state.isPlaying to determine whether the track should be played or paused, and calls the associated method on the playbackInstance accordingly. Finally, we need to update the value of  isPlaying for state:
  handlePlayPause = async () => {
const { isPlaying, playbackInstance } = this.state;
isPlaying ? await playbackInstance.pauseAsync() : await playbackInstance.playAsync();
this.setState({
isPlaying: !isPlaying
});
}
  1. Next, let's define the function for handling skipping to the previous track. First, we'll clear the current track from the playbackInstance by calling unloadAsync. We'll update the currentTrackIndex value of state to either one less than the current value, or 0 if we're at the beginning of the playlist array. Then, we'll call this.loadAudio to load the proper track:
  handlePreviousTrack = async () => {
let { playbackInstance, currentTrackIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentTrackIndex === 0 ? currentTrackIndex = playlist.length
- 1 : currentTrackIndex -= 1;
this.setState({
currentTrackIndex
});
this.loadAudio();
}
}
  1. Not surprisingly, handleNextTrack is the same as the preceding function, but this time we'll either add 1 to the current index, or set the index to 0 if we're at the end of the playlist array:
  handleNextTrack = async () => {
let { playbackInstance, currentTrackIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentTrackIndex < playlist.length - 1 ? currentTrackIndex +=
1 : currentTrackIndex = 0;
this.setState({
currentTrackIndex
});
this.loadAudio();
}
}
  1. It's time to define our render function. We will need three basic pieces in our UI: a 'Buffering...' message when the track is playing but still buffering, a section for displaying information for the current track, and a section to hold the player's controls. The 'Buffering...'  message will only display if both this.state.isBuffering and this.state.isPlaying are true. The song info is rendered via the renderSongInfo method, which we'll define in step 12:
  render() {
return (
<View style={styles.container}>
<Text style={[styles.largeText, styles.buffer]}>
{this.state.isBuffering && this.state.isPlaying ?
'Buffering...' : null}
</Text>
{this.renderSongInfo()}
<View style={styles.controls}>

// Defined in next step.

</View>
</View>
);
}
  1. The player controls are made up of three TouchableOpacity button elements, each with a corresponding icon from the Feather icon library. You can find more information on using icons in Chapter 3Implementing Complex User Interfaces – Part I. We'll determine whether to display the Play icon or the Pause icon depending on the value of this.state.isPlaying:
        <View style={styles.controls}>
<TouchableOpacity
style={styles.control}
onPress={this.handlePreviousTrack}
>
<Feather name="skip-back" size={32} color="#fff"/>
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handlePlayPause}
>
{this.state.isPlaying ?
<Feather name="pause" size={32} color="#fff"/> :
<Feather name="play" size={32} color="#fff"/>
}
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handleNextTrack}
>
<Feather name="skip-forward" size={32} color="#fff"/>
</TouchableOpacity>
</View>
  1. The renderSongInfo method returns basic JSX for displaying the metadata associated with the track currently playing:
  renderSongInfo() {
const { playbackInstance, currentTrackIndex } = this.state;
return playbackInstance ?
<View style={styles.trackInfo}>
<Text style={[styles.trackInfoText, styles.largeText]}>
{playlist[currentTrackIndex].title}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{playlist[currentTrackIndex].artist}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{playlist[currentTrackIndex].album}
</Text>
</View>
: null;
}
  1. All that's left to add are the styles. The styles defined here are well-covered ground by now, and don't go beyond centering, colors, font size, and adding padding and margins:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#191A1A',
alignItems: 'center',
justifyContent: 'center',
},
trackInfo: {
padding: 40,
backgroundColor: '#191A1A',
},
buffer: {
color: '#fff'
},
trackInfoText: {
textAlign: 'center',
flexWrap: 'wrap',
color: '#fff'
},
largeText: {
fontSize: 22
},
smallText: {
fontSize: 16
},
control: {
margin: 20
},
controls: {
flexDirection: 'row'
}
});
  1. You can now check out your app in the simulator, and you should have a fully working audio player! Note that audio playback in the Android emulator may be too slow for the playback to work properly, and may sound very choppy. Open the app on a real Android device to hear the track playing properly: