Hello!
I’m building first ReactNative app and am super grateful to only use Javascript with Expo – Thank you!
We’re moving towards a Standalone app eventually, but are still wrapping up basic functionality and styling.
Our app features an audio player (using Expo’s Audio api) that will allow for playing one (meditation) track at a time.
Using the example app + Stream’s audio-only version, I put together a player that appears to work fine in iOS but in Android (device) I get the error: “Warning: Can only update a mounted or mounting component. This usually means you called setState…”
Here’s when it happens:
MediationPlayerScreen opens
Press play (track plays and then finishes)
Hit back button (GET ABOVE ERROR)
And the error keeps logging new instances that, to me, implies that _onPlaybackStatusUpdate is continuing to fire even though:
if (status.didJustFinish) {
this.playbackInstance.unloadAsync()
}
(again, in iOS, no error and the didJustFinish seems to unload instance.)
My debugging console.logs aren’t showing up when running on the Android.
Summary – the Component works, but error message indicates not working properly in Android.
Can you help? Code for offending Screen below ( I removed timestamp code to simplify). Many thanks!
Shawn
/**
* @flow
*/
import React, { Component } from 'react';
import {
Dimensions,
Image,
Platform,
Slider,
StyleSheet,
Text,
TouchableHighlight,
View
} from 'react-native';
import { Icon } from 'react-native-elements';
import { MaterialIcons } from '@expo/vector-icons';
import { Asset, Audio } from 'expo';
const ICON_TRACK = require('../assets/images/line-white-thin.png');
const ICON_THUMB = require('../assets/images/dot-sm.png');
const { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = Dimensions.get('window');
const BACKGROUND_COLOR = '#000';
const DISABLED_OPACITY = 0.5;
const FONT_SIZE = 14;
const LOADING_STRING = '... loading ...';
class MeditationPlayerScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'iSleep',
headerLeft:
<Icon
name='navigate-before'
size={32}
onPress={ () => navigation.goBack() }
/>,
headerStyle: { marginTop: Platform.OS === 'android' ? 24: 0 }
});
constructor(props) {
super(props);
this.playbackInstance = null;
this.isSeeking = false;
this.shouldPlayAtEndOfSeek = false;
this.state = {
meditationTrack: this.props.navigation.state.params.meditation,
playbackInstanceName: LOADING_STRING,
playbackInstancePosition: null,
playbackInstanceDuration: null,
shouldPlay: false,
isPlaying: false,
isLoading: true
};
}
componentDidMount() {
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
shouldDuckAndroid: false,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
});
this._loadNewPlaybackInstance(false);
}
async _loadNewPlaybackInstance(playing) {
if (this.playbackInstance != null) {
await this.playbackInstance.unloadAsync();
this.playbackInstance.setOnPlaybackStatusUpdate(null);
this.playbackInstance = null;
}
const initialStatus = {
shouldPlay: playing
};
const source = this.state.meditationTrack.id == 1 ? require('../assets/sounds/test-audio.mp3') : require('../assets/sounds/test-audio-2.mp3');
try {
const { sound, status } = await Audio.Sound.create(
source,
initialStatus,
this._onPlaybackStatusUpdate
)
this.playbackInstance = sound;
this._updateScreenForLoading(false);
} catch(e) {
console.log("Problem creating sound object: ", e)
}
}
_updateScreenForLoading(isLoading) {
if (isLoading) {
this.setState({
isPlaying: false,
playbackInstanceName: LOADING_STRING,
playbackInstanceDuration: null,
playbackInstancePosition: null,
isLoading: true
});
} else {
this.setState({
playbackInstanceName: this.state.meditationTrack.title,
isLoading: false
});
}
}
_onPlaybackStatusUpdate = status => {
if (!status.isLoaded) {
// Update your UI for the unloaded state
if (status.error) {
console.log(`Encountered a fatal error during playback: ${status.error}`);
// Send Expo team the error on Slack or the forums so we can help you debug!
}
} else {
if (status.isLoaded) {
this.setState({
playbackInstancePosition: status.positionMillis,
playbackInstanceDuration: status.durationMillis,
shouldPlay: status.shouldPlay,
isPlaying: status.isPlaying,
});
if (status.didJustFinish) {
this.playbackInstance.unloadAsync()
}
} else {
if (status.error) {
console.log(`FATAL PLAYER ERROR: ${status.error}`);
}
}
}
};
_onPlayPausePressed = () => {
if (this.playbackInstance != null) {
if (this.state.isPlaying) {
this.playbackInstance.pauseAsync();
} else {
this.playbackInstance.playAsync();
}
}
};
render() {
return(
<View style={styles.container}>
<Image
source={ require('../assets/images/meditation-on-beach.jpg') }
style={styles.image}
resizeMode='contain'
/>
<Text style={styles.title}>
{this.state.playbackInstanceName}
</Text>
<Text style={styles.title}>
YOGA NIDRA
</Text>
<View style={styles.round}>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
onPress={this._onPlayPausePressed}
disabled={this.state.isLoading}
>
<View>
{this.state.isPlaying ? (
<MaterialIcons
name="pause"
size={50}
color="#56D5FA"
/>
) : (
<MaterialIcons
name="play-arrow"
size={50}
color="#56D5FA"
/>
)}
</View>
</TouchableHighlight>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingBottom: '10%',
backgroundColor: BACKGROUND_COLOR
},
image: {
flex: 1,
height: DEVICE_WIDTH * .5,
width: DEVICE_WIDTH,
},
title: {
color: '#fff',
fontWeight: 'bold',
fontSize: 20,
paddingBottom: 10
},
timestampRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
alignSelf: 'stretch',
maxHeight: FONT_SIZE * 2
},
text: {
color: '#fff',
fontSize: 12
},
playbackSlider: {
width: DEVICE_WIDTH * .6
},
round: {
height: 70,
width: 70,
borderRadius: 35,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: '#fff',
backgroundColor: BACKGROUND_COLOR
}
});
export default MeditationPlayerScreen;