Expo(React Native)应用中如何根据音频播放状态持续调整Slider位置?
问题
基于Expo(React Native)开发适配Android、iOS和Web的应用时,尝试通过修改Slider的value属性跟随音频播放进度更新滑块位置,但代码仅在调用PlayAudio()的初始阶段生效一次,无法实现滑块位置随播放状态持续调整。相关代码如下:
import * as React from "react"; import { View, StyleSheet } from "react-native"; import { Audio } from "expo-av"; import { activateKeepAwake, deactivateKeepAwake } from "expo-keep-awake"; import AudioTouchable from "./AudioTouchable"; import Slider from "@react-native-community/slider"; export default function ChapterAudio({ chapterAudio }) { const LoadAudio = async () => { try { UnloadAudio(); await audio.current.loadAsync({ uri: chapterAudio }, {}, true); audio.current.setOnPlaybackStatusUpdate(UpdateAudio); } catch (e) {} }; const PlayAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded && audioStatus.isPlaying === false) { audio.current.playAsync(); activateKeepAwake(); } } catch (e) {} }; const PauseAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded) { if (audioStatus.isPlaying === true) { audio.current.pauseAsync(); deactivateKeepAwake(); } } } catch (e) {} }; const StopAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded) { PauseAudio(); audio.current.setPositionAsync(0); } } catch (e) {} }; const UnloadAudio = async () => { try { await audio.current.unloadAsync(); deactivateKeepAwake(); } catch (e) {} }; const UpdateAudio = async (playbackStatus) => { if (playbackStatus.isPlaying) { try { const audioDuration = playbackStatus.durationMillis; const audioPosition = playbackStatus.positionMillis; const positionRatio = audioPosition / audioDuration; setSliderValue(positionRatio); } catch (e) {} } playbackStatus.didJustFinish && deactivateKeepAwake(); }; const audio = React.useRef(new Audio.Sound()); const [sliderValue, setSliderValue] = React.useState(0); LoadAudio(); return ( <> <View style={styles.player}> <AudioTouchable name="play" onPress={PlayAudio} /> <AudioTouchable name="pause" onPress={PauseAudio} /> <AudioTouchable name="stop" onPress={StopAudio} /> </View> <Slider style={{ width: "100%", paddingTop: 30, height: 60 }} minimumValue={0} maximumValue={1} minimumTrackTintColor="#555555" maximumTrackTintColor="#AAAAAA" thumbTintColor="teal" value={sliderValue} /> </> ); } const styles = StyleSheet.create({ player: { flex: 1, flexDirection: "row", justifyContent: "center", width: "100%", }, });
解决方案
你的代码存在几个关键问题导致滑块无法持续更新,修复方案如下:
1. 修复音频加载逻辑
直接在组件顶层调用LoadAudio()会导致每次组件渲染都重复加载音频,干扰播放状态监听。改用useEffect包裹,仅在chapterAudio变化时执行加载,同时添加卸载清理:
React.useEffect(() => { LoadAudio(); return () => { UnloadAudio(); // 组件卸载时清理音频资源 }; }, [chapterAudio]);
2. 调整播放状态更新逻辑
原UpdateAudio只在音频播放时更新滑块,忽略了暂停、停止等状态的同步,同时未处理音频时长未加载完成的情况。修改后:
const UpdateAudio = (playbackStatus) => { if (playbackStatus.isLoaded && playbackStatus.durationMillis > 0) { const positionRatio = playbackStatus.positionMillis / playbackStatus.durationMillis; setSliderValue(positionRatio); } if (playbackStatus.didJustFinish) { setSliderValue(0); // 播放结束后滑块归位 deactivateKeepAwake(); } };
3. 同步停止操作的滑块状态
原StopAudio仅设置音频位置为0,但未同步更新滑块状态,添加setSliderValue(0)确保滑块立即归位:
const StopAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded) { await audio.current.pauseAsync(); await audio.current.setPositionAsync(0); setSliderValue(0); deactivateKeepAwake(); } } catch (e) {} };
修改后的完整代码
import * as React from "react"; import { View, StyleSheet } from "react-native"; import { Audio } from "expo-av"; import { activateKeepAwake, deactivateKeepAwake } from "expo-keep-awake"; import AudioTouchable from "./AudioTouchable"; import Slider from "@react-native-community/slider"; export default function ChapterAudio({ chapterAudio }) { const LoadAudio = async () => { try { await UnloadAudio(); await audio.current.loadAsync({ uri: chapterAudio }, {}, true); audio.current.setOnPlaybackStatusUpdate(UpdateAudio); } catch (e) { console.error("加载音频失败:", e); } }; const PlayAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded && !audioStatus.isPlaying) { await audio.current.playAsync(); activateKeepAwake(); } } catch (e) { console.error("播放音频失败:", e); } }; const PauseAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded && audioStatus.isPlaying) { await audio.current.pauseAsync(); deactivateKeepAwake(); } } catch (e) { console.error("暂停音频失败:", e); } }; const StopAudio = async () => { try { const audioStatus = await audio.current.getStatusAsync(); if (audioStatus.isLoaded) { await audio.current.pauseAsync(); await audio.current.setPositionAsync(0); setSliderValue(0); deactivateKeepAwake(); } } catch (e) { console.error("停止音频失败:", e); } }; const UnloadAudio = async () => { try { await audio.current.unloadAsync(); deactivateKeepAwake(); } catch (e) { console.error("卸载音频失败:", e); } }; const UpdateAudio = (playbackStatus) => { if (playbackStatus.isLoaded && playbackStatus.durationMillis > 0) { const positionRatio = playbackStatus.positionMillis / playbackStatus.durationMillis; setSliderValue(positionRatio); } if (playbackStatus.didJustFinish) { setSliderValue(0); deactivateKeepAwake(); } }; const audio = React.useRef(new Audio.Sound()); const [sliderValue, setSliderValue] = React.useState(0); React.useEffect(() => { LoadAudio(); return () => { UnloadAudio(); }; }, [chapterAudio]); return ( <> <View style={styles.player}> <AudioTouchable name="play" onPress={PlayAudio} /> <AudioTouchable name="pause" onPress={PauseAudio} /> <AudioTouchable name="stop" onPress={StopAudio} /> </View> <Slider style={{ width: "100%", paddingTop: 30, height: 60 }} minimumValue={0} maximumValue={1} minimumTrackTintColor="#555555" maximumTrackTintColor="#AAAAAA" thumbTintColor="teal" value={sliderValue} /> </> ); } const styles = StyleSheet.create({ player: { flex: 1, flexDirection: "row", justifyContent: "center", width: "100%", }, });
内容的提问来源于stack exchange,提问作者Vorvule
相关产品推荐
相关产品推荐

