ReactNative & Expo: Trouble incrementing/decrementing to cycle through an Array - android

I am attempting to cycle through sound objects in an Array, using an index value that begins at 0 and increments/decrements depending on whether I press next or back.
This is for a music player for react-native using the Expo//expo-av library. I'll include all relevant code.
State I have in my Context file:
const initialState = {
startMusic: () => null,
stopMusic: () => null,
soundObject: null,
isPlaying: false,
currentIndex: 0,
}
useState()
const [soundObject, setSoundObject] = useState(initialState.soundObject)
const [isPlaying, setIsPlaying] = useState(initialState.isPlaying)
const [currentIndex, setCurrentIndex] = useState(initialState.currentIndex)
Start Music function
const startMusic = async () => {
try {
const songToPlay = songs[currentIndex].song
const source = songs[currentIndex].path
await songToPlay.loadAsync(source)
await songToPlay.playAsync()
setSoundObject(songToPlay)
setIsPlaying(true)
return new Promise(resolve => { // I made this promise when I was setting a loop to play through music. May not need this anymore
songToPlay.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Song finished")
resolve()
}
})
})
} catch (error) {
console.log(`Error: ${error}`)
return
}
}
And finally, the handler functions that are supposed to cycle through songs:
const handlePreviousTrack = async () => {
if (soundObject) {
await soundObject.stopAsync()
await soundObject.unloadAsync()
// setSoundObject(null)
let newIndex = currentIndex
newIndex < songs.length - 1 ? newIndex-- : (newIndex = 0)
setCurrentIndex(newIndex)
startMusic()
console.log(currentIndex)
}
}
const handleNextTrack = async () => {
if (soundObject) {
await soundObject.stopAsync()
await soundObject.unloadAsync()
// setSoundObject(null)
let newIndex = currentIndex
newIndex < songs.length - 1 ? newIndex++ : (newIndex = 0)
setCurrentIndex(newIndex)
startMusic()
console.log(currentIndex)
}
}
Cycling through next/previous does not go in order.
Sometimes it works, sometimes previous goes to the next song, sometimes pressing next just replays the first song.
Am I manipulating the state via currentIndex incorrectly?

I don't see anything alarmingly wrong in the above code by quickly looking through it. One suggestion I would have is to take your startMusic() call out of your two handleTrack methods and instead just have them set index state as they are doing.
Instead add a useEffect that will fire every time the index changes and put your startMusic() in that useEffect.
It would look something like this:
useEffect(() => {
... (other logic)
startMusic()
}, [currentIndex])
This will ensure startMusic will only get called after the currentIndex state has been changed and not be called too soon etc. If not it may make it easier to track down when the state is changing regardless

Related

Create a custom notification with Actions Buttons

