javascript - Jak přehrát audio stopu znovu pomocí Expo AV
Pracuji na hudební aplikaci s React Native, AWS a Expo. Používám knihovnu Expo AV k přehrávání audio souborů. Mám problém s tím, aby se píseň po skončení automaticky přehrála znovu.
Níže jsou mé pokusy o toto.
Neúspěšné přístupy:
Vidím booleanovou proměnnou didjustFinish. Snažím se ji resetovat na true po dokončení přehrávání zvuku, pak mohu
await sound.playAsync();, ale zdá se, že to nefungujeSnažím se sladit durationMillis s playableDurationMillis - pokud jsou stejné, pak volejte
await sound.playAsync();. To také nefunguje.
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;
Odpověď č. 1
Podívej se do dokumentace
Je třeba mít na paměti několik bodů:
Poté, co přehrajete stopu jednou, volání play na ní znovu nebude mít žádný efekt. Můžete však zavolat sound.replayAsync(), abyste stopu znovu spustili.
Můžete nastavit, aby se zvuk opakoval, takže se automaticky restartuje, pokud dosáhne konce, použitím (citace dokumentace):
playbackObject.setIsLoopingAsync(value) To je ekvivalentní s playbackObject.setStatusAsync({ isLooping: value })
Potřebujete refaktorovat svou metodu přehrávání/pauzy, aby lépe zvládala různé situace. Například, pokud je dokončeno, ale má stále hrát (můžete zkusit zavolat replayAsync místo playAsync).
Další nápad je restartovat skladbu, pokud skončila, ale měla by stále hrát. Takže pokud nebudete používat opakování, můžete tuto podmínku odstranit.
if (finish) {
await sound.playAsync();
}
a vložit to do useEffect, který sleduje 'finish'. Asi je jednodušší použít příznak smyčky.
Odpověď č. 2
Vím, že je to staré, ale pro každého, kdo by mohl mít zájem
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);
}
};
