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
Related
I am trying to connect react native webrtc lib to my react native project. I can go to screen normally, but when I press button to start the call I receive following error:
[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'navigator.mediaDevices.enumerateDevices')]
This was tested on both android emulator and android phone. Both had same issue.
const startLocalStream = async () => {
// isFront will determine if the initial camera should face user or environment
const isFront = true;
let devices = await navigator.mediaDevices.enumerateDevices(); //this is where I get error
console.log(devices);
const facing = isFront ? 'front' : 'environment';
const videoSourceId = devices.find(device => device.kind === 'videoinput' && device.facing === facing);
const facingMode = isFront ? 'user' : 'environment';
const constraints = {
audio: true,
video: {
mandatory: {
minWidth: 500, // Provide your own width, height and frame rate here
minHeight: 300,
minFrameRate: 30,
},
facingMode,
optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
},
};
const newStream = await mediaDevices.getUserMedia(constraints);
setLocalStream(newStream);
};
const startCall = async id => {
const localPC = new RTCPeerConnection(configuration);
localPC.addStream(localStream);
const roomRef = await db.collection('rooms').doc(id);
const callerCandidatesCollection = roomRef.collection('callerCandidates');
localPC.onicecandidate = e => {
if (!e.candidate) {
console.log('Got final candidate!');
return;
}
callerCandidatesCollection.add(e.candidate.toJSON());
};
localPC.onaddstream = e => {
if (e.stream && remoteStream !== e.stream) {
console.log('RemotePC received the stream call', e.stream);
setRemoteStream(e.stream);
}
};
const offer = await localPC.createOffer();
await localPC.setLocalDescription(offer);
const roomWithOffer = { offer };
await roomRef.set(roomWithOffer);
roomRef.onSnapshot(async snapshot => {
const data = snapshot.data();
if (!localPC.currentRemoteDescription && data.answer) {
const rtcSessionDescription = new RTCSessionDescription(data.answer);
await localPC.setRemoteDescription(rtcSessionDescription);
}
});
roomRef.collection('calleeCandidates').onSnapshot(snapshot => {
snapshot.docChanges().forEach(async change => {
if (change.type === 'added') {
let data = change.doc.data();
await localPC.addIceCandidate(new RTCIceCandidate(data));
}
});
});
setCachedLocalPC(localPC);
};
How to fix it? I am using official lib for react native webrtc.
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
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)} />
)
}
Notifylisteners is continuously running on a loop
Future<void> fetchAndSetMeal() async {
final List<Meal> loadedProducts = [];
var data = await DataBaseHelpers.getData();
if (data.documents == null) {
return;
}
List<DocumentSnapshot> list = data.documents;
list.forEach((document) async {
var url = await DataBaseHelpers.getImageUrl(document.documentID);
loadedProducts.insert(
0,
Meal(
id: document.documentID,
category: document.data['category'],
description: document.data['description'],
title: document.data['title'],
price: document.data['price'],
imgUrl: url));
});
await Future.delayed(Duration(milliseconds: 300));
print(loadedProducts[0]);
notifyListeners();
_items = loadedProducts;
}
}
//The print and network call statements are also running twice as opposed to //just once for every call. notifyListeners() is running on a loop;
You got your notifyListeners inside foreach loop. Move it outside
i have a cloud function where i pass an array of numbers and compare those numbers with collection in the firestore . And if the numbers are present than return an array with those numbers. But before comparing those numbers the function return empty value in the promise.
I've tried using async await but the execution sequence remained same.
//sort contact list
export const addMessage= functions.https.onCall(async (data:any, context) => {
const col=admin.firestore().collection("joshua");
var match:[]
match=data.list
var perm1=new Array()
res11.push("454675556")
console.log("above resolve")
for(let val in match){
var inter=await Promise.all([getValues(col,val)])
console.log("inside resolve"+inter)
}
perm1.push("23432")
console.log("just before resolve")
return new Promise((res,rej)=>{
res(perm1)
})
});
//the async function which is suppose to process on every iteration
function getValues(col1:any,val1:any)
{
return new Promise(async(res,rej)=>{
var query= await col1.where('Listed','array-contains',val1)
var value=await query.get()
res(value)
})
.catch(err=>{
console.log(err)
})
}
i want the sequence to be asynchronous where the return value from getValues is waited upon and inside getValues result of query.get is waited upon.
so that at last return only be sent when all process is finished.
I think this is what you are looking for
export const addMessage= functions.https.onCall(async (data:any, context) => {
const col = admin.firestore().collection("joshua");
var match:[]
match = data.list
var perm1 = []
// res11.push("454675556") // ? undefined
for(let val in match){
var inter = await getValues(col,val)
console.log("inside resolve" + inter)
}
perm1.push("23432") // ??
// console.log("just before resolve")
return Promise.resolve(perm1)
});
const getValues = async (col1:any, val1:any) => {
const query = col1.where('Listed','array-contains', val1)
var value = await query.get().then(getAllDocs)
return value
}
const getAllDocs = function(data: any) {
const temp: Array<any> = []
data.forEach(function (doc: any) {
temp.push(doc.data())
})
return temp
}