javascript - Cara memainkan semula trek audio menggunakan Expo AV
Saya sedang bekerja pada aplikasi muzik dengan React Native, AWS dan Expo. Saya menggunakan perpustakaan Expo AV untuk memainkan fail audio. Saya menghadapi masalah untuk membuat lagu dimainkan semula secara automatik selepas ia tamat.
Di bawah adalah percubaan saya terhadap ini.
Pendekatan yang gagal:
Saya melihat satu pembolehubah boolean didjustFinish. Saya cuba menetapkannya semula kepada true selepas audio selesai dimainkan, kemudian saya boleh
await sound.playAsync();tetapi nampaknya itu tidak berfungsiSaya cuba memadankan durationMillis dengan playableDurationMillis - jika mereka sama maka panggil
await sound.playAsync();. Ini juga tidak berjaya.
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;
Jawapan #1
Lihat dokumen tersebut
Terdapat beberapa perkara yang perlu diingat:
Selepas anda memainkan trek itu sekali, memanggil main padanya sekali lagi tidak akan memberi kesan. Walau bagaimanapun, anda boleh memanggil sound.replayAsync() untuk memulakan semula trek tersebut.
Anda boleh membuat bunyi berulang, supaya ia bermula semula secara automatik jika ia sampai ke akhir dengan menggunakan (memetik dokumen):
playbackObject.setIsLoopingAsync(value) Ini adalah setara dengan playbackObject.setStatusAsync({ isLooping: value })
Anda perlu menstruktur semula kaedah main/jeda anda untuk menangani kes-kes yang berbeza dengan lebih baik. Sebagai contoh, jika ia telah selesai tetapi masih sepatutnya dimainkan (mungkin cuba memanggil replayAsync dan bukannya playAsync).
Satu lagi idea adalah untuk memulakan semula trek jika ia sudah selesai tetapi masih sepatutnya dimainkan. Jadi jika anda tidak akan menggunakan pengulangan, anda boleh membuang syarat itu
if (finish) {
await sound.playAsync();
}
dan letakkannya dalam useEffect yang memerhatikan 'finish'. Saya rasa menggunakan bendera pengulangan lebih mudah.
Jawapan #2
Saya tahu ini sudah lama tetapi untuk sesiapa yang mungkin berminat
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);
}
};
