You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React自定义Bootstrap音频播放器:快进/后退5秒及滑块调进度功能异常(触发音频重启)

React自定义Bootstrap音频播放器:快进/后退5秒及滑块调进度功能异常(触发音频重启)

看起来你遇到的问题很典型——调整音频进度时意外触发了重新加载,大概率是因为没正确维护音频实例的引用,或者进度控制逻辑里不小心重置了音频源。我来帮你梳理问题并给出修复方案。

问题核心原因

  • 没有用useRef保存音频元素的引用,每次渲染都重新创建了音频实例
  • 进度调整时错误触发了音频src的重新赋值,导致音频从头播放
  • 没有通过HTML5 Audio API的currentTime属性直接修改进度,而是用了错误的操作逻辑

修复后的完整实现

下面是可以正常工作的AudioPlayer组件代码,包含快进/后退5秒、滑块(进度条)调整进度的功能:

import { useState, useRef, useEffect } from 'react';
import { Button, ProgressBar } from 'react-bootstrap';

export default function AudioPlayer({ audioSrc, name }) {
  // 用useRef持久化音频元素引用,避免每次渲染重建实例
  const audioRef = useRef(null);
  // 播放状态管理
  const [isPlaying, setIsPlaying] = useState(false);
  // 当前播放时间(秒)
  const [currentTime, setCurrentTime] = useState(0);
  // 音频总时长(秒)
  const [duration, setDuration] = useState(0);

  // 初始化时绑定音频事件监听
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;

    // 音频加载完成后获取总时长
    const handleLoadedMetadata = () => {
      setDuration(audio.duration);
    };

    // 实时同步播放进度到组件状态
    const handleTimeUpdate = () => {
      setCurrentTime(audio.currentTime);
    };

    audio.addEventListener('loadedmetadata', handleLoadedMetadata);
    audio.addEventListener('timeupdate', handleTimeUpdate);

    // 组件卸载时清理事件监听
    return () => {
      audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
      audio.removeEventListener('timeupdate', handleTimeUpdate);
    };
  }, []);

  // 播放/暂停切换
  const togglePlay = () => {
    const audio = audioRef.current;
    isPlaying ? audio.pause() : audio.play();
    setIsPlaying(!isPlaying);
  };

  // 快进5秒(不超过总时长)
  const fastForward = () => {
    const audio = audioRef.current;
    audio.currentTime = Math.min(audio.currentTime + 5, duration);
    setCurrentTime(audio.currentTime);
  };

  // 后退5秒(不小于0)
  const rewind = () => {
    const audio = audioRef.current;
    audio.currentTime = Math.max(audio.currentTime - 5, 0);
    setCurrentTime(audio.currentTime);
  };

  // 滑块/进度条调整进度
  const handleProgressChange = (targetTime) => {
    const audio = audioRef.current;
    audio.currentTime = targetTime;
    setCurrentTime(targetTime);
    // 保持原播放状态:如果之前在播放,调整后继续播放
    if (isPlaying) audio.play();
  };

  // 格式化时间为MM:SS格式
  const formatTime = (time) => {
    if (isNaN(time)) return '00:00';
    const minutes = Math.floor(time / 60);
    const seconds = Math.floor(time % 60);
    return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  };

  return (
    <div className="audio-player">
      <h5>{name}</h5>
      {/* 隐藏的原生音频元素,通过ref控制 */}
      <audio ref={audioRef} src={audioSrc} />
      <div className="d-flex align-items-center gap-3 my-3">
        <Button variant="secondary" onClick={rewind}>⏮️ -5s</Button>
        <Button variant="primary" onClick={togglePlay}>
          {isPlaying ? '⏸️ 暂停' : '▶️ 播放'}
        </Button>
        <Button variant="secondary" onClick={fastForward}>⏭️ +5s</Button>
      </div>
      <div className="d-flex align-items-center gap-2">
        <span>{formatTime(currentTime)}</span>
        {/* 可点击的进度条,点击任意位置调整进度 */}
        <ProgressBar
          now={(currentTime / duration) * 100}
          min={0}
          max={100}
          onClick={(e) => {
            const rect = e.currentTarget.getBoundingClientRect();
            const percent = (e.clientX - rect.left) / rect.width;
            handleProgressChange(percent * duration);
          }}
          className="flex-grow-1 cursor-pointer"
        />
        <span>{formatTime(duration)}</span>
      </div>
    </div>
  );
}

关键注意点

  1. 用useRef保存音频实例:这是核心,确保我们始终操作同一个音频元素,不会因为组件重新渲染而创建新实例,避免进度调整时音频重启。
  2. 直接修改currentTime:快进/后退和进度条调整都通过修改音频元素的currentTime属性实现,这是HTML5 Audio API的标准用法,不会触发音频重新加载。
  3. 同步状态与实际播放进度:通过timeupdate事件实时同步音频的播放进度到组件状态,保证UI显示和实际播放一致。
  4. 避免意外重置src:不要在进度控制逻辑中重新设置audioSrc,保持src只在组件初始化时赋值一次。

你的App.js可以保持原有结构,只要确保正确引入这个修复后的AudioPlayer组件即可。

备注:内容来源于stack exchange,提问作者MrWho

火山引擎 最新活动