javascript - Come riprodurre di nuovo una traccia audio usando Expo AV
Sto lavorando a un'app musicale con React Native, AWS ed Expo. Sto usando la libreria Expo AV per riprodurre file audio. Ho problemi a far sì che la canzone si riavvii automaticamente dopo che finisce.
Di seguito ci sono i miei tentativi in merito.
Approcci falliti:
Vedo una variabile booleana didjustFinish. Provo a resettarla a true dopo che l'audio ha terminato la riproduzione, poi posso
await sound.playAsync();ma sembra che non funzioniCerco di far coincidere durationMillis con playableDurationMillis - se sono uguali allora chiamo
await sound.playAsync();. Anche questo non funziona.
import React, { useContext, useEffect, useState } from 'react';
import { Text, Image, View, TouchableOpacity } from 'react-native';
import { AntDesign, FontAwesome } from "@expo/vector-icons";
import { API, graphqlOperation } from 'aws-amplify';
import styles from './styles';
import { Song } from "../../types";
import { Sound } from "expo-av/build/Audio/Sound";
import { AppContext } from '../../AppContext';
import { getSong } from "../../src/graphql/queries";
const PlayerWidget = () => {
const [song, setSong] = useState(null);
const [sound, setSound] = useState<Sound | null>(null);
const [isPlaying, setIsPlaying] = useState<boolean>(true);
const [duration, setDuration] = useState<number | null>(null);
const [position, setPosition] = useState<number | null>(null);
const [finish, setFinish] = useState<boolean>(true);
const { songId } = useContext(AppContext);
useEffect(() => {
const fetchSong = async () => {
try {
const data = await API.graphql(graphqlOperation(getSong, { id: songId }))
setSong(data.data.getSong);
} catch (e) {
console.log(e);
}
}
fetchSong();
}, [songId])
const onPlaybackStatusUpdate = (status) => {
setIsPlaying(status.isPlaying);
setDuration(status.durationMillis);
setPosition(status.positionMillis);
setFinish(status.didJustFinish);
// console.log(finish);
console.log(status);
}
const playCurrentSong = async () => {
if (song.artist.length > 10) {
song.artist = song.artist.substring(0, 6) + "...";
}
if (song.title.length > 8) {
song.title = song.title.substring(0, 5) + "...";
}
if (sound) {
await sound.unloadAsync();
}
const { sound: newSound } = await Sound.createAsync(
{ uri: song.uri },
{ shouldPlay: isPlaying },
onPlaybackStatusUpdate
)
setSound(newSound)
}
useEffect(() => {
if (song) {
playCurrentSong();
}
}, [song])
const onPlayPausePress = async () => {
if (!sound) {
return;
}
if (isPlaying) {
await sound.pauseAsync();
}
else {
await sound.playAsync();
}
if (finish) {
await sound.playAsync();
}
}
const getProgress = () => {
if (sound === null || duration === null || position === null) {
return 0;
}
return (position / duration) * 100;
}
if (!song) {
return null;
}
return (
<View style={styles.container}>
<View style={[styles.progress, { width: `${getProgress()}%` }]} />
<View style={styles.row}>
<Image source={{ uri: song.imageUri }} style={styles.image} />
<View style={styles.rightContainer}>
<View style={styles.nameContainer}>
<Text style={styles.title}>{song.title}</Text>
<Text style={styles.artist}>{song.artist}</Text>
</View>
<View style={styles.iconsContainer}>
<AntDesign name="hearto" size={20} color={'white'} />
<TouchableOpacity onPress={onPlayPausePress}>
<AntDesign name={isPlaying ? 'pausecircleo' : 'playcircleo'} size={25} color={'white'} />
</TouchableOpacity>
</View>
</View>
</View>
</View>
)
}
export default PlayerWidget;
Risposta n. 1
Dai un'occhiata ai documenti
Ci sono alcuni punti da tenere a mente:
Dopo aver riprodotto la traccia una volta, richiamare play su di essa di nuovo non avrà alcun effetto. Tuttavia, puoi chiamare sound.replayAsync() per riavviare la traccia.
Potresti far sì che il suono si ripeta, in modo che si riavvii automaticamente se arriva alla fine usando (citando la documentazione):
playbackObject.setIsLoopingAsync(value) Questo è equivalente a playbackObject.setStatusAsync({ isLooping: value })
Devi rifattorizzare il tuo metodo play/pause per gestire meglio i diversi casi. Ad esempio, se è terminato ma dovrebbe continuare a suonare (potresti provare a chiamare replayAsync invece di playAsync).
Un'altra idea è riavviare la traccia se è finita ma ancora destinata a essere riprodotta. Quindi, se non intendi usare il loop, puoi rimuovere la condizione
if (finish) {
await sound.playAsync();
}
e mettilo in un useEffect che osserva 'finish'. Immagino che usare il flag di loop sia più facile.
Risposta n. 2
So che questo è vecchio, ma per chiunque potrebbe essere interessato
const [isPlaying, setIsPlaying] = useState(false);
const [sound, setSound] = useState(null);
const [numberOfLoops, setNumberOfLoops] = useState(0);
const N = 2; // Number of times to loop
const dir = FileSystem.cacheDirectory + 'xxx/';
const baseuri = `xxxx`;
const fileUri = dir;
const onPlaybackStatusUpdate = playbackStatus => {
if (playbackStatus.didJustFinish) {
if (numberOfLoops < N - 1) {
sound.setPositionAsync(0);
sound.playAsync();
setNumberOfLoops(numberOfLoops + 1);
} else {
sound.setIsLoopingAsync(false);
setIsPlaying(false);
}
}
};
const startSound = async (id) => {
const audioFile = fileUri + id + ". 1.mp3";
console.log('Playing File:', audioFile);
try {
const { sound: newSound } = await Audio.Sound.createAsync(
{ uri: audioFile },
{ shouldPlay: true }
);
setSound(newSound);
setNumberOfLoops(0);
newSound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
newSound.setIsLoopingAsync(true);
setIsPlaying(true);
} catch (e) {
console.log(`Cannot play the sound file`, e);
}
};