import BackgroundService from 'react-native-background-actions';
const sleep = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
// You can do anything in your task such as network requests, timers and so on,
// as long as it doesn't touch UI. Once your task completes (i.e. the promise is resolved),
// React Native will go into "paused" mode (unless there are other tasks running,
// or there is a foreground app).
const veryIntensiveTask = async (taskDataArguments) => {
// Example of an infinite loop task
const { delay } = taskDataArguments;
await new Promise( async (resolve) => {
for (let i = 0; BackgroundService.isRunning(); i++) {
console.log(i);
await sleep(delay);
}
});
};
const options = {
taskName: 'Example',
taskTitle: 'ExampleTask title',
taskDesc: 'ExampleTask description',
taskIcon: {
name: 'ic_launcher',
type: 'mipmap',
},
color: '#ff00ff',
linkingURI: 'yourSchemeHere://chat/jane', // See Deep Linking for more info
parameters: {
delay: 1000,
},
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({taskDesc: 'New ExampleTask description'}); // Only Android, iOS will ignore this call
// iOS will also run everything here in the background until .stop() is called
await BackgroundService.stop();
I am using react-native-background-actions. I need 2 buttons in this. But I don’t have any good experience with native code in react native so let me know how can I add custom buttons In it and changes buttons according condition. How can I make changes in the native android for notification buttons.

React Native axios GET method to get all data from subpages and display them

Hello i'm new with axios and i wanna to display all my data from subpages to screen but I have some trouble because when I display them it shows me the data from the last subpage instead of all of them at the bottom I throw the code how I download the data through axios. How do I display them all ?
const [data, setData] = useState([]);
async function test() {
for (let id = 1; id < 3; id ++) {
axios.get(`https://api.jsonbin.io/b/61f98c361960493ad1865911/${id}`)
.then(({data}) => {
setData(data.commentData)
console.log(data.commentData)
})
.catch((error) => console.error(error))
}
}
useEffect(() => {
test()
}, []);
You are overwriting your data state in each loop, so the last loop iteration is the one that you see.
async function test() {
for (let id = 1; id < 3; id++) {
axios.get(`https://api.jsonbin.io/b/61f98c361960493ad1865911/${id}`)
.then(({ data }) => {
setData(data.commentData) // <-- overwrites previous state
console.log(data.commentData)
})
.catch((error) => console.error(error));
}
}
Use a functional state update to correctly update from the previous state.
async function test() {
setData([]);
for (let id = 1; id < 3; id++) {
axios.get(`https://api.jsonbin.io/b/61f98c361960493ad1865911/${id}`)
.then(({ data }) => {
setData(data => [...data, data.commentData]) // <-- append new data
console.log(data.commentData)
})
.catch((error) => console.error(error));
}
}
You could also map an array of Promises and use Promise.all to get a single array of resolved data values.
async function test() {
const requests = [1,2,3].map(id => axios.get(`https://api.jsonbin.io/b/61f98c361960493ad1865911/${id}`));
try {
const dataArr = await Promise.all(requests);
setData(dataArr.map(({ data }) => data.commentData));
} catch(error) {
console.error(error);
}
}

ReactNative & Expo - Error: "value" is read-only

I'm adding a music-player component/functionality to this react native app.
I'm gonna include all the relevant code to give you the best understanding of the setup.
In MusicContext.js, the context file that handles the logic for the music player, I have a few State objects for determining which song to play.
const initialState = {
startMusic: () => null,
stopMusic: () => null,
soundObject: null,
isPlaying: false,
currentIndex: 0
}
My songs array:
mainTheme = new Audio.Sound()
mainTheme2 = new Audio.Sound()
mainTheme3 = new Audio.Sound()
const songs = [
{ path: require("../assets/sounds/MainTheme.mp3"), song: mainTheme },
{ path: require("../assets/sounds/MainTheme2.mp3"), song: mainTheme2 },
{ path: require("../assets/sounds/MainTheme3.mp3"), song: mainTheme3 },
]
currentIndex begins at 0 so the music player will start at songs[0].
The function to start playing the music is as follows:
const startMusic = async () => {
try {
const songToPlay = songs[currentIndex].song // first song in the array of songs
const source = songs[currentIndex].path // the path that loadAsync() function needs to load file
await songToPlay.loadAsync(source)
await songToPlay.playAsync()
setSoundObject(songToPlay)
setIsPlaying(true)
return new Promise(resolve => {
songToPlay.setOnPlaybackStatusUpdate(playbackStatus => {
if (playbackStatus.didJustFinish) {
console.log("Song finished")
resolve()
}
})
})
} catch (error) {
console.log(`Error: ${error}`)
return
}
}
At this point, soundObject = the song that is playing, and currentIndex = 0.
My function for skipping to the next song is:
const handleNextTrack = async () => {
if (soundObject) {
await soundObject.stopAsync()
await soundObject.unloadAsync()
setSoundObject(null)
currentIndex < songs.length - 1 ? (currentIndex += 1) : (currentIndex = 0)
setCurrentIndex({ currentIndex })
this.startMusic()
}
}
After that, it goes back to startMusic(), except now with the currentIndex being 1, which should play the next song in the array.
THE ERROR I'M GETTING is
Possible Unhandled Promise Rejection (id: 5): Error: "currentIndex" is read-only
I tried a few different ways of implementing handleNextTrack but it all leads to the same error.
UPDATE
const handlePreviousTrack = async () => {
if (soundObject) {
await soundObject.stopAsync()
await soundObject.unloadAsync()
setSoundObject(null)
let newIndex = currentIndex
newIndex < (songs.length - 1) ? (--newIndex) : (newIndex = 0)
setCurrentIndex(newIndex)
startMusic()
}
}
const handleNextTrack = async () => {
if (soundObject) {
await soundObject.stopAsync()
await soundObject.unloadAsync()
setSoundObject(null)
let newIndex = currentIndex
newIndex < (songs.length - 1) ? (++newIndex) : (newIndex = 0)
setCurrentIndex(newIndex)
startMusic()
}
}
Song[0] plays when app opens. <~GOOD
Song[0] plays again when I hit next <~BAD
Song[1] plays only if I hit next a second time <~GOOD
Song[2] plays if I hit next again <~GOOD
After that I can cycle through a bit, hitting previous and next a few times, but it just broke and somehow the soundObject is back to null

How to Play Multiple Audio's in React Native?

I want to play multiple audio files in my React Native application. Currently, it is one audio at a time and i also want single audio to be played at a time. What I want is if one audio is playing and suddenly the user tapped on the second audio button the first one will get paused and second should play. And when the user tapped the first again the paused audio will start again from where it is paused. Similar to whatsapp Audio Messages.
I am using react-native-audio-recorder-player for recording and playing audio in my application.
check app design
My FlatList Item Design:
{ { this.toggleMediaPlayer(item.audiourl, index) } }}>
<TouchableOpacity
style={styles.viewBarWrapper}
onPress={this.onStatusPress}
>
<View style={styles.viewBar}>
<View style={styles.viewBarPlay} />
</View>
</TouchableOpacity>
<Text style={styles.txtCounter}>
{/* {this.state.playTime} / {this.state.duration} */}
</Text>
</View>
MediaPlayer Function:
`toggleMediaPlayer(mediaPath, index) {
if (this.state.mediaFlag[index] == false) {
this.onStartPlay(mediaPath, index)
this.state.mediaFlag[index] = true;
var cloneObj = Object.assign({}, this.state.mediaFlag);
this.setState({ mediaFlag: cloneObj });
console.log(this.state.mediaFlag)
}
else {
this.onPausePlay(mediaPath)
this.state.mediaFlag[index] = false;
var cloneObj = Object.assign({}, this.state.mediaFlag);
this.setState({ mediaFlag: cloneObj });
console.log(this.state.mediaFlag)
}
}
`
REST CODE
audioRecorderPlayer = new AudioRecorderPlayer();
async onStartPlay(path, index) {
console.log('onStartPlay');
this.audioRecorderPlayer.stopPlayer();
const msg = await this.audioRecorderPlayer.startPlayer(path);
console.log(msg);
this.audioRecorderPlayer.addPlayBackListener(async (e) => {
if (e.current_position === e.duration) {
console.log('finished');
// await this.setState({ mediaFlag: !this.state.mediaFlag });
this.state.mediaFlag[index] = false;
var cloneObj = Object.assign({}, this.state.mediaFlag);
this.setState({ mediaFlag: cloneObj });
console.log(this.state.mediaFlag)
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
}
else {
this.setState({
currentPositionSec: e.current_position,
currentDurationSec: e.duration,
playTime: this.audioRecorderPlayer.mmssss(Math.floor(e.current_position)),
duration: this.audioRecorderPlayer.mmssss(Math.floor(e.duration)),
})
}
return;
});
};
onPausePlay = async () => {
await this.audioRecorderPlayer.pausePlayer();
};
async onStopPlay(index) {
console.log('onStopPlay');
this.audioRecorderPlayer.stopPlayer();
this.audioRecorderPlayer.removePlayBackListener();
};
As of v2.4.3, it is not a feature of react-native-audio-recorder-player (see https://github.com/dooboolab/react-native-audio-recorder-player/issues/130)
But i had to do it myself so here's how i managed to do it:
The main point is to have a parent component of all players (or a store - like redux or react context) which knows the state of the players (is a player reading its sound? which one?)
Then, when a player plays a sound, it calls the parent component and the parent component will stop any other player currently reading - it has to be done in a certain order to avoid odd behaviour
Here's my code simplified:
const ParentComponent = () => {
const [playerRecording, setPlayerRecording] = useState(false); // Which recording is currently playing?
const onPlay = async (onStartPlay, playerId) => {
// No player are reading or it is the same player
if (!playerRecording || playerRecording === playerId)
return onStartPlay();
// Another player is reading - stop it before starting the new one
audioRecorderPlayer.removePlayBackListener();
await audioRecorderPlayer.resumePlayer();
await audioRecorderPlayer.stopPlayer();
// Start the new player
onStartPlay();
};
}
const PlayerComponent = ({onPlay, setPlayerRecording, playerId}) => {
const onStartPlay = async () => {
setPlayerRecording(playerId);
audioRecorderPlayer.startPlayer(path);
...
}
return (
<PlayButton onPress={() => onPlay(onStartPlay, playerId)} />
)
}

React native / Trouble with AsyncStorage get item

I am building an Android app and I am struggling using the AsyncStorage. I want to create a function that takes a key as input and give me back the item. My problem is that when I call this function, it returns { _40: 0, _65: 0, _55: null, _72: null } instead the value I am looking for.
Here is my code :
renderList() {
async function save() {
await AsyncStorage.setItem('Title', 'hello');
}
async function fetch(key) {
const value = await AsyncStorage.getItem(key);
console.log(value) ; // Return hello
return value
}
save() ;
fetch() ;
const reponse = fetch() ;
console.log(reponse) ; // Return { _40: 0, _65: 0, _55: null, _72: null }
return (
<Text style={styles.noteElementTitle}> Aa </Text>
)
}
Edit :
I tried this :
async function getBody() {
await save();
const response = await fetch('Title');
console.log(response);
this.setstate({ title : response}) ;
}
getBody() ;
But I still get an error : TypeError: undefined is not a function (evaluating 'this.setstate({ title: response })')
What you're seeing is the Promise object returned by a function marked async. You need to either use await when calling the function, or treat the value as a promise and use then and catch. For example:
await save();
const response = await fetch();
console.log(response);
or
save()
.then(() => fetch())
.then(response => console.log(response));
Also, keep in mind that you should not be using async functions inside a render function. In your code above, you'll want to put the result of your fetch() function into component state.

Categories

Resources