You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 02:15:38